learncpp-7 作用域、生存期、链接
7 作用域、生存期、链接
7.2 用户自定义命名空间和范围解析运算符
- 为了避免命名冲突,在尽可能小的作用域内定义标识符
- 一个命名空间要么在全局作用域内定义,要么在另一个命名空间内定义
- 使用范围解析运算符
::
可以告诉编译器去指定命名空间查找指定标识符(如果::
前没有命名空间则表示全局命名空间) - 如果使用标识符时没有带范围解析运算符,则编译器首先在使用该标识符的命名空间内查找是否有匹配的声明;如果没有则往外层的命名空间继续查找;最后查找全局命名空间
// 以下代码输出Foo; 如果注释掉Foo里的print()则输出Out;如果再注释掉Out里的print()则输出global
#include "iostream"
void print() {
std::cout << "global";
}
namespace Out {
void print() {
std::cout << "Out";
}
namespace Foo {
void print() {
std::cout << "Foo";
}
void printHelloThere() {
print();
}
}
}
int main() {
Out::Foo::printHelloThere();
return 0;
}
- 对于一个命名空间里的标识符,它们的前向声明也应该放在该命名空间里
// add.h
namespace BasicMath{
int add(int x,int y);// 在BasicMath命名空间里声明add()
}
// add.cpp
namespace BasicMath{
int add(int x,int y){ // 在BasicMath命名空间里定义add()
return x+y;
}
}
// main.cpp
#include "add.h" // 为了引入BasicMath::add()
#include <iostream>
int main(){
std::cout<<BasicMath::add(3,4)<<'\n';
return 0;
}
如果add()前向声明没有放在BasicMath命名空间里,则add()会在全局作用域中声明,则编译器会报错找不到BasicMath::add()的声明;
如果add()的定义没有放在BasicMath命名空间里,编译不会报错,但是链接器会报错BasicMath::add()未定义
- 可以在多个位置(跨多个文件或一个文件的多个位置)声明命名空间块
标准库大量使用了这个特性,因为每个标准库头文件都包含了
std
命名空间的声明,否则整个标准库都得在一个头文件中定义
这意味着可以将自己的代码添加到std
命名空间中,但这可能会导致未定义的行为,因为std
命名空间不允许用户扩展
- 嵌套命名空间有两种形式:
namespace a{namespace b{}}
或者namespace a::b{}
(自从c++17支持) - 命名空间别名:
namespace c = a::b;
7.13 匿名命名空间和内联命名空间
- 匿名命名空间中声明的所有内容都被视为
父命名空间的一部分
#include <iostream>
namespace // unnamed namespace
{
void doSomething() // can only be accessed in this file
{
std::cout << "v1\n";
}
}
int main()
{
doSomething(); // we can call doSomething() without a namespace prefix
return 0;
}
- 匿名命名空间中的所有标识符都被视为具有
内部链接
,这意味着匿名命名空间的内容只能被本文件所访问
(对于函数来说,这相当于将匿名命名空间中的所有函数定义为static函数)
#include <iostream>
static void doSomething() // can only be accessed in this file
{
std::cout << "v1\n";
}
int main()
{
doSomething(); // we can call doSomething() without a namespace prefix
return 0;
}
使用匿名命名空间
确保某些内容只在本文件中生效
,这比将所有声明单独标记为static更加方便
匿名空间也可以使得用户自定义的类型只在本文件中生效
,这没有其他方法可以做到
内联命名空间通常用于版本内容。和匿名命名空间一样,内联命名空间中所有声明的内容都被视为父命名空间的一部分
,但是不会影响链接