C++ 基础概念详解
C++ 设计之初就保持了与 C 语言的兼容性,因此我们完全可以在 C++ 环境下编写 C 代码。
C++ 之父:本贾尼・斯特劳斯特卢普。
一、命名空间
在 C++ 中,标识符(变量、函数、类、常量、命名空间)的查找遵循特定规则。简单来说,编译器会从当前最内层的作用域开始查找;一旦找到,就不会再去外层作用域查找。
这就引出了一个问题:如果同一作用域下存在同名的标识符怎么办?
1. 命名冲突的本质
#include <stdio.h>
#include <stdlib.h>
int main() {
int rand = 10;
printf("%d\n", rand); // 代码可以正确执行
}
上面的代码能跑通,因为局部变量 rand 屏蔽了全局的 rand 函数。但如果把 rand 定义成全局变量呢?
#include <stdio.h>
#include <stdlib.h>
int rand = 10; // 全局变量
int main() {
printf("%d\n", rand); // 会编译报错
}
这是因为 stdlib.h 中也声明了 rand 函数,导致全局作用域下出现同名标识符冲突。
2. 命名空间的概念
为了解决这个问题,C++ 引入了命名空间。你可以把它理解为自己造的一个域,用来存放标识符,避免污染全局环境。
语法:
namespace 命名空间名 {
// 你自定义的标识符
}
使用方式:
通过 命名空间名::标识符 来访问域里的内容。
#include <stdio.h>
#include <stdlib.h>
namespace mycode {
int rand = 10;
}
int main() {
printf("%d\n", mycode::rand); // 正常打印出 10
}
3. 命名空间的嵌套
如果在同一个命名空间下还有冲突,可以使用嵌套命名空间。这就像在父域里开辟多个子域。
#include <stdio.h>
namespace code {
namespace codeA {
int a = 10;
}
namespace codeB {
int a = 20;
}
}
int main() {
printf("%d\n", code::codeA::a); // 打印 10
printf("%d\n", code::codeB::a); // 打印 20
}
4. 命名空间的使用技巧
除了显式指定,我们还可以展开成员或整个命名空间。
- 展开某个成员:
using name::a; - 展开命名空间的全部成员:
using namespace name;
注意: 在多个文件中使用命名空间时,C++ 允许同一个命名空间被写在不同的文件、不同的位置,编译器会自动把它们合并成一个。这也是为什么 C++ 标准库都存放在 std (standard) 这个命名空间中。
二、C++ 的输入输出
1. iostream 头文件
<iostream> 是 Input Output Stream 的缩写,提供了 cin、cout 以及操作符 <<、>>。
2. cin 和 cout
std::cin 代表控制台输入,std::cout 代表控制台输出。由于它们都在 std 命名空间下,使用前通常加上 std:: 前缀,或者引入 using namespace std;。
cin:Console Inputcout:Console Output
3. 流运算符
<<:流插入运算符,配合cout使用。可以一次打印多个内容。>>:流提取运算符,配合cin使用。也可以一次读取多个内容。
#include <iostream>
using namespace std;
int main() {
cout << "hello world" << endl;
cout << "第一行内容" << endl << "第二行内容" << endl;
}
#include <iostream>
using namespace std;
int main() {
int a, b;
cout << "请输入两个整数:";
cin >> a >> b;
cout << "两个数之和为:" << a + b << endl;
}
这里有个便利之处:C++ 可以自动识别变量类型,不需要像 C 语言那样手动指定占位符。
endl:表示换行并刷新缓冲区。
三、缺省参数
1. 基本概念
定义函数时,给参数一个默认值。调用时如果不传参,就用默认值;如果传了,就用实参。
#include <iostream>
using namespace std;
void Print(int a = 3) {
cout << a << endl;
}
int main() {
Print(); // 打印 3
Print(6); // 打印 6
}
2. 全缺省与半缺省
- 全缺省: 所有参数都有默认值。
- 半缺省: 部分参数有默认值。关键点: 缺省值必须从右向左连续设置。调用时必须传够没有缺省值的参数,且顺序是从左向右。
#include <iostream>
using namespace std;
// 错误示例:Print(); 没有传够参数,会触发编译错误
// 正确:Print(0); 打印 0,1,2
void Print(int a, int b = 1, int c = 2) {
cout << a << "," << b << "," << c << endl;
}
函数声明与定义分离时的注意事项: 缺省参数只能出现一次,且必须出现在声明部分。
#include <iostream>
using namespace std;
void Print(int a, int b = 3); // 声明部分,缺省值写在这里
void Print(int a, int b) { // 定义部分
cout << a << "," << b << endl;
}
int main() {
Print(3); // 打印 3,3
Print(2, 6); // 打印 2,6
}
四、函数重载
C++ 允许在同一作用域内存在多个同名函数,但必须满足以下条件之一:
- 参数个数不同
- 参数类型不同
- 参数顺序不同
#include <iostream>
using namespace std;
// 参数个数不同
int Add(int a, int b, int c) {
return a + b + c;
}
int Add(int a, int b) {
return a + b;
}
int main() {
int a = Add(1, 1, 1);
int b = Add(1, 1);
cout << a << endl << b << endl;
}
#include <iostream>
using namespace std;
// 参数类型不同
int Add(int x, int y) {
return x + y;
}
double Add(double x, double y) {
return x + y;
}
int main() {
int a = Add(2, 3);
double b = Add(1.2, 1.3);
cout << a << endl << b << endl;
}
#include <iostream>
using namespace std;
// 参数顺序不同
double Calc(int a, double b) {
return a + b;
}
double Calc(double a, int b) {
return a - b;
}
int main() {
double a = Calc(2, 2.2); // 匹配第一个重载,结果 4.2
double b = Calc(2.2, 2); // 匹配第二个重载,结果 0.2
cout << a << endl;
cout << b << endl;
}
五、引用(Reference)
1. 基本概念
引用是给已有的变量起一个别名,直接在原内存上操作,不产生新的副本。
语法:
类型& 引用别名 = 引用对象;
2. Swap 函数的对比
用指针交换需要解引用,比较繁琐;用引用则更直观。
指针写法:
#include <iostream>
using namespace std;
void Swap(int* a, int* b) {
int tmp = *a;
*a = *b;
*b = tmp;
}
int main() {
int a = 2, b = 3;
cout << "交换前 a = " << a << " b = " << b << endl;
Swap(&a, &b);
cout << "交换后 a = " << a << " b = " << b << endl;
}
引用写法:
#include <iostream>
using namespace std;
void Swap(int& a, int& b) {
int tmp = a;
a = b;
b = tmp;
}
int main() {
int a = 2, b = 3;
cout << "交换前 a = " << a << " b = " << b << endl;
Swap(a, b);
cout << "交换后 a = " << a << " b = " << b << endl;
}
3. 引用的要求
- 初始化: 局部或全局引用必须初始化。函数形参的引用在传入实参时即完成初始化。
- 命名: 不同作用域下引用别名可相同,但同一作用域下不能与原变量名相同。
- 多引用: 一个变量可以有多个引用,修改其中一个,其他引用指向的值也会变。
- 绑定问题: 引用一旦绑定某个实体,就不能再改变绑定对象(虽然可以赋值修改其值)。
#include <iostream>
using namespace std;
int main() {
int a = 2;
int& b = a;
int& c = a; // 也可以写成 b
cout << "变化前 a = " << a << ", b = " << b << ", c = " << c << endl;
c++; // 修改 c
cout << "变化后 a = " << a << ", b = " << b << ", c = " << c << endl;
}
4. 引用的优点
引用常用于函数传参或返回值,因为它直接操作内存,避免了拷贝开销,提高了效率。
六、const 引用
1. 概念
被 const 修饰的引用。权限只能不变或变小,不能放大。
- 原变量只读 -> const 引用仍只读。
- 原变量可读可写 -> const 引用变为只读。
int main() {
int a = 3; // 可读可写
const int& ra = a; // 只读
a = 6; // 正确,未超出原变量权限
// ra = 3; // 错误,超出了 const 引用权限
}
2. 修饰临时变量
普通引用不能绑定临时变量(如字面量、表达式结果),因为临时变量生命周期短且不安全。但 const 引用 可以延长临时变量的生命周期,直到引用销毁。
int main() {
const int& ra = 20; // 安全
// int& rra = 20; // 不安全,属于权限放大
int a = 20;
const int& ra2 = a * 3; // 安全
// int& rra2 = a * 3; // 不安全
}
3. 引用与指针的区别
| 特性 | 引用 | 指针 |
|---|---|---|
| 语法 | 别名,不开新空间 | 存储地址,需开空间 |
| 初始化 | 必须初始化 | 建议初始化 |
| 绑定后 | 不可更改 | 可重新绑定 |
| 访问 | 直接访问 | 需解引用 |
| 大小 | 取决于对象 | 取决于系统位数 |
| 安全性 | 无空指针/野指针 | 可能出现空/野指针 |
七、宏
1. 概念
宏是在预处理阶段进行的文本替换,分为有参和无参宏。
2. 无参宏
#define val 18
int main() {
cout << val << endl; // 编译器会将 val 替换为 18
}
3. 有参宏
#define Add(a,b) ((a) + (b))
int main() {
cout << Add(2, 3) << endl; // 输出 5
}
注意括号的使用是为了避免运算符优先级带来的计算错误。
八、inline 内联函数
1. 概念
被 inline 修饰的函数,编译器会在调用处展开代码,无需建立栈帧,从而提高效率。
2. 注意
inline 只是建议,编译器有权决定是否采纳。通常用于几行的小代码。
#include <iostream>
using namespace std;
inline int Add(int a, int b) {
return a + b;
}
int main() {
cout << Add(2, 3) << endl; // 相当于 cout << 2+3 << endl;
}
3. 内联函数 vs 宏
- 本质: 内联是函数,宏是文本替换。
- 调试: 内联函数可调试,宏不可。
- 类型检查: 内联函数有类型检查,宏没有。
- 优先级: 内联函数不受影响,宏需注意括号。
九、nullptr
1. 概念
nullptr 是 C++11 引入的关键字,专门用来表示空指针。
2. 引入原因
在 C++ 中,NULL 通常定义为 0。在函数重载时,0 既可以匹配整型参数,也可以匹配指针参数,导致歧义。
#include <iostream>
using namespace std;
void Print(int* x) {
cout << "int*" << endl;
}
void Print(int x) {
cout << "int" << endl;
}
int main() {
Print(NULL); // 可能调用 Print(int),因为 NULL 是 0
Print(nullptr); // 明确调用 Print(int*),符合预期
}
使用 nullptr 可以避免这种二义性,让代码意图更清晰。

