1. 命名空间
1.1 namespace 的价值
在 C/C++ 中,变量、函数名以及类大量存在,这些名称都存在于全局作用域中,可能会导致冲突。使用命名空间的目的是为了解决命名冲突的问题。
#include <stdio.h>
#include <stdlib.h>
int rand = 20;
int main() {
// 重定义,rand 是一个函数,包含在 stdlib.h 里
printf("%d\n", rand);
return 0;
}
本意是打印整型变量,但在 C++ 里,编译器会在局部域先进行查找,找不到再去全局域查找。此时全局域有两个 rand,一个是 rand 函数,一个是 rand 整型变量,这时就会发生报错。
1.2 namespace 的定义
定义命名空间,需要使用 namespace 关键字,后面跟命名空间的名字,然后接一对 {} 即可。{} 中即为命名空间的成员。命名空间中可以定义变量、函数、类型等。
// 解决了命名冲突的问题
namespace LC {
int rand = 20;
struct Node {
int val;
struct Node* next;
};
}
int main() {
// 这里默认访问的是全局域里的 rand 函数指针
printf("%p\n", rand);
// 指定了 rand 的出处,就会直接在 LC 命名空间里去查找
printf("%d\n", LC::rand);
return 0;
}
namespace 本质上是定义出了一个域,这个域跟全局域各自独立,不同的域可以定义同名变量。 C++ 中有局部域、全局域、命名空间域、类域;域影响的是编译器语法查找变量/函数/类型出处的逻辑,所以有了域隔离,名字冲突的问题就解决了。局部域和全局域除了会影响编译器的查找逻辑,还会影响变量的生命周期,命名空间域和类域不会影响变量的生命周期。 namespace 只能定义在全局,也可以嵌套定义。
int main() {
// 命名空间不能定义在局部域
namespace LC {
int a = 10;
int Add(int left, int right) {
return left + right;
}
}
return 0;
}
项目工程中多文件中定义的同名 namespace 会认为是同一个 namespace,不会发生冲突。 C++ 标准库都放在一个叫 std(standard) 的命名空间中。
// 命名空间的嵌套定义
namespace LC {
namespace NSY {
int rand = 100;
double Add(double left, double right) {
return left + right;
}
}
int a = 10;
int Add(int left, int right) {
return left + right;
}
}
int main() {
printf("%d\n", LC::a);
printf("%d\n", LC::NSY::rand);
printf("%d\n", LC::Add(2, 4));
printf("%.2f\n", LC::NSY::Add(2.2, 4.4));
return 0;
}
1.3 命名空间的使用
编译器在查找一个变量的声明或者定义时,默认只会在局部域和全局域查找,不会到命名空间域里去查找。
namespace LC {
int a = 10;
int Add(int left, int right) {
return left + right;
}
}
int main() {
printf("%d\n", a);
return 0;
}
所以,我们要使用命名空间里定义的变量和函数,有以下几种方式:
- 指定命名空间访问。
- using 将命名空间中的某个成员展开。
- 展开命名空间中全部成员。
namespace LC {
int a = 10;
double b = 3.14;
char ch = 'a';
}
// 展开命名空间部分成员
using LC::b;
// 展开命名空间全部成员
using namespace LC;
int main() {
// 指定命名空间访问
printf("%d\n", LC::a);
printf("%.2f\n", b);
printf("%c\n", ch);
return 0;
}
2. C++ 输入&输出
#include <iostream>
int main() {
int a = 10;
double b = 3.14;
char ch = 'c';
// cout, cin 是标准输入输出流的对象,属于 C++ 标准库
// 不需要像 C 语言使用占位符来指定打印格式,cout, cin 会自动识别类型
std::cout << a << " " << b << " " << ch << std::endl;
std::cin >> a >> b >> ch;
std::cout << a << " " << b << " " << ch << std::endl;
return 0;
}
#include <iostream>
// 将 std(标准命名空间)全部成员展开
using namespace std;
int main() {
int a = 10;
double b = 3.14;
char ch = 'c';
// 不需要在指定 std
cout << a << " " << b << " " << ch << endl;
cin >> a >> b >> ch;
cout << a << " " << b << " " << ch << endl;
return 0;
}
3. 缺省参数 定义:函数声明或函数定义时为函数参数指定一个缺省值。 在调用函数时,如果没有指定实参,则采用形参的缺省值,否则,使用指定的实参。 缺省参数分为全缺省和半缺省参数。全缺省就是全部形参给缺省值,半缺省就是部分形参给缺省值。C++ 规定,半缺省参数必须从右往左依次连续缺省,不能间隔跳跃给缺省值。 带缺省参数的函数调用,C++ 规定必须从左到右依次给实参,不能跳跃给实参。 函数声明和定义分离时,缺省参数不能在函数声明和函数定义中同时出现,C++ 规定,必须在函数声明的地方给缺省值。
4. 函数重载 定义:C++ 支持在同一作用域中出现同名函数,但是要求这些同名函数的形参不同,可以是参数不同也可以是类型不同。
- 形参类型不同
- 形参个数不同
- 存在歧义问题
5. 引用
5.1 引用的概念和定义
概念:引用不是新定义一个变量,而是对已存在的变量取别名,编译器不会对引用变量开辟内存空间,它和它引用的变量共用同一块空间。
定义:类型& 引用别名 = 引用对象
5.2 引用的特性
- 引用在定义时必须初始化。
- 引用一旦引用一个实体以后就不能再引用其它实体。
- 一个变量可以有多个引用。
5.3 引用的使用
引用在实践中主要用于引用传参和引用作返回值,可以减少拷贝提高效率,改变引用对象的同时可以改变被引用对象。 这里传参只是传递了一个整型,拷贝并不会损失多少效率,但是如果是一个大对象呢?例如 C 中的结构体,拷贝就会付出很大的代价。
5.4 const 引用
引用 const 对象必须用 const 引用,const 引用也可以引用普通对象,因为对象的访问权限可以在引用过程中进行平移和缩小,但不能放大。
int main() {
int a = 10;
// 权限平移
int& ra = a;
// const 引用也可以引用普通对象
// 权限缩小
const int& rra = a;
int b = 20;
// 2 * b 是一个表达式,2 * b 的结果存在临时对象中
// 临时对象具有常性,要常引用才可以
// int& rb = 2 * b; // err
const int& rb = 2 * b;
// b 是 int 类型,rrb 是 double 类型
// 存在类型转换的问题,b 会转换成 double 类型存放在临时对象中
// double& rrb = b; // err
const double& rrb = b;
return 0;
}
临时对象就是编译器需要一个空间暂存表达式的求值结果时临时创建的一个未命名的对象,C++ 把这个未命名对象叫做临时对象。临时对象具有常性。
6. 指针和引用的关系
- 引用是给已存在的变量取别名,不开辟内存空间,指针存储变量的地址,要开空间。
- 引用一旦引用一个实体就不能再引用其它实体,指针可以不断的改变指向对象。
- 引用必须初始化,指针建议初始化。
- 引用可以直接访问指向对象,指针需要解引用才可以访问指向对象。
- sizeof 中含义不同,引用结果为引用类型的大小,但指针始终是地址空间所占字节个数(与平台大小有关)。
- 指针容易出现空指针、野指针的问题;引用相对安全一些。
7. nullptr NULL 实际上是一个宏,在传统的 C 头文件 (stddef.h) 中,有如下代码:

- 在 C++ 中,NULL 被定义为字面常量 0,C 中被定义为无类型指针 (void*) 的常量。
- C++11 中引入了 nullptr,nullptr 是一个特殊的关键字,nullptr 是一种特殊类型的字面量,它可以转换成任意其他类型的指针类型。使用 nullptr 定义空指针可以避免类型转换的问题,因为 nullptr 只能被隐式的转换为指针类型,而不能被转换为整数类型。
void f(int x) { cout << "f(int x)" << endl; }
void f(int* ptr) { cout << "f(int* ptr)" << endl; }
int main() {
// 本意是调用 f(int* ptr) 函数,但由于 NULL 被定义成 0,所以调用 f(int x) 函数
f(NULL); // 强转
f((int*)NULL);
// nullptr 能被转换成任意类型的指针类型
f(nullptr);
return 0;
}


