目录
- 命名空间
- 域的概念
- 命名空间的概念
- 示例1
- 示例2
- 命名空间的嵌套定义
- 命名空间的访问
- 指定命名空间访问
- using访问命名空间
- 对于嵌套命名空间的访问
- 输入&输出
- 输入
- 输出
- 输入输出的特性
- 换行
命名空间
在C/C++中,变量、函数和类的定义很多,在某些方面我们难免要使用库文件中的对象,但在一个比较庞大的项目中我们人为的去创建对象这些难免有可能与我们库文件中对象名字相同而发生冲突。这时候为了解决这个冲突问题,我们C++就推出了命名空间。用namespace关键字来定义命名空间
下列我们看一个经典的对象名冲突的代码:
#include <stdio.h>
#include <stdlib.h>int rand = 0;int main(void)
{printf("rand = %d\n", rand);return 0;
}
我们stdlib.h库文件中有一个rand()随机值函数,这个函数在预编译的时候就进行了头文件展开(把头文件中的内容拷贝过来),这时候我们定义一个全局变量就会和rand()函数的名字发生冲突。
- 不光是在包含头文件的时候会发生命名冲突,假设我们在公司完成一个项目,我们需要把自己的代码传送到一个指定的头文件中,这时候同事如果跟我们用了一样的对象名也会发生命名冲突
那要怎么去解决这个冲突呢?此时就可以使用到【namespace】命名空间了
域的概念
搞清楚作用域运算符之前我们需要知道什么是域
: :是作用域限定符,下面例子介绍他的作用
C++中域有函数局部域,全局域,命名空间域,类域;就是因为有了域,我们在每个域中定义所需要的变量,函数这些,又因为每个域中的作用域不同,函数、变量这些在各个域的生命周期就不同。
注意:
- 局部域和全局域除了会影响编译查找逻辑,还会影响变量的⽣命周期,命名空间域和类域不影响变量⽣命周期
int a = 0;
void f1()
{int a = 1;printf("a = %d\n", a);
}void f2()
{int a = 2;printf("a = %d\n", a);
}int main(void)
{f1();return 0;
}
这里就分为3个域,main函数的局部域,f2函数的局部域,f1函数的局部域
- 这里f1,f2函数中都有a这个变量,但是这两个不是同一个a,在f1函数中的a他的生命周期就是f1函数这个局部域
- 对于f2函数中的a他的生命周期也只限于f2这个函数这个局部域。但是对于所以函数之外定义的a这个对象,他就是在全局域中定义的这个对象,所以他的生命周期是全局域。
注意:这里我们C/C++中如果有同名的对象,那么我们优先访问局部域中的对象
如上面,我们调用f1函数打印a
但是如果我们非要强行先访问全局域中的变量a,这时我们就要用到: :这个运算符
【域作用限定符】
- 可以看到,直接在对象a左边用: : 运算符,因为这个运算符左边是空的,所以他代表的是全局域,就会全全局域中找这个对象a
基本了解后,我们回到原来的问题来解决了
命名空间的概念
在C/C++中,变量、函数和后面要学到的类都是大量存在的,这些变量、函数和类的名称将都存在于全局作用域中,可能会导致很多冲突。使用命名空间的目的是对标识符的名称进行本地化,以避免命名冲突或名字污染,namespace关键字的出现就是针对这种问题的
- 定义命名空间的格式:namespace 命名空间名{ …}
例如:
namespace ljj
{int rand = 0;
}
示例1
#include <stdio.h>
#include <stdlib.h>int rand = 0;int main(void)
{printf("rand = %d\n", rand);return 0;
}
如果我们要解决这个问题,我们就可以使用:
namespace ljj
{int rand = 0;
}
这时候我们的rand这个对象就定义在命名空间域里面,如果想要使用就必须通过域限定符来访问.。
- 这时候我们就可以直接去打印这个rand的话出来的结果就不是0了,而是一个随机数,此时它使用的其实就是库中的rand()
不过有同学会疑惑函数不是要加上()吗,为何这里的rand就可以直接调用这个函数了呢❓
我们看一下vs的警告
- 对于int(__cdecl *)(void)而言其实是一个函数指针,它指向了这个函数的地址,又刚好因为这个函数我们不需要传入参数,void表示无参,所以也相当于是调用了这个库函数【__cdecl是VS函数名修饰规则,函数重载会讲到】
示例2
- 我这里定义了两个.h的头文件,里面分别有队列和单链表的结构体,不过对于它们来说我都将结点的结构体名称定义成了Node,此时我若是在.cpp的源文件中同时包含了这两个头文件就会出现问题
#include "Queue.h"
#include "List.h"int main(void)
{struct Node* qu;struct Node* l;return 0;
}
- 通过运行代码可以看到,编译器报出了【类型重定义】的错误
- 那这个时候应该怎么办呢?此时【命名空间】它又可以派上用场了
namespace Queue
{//队列结构体...
}
namespace SingleList
{//单链表结构体...
}
- 可以说他们封装在各自的命名命名空间中由于他们的作用域不同就不会发生冲突了
命名空间的嵌套定义
命名空间里面也可以嵌套命名空间。就像结构体里面也可以嵌套结构体
这时候我们对于:
是不是也可以封装在一个命名空间里面在嵌套定义两个命名空间
namespace ljj
{namespace Queue{//队列代码{namespace SList{//单链表代码}
}
命名空间的访问
指定命名空间访问
指定命名空间访问(适用环境是【做项目】)
【使用格式】:命名空间名称::成员
注意:如果要访问命名空间中的结构体成员,struct关键字要放在命名空间名称的前面
- 若是要去访问命名空间内部的其他函数和普通变量的话,也可以这么去用
Queue::InitQueue(&qu);
Queue::Push(&qu, 1);
Queue::max = 200;SList::PushBack(&sl, 1);
SList::PopBack(&sl);
SList::max = 300;
但是这只样一次次的去使用作用域限定符去访问很繁琐,有可能我们要频繁的去使用这个对象每次都要这样写,这时候就请出我们的using关键字
using访问命名空间
第一种:
- using 命名空间名(全部展开)
这样我们就相当于把命名空间的变量移到全局域中,我们就不用指定命名空间来访问命名空间中点变量
namespace ljj
{int b = 1;int a = 0;
}
using namespace ljj;
int main()
{printf("%d %d\n", a, b);return 0;
}
注意:但是这种用法仅仅限于我们平常练习中的用法,在做项目时还是老老实实去用指定命名空间来访问。
不然:
namespace ljj
{int b = 1;int a = 0;
}
using namespace ljj;
int main()
{int a = 3;printf("%d %d\n", a, b);return 0;
}
像这种在局部域中定义一个相同的,就会直接优先访问局部的。诸如各种此类问题
第二种:
- using 命名空间名::命名空间对象名(部分展开)
这种就是一个折中的办法,像调用初始化栈和队列这些函数一般只需要使用一次,我们就可以使用这种方法来频繁使用一个命名空间中的对象
using Queue::Push;
using SList::PushBack;
using SList::PopBack;
- 在面对C++中std标准库里的【cout】和【endl】时,我们就需要使用到using namespace std;,因为他们都定义在了标准库中,只是包含头文件是不够的
注意:这里cout和endl是c++的printf和换行
但是前面说了,这样直接全部展开很容易发生冲突*
我们就使用部分展开
using std::cout;
using std::endl;
对于嵌套命名空间的访问
这里就想到我们的结构体嵌套假如有
struct S1
{struct S2{int a;}p;
}ps;
ps.p.a = 1;
printf("%d\n", ps.p.a );
我就就是先找到外层结构体变量ps,再找到内层结构体变量p,再找到内层结构体变量a。
对于嵌套的命名空间访问也差不多
- using版本是这样
using namespace ljj::hg;
using namespace ljj::pg;
printf("%d %d\n", a, b);
注意:不能直接展开ljj命名空间来访问被嵌套在这个空间的命名空间对象,就像不能直接找到结构体ps变量访问a的变量一样。
- printf(“%d %d\n”, ljj::pg::a, ljj::hg::b);
指定命名空间访问就是这样
输入&输出
在C语言中我们使用printf和scanf进行输入和输出,但是C++中不再使用这种函数来输入和输出。
< iostream> 是 Input Output Stream 的缩写,是标准的输⼊、输出流库,定义了标准的输⼊、输
出对象
输入
std::cin 是 istream 类的对象,它主要⾯向窄字符(narrow characters (of type char))的标准输
⼊流。
我们想要进行输入还得使用:>>流提取运算符,
namespace ljj
{int a;int b;int c;
}using namespace ljj;
using std::cin;
using std::cout;
int main()
{cin >> a;cout << a;return 0;
}
注意:使用cin和cout要打开std命名空间;
- std命名空间是C++标准类和对象这些定义的空间
输出
```cpp
namespace ljj
{int a;int b;int c;
}using namespace ljj;
using std::cin;
using std::cout;
int main()
{cin >> a;cout << a;return 0;
}
输出就是cout语句,要配合<<是插⼊运算符使用、
输入输出的特性
namespace ljj
{int a;float b;char c;
}using namespace ljj;
using std::cin;
using std::cout;
int main()
{cin >> a >> b >> c;cout << a << ' ' << b << ' ' << c;return 0;
输入和输出都不用像C语言那样加格式转化符来进行不同类型的输入输出,他会自动转换类型进行输入输出。
注意:c++输出浮点型数据不会像c语言一样保留6位小数而是输入的是几位就是几位
换行
std::endl 是⼀个函数,流插⼊输出时,相当于插⼊⼀个换⾏字符加刷新缓冲区。
如图:
他会刷新缓冲区的数据。
与cout和cin一样需要指定命名空间:using std::endl
using std::cin;
using std::cout;
using std::endl;
int main()
{cin >> a >> b >> c;cout << a << ' ' << b << ' ' << c<< endl;cout << "hello world\n" << endl;return 0;
}