C++命名空间

命名空间

为什么需要命名空间

  • 命名空间是ANSI C++引入的可以由用户命名的作用域,用来处理程序中常见的同名冲突
  • 在C语言中定义了3个层次的作用域,文件(编译单元)、函数复合语句。C++又引入了作用域,类是出现在文件内的。在不同的作用域中可以定义相同名字的变量,互不干扰,便于系统区别它们。
  • 如果在文件中定义了两个类,在这两个类中可以有同名的函数。在引用时,为了区别,应该加上类名作为限定
  • 如果在同一个程序文件中出现了多个同名的类或函数,显然是重复定义,这就是名字冲突,即在同一个作用域中有两个或多个同名的实体
  • 不仅如此,在程序中还往往需要引用一些库(包括C++编译系统提供的库、由软件开发商提供的库或者用户自己开发的库),为此需要包含有关的头文件。如果在这些库中包含有与程序的全局变量同名的实体,或者不同的库中有相同的实体名,则在编译时就会出现名字冲突。有人称之为全局命名空间污染(global namespace pollution)。

什么是命名空间

  • 为了解决上述问题,ANSI C++增加了命名空间(namesoace)。所谓命名空间,实际上就是一个由程序设计者命名的内存区域。程序设计者可以根据需要指定一些有名字的空间域,把一些全局实体分别放在各个命名空间中,从而与其他全局实体分隔开来。如:
namespace ns1 //指定命名空间nsl
{ 
	int a;
	double b; 
}
  • namespace是定义命名空间所必须写的关键字,ns1是用户自己指定的命名空间的名字(可以用任意的合法标识符,这里用ns1是因为ns是namespace的缩写,含义清楚),在花括号内是声明块,在其中声明的实体称为命名空间成员(namespace member)。现在命名空间成员包括变量a和b,注意a和b仍然是全局变量,仅仅是把它们隐藏在指定的命名空间中而已。如果在程序中要使用变量a和b,必须加上命名空间名和作用域分辨符"::",如ns1::a,ns1::b。这种用法称为命名空间限定(qualified),这些名字(如ns1::a)称为被限定名(qualified name)。
  • 命名空间的作用是建立一些互相分隔的作用域,把一些全局实体分隔开来。以免产生名字冲突
  • 可以根据需要设置许多个命名空间,每个命名空间名代表一个不同的命名空间域,不同的命名空间不能同名。这样,可以把不同的库中的实体放到不同的命名空间中,或者说,用不同的命名空间把不同的实体隐蔽起来。过去我们用的全局变量可以理解为全局命名空间,独立于所有有名的命名空间之外,它是不需要用 namespace声明的,实际上是由系统隐式声明的,存在于每个程序之中。
  • 在声明一个命名空间时,花括号内不仅可以包括变量,而且还可以包括以下类型:变量(可以带有初始化)、常量、函数(可以是定义或声明)、结构体、类、模板、命名空间(在一个命名空间中又定义一个命名空间,即嵌套的命名空间)。

如:

namespace ns1
{
	const int RATE = 0.08; //常量
	double pay; //变量
	double tax() //函数
	{
		return a * RATE;
	}
	namespace ns2 //嵌套的命名空间
	{
		int age;
	}
}

如果想输出命名空间nsl中成员的数据,可以采用下面的方法:

cout << ns1::RATE << endl;
cout << ns1::pay << endl;
cout << ns1::tax() << endl;
cout << ns1::ns2::age << endl; //需要指定外层的和内层的命名空间名
  • 可以看出,命名空间的声明方法和使用方法和类的声明方法和使用方法差不多。但它们之间有一点差别:在声明类时在右花括号的后面有一分号,而在定义命名空间时,花括号的后面没有分号。

使用命名空间解决名字冲突

例:利用命名空间来解决名字冲突问题

//header1.h(头文件1)
#include
#include
using namespace std;
namespace ns1 //声明命名空间ns1
{
	class Student //在命名空间ns1内声明Student类
	{
	public:
		Student(int n, string nam, int a)
		{ num = n; name = nam; age = a; }
		void get_data();
	private:
		int num;
		string name;
		int age;
	};

	void Student::get_data() //定义成员函数
	{
		cout << num << " " << name << " " << age << endl;
	}

	double fun(double a, double b) //在命名空间ns1内定义fun函数
	{ return sqrt(a + b); }
}

