立此存照(19)[C++]C++中的名称查找(name lookup)

时间:2022-05-31 21:08:18

1命名空间是作用域,要使某个名字成为命名空间的成员,要定义这个名字,函数可先声明,然后可在别处定义


2.当编译器碰到一个名字,此时它需要知道这个名字是哪个作用域提供的,这时就会用到名称查找。

3.名称查找的一般次序是:从当前作用域逐层向外查找,直到查找到全局作用域为止。

4.同一作用域中的名称查找一般原则:只能从使用该名字的使用点,向上查找已定义的名字。

例外情况:当类的成员是数据成员时,出现在使用点后的名字也可以被当前使用点合法使用,但对于类的类型成员,如上面的myint,是不成立的,类的类型成员函数必须先定义,后使用。遵循一般名字查找原则。

代码:

#include <iostream>

namespace myspace{
	//int v3 = v1;//v3使用点,还未定义v1,故不能使用v1
	int v1 = 10;
	int v2 = v1;

}
class myclass{
	int v1 = v2;//没问题,可以使用v2
	int v2 = 10;
	int v3 = v2;
};
class myclass2{
	//在当前使用点使用v1时,myint类型还未定义,故不能使用
	//myint v1;//必须先定义后使用
	typedef int myint;
	myint v2;
};
5.当前作用域与上级作用域之间的名称查找一般原则: 名称在上级作用域中查找时,从当前作用域定义点向上查找,也就是说,上级作用域当前拥有的名字是从在上级作用域中定义当前作用域以上的名字,至于一下的名字,还没定义为上级作用域的名字,当然无法访问。

#include <iostream>

namespace myspace{
	int i = 0;
	void func(){
		std::cout << "myspace::func()#" << i << std::endl;
	}
}

namespace myspace2{
	void func(){
		//由于名字i的声明在func函数之后,
		//所以无法找到名字i
		//std::cout << "myspace2::func()#" << i << std::endl;//C2065
	}
	int i = 0;

}

int main(){
	myspace::func();
	myspace2::func();

	system("pause");
	return 0;
}
例外情况:对于类作用域而言,其成员函数作用域可以使用类作用域中任何位置的名字

#include <iostream>

//函数作用域与类作用域
class myclass{
public:
	typedef int myint;
	void f(){
		myint i = 0;
		mydouble j = 0.0;//没问题,可以使用函数作用域后面定义的类型名字
	}
	typedef double mydouble;
};
class myclass2{
	void f1(){
		f2();//完全可以使用,没问题
	}
	void f2(){
		std::cout << "myclass2::f2()" << std::endl;
	}
	void f3(){
		f2();
	}
};

注意事项:对如下情况:f2函数是在myspace作用域定义完之后才定义的作用域,因此在f2作用域内可以访问名字i,而f1函数作用域定义时,其上级作用域才定义了比f1名字定义更早的名字(这里没有比myspace作用域中没有比f1更早的名字了),此时,上级作用域中的以定义的名字只有f1,所以f1作用域中找不到名字i

#include <iostream>

namespace myspace{
	void f1(){
		//当前f1函数作用域无法找到名字i
		//f1的上一级作用域,在f1作用域定义点之前的范围内,也没有找到名字i
		//std::cout << "myspace::f1()=" << i << std::endl;//C2065
	}
	void f2();//声明
	int i = 0;
}

void myspace::f2(){//此时myspace作用域名字已经定义完毕,可以使用名字i
	std::cout << "myspace::f2()=" << i << std::endl;
}

int main(){
	myspace::f2();

	system("pause");
	return 0;
}
执行结果:

立此存照(19)[C++]C++中的名称查找(name lookup)
可以通过如下示例理解前面2条原则:

#include <iostream>

namespace A{
	int Ai = 0;
	namespace B{
		int Bi = 1;
		namespace C{
			void Cf1(){
				++Ai;//A作用域中已有Ai这个名字
				//Cf1作用域无法访问Aj,因为此时A作用域中还只有Ai,B这2个名字
				//++Aj;//C2065
				++Bi;
				//++Bj;//同Ai一样,Bj无法访问
			}
			void Cf2();
			namespace D{
				void Df1(){
					//定义作用域C定义名字D时,还未定义名字Cf3
					//但已定义了名字Cf2,这里的"定义"是指已经声明这个Cf2这个名字
					Cf2();
					//Cf3();C3861
				
				}
			}
			void Cf3();

		}
		int Bj = 1;
	}
	void B::C::Cf2(){
		++Ai;
		//++Aj;//Aj名字还没出现在作用域A中
		++Bi;
		++Bj;//但可以访问Bj了,因为此时B作用域已定义完成,有了Bi, C, Bj 3个名字
	}
	int Aj = 0;
}
//此时A作用域已定义完成(当然B,C作用域也定义完成),
//当然可以使用各个作用域中的所有名字
void A::B::C::Cf3(){
	++Ai;
	++Aj;
	++Bi;
	++Bj;
}

