笔者正在学习C++语言,啃书系列将会持续更新,希望可以同大家一起学习,一起进步。如果文章有帮助的话,记得点赞、收藏、关注一条龙哦。
系列文章:
啃书《C++ Primer Plus》之 C++ 函数指针
啃书《C++ Primer Plus》之 C++ 名称空间2
名称空间是C++语言不可或缺的特色,在名称管理上有着举足轻重的作用。这篇文章就来总结一下名称空间这个知识点。
参考资料:《C++ Primer Plus》
本文思维导图如下:
由于内容较多,该节拆分成两部分,本文为上部分,内容到名称空间的冲突。下部分见《名称空间2》
名称空间的作用与使用规则
名称空间解决的问题
在名称空间之前,C和C++语言通过局部、全局、静态等属性限制一个变量或函数的作用域,同时限定其名称作用范围。但是这样的解决方式在面临大项目时还是会出现问题,不同文件可能会定义相同名称的全局变量,这样如果一个文件同时引用了包含同一名称全局变量的两个文件,就会造成名称空间问题。
名称空间的引入,就是为了解决这类问题,为名称提供一个名称空间,不同的名称空间,可以包括相同的名称,彼此互不干涉。
另外,上文提到的全局变量,在引入名称空间的机制后,也有了对应的名称空间——全局名称空间(global namespace)。这个名称空间包含了所有的全局变量。
名称空间特点与成员的声明定义
名称空间特点
名称空间的声明与定义,需要用到关键字: namepace,写法如下:
namespace 名称空间名称{
.
.
.
}
名称空间的作用域是全局。也就是说,对于整个工程,某个文件A引入了包含名称空间S的文件B,那么文件A便可以使用名称空间S。常见的,我们引用 iostream 其中就包含了 std这个名称空间。(这个提点在嵌套时是例外的,在下文会谈到)
名称空间是开放的。 这意味着可以加入一个名称到已有的名称空间中,语法同上。这也就代表着,当编译器检测到上文的语法时,如果已经存在这个名称空间,则会将这个代码块中的内容添加到已有的名称空间中,否则就创建一个新的名称空间。
需要特别注意的是:
名称空间不能位于代码块中!
名称空间不能位于代码块中!
名称空间不能位于代码块中!
名称空间不能位于代码块中!
’写名称空间内容的时候,一定要在代码快之外,不要放在任何的函数等的代码块中。
名称空间内容
在名称空间大括号中可以定义结构,类,变量,函数等,他们都属于这个名称空间。
需要注意的是,结构,类,函数这些内容的声明与定义通常是分开的,而在命名空间中,声明和定义也可以分开写。
我们知道,在c++语言中,声明和定义是两个不同的概念,声明告诉编译器格式,而定义是具体实现。
通常,将成员定义在名称空间中的代码放在 .cpp文件中,而将成员声明在名称空间的代码放在对应的头文件中。
举个栗子:
//test.h
#ifndef TEST_H_INCLUDED
#define TEST_H_INCLUDED
namespace test
{
int k = 10;
int f(int,int);
class A
{
public:
int getA();
void setA(int);
private:
int a;
};
}
#endif // TEST_H_INCLUDED
//test.cpp
#include "test.h"
namespace test
{
int f(int a,int b)
{
return a + b;
}
int A::getA()
{
return this->a;
}
void A::setA(int a)
{
this->a = a;
}
}
这个程序中,定义了名称空间 test 。在头文件中,向 test 空间内放入了函数 f 以及类 A 的声明。在对应的 .cpp文件中对他们进行了定义。
使用名称空间
定义了名称空间并向其中填写内容后,可以在代码块中使用这个名称空间中的名称。这里介绍三个使用方式。
作用域解析符 “::”
首先,作用域解析符 "::"提供了访问名称空间中名称的方式。用法如下:
名称空间::某名称;
功能类似于结构或类对象访问其成员用的 “.”,相当于表明调用某个名称空间下的某名称。
常见的,调用 std 名称空间下的 cout、endl 关键字,例如:
...
std::cout << "hello world!" << std::endl;
...
除了作用域解析符的访问方式,C++提供了两种机制来简化对名称的使用,下面来介绍他们。
using声明
在这里,需要引入一个新的关键字:using,那么它的含义就如同其字面意思:使用。
使用什么呢?using声明提供了一种使用名称空间下名称的方式。它的用法如下:
using 名称空间::名称;
using声明将名称添加到所属的声明区域中。如果这条语句出现在某个代码块内,那么只在这个代码块中可以直接使用这个名称。如果这条语句出现在全局,所有在这之后的语句都可以直接使用这个名称。
用using声明实现上面的代码:
...
using std::cout;
using std::endl;
cout << "hello world!" << endl;
...
nice !!~~~
using编译指令
不同于using声明使一个名称可用。using编译指令可使名称空间中所有名称可用。它的写法如下:
using namespace 名称空间;
是不是非常的熟悉呢,在编写c++程序的时候,常常会在文件引用完头文件后,加上这么一句:
using namespace std;
这句话的意思就是,使用using编译指令,使用所有 std 名称空间的名称。也因此,我们才可以在程序中直接的使用 cin cout endl string 等关键字。
再来用using编译指令实现上面的代码:
using namespace std;
...
cout << "hello world" << endl;
...
名称空间的冲突
名称空间的使用并非能避免所有的名称冲突问题,如果使用不当,仍会造成问题。
using编译指令的冲突问题
using编译指令引入一个名称空间中所有的名称,其中不乏有一段程序中本身就包含的名称。另外前面还提到,不同的名称空间可以允许相同的名称存在,但是当同一段程序同时使用using编译指令引用两个包含相同名称的名称空间时,问题就会出现。
名称空间之间的冲突
当使用using编译指令使用两个名称空间时,若其中包含有相同的名称,则编译器会认为这个名称具有两义性,从而报错。
名称空间与原有名称的冲突
当引入的名称空间中的名称与原有的变量等名称像冲突时,参考如下代码:
#include <iostream>
using namespace std;
namespace A
{
int k = 10;
int l = 9;
int f(){return 10086;}
struct Node{};
class Tree{};
}
int f();
struct Node{};
class Tree{};
int l = 99;
int main()
{
int k = 20;
using namespace A;
cout << k << endl;
cout << l << endl;
cout << f() << endl;
Node node;
Tree tree;
return 0;
}
int f(){return 10010;}
编译器给出如下错误信息:
提示全局变量l、函数f、结构Node、类Tree含义模糊
可知,新引入的名称覆盖了原有的局部变量的名称,并和已经存在的函数、全局变量、结构、类等产生冲突。
要解决这些问题,在使用特定的名称时,可以使用域名解析符来使用名称空间中的名称。
using声明的名称冲突
上面说明了using编译指令引入全部名称所带来的问题,那么单个引入名称的using声明会不会造成同样的问题呢?
将上面的代码替换成:
#include <iostream>
using namespace std;
namespace A
{
int k = 10;
int l = 9;
int f(){return 10086;}
struct Node{};
class Tree{};
}
int f();
struct Node{};
class Tree{};
int l = 99;
int main()
{
int k = 20;
using A::k;
using A::l;
using A::f;
using A::Node;
using A::Tree;
cout << k << endl;
cout << l << endl;
cout << f() << endl;
Node node;
Tree tree;
return 0;
}
int f(){return 10010;}
此时,编译器告知:错误仅有一个,就是变量k已经存在,不能引入,而对其它成员,则没有问题。
删除引入k,运行程序,得到如下结果:
最后得出结论:using声明的名称冲突与using编译指令不同。对于已经储存在的局部变量,会产生多重定义的错误,对于全局变量,函数,结构,类等则会进行覆盖。