//header2.h(头文件2)
#include
#include
namespace ns2 //声明命名空间ns2
{
	class Student //在命名空间ns2内声明Student类
	{
	public:
		Student(int n, string nam, char s)
		{ num = n; name = nam; sex = s; }
		void get_data();
	private:
		int num;
		string name;
		char sex;
	};

	void Student::get_data() //定义成员函数
	{ cout << num << " " << name << " " << sex << endl; }

	double fun(double a, double b) //在命名空间ns2内定义fun函数
	{ return sqrt(a - b); }
}

//main file(主函数)
#include
#include"header1.h" //包含头文件1
#include"header2.h" //包含头文件2
int main()
{
	ns1::Student stud1(101, "Wang", 18); //用命名空间ns1中的Student类定义stud1
	stud1.get_data(); //不要写成ns1::stud1.get_data()
	cout << ns1::fun(5, 3) << endl; //调用命名空间ns1中的fun函数
	ns2::Student stud2(102, "Li", 'f'); //用命名空间ns2中的Student类定义stud2
	stud2.get_data();
	cout << ns2::fun(5, 3) << endl; //调用命名空间ns1中的fun函数
	return 0;
}

程序分析:

  • 解决本题的关键是建立了两个命名空间ns1和ns2,将原来两个头件的内容分别放在命名空间ns1和ns2中。注意:在头文件中,不要把#include指令放在命名空间中,命名空间中的内容不能包括预处理指令,否则编译会出错。
  • ns1::fun()和ns2::fun()是两个不同的函数。注意:对象stud1是用ns1::Student定义的,但对象stud1并不在命名空间ns1中。Stud1的作用域为main函数范围内。在调用对象stud1的成员函数get_data时,应写成stud1.get_data(),而不应写成ns1::stud1.get_data()。

使用命名空间成员的方法

  • 在引用命名空间成员时,要用命名空间名和作用域分辨符对命名空间成员进行限定,以区别不同的命名空间中的同名标识符。即:命名空间名::命名空间成员名。这种方法是有效的,能保证所引用的实体有唯一的名字。

C++提供了一些机制,能简化使用命名空间中的成员的手续

  1. 使用命名空间别名
    可以为命名空间起一个别名(namespace alias),用来代替较长的命名空间名。如:
namespace Television //声明命名空间,名为Television
{}

可以用一个较短而易记的别名代替它。如:namespace TV=Television; //别名TV与原名Television等价
也可以说,别名TV指向原名Television,在原来出现Television的位置都可以无条件地用TV来代替。

  1. 使用"using命名空间成员名"
    using后面的命名空间成员名必须是由命名空间限定的名字。例如:using ns1::Student;
    以上语句声明:在本作用域(using语句所在的作用域)中会用到命名空间ns1中的成员Student,在本作用域中如果使用该命名空间成员时,不必再用命名空间限定。例如在用上面的using声明后,在其后程序中出现的Student就是隐含地指ns1::Student。
    using声明的有效范围是从using语句开始到using所在的作用域结束。如果在以上的using语句之后有以下语句:
Student studl(101, "Wang" ,18); //此处的Student相当于ns1::Student
//上面的语句相当于
nsl::Student studl(101, "Wang", 18)//又如
using nsl::fun; //声明其后出现的fun是属于命名空间ns1中的fun
cout << fun(5, 3) << endl; //此处的fun函数相当于ns1::fun(5,3)

显然,这可以避免在每一次引用命名空间成员时都用命名空间限定,使得引用命名空间成员变得方便易用。
但是要注意:在同一作用域中用using声明的不同命名空间的成员中不能有同名的成员。

  1. 使用"using namespace命名空间名"
    如:using namespace ns1;,声明了在本作用域中要用到命名空间ns1中的成员,在使用该命名空间内的任何成员时都不必使用命名空间限定。如果在作了上面的声明后有以下语句:
Student stud1(101, "Wang", 18); //Student隐含指命名空间ns1中的Student
cout << fun(5, 3) << endl; //这里的fun函数是命名空间ns1中的fun函数

无名的命名空间

  • C++允许使用没有名字的命名空间,如在文件A中声明了以下的无名命名空间:
namespace //命名空间没有名字
{
	void fun() //定义命名空间成员
	{ cout<<"OK."<<endl; }
}

