为什么要使用命名空间?
一个大型的工程往往是由若干个人独立完成的,不同的人分别完成不同的部分,最后再组合成一个完整的程序。由于各个头文件是由不同的人设计的,有可能在不同的头文件中用了相同的名字来命名所定义的类或函数,这样在程序中就会出现名字冲突。不仅如此,有可能我们自己定义的名字会与C++库中的名字发生冲突。名字冲突就是在同一个作用域中有两个或多个同名的实体,为了解决命名冲突 ,C++中引入了命名空间,
所谓命名空间就是一个可以由用户自己定义的作用域,在不同的作用域中可以定义相同名字的变量,互不干扰,系统能够区分它们。
什么是命名空间?
命名空间又称为名字空间,是程序员命名的内存区域,程序员根据需要指定一些有名字的空间域,把一些全局实体分别存放到各个命名空间中,从而与其他全局实体分隔开。通俗的说,每个名字空间都是一个名字空间域,存放在名字空间域中的全局实体只在本空间域内有效。名字空间对全局实体加以域的限制,从而合理的解决命名冲突。
C++中定义命名空间的基本格式如下:
cpp
namespace wd
{
int val1 = 0;
char val2;
}// end of namespace wd
在声明一个命名空间时,大括号内不仅可以存放变量,还可以存放以下类型:
变量
常量
函数,可以是定义或声明
结构体
类
模板
命名空间,可以嵌套定义如下
cpp
namespace wd
{
int number = 0;
struct Foo
{
char ch;
int val;
};
void display();
}// end of namespace wd
定义在名称空间中的变量或者函数都称为实体,名称空间中的实体作用域是全局的, 并不意味着其可见域是全局的。如果不使用作用域限定符和using机制,抛开名称空间嵌套和内部屏蔽的情况,实体的可见域是从实体创建到该名称空间结束。
在名称空间外,该实体是不可见的。
命名空间的使用方式
命名空间一共有三种使用方式,分别是
1.using编译指令
2.作用域限定符
3.using声明机制
using编译指令:
我们接触的第一个C++程序基本上都是这样的,其中std代表的是标准命名空间。
cpp
#include<iostream>
// using namespace std; 是using编译指令,引入了整个std命名空间
// 可以使用std命名空间中的所有实体,但在大型项目中不推荐全局使用
using namespace std; // std表示C++标准库的命名空间
void test()
{
// 由于使用了using namespace std,可以直接使用cout和endl
// cout是std::ostream类的一个预定义全局对象(标准输出流)
// endl是一个操纵器函数,用于插入换行符并刷新输出缓冲区
cout << "Hello World!" << endl;
}
int main()
{
test();
return 0;
}
其中第二行就使用了using编译指令。如果一个名称空间中有多个实体,使用using编译指令,就会把该空间中的所有实体一次性引入到程序之中;对于初学者来说,如果对一个命名空间中的实体并不熟悉时,直接使用这种方式,有可能还是会造成名字冲突的问题,而且出现错误之后,还不好查找错误的原因,比如下面的程序就会报错,当然该错误是人为造成的。
cpp
#include <iostream>
using namespace std;
double cout()
{
return 1.1;
}
int main(void)
{
cout();
return 0;
}
作用域限定符:
第二种方式就是直接使用作用域限定符::啦。每次要使用某个名称空间中的实体时,都直接加上,例如:
cpp
#include<iostream>
namespace zjy
{
int num = 19;
void display()
{
//cout,endl都是std空间中的实体,所以都加上'std::'命名空间
std::cout << "zjy::display()" << std::endl;
}
}
void test()
{
std::cout << zjy::num << std::endl;
zjy::display();
}
int main()
{
test();
return 0;
}
这种方式会显得比较冗余,所以还可以采用第三种使用方式。
using声明机制:
using声明机制的作用域是从using语句开始,到using所在的作用域结束。要注意,在同一作用域内用。
using声明的不同的命名空间的成员不能有同名的成员,否则会发生重定义。
cpp
#include <iostream>
using std::cout;
using std::endl;
namespace zjy
{
int number = 10;
void display()
{
cout << "zjy::display()" << endl;
}
}
using zjy::number;
using zjy::display;
void test()
{
cout << "zjy::number = " << number << endl;
display();
}
int main()
{
test();
return 0;
}
在这三种方式之中,我们推荐使用的就是第三种,需要哪个实体的时候就引入到程序中,不需要的实体就不引入,尽可能减小犯错误的概率。
namespace的嵌套使用:
cpp
#include <iostream>
using std::cout;
using std::endl;
namespace zjy
{
int number = 1;
void display()
{
cout << "zjy::display()" << endl;
}
namespace cpp
{
int number = 11;
void display()
{
cout << "zjy::cpp::display()" << endl;
}
}//end of cpp
}//end of zjy
void test()
{
zjy::display();
cout << zjy::number << endl;
zjy::cpp::display();
cout << zjy::cpp::number << endl;
}
int main()
{
test();
return 0;
}
感觉和文件目录很像,一层一层的
匿名命名空间:
命名空间还可以不定义名字,不定义名字的命名空间称为匿名命名空间。由于没有名字,该空间中的实体,其它文件无法引用,它只能在本文件的作用域内有效,它的作用域是从匿名命名空间声明开始到本文件结束。在本文件使用无名命名空间成员时不必用命名空间限定。其实匿名命名空间和static是同样的道理,都是只在本文件内有效,无法被其它文件引用。
cpp
namespace
{
int val1 = 10;
void func();
}//end of anonymous namespace
在匿名空间中创建的全局变量,具有全局生存期,却只能被本空间内的函数等访问,是static变量的有效替代手段。
cpp
#include <iostream>
using std::cout;
using std::endl;
//匿名命名空间,没有名字
//只能在当前命名空间使用
namespace
{
int number = 1;
}
void test()
{
cout << number << endl;
}
int main()
{
test();
return 0;
}
匿名空间存在的意义:
匿名命名空间在C++中存在的核心意义在于提供了一种强有力且一致的封装机制,它:
-
保护实现细节不被意外使用
-
防止跨文件的命名冲突
-
提供比static更全面和现代的作用域控制
-
促进更好的软件架构设计
-
为编译器优化创造更多机会
-
支持现代C++编程的最佳实践
这种设计体现了C++"你不用的东西不需要付出代价"的零开销原则,同时为大型软件项目的可维护性和健壮性提供了重要保障。
全局命名空间:
全局命名空间是C++程序中最基础、最外层的命名空间,它是所有命名空间的根源和起点。在C++程序中,任何没有显式指定命名空间的标识符都默认属于全局命名空间。这个命名空间没有具体的名称,但可以通过空的作用域解析运算符"::"来显式访问。
cpp
#include <iostream>
using std::cout;
using std::endl;
void test()
{
//printf()是c语言中的函数,在c++中有它的命名空间吗
//其实c语言中的函数都在全局命名空间中
//可以通过::来访问它
::printf("Hello World\n");
}
int main()
{
test();
return 0;
}
核心特征
隐式存在性
全局命名空间不需要程序员显式声明,它是编译器自动创建和维护的。当我们在代码中直接定义函数、变量或类而不将其放入任何命名空间时,这些实体就自动归属于全局命名空间。
访问机制
访问全局命名空间中的成员有两种方式:隐式访问和显式访问。隐式访问是直接使用标识符名称,编译器会自动在全局命名空间中查找;显式访问则是使用"::"前缀,如"::functionName",这种方式可以明确指示编译器在全局命名空间中查找,避免与局部或其他命名空间中的同名标识符冲突。
链接性规则
全局命名空间中的实体默认具有外部链接特性,这意味着它们可以被程序中的其他翻译单元(源文件)访问。然而,通过使用static关键字或const限定符(在C++中const全局变量默认具有内部链接),可以限制其链接性,使其仅在当前文件内可见。
内容组成
内置类型与函数
全局命名空间包含了C++语言的所有内置数据类型(如int、float、char等)和基本操作。同时,当使用C风格头文件(如<stdio.h>、<stdlib.h>)时,相应的C标准库函数也会被放置在全局命名空间中。
用户定义实体
程序员定义的全局函数、全局变量、全局类和全局枚举等,只要没有显式放入特定命名空间,都会成为全局命名空间的成员。
特殊函数
程序的入口点main函数必须位于全局命名空间中,这是C++语言标准的规定。此外,全局操作符函数和全局友元函数也属于全局命名空间。
匿名命名空间 vs 全局命名空间对比
定义与标识
-
匿名命名空间 :没有名称的命名空间,使用
namespace {}语法定义 -
全局命名空间:默认的根命名空间,所有不在显式命名空间中的实体都位于此
作用范围
-
匿名命名空间:仅限于定义它的当前文件内部,从声明处到文件结束
-
全局命名空间:整个程序范围内可见,可被所有文件访问
链接性
-
匿名命名空间:内部链接,实体对其他翻译单元完全不可见
-
全局命名空间:默认外部链接,实体可被其他文件引用(除非使用static限制)
访问方式
-
匿名命名空间:直接使用成员名称,无法使用命名空间限定符(因为没有名称)
-
全局命名空间 :可直接使用名称,也可用
::前缀显式指定
主要用途
-
匿名命名空间:隐藏文件内部的实现细节,防止命名冲突,替代static关键字
-
全局命名空间:放置真正需要全局访问的实体,与C语言库交互
设计意图
-
匿名命名空间:封装和隔离,促进模块化设计,避免意外依赖
-
全局命名空间:提供程序级共享机制,维护与C语言的兼容性
现代实践
-
匿名命名空间:推荐用于文件局部实体,替代传统的static用法
-
全局命名空间:应谨慎使用,避免污染,优先考虑自定义命名空间
匿名命名空间强调封装和隔离 ,全局命名空间强调共享和兼容,两者在C++中各有其不可替代的作用。