%%
# 纲要
> 主干纲要、Hint/线索/路标
- 编译时名称查找顺序
- **名称隐藏**
- 类的编译时名称查找
# Q&A
#### 已明确
#### 待明确
> 当下仍存有的疑惑
**❓<font color="#c0504d"> 有什么问题?</font>**
%%
# 编译时名称查找顺序
> [!important] 编译时名称查找顺序
>
> 对于 "**函数**"中或 "**类**"中使用的 ==**名称**== ,编译器在编译时查找的**顺序**依次为:
>
> - **局部作用域查找**(只考虑在 **==名字使用之前==出现的声明**)
> - **类作用域查找**(会考虑**类中的所有成员**)
> - **当前类作用域查找**
> - **基类作用域查找**
> - **直接基类作用域查找**
> - **间接基类作用域查找**
> - **命名空间作用域内查找** (只考虑 **=="类定义之前"以及"函数定义之前"==的声明**)
> - **全局作用域/顶层命名空间内查找**(只考虑 **=="类定义之前"以及"函数定义之前"==的声明**)
>
> 上述过程中,一旦找到名称则编译器就不再继续查找。如果上述整个过程都未找到,则将报编译错误。
>
> > [!NOTE] 涉及多重继承时,"**类作用域查找**" 这一步将在 **派生类的==所有直接基类==** 中**同时进行**。
> >
> > 如果名字在多个直接基类中都存在,则**对该名字的使用将报告==二义性错误==**。
> > 由于 **"==名称查找==" 先于 "==类型检查=="**,因此对于 **同名函数** 即使 **==形参列表不同==**,也将报告二义性错误,而不构成重载。
> >
> > 解决办法:**对于继承来的多个同名成员,在调用其一时需要使用类作用域 `::` 显式指定**。
> >
> > ```cpp
> > struct A {
> > void func(int, int) {}
> > };
> >
> > struct B {
> > void func(double) {}
> > };
> >
> > struct C : public A, public B {
> > void dd() {
> > // func(4.32); // error 二义性错误, `func`名称在两个基类中均存在, 即使形参列表不同, 不构成重载
> > B::func(4.32); // 正确, 必须指明基类
> > }
> > };
> > ```
> >
>
> > [!NOTE] 对于 "**==函数==**" 名本身,当给函数传递一个**"==类类型对象=="** 时,除上述常规查找过程外,还会**在该 =="实参类及其基类" 所属的命名空间==中**进行查找。[^4]
> >
> > 例如:
> > - 示例一:输入运算符重载 `istream& operator>>(istream&, std::string)`定义于`<string>`头文件里, `std`命名空间中。
> > 当**引入头文件 `<string>` 后,即可直接使用 `cin >> s`**,而无需声明 `using std::operator>>;`
> > - 示例二:
> >
> > ```cpp
> > namespace MySpace {
> > class MyClass;
> > void f2() {}
> > void f(const MyClass&) {}
> >
> > class MyClass {
> > friend void f2();
> > friend void f(const MyClass&);
> > };
> > }
> >
> > int main() {
> > MySpace::MyClass obj;
> > f(obj); // 正确: 函数参数包含类类型对象, 将在该类类型对象所在的命名空间中去查找函数声明.
> > // f2(); // error, 必须通过`MySpace::f2()`;
> > }
> > ```
> >
>
>
<br><br><br>
# 名称隐藏
> **名称隐藏**(name shadowing)
**内层作用域**中声明的名字,将 "**==隐藏==**" **外层作用域中的同名实体**,不仅对于 "**==变量==**",也包括函数[^1]:
- (1)**内层作用域的函数将 "==隐藏==" (而不是重载)外层作用域中的所有同名函数**;
- (2)**派生类中函数将 "==隐藏==" 基类中的所有同名函数**(无论参数列表是否相同,包括虚函数);
> [!example] 示例一:
>
> ```cpp
> void func(const string&);
> void func(double);
>
> int main() {
> // 定义于其他文件中的函数, 此处声明后将隐藏外部作用域的两个同名函数.
> void func(int);
> // func("hello"); // error: `func(const string&)` 已被隐藏
> func(3.14); // 调用 `func(int), 而`func(double)` 已被隐藏.
> }
> ```
>
> [!example] 示例二:
>
> ```cpp
> struct Base {
> void memfcn();
> virtual void memfcn(int);
>
> };
>
> struct Derived : Base {
> void memfcn(string); // 将 "隐藏"基类的所有 memfcn, 而不是构成重载.
> };
>
> int main() {
> Derived d;
> d.memfcn("hello");
> // d.memfcn(); // error: 基类的`memfcn()`已经被隐藏, 而不构成重载
> // d.memfcn(10); // error: 基类的`memfcn(int)`已经被隐藏, 而不构成重载
> d.Base::memfcn(); // 调用基类被隐藏的 memfcn, 必须加上类作用域.
> d.Base::memfcn(10);
>
> Base* ptr_base = &d;
> ptr_base->memfcn(); // 通过基类指针调用基类的`memfcn()`
> ptr_base->memfcn(42); // 通过基类指针调用基类的`memfcn(int)`虚函数, 因为派生类未重写.
> }
> ```
> ![[_attachment/02-开发笔记/01-cpp/类与对象/cpp-多态.assets/IMG-cpp-多态-3883F2A761351CBD114ABCEF88B2C077.png|590]]
>
> [!example] 示例三:虚继承中
>
> - 对 VIM 而言,同时存在变量 `Derived1::ival` 以及 `Base::ival`,**后者被前者隐藏**。
> - 对 VIM 而言,同时存在函数 `Derived1::bar(char)` 以及 `Base::bar(int)`,**后者被前者隐藏**.。
>
> ```cpp
> struct Base {
> void bar(int) { cout << "Base::bar(int)" << endl; }
> int ival = 1;
> double dval = 3.14;
> };
>
> struct Derived1 : virtual public Base {
> void bar(char) { cout << "Derived1::bar(char)" << endl; }
> void foo(char) { cout << "Derived1::foo(char)" << endl; }
> char cval = 'A';
> };
>
> struct Derived2 : virtual public Base {
> void foo(char) { cout << "Derived2::foo(char)" << endl; }
> int ival = 2;
> char cval = 'B';
> };
>
> struct VIM : public Derived1, public Derived2 {
> void test() {
> // foo('G'); // 二义性错误, 在其直接基类Derived1与Derived2中均存在
> Derived1::foo('G');
> // cout << cval << endl; // 二义性错误
> cout << Derived1::cval << endl;
>
> // 尽管Derived2中也有继承自基类的`Base::bar(int)`, 但被VIM的直接基类中的`Derived1::bar(char)`隐藏.
> bar('G'); // Derived1::bar(char).
> // 尽管Derived1中也有继承自基类的`Base::ival`, 但被VIM的直接基类中的`Derived2::ival`隐藏.
> cout << ival << endl; // Derived2::ival
>
> // 虚基类Base::dval, 未被其任何派生类同名成员覆盖或隐藏, 故在间接子类中可以被直接访问.
> cout << dval << endl;
> }
> };
> ```
>
> [!example] 示例四:对于 lambda 函数
>
> ```cpp
> int main() {
> int x = 10;
> auto lambda = [&](int x) {
> // 局部作用域内的`x`遮蔽外层作用域的`x`, 无论是按值捕获函数按引用捕获.
> cout << x << endl;
> };
> lambda(25); // 输出25
> }
> ```
>
>
<br><br><br>
# 类的编译时名称查找
> 参见 [^2]
总结:
- **==类内声明==中使用的名字**,包括在**类的成员函数声明中**的 "**==返回类型==**"、"**==参数列表==**" 里使用的名字,都**必须==确保使用前已可见**==。
- **==类的成员函数的"函数体"中==可以使用类中定义的任何名字**,而无需考虑这些名字在类定义中的声明先后顺序。
> [!info] 编译器对 "类" 中名称的处理顺序
>
> 编译器分两步进行,首先编译**类成员的"==声明=="**,**处理完类中所有声明,即整个类可见后,后才会处理 "==成员函数的定义=="**。
> 因此:
>
> - **成员函数的 "==返回类型、形参=="** 里使用的名称,**必须在该成员函数之前可见**;
> - **成员函数的 "==函数体内=="** 可以使用类中定义的任何名字。(与 Python 等脚本语言不同)
> - 用**一个数据成员初始化另一个数据成员**时(以成员初始化列表 or 类内初始化项的形式),前者必须声明于后者之前。
>
#### 说明示例
> 参见 [^3]
> [!example] 示例一:**在 "==类定义==" 之前的 "==全局作用域==" 下搜索名称**
>
> - **函数声明**(返回类型,形参列表)中出现的名字,必须在该声明之前存在。
> - **函数体内可使用类中声明的任何名字**;
>
> ![[_attachment/02-开发笔记/01-cpp/类相关.assets/IMG-类相关-55463C44BBC67489241AD2C855C0687C.png|475]]
>
> [!example] 示例二:**在"类成员函数的==外部定义之前==" 的"==全局作用域=="下搜索名称**
>
> 成员函数 `setHeight()` 是在类外部定义的,所以**全局作用域下,在其之前出现的名称**均可被使用。
>
> ![[_attachment/02-开发笔记/01-cpp/类相关.assets/IMG-类相关-A7769DC393601A9137DF7611480421CC.png|475]]
>
<br><br>
# Buffer
## 闪念
> sudden idea
## 候选资料
> Read it later
<br><br>
# ♾️参考资料
# Footnotes
[^1]: 《C++ Primer》P210、P549-550
[^2]: 《C++ Primer》P232, P255, P715
[^3]: 《C++ Primer》P257
[^4]: 《C++ Primer》P706