6.先看一段代码:

#include <iostream>
#include <string>

namespace myspace{
	class C{};
	void f1(C c){
		std::cout << "f1(C c)" << std::endl;
	}
	void f2(C &c){
		std::cout << "f2(C &c)" << std::endl;
	}
	void f3(C *c){
		std::cout << "f3(C *c)" << std::endl;
	}
}

namespace myspace2{
	void func(){
		myspace::C c;
		f1(c);
		f2(c);
		f3(&c);
	}
}

int main(){
	myspace2::func();

	system("pause");
	return 0;
}
执行结果:

立此存照(19)[C++]C++中的名称查找(name lookup)
上述代码,对于f1,f2, f3 这3个名字,若按照之前的名字查找,是找不到的,但为什么现在却可以正常使用。这就涉及到C++中另一个名字查找原则实参相关的查找方式:

实参相关的查找方式:当函数形参是类类型(包括类类型的引用,指针,直接传值)时,编译器将会对所有类类型名字所在的作用域也进行查找。

上述代码中对形参类型名字所在作用域myspace查找的结果是找到了f1,f2,f3这3个名字,函数得到了正确的调用。

接下来考虑一下编译器进行名字查找的次序:即常规名字查找和实参相关的查找方式的优先次序

通过代码:

#include <iostream>
#include <string>

namespace myspace{
	class C{};
	void f1(C c){
		std::cout << "f1(C c)" << std::endl;
	}
	void f2(C &c){
		std::cout << "f2(C &c)" << std::endl;
	}
	void f3(C *c){
		std::cout << "f3(C *c)" << std::endl;
	}
}

void f1(myspace::C c){
	std::cout << "global::f1()" << std::endl;

}
namespace myspace2{
	void func(){
		myspace::C c;
		//f1(c);//同时找到2个f1的名字,即global::f1, myspace::f1,编译器无从下手
		f2(c);//不存在问题,因为从当前使用点查找,找不到global::f1详见前面的分析
		f3(&c);
	}
}
void f2(myspace::C &c){
	std::cout << "global::f1()" << std::endl;

}


int main(){
	myspace2::func();

	system("pause");
	return 0;
}
执行结果:

立此存照(19)[C++]C++中的名称查找(name lookup)
通过上述代码,可以得出结论,编译器会同时进行2中方式的名字查找:常规和实参相关的查找,若2种方式都找到了目标名字,编译器无法决定使用哪个名字,就会产生错误。

7.下面讨论一个特殊的话题:友元的作用域问题,同样通过代码来说明问题:

#include <iostream>

namespace myspace{
	void f1(){
		//C c;//C2065,不能使用名字C,具体原因详见之前讨论
		//ff(c);
	}
	class C{
		friend void ff1(){
			std::cout << "myspace::C::friend ff()" << std::endl;
		}
		friend void ff2(C c){
			std::cout << "myspace::C::friend ff(C c)" << std::endl;
		}
	};
	void f2(){
		//ff1();找不到ff1标识符
		C c;
		ff2(c);
	}
}
int main(){
	myspace::f2();

	system("pause");
	return 0;
}

执行结果:

立此存照(19)[C++]C++中的名称查找(name lookup)

ff1标识符未找到,可以理解,但ff2标识符竟然找到了,通过常规名字查找方式和实参相关方式都该找不到才是,但找到了。这里的原因是,友元函数在类中进行友元声明之后,实际上拥有了和它所在的类在同一作用域的扩展。这样通过,实参相关方式就可以找到该名字。
最后一个示例:

#include <iostream>

namespace AA{
	namespace BB{
		class C{
			friend void ff(){
				std::cout << "AA::BB::ff()" << std::endl;
			}
			friend void ff2(C c){
				std::cout << "AA::BB::ff(C c)" << std::endl;
			
			}
		};
		//ff();//无法访问
		void f1(){
			C c;
			ff2(c);
		}
	}
	void f2(){
		BB::C c;
		ff2(c);
	}
}

int main(){
	AA::BB::f1();
	AA::f2();

	system("pause");
	return 0;
}

执行结果:

立此存照(19)[C++]C++中的名称查找(name lookup)

对于参数类型没有所在类的类类型的友元函数,目前认为不会进行作用域提升。

【1】以上代码均在VS2013中编译运行

【2】参考书目C++ Primer(4th)中文版