%% # 纲要 > 主干纲要、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