欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

第18章 用于大型程序的工具 18.2命名空间

程序员文章站 2022-07-14 19:49:04
...

18.2 命名空间

命名空间可以有效的防止变量名重复,命名空间就是一个作用域,可以在命名空间中定义命名空间,但是不能在类和函数中定义。

18.2.1 命名空间定义

使用这种方法来定义命名空间

namespace xxx{
}

命名空间都是一个作用域,所以和作用域中的变量是一样的,变量名字不能重复。

和作用域不一样的是,命名空间可以不是连续的。我们定义一个命名空间,如果之前没有这个空间,则创建这个空间,如果之前就已经有了这个空间,那么就是在这个空间中添加新的成员。

对于头文件和cpp文件,我们可以分别将其写在命名空间中
第18章 用于大型程序的工具 18.2命名空间
头文件通常不包含在命名空间中,因为这样做会导致头文件拥有的成员也在该空间中。

全局命名空间通过
::来访问

命名空间是可以嵌套的,但是外层的命名无法直接访问内层命名空间定义的成员,使用inline命名空间可以解决这个问题

namespace B{
inline namespace A{
}
}

命名空间B可以直接访问命名空间A中的变量了。

我们可以不为命名空间命名,这样的空间叫做
未命名的命名空间,在未命名的命名空间中声明的变量都是静态类型,可以直接访问,因为它没有名字,所以我们无法通过类作用域符来访问。 但是其作用域在当前的文件中,无法跨多个文件,这点和全局变量不一样。

因为它可以直接访问,所以这就要求全局变量的名字和未命名空间的名字不能一样,否则会有二义性。

修饰为static的全局变量作用域为当前的文件,无法通过extern在其他文件中访问。

18.2.2 使用命名空间

我们可以通过namespace::变量名来访问变量,但是这样比较繁琐,尤其是变量名字很长的时候。

我们可以为命名空间起别名

namespace c_p_p = C_plus_plus;
namespace A = BB::CC::AA;

这对于嵌套的命名空间也是适用的。

using声明

我们也可以适用using来引入命名空间的一个成员,using声明可以适用在任何作用域中。包含全局作用域,局部作用域,命名空间作用域和类作用域等中。。。

using指示

using指示使用

using namespace std;

的方式,将命名空间中的所有成员都引入当前的作用域,因为这么做我们无法确定引入了什么成员,所以这么做是由风险的。
同时using指示只能用在全局,局部、命名空间作用域中。

using指示会导致命名空间的左右成员对于使用的地方都是全部可见的,如果有多个命名空间使用了using指示,可能会导致多个命名空间之间由相同名字的变量,从而导致冲突。

另一个问题是,可能当前作用域中定义的变量和命名空间的成员名字有冲突,导致报错。

所以使用using指示是存在一定风险的,但是如果写一般的小代码,不是大型的项目的话,使用一下还是很方便的。

18.2.3 类、命名空间和作用域

在命名空间中名字查找是从内开始向外找的,同时名字查找只从其声明语句之前的语句中查找,

namespace A{
	int a;
	int b;
	class cls_A{
	public:
	void f(){
	++a;//正确
};
void f1(){
++c;//错误
}
	}
	int c;
}

在命名空间中的类,在使用名字的时候,首先从当前函数的作用域中查找,然后从类的数据成员中找,然后从基类的数据成员中找,然后再从外部命名空间找,逐层向外的方式。

有一个例外,在我们使用

stirng s;
cout<<s;

的时候,对于<<是重载在string中的,但是如果不显式的引入该成员,上面的操作依旧是对的。

当我们给函数传递一个类类型的对象时,除了在常规的作用域查找之外还会查找实参类所属的命名空间。这对传递类的引用或者指针的调用同样有效。

对于<<,其形参是cout和string类型,所以编译器会在ostream和stirng的命名空间中查找对应的<<操作。此operator<<(std::cout,string s)定义在std中,找得到所以会直接调用。

对于友元函数,之前学习到,如果一个类中声明了一个友元函数,但是在类外面没有该函数的声明,那么这个友元函数是不可见的。

但是如果在命名空间中,在命名空间的类名中定义了友元,但是没有任何声明。编译器会认为这些友元函数是它最近的外层命名空间的成员。 尽管我们并没有对此做任何声明和定义。

第18章 用于大型程序的工具 18.2命名空间
书上的例子就是一个很好的例子,定义了cobj。f(cobj),此时A命名空间是没有在当前作用域中的,但是f(cobj)调用成功了,因为他会在实参的命名空间中查找这个函数是否存在,答案是存在的,所以调用成功,但是对于f2(),因为其没有形参,无法进行推断,所以是错误的。

我觉得这种方法反而让C++变得更加复杂化了。

练习
18.18

如果是string,那么调用的是string的命名空间std中定义的swap版本,而如果是int则使用的默认版本,如果我们定义了自己版本的string或者int类的swap,那么调用的就是自己定义的swap。

18.19

如果使用的是std::swap()那么调用的一致就是std标准库的版本。

18.2.4 重载和命名空间

之前学习到,一可以通过函数的实参推算出具体调用哪一个函数。以及在该实参类型的命名空间中寻找这个函数,其实这就以及构成了重载。

就和swap一样,首先使用using std::swap();然后定义自己的版本,我们调用了swap(),就可以根据实参类型来推断。

重载和using声明

当我们使用using namespace::func;的时候,在该命名空间下的所有func都对于当前作用域是可见的,所以和当前作用域的同名函数构成了重载的关系,因此在当前作用域中调用func的时候,所以的func都是候选函数。

但是如果命名空间和当前作用域的函数有一个一摸一样的时候,则编译器会报错。。
但是使用using指示是不会的,就算所有参数都一样,using指示依旧不会报错

所以使用using声明,看起来更好。

跨越多个using指示的重载

其实这个和上面的是一样的,只要是同名函数就会被选入候选列表。

练习
18.20

所有compute函数都是候选函数,
其中
compute(const void*);
compute(int);
compute(double,double=3.4);
compute(char*,char*=0);
是可行函数,但是除了int,其余的函数都需要类型转换,所以comput(int)是最终匹配的函数。

如果using放在main函数中,那么外面的compute函数都会被覆盖,此时mian函数中可见的函数时

compute();

compute(const void*);

但是compute()不是可行的,compute(const void*);是可行的,所以直接调用的是compute(const void*);

相关标签: C++ C++ 基础