由于没有名字,所以其它文件无法引用,它只在本文件的作用域内有效。无名命名空间的成员fun函数的作用域为文件A(确切地说,是从声明无名命名空间的位置开始到文件A结束)。在文件A中使用无名命名空间的成员,不必(也无法)用命名空间名限定。如果在文件A中有以下语句:fun();,则执行无名命名空间中的成员fun函数,输出"OK."

  • 在本程序的其他文件中也无法使用该fun函数,也就是把fun函数的作用域限制在本文件范围中。可以想到:在C浯言中可以用static声明一个函数,其作用也是使该函数的作用域限于本文件。C++保留了用static声明函数的用法,同时提供了用无名命名空间来实现这一功能。随着越来越多的C++编译系统实现了ANSI C++建议的命名空间的机制,相信使用无名命名空间成员的方法将会取代以前习惯用的对全局变量的静态声明。

标准命名空间std

  • 为了解决C++标准库中的标识符与程序中的全局标识符之间以及不同库中的标识符之间的同名冲突,应该将不同库的标识符在不同的命名空间中定义(或声明)。标准C++库的所有的标识符都是在一个名为std的命名空间中定义的,或者说标准头文件(如iostream)中函数、类、对象和类模板是在命名空间std中定义的。std是standard(标准)的缩写,表示这是存放标准库的有关内容的命名空间。
  • 这样,在程序中用到C++标准库时,需要使用std作为限定。如:std::cout<<"OK."<。在有的C++书中可以看到这样的用法。但是在每个cout,cm以及其他在std中定义的标识符前面都用命名空间std作为限定,显然是很不方便的。在大多数的C++书以及C++程序中常用using namespace语句对命名空间std进行声明,这样可以不必对每个命名空间成员一一进行处理,在文件的开头加入以下 using namespace声明:using namespace std;
  • 这样,在std中定义和声明的所有标识符在本文件中都可以作为全局量来使用。但是应当绝对保证在程序中不出现与命名空间std的成员同名的标识符,例如在程序中不能再定义一个名为cout的对象。由于在命名空间std中定义的实体实在太多,有时程序设计人员也弄不请哪些标识符已在命名空间std中定义过,为减少出错机会,有的专业人员喜欢用若干个"using 命名空间成员"声明来代替"using namespace命名空间”声明,如:
using Std::string;
using Std::cout;
using Std::cin;

等。为了减少在每一个程序中都要重复书写以上的using声明,程序开发者往往把编写应用程序时经常会用到的命名空间std成员的using声明组成一个头文件,然后在程序中包含此头文件即可。

使用早期的函数库

  • C语言程序中各种功能基本上都是由函数来实现的,在C语言的发展过程中建立了功能丰富的函数库,C++从C语言继承了这份宝贵的财富。在C++程序中可以使用C语言的函数库。
  • 如果要用函数库中的函数,就必须在程序文件中包含有关的头文件,在不同的头文件中,包含了不同的函数的声明。
  • 在C++中使用这些 头文件有两种方法。
  1. 用C语言程序中使用的传统老方法。头文件名包括后缀.h,如stdio.h,math.h等。由于C语言没有命名空间,头文件并不存放在命名空间中,因此在C++程序文件中如果用到带后缀.h的头文件时,不必用命名空间。只需在文件中包含所用的头文件即可。如:#include

  2. 用C++的新方法。C++标准规定头文件不包括后缀.h,例如iostream、string。为了表示与C 语言的头文件有联系又有区别,C++所用的头文件名是在C语言的相应的头文件名(但不包括后缀.h)之前加一字母c。例如,C语言中有关输入与输出的头文件名为stdio.h在C++中相应的头文件名为cstdio。C语言中的头文件math.h,在C++中相应的头文什名为cmath。C语言中的头文件string.h在C++中相应的头文件名为cstring(注意在C++中,头文件cstring和头文件string不是同一个文件。前者提供C语言中对字符串处理的有关函数(如strcmp,ctrcpy)的声明,后者提供C++中对字符串处理的新功能。

  • 此外,由于这些函数都是在命名空间std中声明的,因此在程序中要对命名空间std作声明。如:
#include
#include
using namespace std;

目前所用的大多数C++编译系统既保留了C的用法,又提供了C++的新方法。

你可能感兴趣的:(C++程序设计,C++,命名空间)