C++学习 之 初识命名空间
Posted halberd-lee
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了C++学习 之 初识命名空间相关的知识,希望对你有一定的参考价值。
声明:
本人自学C++, 没有计算机基础,在学习的过程难免会出现理解错误,出现风马牛不相及的现象,甚至有可能会贻笑大方。 如果有幸C++大牛能够扫到本人的博客,诚心希望大牛能给予批评与指正!不胜感激!
学习的过程分为初识、入门、进阶三个阶段。
因为对C++没有什么了解,这样的学习设定可能也有失准确性。望兄弟们多指点。谢谢!
目录:
科普:标识符、作用域
1. 命令空间出现的由来及什么是命名空间及全局命名空间
2. 命令空间的调用
3. 定义、添加命名空间及成员
4. 嵌套命名空间
5. 未命名的命名空间
回顾
科普:
在学习命名空间时啊,有这么一个概念一定要先弄明白。不然你会学得和尚摸不着头脑的。这个就是标识符。
标识符:
标识符是用于表示以下内容之一的字符序列:
对象或变量名称
类、结构或联合名称
枚举类型名称
类、结构、联合或枚举的成员
函数或类成员函数
typedef 名称
标签名称
宏名称
宏参数
说简单一点,标识符就是指的对象、变量、类、结构等的名字
标识符的组成规则:第一个字符必须是字母或者是下划线, 除了第一个字符外,由字符、数字、下划线组成
作用域:
通常来说,一段程序代码中某个标识符的使用都是有范围限制的,而这个标识符在哪个代码范围内可以被使用就是这个标识符的作用域。
对于对象而言(其他也是一样的),比如在main函数中,对象的作用域为它所在的最近的一对花括号内。全局的对象的作用域为声明之后的整个文件(单个文件内,不涉及多文件的工程)。
我们拿个变量来举个例子:
halberd:~ # cat scope1.cpp
#include <iostream> using std::cout; using std::endl; int a=2; class test public: test() cout << \\"Testing varialbe \\\\\\"a\\\\\\" is used out of main()!\\" << endl; cout << \\"Testing variable \\\\\\"a\\\\\\" is :\\" << a << endl; test1(); static void test1() cout << \\"Testing varialbe \\\\\\"b\\\\\\" cout not be used out of main()!! \\" << endl; //cout << \\"Testing variable \\\\\\"b\\\\\\" is \\" << b << endl; //关键是这一行!请注意被注释的情况下跟没被注释的情况 ; static test test; int main() int b=3; cout << \\"variable \\\\\\"a\\\\\\" be used in main is :\\" << a << endl; cout << \\"variable \\\\\\"b\\\\\\" be used in main is :\\" << b << endl; return 0;
halberd:~ # g++ scope1.cpp -o scope1 halberd:~ # ./scope1 Testing varialbe "a" is used out of main()! Testing variable "a" is :2 Testing varialbe "b" cout not be used out of main()!! variable "a" be used in main is :2 variable "b" be used in main is :3
a定义在外,b定义在内。
我们看到上面程序代码编译正常,执行正常,a 可以在不同内调用,b也可以在内调用,但是是否可以在不同 内调用呢?
再看下取消注释行后的情况 :
halberd:~ # cat scope1.cpp
#include <iostream> using std::cout; using std::endl; int a=2; class test public: test() cout << \\"Testing varialbe \\\\\\"a\\\\\\" is used out of main()!\\" << endl; cout << \\"Testing variable \\\\\\"a\\\\\\" is :\\" << a << endl; test1(); static void test1() cout << \\"Testing varialbe \\\\\\"b\\\\\\" cout not be used out of main()!! \\" << endl; cout << \\"Testing variable \\\\\\"b\\\\\\" is \\" << b << endl; //关键是这一行!请注意被注释的情况下跟没被注释的情况 ; static test test; int main() int b=3; cout << \\"variable \\\\\\"a\\\\\\" be used in main is :\\" << a << endl; cout << \\"variable \\\\\\"b\\\\\\" be used in main is :\\" << b << endl; return 0;
halberd:~ # g++ scope1.cpp -o scope1 scope1.cpp: In static member function ‘static void test::test1()’: scope1.cpp:15:49: error: ‘b’ was not declared in this scope cout << "Testing variable \\"b\\" is " << b << endl;
这里我们看到,编译时出现错误 。提示在b在作用域中没有被声明。从这里我们证明了,b 只能在被声明的那个内使用,出了这部分代码区域,程序就无法识别了。
上面这个例子中 标识符 a 的作用域是整个文件,无论是在main外还是main函数内都可以使用,标识符 a 的使用在此段代码文件中是没有限制的,也就是说 标识符 a的作用域是整个文件。
而b的可用范围是在main 函数的内部,也就是说b的作用域在main 函数的内。当在main函数外部 调用时,我们发现编译时无法通过,提示:error: ‘b’ was not declared in this scope(‘b‘在该作用域中没有被声明)。那是在哪个作用域中没有声明呢?就是test1() 的花括号内了。
所以经过我们的测试,是否可以这样说呢:
单个文件内作用域最大范围是整个代码文件,最小范围是标识符所在的花括号内的代码范围,
为什么特别说明是单个文件内呢?因为随着我们学习知识越来越多,到时候会遇到作用域延伸的情况,比如某个标识符通过extern 被引用到另外一个文件中,这样这个标识符的作用域就得到了延伸。等我们入门以后再来学吧。这里只需要意识到还有其他情况 就可以了。
1. 命名空间的由来、什么是命名空间及全局命名空间
在认识什么是命名空间前,我们先来了解下,为什么会出现命名空间这个东西。
在了解前,我们先想想,我们上学的时候,是不是会经常遇到有同名同性的同学啊?我相信有不少人会有这种经历的。那么我们先假设下,在没有分专业,没有分班 级前,开了一次全校大会,大会上校长激昂陈词,要叫一个学生A上台,至于为啥要叫上台,叫上台干嘛我们不考虑,只想象下这个场景,而这个学校里有10个同 学叫A,校长这一,台下呼啦站起10个同学,校长是不是会蒙掉啊?哈哈
但是,校长了解到他想叫的这个同学是哪个院校哪个专业哪个班级的,校长找这个人的时候根据这些信息是不是就不会出现上面的情况了?校长把同学A的院校、专业、班级信息一起说出来,就避免了人名冲突的问题。
在C++开发过程中,也会遇到类似的情况,相同的标识符在同一个作用域中声明了多次,当对程序进行编译时就会出现冲突,编译器不知道应该 使用哪个标识符,这种冲突,在C++叫被叫作 Namespace Pollution。那为了避免或者说为了解决 这个问题,我们需要给这些标识符加上一些附加信息:“院校”、“专业”、“班级”,这些附加信息对应我们的代码文件中,就是命名空间了。
现在我们知道命名空间是怎么来的了,那我们怎么理解这个命名空间呢?我觉得应该从两个角度来理解,一个是组成,一个是作用。
同样的比喻,“班级”是一个命名空间,那班级里有什么呢?班级里是不是都是人啊?有教授,有助教,有男同学,有女同学等。那么我们就可以这样理解:从命名 空间的组成角度来讲,命名空间就是一个集合体,什么东西的集合体呢?
命名空间是标识符及其声明部分的集合。
那么从命名空间的作用角度又该怎么理解呢?
我们知道了,命名空间是用来避免标识符声明冲突的情况。那命名空间本身是以什么角色出现的呢?其实我们可以这样理解,命名空间就是一个作用域。C++程序 代码,由不同的作用域组成,在单个作用域中有不同的标识符,不同的作用域中有存在相同标识符的情况,当我们希望指定某个标识符时,其中一种方式就是使用命 名空间。也就是说,命名空间本身就是一个作用域。
综上,我们知道了,命名空间本身是一个作用域,由标识符及其声明部分组成。
那我们怎么理解全局命名空间呢?
《C++ Primer》中这样写道:
Names defined at global scopenames declared outside any class, function, or namespaceare defined inside the global namespace. The global namespace is implicitly declared and exists in every program. Each file that defines entities at global scope adds those names to the global namespace
按照我的理解,全局命名空间,是隐式定义的命名空间,没有明确的名字,整个程序里可以共用的(函数、类、命名空间以外)声明代码,都是声明在全局命名空间的。
使用全局命名空间中的标识符,需要使用作用域标识符“::”
2. 命令空间的调用
哎呀,我们搞明白了什么是命名空间,现在来学学怎么用吧。我们要学以至用,学了不用不成了纸上谈兵了么……嘿嘿
命名空间有三种用法(以std为例):
调用方式 | 优点 | 缺点 | 推荐 |
using namespace std; | 使用方便,无需重复指定,下次使用时不需要指定命名空间 | 整个命名空间全部对文件开放,容易引起标识符的冲突问题 | 否 |
using std:cout; | 定位精准,无需重复指定,下次使用时不需要指定命名空间,不容易出现标识符冲突问题。 | 未知 | 是 |
std::cout; | 定位精准, | 下次使用需要重新指定命名空间但是书写繁琐 | 否 |
关于调用命名空间关键词、符号的说明:
using: 指示符,当using 作为指示符使用时,后面必须跟namespace 关键词,如果后面跟的不是已声明的命名空间,会报错。当然,using 还有其他功能,如重新声明基类成员,这个后面再研究吧。一下子吃不透那么多东西。
:: : 作用域限定符,它的作用是说明“::”后面所跟的标识符所在的作用域,它是同namespace一同出现的,用于避免标识符冲突的问题。 如:std::cout ,说明标识符cout 是作用域std中的cout,程序发现了"::"就会到该限定符前的作用域中去查找该标识符的声明代码,以确定标识符的功能。
如果该限定符前面什么都没有呢?那程序就会从全局作用域中查找该标识符的定义、声明。(什么时候会用到呢?蛋疼啊~知道的太少,实在想不出场景~望大神能给个例子,救人一命胜造七级浮图啊~教人一卷胜过吃饭千碗,功德无量!!感谢老戴提示:类静态成员和函数时会用到。等 以后学到类和函数时再研究)
3. 定义、添加命名空间及成员
折腾好几天,终于算是对命名空间是个什么东西了,下面我们来学习定义命名空间吧~激动人心的时刻来临啦~
3.1 定义命名空间的语法结构
namespace namespace_name ………… //声明部分
在具体编写前,我们要先了解定义命名空间有哪些限制:
a) 由于命名空间只是作用域的一种表现形式,因此也需要遵守作用域中标识符的命名规则:作用域中的名字在该作用域中只能是唯一的,不然会出现冲突 。命名空间也一样,在一个命名空间中,名字也必须 是唯一的。
b) 命名空间可以在全局作用域或者其他作用域中定义,但是不能在函数、类结构内部定义
c) 命名空间作用域不能以分号结束
那命名空间允许我们定义哪些内容呢? 嘿嘿,只要是可以出现在全局作用域中的任意声明都可以在命名空间中定义,如类、变量及初始化、函数及定义、模板及其他命名空间(注意没有?命名空间中还可 以有命名空间呢,也就是说命名空间其实是可以嵌套的,后面会对这一点进一步了解。)
来来来,我们来写一个灰常简单的命名空间,先享受一次会写命名空间的乐趣,嘿嘿。
代码如下 :
#include <iostream> using std::cout; using std::cin; using std::endl; namespace halberd_ns int a = 1; int a = 2; int main() cout << \\"This variable is from namespace halberd_ns: a=\\"<<halberd_ns::a<<endl; cout <<\\"This variable is from global namesapce :: a=\\"<<a<<endl; return 0;
编译:
halberd:/home/C++/codes # g++ ./namespace_.cpp -o namespace_
执行:
halberd:/home/C++/codes # ./namespace_
This variable is from namespace halberd_ns: a=1 //从这里我们看到命名空间中变量的定义及初始化成功了,并在命名空间外成功调用命名空间中的变量
This variable is from global namesapce :: a=2
哈哈,成功了。
3.2 合并命名空间(2014-06-30 补充)
其实C++命名空间,可以分开写的。可以在同一个文件里分别声明不同功能的部分,也可以分到几个文件里声明。下面我们做个实验:
在上一小节,我定义了一个命名空间:halberd_ns . 下面,我们以这具为基础,进行实验。首先在文件内部再声明同名命名空间:
halberd:/home/C++/codes # cp namespace_.cpp ./namespace_1.cpp
halberd:/home/C++/codes # vi namespace_.cpp
halberd:/home/C++/codes # cat namespace_1.cpp
#include <iostream> using std::cout; using std::cin; using std::endl; namespace halberd_ns int a = 1; int a = 2; namespace halberd_ns //新声明命名空间 --看看是什么作用 int b = 3; int main() cout << \\"This variable is from namespace halberd_ns: a=\\"<<halberd_ns::a<<endl; cout <<\\"This variable is from global namesapce : a=\\"<<a<<endl; cout <<\\"This variable is used to test namespace consolidation: b=\\" <<halberd_ns::b<<endl; //验证我们的猜想 return 0;
看上面的修改的部分,一个是新声明了一个命名空间halberd_ns,然后添加一行,用以验证两人个命名空间是否同时生效。
看下结果:
halberd:/home/C++/codes # g++ ./namespace_1.cpp -o namespace_1
halberd:/home/C++/codes # ./namespace_1
This variable is from namespace halberd_ns: a=1
This variable is from global namesapce : a=2
This variable is used to test namespace consolidation: b=3
我们看到:
第一,原命名空间没有被覆盖,原来的声明还有效。
第二,声明已存在 的命名空间,会将两个命名空间的内容合并起来,同时生效,这就相当于命名空间的合并。
下面我们再做个实验,我们在不同的文件中声明同一个命名空间,看会是什么效果呢:
alberd:/home/C++/codes # vi attach_namespace.cpp
halberd:/home/C++/codes # cat attach_namespace.cpp
#include <iostream> namespace halberd_ns int c=4;
halberd:/home/C++/codes # vi namespace_1.cpp
halberd:/home/C++/codes # cat namespace_1.cpp
#include <iostream> #include \\"attach_namespace.cpp\\" using std::cout; using std::cin; using std::endl; namespace halberd_ns int a = 1; int a = 2; namespace halberd_ns int b = 3; int main() cout << \\"This variable is from namespace halberd_ns: a=\\"<<halberd_ns::a<<endl; cout <<\\"This variable is from global namesapce : a=\\"<<a<<endl; cout <<\\"This variable is used to test namespace consolidation: b=\\" <<halberd_ns::b<<endl; cout <<\\"This variable is used to test outer file\\‘s namespace consolidattion: c=\\" <<halberd_ns::c<<endl; return 0;
halberd:/home/C++/codes # g++ ./namespace_1.cpp -o namespace_1
halberd:/home/C++/codes # ./namespace_1
This variable is from namespace halberd_ns: a=1
This variable is from global namesapce : a=2
This variable is used to test namespace consolidation: b=3
This variable is used to test outer file‘s namespace consolidattion: c=4
从上面的实验,我们看到,命名空间halberd_ns, 分别在attach_namespace.cpp 中声明一次,并在namespace_1.cpp中声明两次。文件attach_namespace.cpp 被引用到namespace_1.cpp中,命名空间的声明也同时被引用过来。而且命名空间中的声明,也可以正常使用。
那么,我们可以这样说:
1) 命名空间的声明、定义可以是不连续的,一个命名空间可以分散到多个文件中,或者在同一个文件中的不同部分进行分别声明或者定义。
2) 同名命名空间的(非重复)声名、定义,可以累积、合并,在外界看来,就如同是只经过一次声明一样。
3.3 添加命名空间成员(待补充)
(感觉跟3.2 内容有些重复,但是确实是一块新东西。跟类相关,与老戴讨论半天,也没弄明白。先放一放吧。等 学完类再来补充这一块)
4. 嵌套命名空间(2014-07-01 补充)
所谓的命名空间嵌套,其实就是在命名空间作用域内再定义一个命名空间。格式类似如下:
namespace halberd_ns namespace halberd_ns1 //definitions ……
来来来,咱做个实验:
点击(此处)折叠或打开
#include <iostream> using std::cout; using std::cin; using std::endl; namespace halberd_ns int a = 1; namespace halberd_ns_nested int b=2; int main() cout << \\"This variable is from namespace halberd_ns: a=\\"<<halberd_ns::a<<endl; cout <<\\"This variable is from nested namesapce halberd_ns_nested:: b=\\"<<halberd_ns::halberd_ns_nested::b<<endl; return 0;
编译:
linux-emf1:/home/C++/codes # g++ ./namespace_nested.cpp -o namespace_nested
执行进行验证:
linux-emf1:/home/C++/codes # ./namespace_nested
This variable is from namespace halberd_ns: a=1
This variable is from nested namesapce halberd_ns_nested:: b=2
嘿嘿,果然又成功了。怎么样,咱学起来,不是很难吧。虽然遇到有不会的,无法 理解的。但是,我们一直在进步呢。对吧。只要在前进,就会离成功越来越近。
5. 未命名的命名空间(2014-07-01 补充)
未命名的命名空间,啥意思,不用我多说了吧,就是没有名字嘛。
定义格式如下:
namespace // definitions ……
前面我们了解过全局命名空间,还有印象不,没有的话,到本页最上面看。这全局命名空间有相似之处。你知道是啥不?对啦,就是没有名字。那他们之间有什么区别呢?弄清楚这个区别,咱这个没有名字的命名空间的特点也就了解了:
对比 | 全局命名空间 | 未命名的命名空间 |
是否允许嵌套 | 否 | 是,允许嵌套在其他命名空间内 |
使用范围 | 全局,可在工程内任意文件中引用成员 | 局部,只能在一个文件内的局部作用域中使用,不能跨文件使用 |
引用成员方式 | 使用作用域限定符"::" | 直接使用成员名,不可以使用作用域限定符"::" |
定义方式 | 隐匿定义(自动定义,每个工程都有自己的默认的一个全局命名空间) | 显式定义 |
未命名的命名空间其他特点:
1) 未命名的命名空间中定义的变量在程序开始时创建,在程序结束时释放一直存在
2) 如果在最外层定义未命名的命名空间,成员名不能与全局作用域中的名字不一样,为啥?会跟全局命名空间的成员冲突呗
回顾:
我们对于命名空间都学习了哪些东西呢?
1. 什么是标识符和作用域
2. 命名空间的由来
3. 什么是命名空间(从作用及组成两方面来考虑)
4. 命名空间的调用
5. 命名空间的定义
6. 命名空间合并
7. 添加命名空间成员(待补充)
8. 未命名的命名空间
9. 嵌套命名空间
以上是关于C++学习 之 初识命名空间的主要内容,如果未能解决你的问题,请参考以下文章