C++基础之命名空间

目录:


1. 为什么要使用命名空间?

2. 什么是命名空间?

3. 命名空间的使用方式

3.1 using编译指令

3.2 作用域限定符

3.3 using声明机制
      带命名空间的函数声明
      命名空间扩展

3.4 匿名命名空间

3.5. 命名空间的嵌套及覆盖

3.6. 对命名空间的思考和总结






1. 为什么要使用命名空间?

一个大型的工程往往是由若干个人独立完成的,不同的人分别完成不同的部分, 后再组合成一个完整的程序。由于各个头文件是由不同的人设计的,有可能在不同的头文件中用了相同的名字来命名所定义的类或函数,这样在程序中就会出现名字冲突。不仅如此,有可能我们自己定义的名字会与C++库中的名字发生冲突。

名字冲突就是在同一个作用域中有两个或多个同名的实体,为了解决命名冲突 , C++中引入了命名空间,所谓命名空间就是一个可以由用户自己定义的作用域,在不 同的作用域中可以定义相同名字的变量,互不干扰,系统能够区分它们。



2. 什么是命名空间?

命名空间又称为名字空间,是程序员命名的内存区域,程序员根据需要指定一些有名字的空间域,把一些全局实体分别存放到各个命名空间中,从而与其他全局实体分隔开。通俗的说,每个名字空间都是一个名字空间域,存放在名字空间域中的全局实体只在本空间域内有效。名字空间对全局实体加以域的限制,从而合理的解决命名冲突。

C++中定义命名空间的基本格式如下:

namespace sp1 
{
int val1 = 0; 
char val2; 
} // end of namespace sp1

在声明一个命名空间时,大括号内不仅可以存放变量,还可以存放以下类型:

  • 变量
  • 常量
  • 函数,可以是定义或声明
  • 结构体
  • 模板
  • 命名空间,可以嵌套定义
//命名空间存放整变量,结构体,函数
namespace wd
{
int number = 0;
struct Foo
{
	char ch;
	int val;
};
void display();  
} // end of namespace wd

定义在名称空间中的变量或者函数都称为实体,名称空间中的实体作用域是全局的, 并不意味着其可见域是全局的。

如果不使用作用域限定符和using机制,抛开名称空间嵌套和内部屏蔽的情况,实体的可见域是从实体创建到该名称空间结束。在名称空间外,该实体是不可见的。



3. 命名空间的使用方式

命名空间一共有三种使用方式,分别是 using编译指令、作用域限定符、using声明机制。

3.1 using编译指令

我们接触的第一个C++程序基本上都是这样的,其中std代表的是标准命名空间

tips:
cout 标准输出
<< 输出流运算符
cin 标准输入
<< 输入流运算符

//using编译指令 打印 helloworld
#include <iostream>

// using编译指令,一次会将std里面的实体全部引出来 
using namespace std;

int main()
{
    cout << "Hello world" << endl;
    
    //对输出流运算符进行重载
    operator << (cout, "Hello world");
    cout.operator << (endl);
     
    operator << (cout, "Hello world").operator << (endl);

	//cin标准输入
    int number = 0;
    cin >> number;
    cout << "number = " << number << endl;
   
    return 0;
}

其中第5行就使用了using编译指令。如果一个名称空间中有多个实体,使用using 编译指令,就会把该空间中的所有实体一次性引入到程序之中;对于初学者来说, 如果对一个命名空间中的实体并不熟悉时,直接使用这种方式,有可能还是会造成名字冲突的问题,而且出现错误之后,还不好查找错误的原因,比如下面的程序就会报错,当然该错误是人为造成的。

// using编译指令 函数调用
#include <iostream> 

using namespace std; 


double cout()
{
	return 1.1; 
}

int main(void) 
{
	cout();
	return 0;
}

报错:

error_cout.cc:13:5: error: reference to ‘cout’ is ambiguous

自定义的函数名cout 与命名空间 std 里的实体 cout 有冲突,下一种方法可以解决这种问题


3.2 作用域限定符

第二种方式就是直接使用作用域限定符 :: 。每次要使用某个名称空间中的实体时, 都直接加上,例如:

#include <iostream>

namespace sp1
{

int num = 1;

void printNum()
{
    std::cout << "sp1::printNum" << std::endl;
}

}

//我们自定义的函数变量名可以与命名空间里面的实体冲突
int cout(int x, int y)
{
    std::cout << "x = " << x << ", y = " << y << std::endl;
    std::cout << "int cout(int, int)" << std::endl;
    return 0;
}


