- 《C++ Primer》:系统学习语法和语言机制。
- 《STL 源码剖析》:理解容器、迭代器和算法的实现。
- 《Effective C++》:学习工程实践和常见陷阱。
- C++23 原计划中的网络库没有如期进入标准,标准特性不等于编译器已经完整支持。
2. 命名空间 namespace
命名空间用于隔离同名变量、函数和类型,解决名字冲突。
#include <iostream>
namespace companyA {
int value = 10;
void Print() { std::cout << "A\n"; }
}
namespace companyB {
int value = 20;
}
int main() {
std::cout << companyA::value << '\n';
std::cout << companyB::value << '\n';
using companyA::Print; // 只展开一个成员
Print();
}
三种使用方式:
std::cout << "hello"; // 推荐:明确指定
using std::cout; // 只引入常用成员
using namespace std; // 全部展开,大型项目不推荐
同名命名空间可以分散在多个文件中,编译器会把它们视为同一个命名空间。命名空间只能定义在全局或另一个命名空间中,不能定义在函数内部。
3. C++输入输出
std::cin 是标准输入对象,std::cout 是标准输出对象。
#include <iostream>
#include <string>
int main() {
std::string name;
int age;
std::cin >> name >> age;
std::cout << name << " is " << age << " years old\n";
}
<< 是流插入运算符,>> 是流提取运算符。std::endl 不仅换行,还会刷新缓冲区;普通换行一般使用 '\n',效率更高。
4. 缺省参数
缺省参数允许调用函数时省略部分实参。
void Print(int a, int b = 10, int c = 20);
int main() {
Print(1); // 1, 10, 20
Print(1, 2); // 1, 2, 20
Print(1, 2, 3);
}
void Print(int a, int b, int c) {
std::cout << a << ' ' << b << ' ' << c << '\n';
}
规则:
-
缺省参数必须从右向左连续出现。
-
传参必须从左向右,不能跳过中间参数。
-
声明和定义分离时,通常只在声明中写缺省值。
-
同一个参数不能重复指定缺省值。
void Func(int a, int b = 2, int c = 3); // 正确
// void Func(int a = 1, int b, int c = 3); // 错误
5. 函数重载
同一作用域中可以存在同名函数,但参数列表必须不同。
int Add(int a, int b) {
return a + b;
}
double Add(double a, double b) {
return a + b;
}
int Add(int a, int b, int c) {
return a + b + c;
}
只有返回类型不同不能构成重载:
int Func(int);
// double Func(int); // 错误:参数完全相同
缺省参数和重载一起使用时要避免调用歧义:
void Show(int);
void Show(int, int = 0);
// Show(1); // 错误:两个函数都能匹配
6. 引用的本质与特性
引用是已有对象的别名,不是一个独立对象。
int a = 10;
int& ra = a;
ra = 20;
std::cout << a; // 20
引用具有三个重要特性:
-
定义时必须初始化。
-
一个变量可以有多个引用。
-
引用绑定后不能改为引用另一个对象。
int a = 10;
int b = 20;
int& r = a;r = b; // 不是让 r 改绑 b,而是把 b 的值赋给 a
引用传参可以避免复制,也可以直接修改实参:
void Swap(int& x, int& y) {
int temp = x;
x = y;
y = temp;
}
指针需要判空和解引用,引用的语法更直接:
void AddOneByPointer(int* p) {
if (p != nullptr)
++*p;
}
void AddOneByReference(int& value) {
++value;
}
7. 传值返回与引用返回
传值返回会产生一个独立结果,不能作为可修改的左值。
int TopValue(const int* data, int size) {
return data[size - 1]; // 返回副本
}
// TopValue(data, 3) = 100; // 错误
引用返回返回原对象的别名,因此可以修改原对象:
int& Top(int* data, int size) {
return data[size - 1];
}
int main() {
int data[] = {1, 2, 3};
Top(data, 3) = 100;
std::cout << data[2]; // 100
}
引用返回的前提是:被引用对象在函数结束后仍然存在。绝对不能返回局部变量的引用:
int& Wrong() {
int local = 10;
return local; // 错误:函数结束后 local 已被销毁
}
图片中的栈顶例子可以写成两个版本:
int STTop(const ST& stack) {
return stack.a[stack.top - 1]; // 只读,返回副本
}
int& STTop(ST& stack) {
return stack.a[stack.top - 1]; // 可修改,返回引用
}
这样非 const 栈可以修改栈顶,const 栈只能读取。
8. const 引用与"权限"
const int& 表示不能通过这个引用修改对象:
int value = 20;
const int& ref = value;
// ref = 30; // 错误
value = 30; // 正确,value 本身不是 const
所谓"权限只能缩小,不能放大":
int a = 10;
const int& r1 = a; // 正确:可读写对象以只读方式访问
const int b = 20;
// int& r2 = b; // 错误:试图获得修改 const 对象的权限
普通复制不受这个限制,因为复制后是新对象:
const int a = 10;
int b = a; // 正确,b 是独立副本
b = 20;
const 引用还能绑定临时对象、表达式结果和类型转换结果:
const int& r1 = 30; // 绑定临时 int
const int& r2 = 10 + 20; // 绑定表达式产生的临时对象
double d = 12.34;
const int& r3 = d; // 先生成值为 12 的临时 int,再绑定
这里 r3 引用的不是 d 本身。因此后续修改 d,r3 不会跟着变化。绑定到局部 const 引用后,临时对象的生命周期会延长到该引用的作用域结束。
函数只读取参数时,优先使用 const T&:
void PrintName(const std::string& name) {
std::cout << name << '\n';
}
它既避免复制,又能接收普通对象、const 对象和临时对象。
9. 指针与引用
引用在语言层面是别名,但编译器底层通常会用类似地址的方式实现。不能简单地说"引用就是指针"。
| 特性 | 引用 | 指针 |
|---|---|---|
| 初始化 | 必须初始化 | 可以不初始化 |
| 空值 | 正常引用不能为空 | 可以是 nullptr |
| 改变目标 | 不能重新绑定 | 可以改变指向 |
| 访问对象 | 直接使用名称 | 需要 * 或 -> |
| 常见用途 | 传参、返回值、别名 | 动态结构、可选对象、遍历 |
链表头指针需要被函数修改时,可以使用指针引用:
struct Node {
int value;
Node* next;
};
void PushFront(Node*& head, int value) {
Node* node = new Node{value, head};
head = node;
}
Node*& 表示"指针变量的引用",因此函数可以修改调用者的头指针。
10. inline、nullptr 与类
inline 建议编译器把较短函数展开,减少调用开销,但编译器可以忽略这个建议。类内部定义的成员函数会被隐式视为 inline。
inline int Square(int x) {
return x * x;
}
它适合短小、频繁调用的函数,不适合递归函数或很长的函数。现在的主要价值还包括允许函数定义安全地放在头文件中。
C++11 应使用 nullptr 表示空指针:
void Func(int);
void Func(int*);
Func(nullptr); // 明确调用 Func(int*)
相比之下,NULL 可能被定义成整数 0,容易导致重载选择错误。
类把数据和操作数据的方法封装在一起:
class Stack {
public:
void Push(int value) {
data[top++] = value;
}
int& Top() {
return data[top - 1];
}
const int& Top() const {
return data[top - 1];
}
private:
int data[100]{};
int top = 0;
};
访问限定符含义:
public:类外可以直接访问。private:只能由类自身及友元访问。protected:类外不能直接访问,主要供派生类使用。class默认访问权限是private,struct默认是public。
最需要记住的一条主线是:引用是别名;普通引用可以修改原对象,const 引用只能读取;返回引用前必须确认被引用对象在函数结束后仍然存活。