int main()
{
    std::cout << sp1::num << std::endl;
    sp1::printNum();
    cout(3, 5);
    return 0;
}

这种方式会显得比较冗余,所以还可以采用第三种使用方式。


3.3 using声明机制

using声明机制的作用域是从using语句开始,到using所在的作用域结束。要注意, 在同一作用域内用using声明的不同的命名空间的成员不能有同名的成员,否则会发生重定义

#include <iostream>

//推荐使用方式,一次只引出一个实体
using std::cout;
using std::endl;

namespace sp1
{

int num = 1;

void printNum()
{
    cout << "sp1::printNum" << endl;
}

}

using sp1::num;
using sp1::printNum;

int main()
{
    //cout << sp1::num << endl;
    cout << num << endl;
    //sp1::printNum();
    printNum();
    return 0;
}

在这三种方式之中,我们推荐使用的就是第三种,需要哪个实体的时候就引入到程序中,不需要的实体就不引入,尽可能减小犯错误的概率

还可以使用带命名空间的函数声明,防止在命名空间相互调用时产生未定义的错误。

//带命名空间的函数声明
#include <iostream>

using std::cout;
using std::endl;

//由于 sp1需要使用 sp2的实体,对 sp2进行声明
namespace sp2
{
int number;
void printsp1();
}

//使用 sp2空间的实体
namespace sp1
{

int num = 1;

void print1()
{
    cout << "sp1::print1" << endl;
}

void printsp2()
{
    cout << "sp1::printsp2" << endl;
    sp2::printsp1();
}

} // end of namespace sp1

//使用 sp1空间的实体
namespace sp2
{

int num2 = 2;

void printsp1()
{
    cout << "sp2::printsp1" << endl;
    sp1::print1();
} // end of namespace sp2

}

int main()
{
    cout << sp1::num << endl;
    sp1::printsp2();
    return 0;
}

打印

1
sp1::printsp2
sp2::printsp1
sp1::print1

命名空间是可以进行扩展的,
标准的命名空间,不建议进行扩展,可能导致冲突

namespace std
{
struct MyStruct
{
    int val;
};
}

3.4 匿名命名空间

命名空间还可以不定义名字,不定义名字的命名空间称为匿名命名空间

由于没有名字,该空间中的实体,其它文件无法引用,它只能在本文件的作用域内有效,它的作用域是从匿名命名空间声明开始到本文件结束。在本文件使用无名命名空间成员时不必用命名空间限定。其实匿名命名空间和static是同样的道理,都是只在本文件内有效,无法被其它文件引用。

namespace 
{
	int val1 = 10;
	void func();
}//end of anonymous namespace

在匿名空间中创建的全局变量,具有全局生存期,却只能被本空间内的函数等访问,是static变量的有效替代手段。


3.5. 命名空间的嵌套及覆盖

#include <iostream>

using std::cout;
using std::endl;

//全局
int a_num = 100;

namespace sp1
{

int num = 1;

void printNum(int number)
{
    cout << "形参 number = " << number << endl;
    cout << "sp1::num = " << sp1::num << endl;
    cout << "全局 a_num = " << ::a_num << endl; //匿名命名空间
}

//命名空间 sp1 内嵌套 sp2
namespace sp2
{
    int num2 = 2;
    void print2()
    {
        cout << "sp1::sp2::print2" << endl;
    }
} // end of namespace sp1


} // end of namespace sp1

int main()
{
    int val = 20;
    sp1::printNum(val);
    cout << endl;
    cout << "sp1::sp2::num2 = " << sp1::sp2::num2 << endl;
    sp1::sp2::print2();
    return 0;
}

打印

形参 number = 20
sp1::num = 1
全局 a_num = 100

sp1::sp2::num2 = 2
sp1::sp2::print2


3.6. 对命名空间的思考和总结

下面引用当前流行的名称空间使用指导原则:

  • 提倡在已命名的名称空间中定义变量,而不是直接定义外部全局变量或者静态全局变量。
  • 如果开发了一个函数库或者类库,提倡将其放在一个名称空间中
  • 对于using 声明,首先将其作用域设置为局部而不是全局
  • 不要在头文件中使用using编译指令,这样,使得可用名称变得模糊,容易出现二义性
  • 包含头文件的顺序可能会影响程序的行为,如果非要使用using编译指令,建议放在所有 #include 预编译指令后。
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值