这是 C++ Notes 系列的第二期,上一期见系列导航。
基础语法
变量和数据类型
C++ 是一种静态类型语言,每个变量在使用前必须先声明其类型。变量声明的基本语法如下:
type variable_name = initial_value;
基本数据类型及其范围:
#include <iostream>
#include <limits>
int main() {
// 整型
int age = 25; // 通常 4 字节 (-2^31 到 2^31-1)
short small = 100; // 通常 2 字节 (-32768 到 32767)
long big = 1000000L; // 通常 4 或 8 字节
long long huge = 10000000000LL; // 通常 8 字节
// 无符号整型(只能表示非负数,正数范围扩大一倍)
unsigned int positive = 4000000000U;
// 浮点型
float pi = 3.14159f; // 通常 4 字节,精度约 6-7 位
double precise = 3.141592653589793; // 通常 8 字节,精度约 15-17 位
// 字符型
char grade = 'A'; // 1 字节,存储 ASCII 码
wchar_t wide = L'中'; // 宽字符,存储 Unicode
// 布尔型
bool isValid = true; // true 或 false
// 空类型
void* ptr = nullptr; // 无类型指针
// 自动类型推导(C++11)
auto x = 10; // int
auto y = 3.14; // double
auto z = "hello"; // const char*
// 查看类型大小和范围
std::cout << "int 大小: " << sizeof(int) << " 字节\n";
std::cout << "int 最大值: " << std::numeric_limits<int>::max() << "\n";
std::cout << "int 最小值: " << std::numeric_limits<int>::min() << "\n";
return 0;
}
重要: 使用
auto进行类型推导时,必须同时初始化变量,否则编译器无法推导类型。
易错点: 整型除法会截断小数部分!
5 / 2结果是2,不是2.5。要得到浮点结果,至少一个操作数必须是浮点类型:5.0 / 2或5 / 2.0。
变量命名规则:
// ✅ 合法的变量名
int age;
int _count;
int studentName; // 驼峰命名法
int student_name; // 下划线命名法
int MAX_SIZE; // 常量常用全大写
// ❌ 非法的变量名
// int 2name; // 不能以数字开头
// int class; // 不能使用关键字
// int my-name; // 不能包含连字符
算术与控制语句
算术
基本算术类型
C++ 提供了丰富的算术运算符:
#include <iostream>
#include <cmath>
int main() {
int a = 17, b = 5;
// 基本算术运算符
std::cout << "a + b = " << (a + b) << "\n"; // 22 (加法)
std::cout << "a - b = " << (a - b) << "\n"; // 12 (减法)
std::cout << "a * b = " << (a * b) << "\n"; // 85 (乘法)
std::cout << "a / b = " << (a / b) << "\n"; // 3 (整数除法,截断小数)
std::cout << "a % b = " << (a % b) << "\n"; // 2 (取模/求余)
// 浮点数除法
double x = 17.0, y = 5.0;
std::cout << "x / y = " << (x / y) << "\n"; // 3.4
// 自增自减运算符
int c = 5;
std::cout << "c++ = " << c++ << "\n"; // 输出 5,然后 c 变为 6
std::cout << "++c = " << ++c << "\n"; // c 先变为 7,然后输出 7
std::cout << "c-- = " << c-- << "\n"; // 输出 7,然后 c 变为 6
std::cout << "--c = " << --c << "\n"; // c 先变为 5,然后输出 5
// 复合赋值运算符
int d = 10;
d += 5; // 等价于 d = d + 5,d = 15
d -= 3; // 等价于 d = d - 3,d = 12
d *= 2; // 等价于 d = d * 2,d = 24
d /= 4; // 等价于 d = d / 4,d = 6
d %= 4; // 等价于 d = d % 4,d = 2
// 数学函数 (需要 #include <cmath>)
std::cout << "sqrt(16) = " << sqrt(16) << "\n"; // 4
std::cout << "pow(2, 3) = " << pow(2, 3) << "\n"; // 8
std::cout << "abs(-5) = " << abs(-5) << "\n"; // 5
std::cout << "floor(3.7) = " << floor(3.7) << "\n"; // 3
std::cout << "ceil(3.2) = " << ceil(3.2) << "\n"; // 4
std::cout << "round(3.5) = " << round(3.5) << "\n"; // 4
return 0;
}
整数
C++ 中的整数类型有不同的存储大小和表示范围:
#include <iostream>
#include <cstdint> // 固定宽度整数类型
int main() {
// 固定宽度整数类型(推荐在需要精确控制大小时使用)
int8_t i8 = 127; // 8 位有符号整数 (-128 到 127)
uint8_t u8 = 255; // 8 位无符号整数 (0 到 255)
int16_t i16 = 32767; // 16 位有符号整数
uint16_t u16 = 65535; // 16 位无符号整数
int32_t i32 = 2147483647; // 32 位有符号整数
uint32_t u32 = 4294967295U; // 32 位无符号整数
int64_t i64 = 9223372036854775807LL; // 64 位有符号整数
uint64_t u64 = 18446744073709551615ULL; // 64 位无符号整数
// 整数后缀
int a = 42; // 默认 int
long b = 42L; // long
long long c = 42LL; // long long
unsigned d = 42U; // unsigned int
unsigned long e = 42UL; // unsigned long
// 不同进制表示
int dec = 42; // 十进制
int oct = 052; // 八进制(以 0 开头)
int hex = 0x2A; // 十六进制(以 0x 开头)
int bin = 0b101010; // 二进制(C++14,以 0b 开头)
std::cout << "十进制 42 = " << dec << "\n";
std::cout << "八进制 052 = " << oct << "\n";
std::cout << "十六进制 0x2A = " << hex << "\n";
std::cout << "二进制 0b101010 = " << bin << "\n";
// 整数溢出
int8_t small = 127;
// small++; // 溢出!结果变成 -128 (undefined behavior for signed)
return 0;
}
重要: 有符号整数溢出是未定义行为(Undefined Behavior),程序可能崩溃、产生错误结果或表现异常。无符号整数溢出是明确定义的(回绕)。
浮点数及其运算
#include <iostream>
#include <iomanip> // 用于格式化输出
#include <cmath>
#include <limits>
int main() {
// 浮点数精度问题
float f = 0.1f;
double d = 0.1;
std::cout << std::setprecision(20);
std::cout << "float 0.1 = " << f << "\n";
std::cout << "double 0.1 = " << d << "\n";
// 浮点数比较:永远不要直接用 ==
double a = 0.1 + 0.2;
double b = 0.3;
// ❌ 错误方式
if (a == b) {
std::cout << "相等(但可能不执行)\n";
}
// ✅ 正确方式:使用 epsilon 比较
const double EPSILON = 1e-9;
if (fabs(a - b) < EPSILON) {
std::cout << "近似相等\n";
}
// C++11 起推荐的方式
if (std::abs(a - b) < std::numeric_limits<double>::epsilon() * 100) {
std::cout << "近似相等(使用 numeric_limits)\n";
}
// 特殊浮点值
double inf = 1.0 / 0.0; // 正无穷
double nan = 0.0 / 0.0; // NaN (Not a Number)
double neg_inf = -1.0 / 0.0; // 负无穷
std::cout << "1.0/0.0 = " << inf << "\n";
std::cout << "0.0/0.0 = " << nan << "\n";
std::cout << "-1.0/0.0 = " << neg_inf << "\n";
// 检查 NaN
if (std::isnan(nan)) {
std::cout << "是 NaN\n";
}
// 科学计数法
double sci = 1.5e3; // 1500
double tiny = 1.5e-3; // 0.0015
return 0;
}
易错点: 浮点数
0.1 + 0.2 != 0.3在许多编程语言中都成立,这是二进制浮点表示的限制。比较浮点数时始终使用 epsilon 比较法。
布尔类型
#include <iostream>
int main() {
bool flag = true; // 或 false
// 布尔值在算术运算中会被转换为整数:true -> 1, false -> 0
int sum = true + true; // sum = 2
// 非零值在布尔上下文中为 true,零为 false
bool b1 = 42; // true
bool b2 = 0; // false
bool b3 = -1; // true(负数也是 true)
bool b4 = 0.001; // true
// 逻辑运算符
bool a = true, b = false;
std::cout << "a && b = " << (a && b) << "\n"; // 逻辑与 (false)
std::cout << "a || b = " << (a || b) << "\n"; // 逻辑或 (true)
std::cout << "!a = " << (!a) << "\n"; // 逻辑非 (false)
// 短路求值
int x = 0;
if (false && (++x > 0)) {
// ++x 不会执行,因为第一个操作数为 false
}
std::cout << "x = " << x << "\n"; // x 仍为 0
return 0;
}
类型转换
#include <iostream>
int main() {
// 1. 隐式类型转换(自动)
int i = 10;
double d = i; // int -> double,安全
double pi = 3.14;
int truncated = pi; // double -> int,小数部分被截断
std::cout << "truncated = " << truncated << "\n"; // 3
// 2. 显式类型转换(C 风格)- 不推荐
double x = 3.7;
int y = (int)x; // C 风格强制转换
// 3. 显式类型转换(C++ 风格)- 推荐
// static_cast:编译时检查,用于相关类型的转换
int a = static_cast<int>(3.14); // 3
double b = static_cast<double>(5) / 2; // 2.5
// const_cast:添加或移除 const 属性
const int val = 10;
int* ptr = const_cast<int*>(&val); // 危险!仅在确定对象可修改时使用
// reinterpret_cast:低级类型转换,不检查类型兼容性
int num = 65;
char* charPtr = reinterpret_cast<char*>(&num);
// dynamic_cast:用于类层次结构中的安全向下转换(需要虚函数)
// Base* base = new Derived();
// Derived* derived = dynamic_cast<Derived*>(base);
// 4. 列表初始化(C++11,窄化转换会报错)
int m = 3.14; // 警告,允许
// int n = {3.14}; // 错误!窄化转换
int n = {3}; // OK
// 类型提升规则(算术运算时)
// int + double -> double
// float + double -> double
// int + long -> long
// unsigned + signed -> unsigned(可能产生意外结果!)
unsigned int u = 10;
int s = -5;
if (s < u) {
std::cout << "-5 < 10\n"; // 可能不执行!
} else {
std::cout << "由于 unsigned 提升,-5 被认为大于 10\n";
}
return 0;
}
重要: 混合使用有符号和无符号整数时要特别小心!当表达式中同时存在
signed和unsigned时,signed会被提升为unsigned,导致负数变成很大的正数。
条件语句(if和switch)
#include <iostream>
int main() {
int score = 85;
// if-else if-else 结构
if (score >= 90) {
std::cout << "A\n";
} else if (score >= 80) {
std::cout << "B\n";
} else if (score >= 70) {
std::cout << "C\n";
} else if (score >= 60) {
std::cout << "D\n";
} else {
std::cout << "F\n";
}
// 条件运算符(三元运算符)
int a = 5, b = 3;
int max = (a > b) ? a : b; // max = 5
// 嵌套三元运算符(可读性较差,谨慎使用)
int num = 0;
std::string result = (num > 0) ? "正数" : (num < 0) ? "负数" : "零";
// switch 语句(仅适用于整型、字符型、枚举)
char grade = 'B';
switch (grade) {
case 'A':
std::cout << "优秀\n";
break; // 不要忘记 break!
case 'B':
std::cout << "良好\n";
break;
case 'C':
std::cout << "中等\n";
break;
case 'D':
std::cout << "及格\n";
break;
case 'F':
std::cout << "不及格\n";
break;
default:
std::cout << "无效成绩\n";
break;
}
// switch 多个 case 共享代码
int month = 3;
switch (month) {
case 3: case 4: case 5:
std::cout << "春季\n";
break;
case 6: case 7: case 8:
std::cout << "夏季\n";
break;
case 9: case 10: case 11:
std::cout << "秋季\n";
break;
case 12: case 1: case 2:
std::cout << "冬季\n";
break;
default:
std::cout << "无效月份\n";
}
// C++17 if with initializer
// if (auto it = map.find(key); it != map.end()) {
// // 使用 it
// }
return 0;
}
易错点:
switch语句中忘记写break会导致”贯穿(fall-through)“,执行多个 case 的代码。这在 C++17 可以用[[fallthrough]]属性显式标记。
循环语句
#include <iostream>
#include <vector>
int main() {
// 1. for 循环
for (int i = 0; i < 5; ++i) {
std::cout << i << " ";
}
std::cout << "\n";
// 多个初始化表达式(C++ 允许逗号表达式)
for (int i = 0, j = 10; i < j; ++i, --j) {
std::cout << "i=" << i << ", j=" << j << "\n";
}
// 2. while 循环(先检查条件)
int count = 0;
while (count < 5) {
std::cout << count << " ";
++count;
}
std::cout << "\n";
// 3. do-while 循环(至少执行一次)
int n = 0;
do {
std::cout << "执行一次,即使条件为假\n";
} while (n > 0);
// 4. 范围 for 循环(C++11,遍历容器)
std::vector<int> nums = {1, 2, 3, 4, 5};
// 只读访问(拷贝)
for (int num : nums) {
std::cout << num << " ";
num = 0; // 不影响原容器
}
std::cout << "\n";
// 修改元素(引用)
for (int& num : nums) {
num *= 2; // 每个元素乘以 2
}
// 避免拷贝(const 引用)
for (const int& num : nums) {
std::cout << num << " "; // 2 4 6 8 10
}
std::cout << "\n";
// auto 配合范围 for
for (const auto& elem : nums) {
std::cout << elem << " ";
}
std::cout << "\n";
// 5. 循环控制语句
for (int i = 0; i < 10; ++i) {
if (i == 3) continue; // 跳过当前迭代
if (i == 7) break; // 跳出循环
std::cout << i << " "; // 输出: 0 1 2 4 5 6
}
std::cout << "\n";
// 6. 嵌套循环和标签
outer: // 标签
for (int i = 0; i < 3; ++i) {
for (int j = 0; j < 3; ++j) {
if (i == 1 && j == 1) {
break; // 只跳出内层循环
}
std::cout << "(" << i << "," << j << ") ";
}
}
std::cout << "\n";
// 7. 无限循环
// for (;;) { /* ... */ }
// while (true) { /* ... */ }
return 0;
}
重要: 在循环中使用
auto&还是const auto&取决于是否需要修改元素。如果不修改,使用const auto&可以避免不必要的拷贝。
数组
#include <iostream>
#include <array> // C++11 std::array
#include <vector> // 动态数组
int main() {
// 1. 静态数组(C 风格)
int arr1[5]; // 未初始化,元素值不确定
int arr2[5] = {}; // 全部初始化为 0
int arr3[5] = {1, 2, 3}; // 剩余元素初始化为 0
int arr4[] = {1, 2, 3, 4, 5}; // 编译器自动推断大小为 5
// 访问元素
arr1[0] = 10;
std::cout << "arr1[0] = " << arr1[0] << "\n";
// 数组大小
int size = sizeof(arr4) / sizeof(arr4[0]); // 5
// 遍历数组
for (int i = 0; i < size; ++i) {
std::cout << arr4[i] << " ";
}
std::cout << "\n";
// 2. 多维数组
int matrix[2][3] = {
{1, 2, 3},
{4, 5, 6}
};
for (int i = 0; i < 2; ++i) {
for (int j = 0; j < 3; ++j) {
std::cout << matrix[i][j] << " ";
}
std::cout << "\n";
}
// 3. C++11 std::array(推荐)
std::array<int, 5> stdArr = {1, 2, 3, 4, 5};
std::cout << "Size: " << stdArr.size() << "\n";
std::cout << "First: " << stdArr.front() << "\n";
std::cout << "Last: " << stdArr.back() << "\n";
// 访问元素(带边界检查)
std::cout << "stdArr[0] = " << stdArr[0] << "\n"; // 不检查
std::cout << "stdArr.at(0) = " << stdArr.at(0) << "\n"; // 检查,越界抛异常
// 遍历
for (const auto& elem : stdArr) {
std::cout << elem << " ";
}
std::cout << "\n";
// 4. 动态数组(std::vector)
std::vector<int> vec;
// 添加元素
vec.push_back(1);
vec.push_back(2);
vec.push_back(3);
// 预分配空间
vec.reserve(100); // 容量设为 100,避免频繁重新分配
// 插入
vec.insert(vec.begin(), 0); // 在开头插入 0
// 删除
vec.pop_back(); // 删除末尾
vec.erase(vec.begin()); // 删除指定位置
vec.clear(); // 清空
// 初始化方式
std::vector<int> v1(5); // 5 个 0
std::vector<int> v2(5, 10); // 5 个 10
std::vector<int> v3 = {1,2,3}; // 列表初始化
std::vector<int> v4{1,2,3}; // 同上
// 获取底层数组指针
int* rawPtr = vec.data();
return 0;
}
重要: C 风格数组在作为函数参数传递时会**退化(decay)**为指针,丢失大小信息。如果需要保留大小,使用引用或
std::array/std::vector。
易错点: 数组下标越界是 C++ 中最常见的错误之一,且不会自动报错。使用
std::vector::at()或调试工具检测越界。
字符串
#include <iostream>
#include <string> // C++ string 类
#include <cstring> // C 风格字符串函数
#include <sstream> // 字符串流
int main() {
// 1. C 风格字符串(字符数组)
char str1[] = "Hello"; // 自动包含 '\0'
char str2[20] = "World";
char str3[20];
// C 风格字符串操作
strcpy(str3, str1); // 复制
strcat(str3, " "); // 连接
strcat(str3, str2);
size_t len = strlen(str1); // 长度(不包含 '\0')
int cmp = strcmp(str1, str2); // 比较
std::cout << "C string: " << str3 << "\n";
std::cout << "Length: " << len << "\n";
// 2. C++ string 类(强烈推荐)
std::string s1 = "Hello";
std::string s2 = "World";
std::string s3 = s1 + " " + s2; // 方便地连接
// 基本操作
std::cout << "Length: " << s3.length() << "\n";
std::cout << "Size: " << s3.size() << "\n";
std::cout << "Empty: " << s3.empty() << "\n";
std::cout << "First char: " << s3[0] << "\n";
std::cout << "Last char: " << s3.back() << "\n";
// 子串
std::string sub = s3.substr(0, 5); // "Hello",从位置 0 开始,长度为 5
// 查找
size_t pos = s3.find("World"); // 返回位置,找不到返回 string::npos
if (pos != std::string::npos) {
std::cout << "Found at: " << pos << "\n";
}
// 插入和删除
s3.insert(5, ","); // "Hello, World"
s3.erase(5, 1); // 删除位置 5 开始的 1 个字符
s3.replace(6, 5, "C++"); // 替换 "World" 为 "C++"
// 比较
if (s1 == "Hello") {
std::cout << "相等\n";
}
// 转换
std::string numStr = "42";
int num = std::stoi(numStr); // string to int
double d = std::stod("3.14"); // string to double
std::string fromNum = std::to_string(42); // int to string
// 迭代
for (char c : s3) {
std::cout << c;
}
std::cout << "\n";
// C++11 raw string literal(原始字符串,不转义)
std::string path = R"(C:\Program Files\App\file.txt)";
std::string json = R"({"name": "John", "age": 30})";
std::cout << "Path: " << path << "\n";
// 多行 raw string
std::string multiline = R"(
Line 1
Line 2
Line 3
)";
// 3. 字符串流(用于格式化)
std::ostringstream oss;
oss << "Name: " << "John" << ", Age: " << 25;
std::string result = oss.str();
std::cout << result << "\n";
// 解析字符串
std::string data = "100 200 300";
std::istringstream iss(data);
int a, b, c;
iss >> a >> b >> c;
std::cout << a << " " << b << " " << c << "\n";
return 0;
}
重要: 优先使用
std::string而非 C 风格字符串。std::string自动管理内存,提供了丰富的操作方法,且更安全。
输入与输出
#include <iostream>
#include <fstream>
#include <sstream>
#include <iomanip>
int main() {
// 1. 标准输入输出
std::cout << "标准输出\n";
std::cerr << "标准错误(无缓冲)\n";
std::clog << "标准日志(有缓冲)\n";
// 2. 格式化输出
double pi = 3.14159265359;
std::cout << "默认: " << pi << "\n";
std::cout << "精度3: " << std::setprecision(3) << pi << "\n";
std::cout << std::fixed; // 固定小数位
std::cout << "固定6位: " << std::setprecision(6) << pi << "\n";
std::cout.unsetf(std::ios::fixed); // 取消固定格式
// 宽度和对齐
std::cout << std::setw(10) << std::left << "Name"
<< std::setw(10) << "Score" << "\n";
std::cout << std::setw(10) << std::left << "Alice"
<< std::setw(10) << 95 << "\n";
// 填充字符
std::cout << std::setfill('0') << std::setw(5) << 42 << "\n";
std::cout << std::setfill(' '); // 恢复默认
// 进制
int num = 255;
std::cout << "十进制: " << num << "\n";
std::cout << "八进制: " << std::oct << num << "\n";
std::cout << "十六进制: " << std::hex << num << "\n";
std::cout << "大写十六进制: " << std::uppercase << num << "\n";
std::cout << std::dec << std::nouppercase; // 恢复
// 布尔值输出
std::cout << std::boolalpha << true << " " << false << "\n"; // "true false"
std::cout << std::noboolalpha; // 恢复 1/0
// 3. 键盘输入
std::string name;
int age;
std::cout << "Enter name: ";
std::cin >> name; // 读到空白字符停止
std::cout << "Enter age: ";
std::cin >> age;
std::cin.ignore(); // 忽略换行符
std::string line;
std::cout << "Enter a line: ";
std::getline(std::cin, line); // 读取整行
// 4. 文件输入输出
// 写入文件
std::ofstream outFile("data.txt");
if (outFile.is_open()) {
outFile << "Hello, File!\n";
outFile << "Line 2\n";
outFile.close();
}
// 读取文件
std::ifstream inFile("data.txt");
if (inFile.is_open()) {
std::string fileLine;
while (std::getline(inFile, fileLine)) {
std::cout << fileLine << "\n";
}
inFile.close();
}
// 追加模式
std::ofstream appendFile("data.txt", std::ios::app);
appendFile << "Appended line\n";
appendFile.close();
// 二进制文件
int data[] = {1, 2, 3, 4, 5};
std::ofstream binOut("data.bin", std::ios::binary);
binOut.write(reinterpret_cast<char*>(data), sizeof(data));
binOut.close();
// 5. 检查输入状态
int value;
std::cout << "Enter an integer: ";
if (!(std::cin >> value)) {
std::cin.clear(); // 清除错误状态
std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n'); // 清空缓冲区
std::cout << "Invalid input!\n";
}
return 0;
}
易错点: 使用
std::cin >> variable后如果要使用std::getline(),必须先调用std::cin.ignore()清除残留的换行符,否则getline会读到空行。
Goto
#include <iostream>
int main() {
// goto 语句:无条件跳转到指定标签
// 在现代 C++ 中很少使用,通常可以用循环和函数替代
int i = 0;
loop_start: // 标签
if (i >= 5) {
goto loop_end;
}
std::cout << i << " ";
++i;
goto loop_start;
loop_end:
std::cout << "\nLoop finished\n";
// 更常见的用途:错误处理和资源清理
int* ptr1 = nullptr;
int* ptr2 = nullptr;
int* ptr3 = nullptr;
ptr1 = new int(1);
if (!ptr1) goto cleanup;
ptr2 = new int(2);
if (!ptr2) goto cleanup;
ptr3 = new int(3);
if (!ptr3) goto cleanup;
// 正常使用资源
std::cout << *ptr1 << " " << *ptr2 << " " << *ptr3 << "\n";
cleanup:
delete ptr1;
delete ptr2;
delete ptr3;
// 现代 C++ 更好的做法:使用 RAII 和智能指针
// 避免使用 goto
return 0;
}
重要: 尽量避免使用
goto。它会使代码难以阅读和维护。现代 C++ 中,goto的唯一合理用途是在 C 风格的错误处理中跳转到统一的清理代码,但更好的做法是使用 RAII、智能指针和异常处理。
函数和参数传递
函数定义和调用
#include <iostream>
#include <vector>
// 函数声明(原型)
int add(int a, int b);
void greet(const std::string& name);
// 函数定义
int add(int a, int b) {
return a + b;
}
void greet(const std::string& name) {
std::cout << "Hello, " << name << "!\n";
}
// 重载函数:同名不同参数
int multiply(int a, int b) {
return a * b;
}
double multiply(double a, double b) {
return a * b;
}
// 返回多个值(使用引用参数)
void divide(int dividend, int divisor, int& quotient, int& remainder) {
quotient = dividend / divisor;
remainder = dividend % divisor;
}
// 返回多个值(使用结构体)
struct Result {
int quotient;
int remainder;
};
Result divide2(int dividend, int divisor) {
return {dividend / divisor, dividend % divisor};
}
// 返回多个值(C++17 结构化绑定)
std::pair<int, int> divide3(int dividend, int divisor) {
return {dividend / divisor, dividend % divisor};
}
// 递归函数
int factorial(int n) {
if (n <= 1) return 1;
return n * factorial(n - 1);
}
// 尾递归(某些编译器可优化)
int factorial_tail(int n, int acc = 1) {
if (n <= 1) return acc;
return factorial_tail(n - 1, n * acc);
}
// 函数模板
template<typename T>
T maximum(T a, T b) {
return (a > b) ? a : b;
}
// Lambda 表达式(匿名函数)
auto lambda_add = [](int a, int b) -> int {
return a + b;
};
int main() {
// 基本调用
int sum = add(3, 5);
greet("Alice");
// 函数重载
std::cout << multiply(3, 4) << "\n"; // int 版本
std::cout << multiply(3.5, 2.0) << "\n"; // double 版本
// 多返回值
int q, r;
divide(17, 5, q, r);
std::cout << "17 / 5 = " << q << " 余 " << r << "\n";
Result res = divide2(17, 5);
std::cout << "17 / 5 = " << res.quotient << " 余 " << res.remainder << "\n";
// C++17 结构化绑定
auto [quot, rem] = divide3(17, 5);
std::cout << "17 / 5 = " << quot << " 余 " << rem << "\n";
// 递归
std::cout << "5! = " << factorial(5) << "\n";
// 模板函数
std::cout << maximum(3, 5) << "\n"; // 自动推导为 int
std::cout << maximum(3.5, 2.5) << "\n"; // 自动推导为 double
// Lambda
std::cout << lambda_add(2, 3) << "\n";
// 立即执行的 lambda
int result = [](int x) { return x * x; }(5);
std::cout << "5^2 = " << result << "\n";
return 0;
}
重要: 函数声明(原型)可以让编译器在函数定义之前就知道函数的签名,这是组织大型代码和多文件项目的基础。
参数传递方式:值传递、引用传递和指针传递
#include <iostream>
#include <string>
// 1. 值传递:创建参数的副本,不影响原变量
void byValue(int x) {
x = 100; // 只修改副本
}
// 2. 引用传递:使用原变量的别名,可以修改原变量
void byReference(int& x) {
x = 100; // 修改原变量
}
// 3. 指针传递:传递地址,可以修改原变量
void byPointer(int* x) {
if (x != nullptr) {
*x = 100; // 解引用修改原变量
}
}
// const 引用:避免拷贝,但不允许修改(推荐用于大型对象)
void printLargeObject(const std::string& str) {
// str += "!"; // 错误!不能修改 const 引用
std::cout << str << "\n";
}
// 对象作为值传递(会调用拷贝构造函数)
class MyClass {
public:
int value;
MyClass(int v) : value(v) {
std::cout << "Constructor\n";
}
MyClass(const MyClass& other) : value(other.value) {
std::cout << "Copy Constructor\n";
}
};
void takeByValue(MyClass obj) {
std::cout << "By value: " << obj.value << "\n";
}
void takeByReference(const MyClass& obj) {
std::cout << "By reference: " << obj.value << "\n";
}
// 输出参数(通过引用返回多个值)
void getMinMax(const int arr[], int size, int& minVal, int& maxVal) {
if (size <= 0) return;
minVal = maxVal = arr[0];
for (int i = 1; i < size; ++i) {
if (arr[i] < minVal) minVal = arr[i];
if (arr[i] > maxVal) maxVal = arr[i];
}
}
// 数组作为参数(退化为指针)
void processArray(int arr[], int size) { // 等同于 int* arr
for (int i = 0; i < size; ++i) {
arr[i] *= 2;
}
}
// 使用引用保留数组大小信息
void processArrayRef(int (&arr)[5]) { // 必须传递大小为 5 的数组
for (int i = 0; i < 5; ++i) {
arr[i] *= 2;
}
}
// C++11 完美转发(转发引用)
template<typename T>
void forward(T&& arg) { // 万能引用
// std::forward<T>(arg) 保持值的类别
}
int main() {
int num = 50;
byValue(num);
std::cout << "After byValue: " << num << "\n"; // 仍为 50
byReference(num);
std::cout << "After byReference: " << num << "\n"; // 变为 100
num = 50;
byPointer(&num);
std::cout << "After byPointer: " << num << "\n"; // 变为 100
// 对比对象传递
MyClass obj(42);
std::cout << "--- Pass by value ---\n";
takeByValue(obj); // 调用拷贝构造函数
std::cout << "--- Pass by reference ---\n";
takeByReference(obj); // 无拷贝
// 数组
int arr[] = {1, 2, 3, 4, 5};
processArray(arr, 5); // 修改原数组
int minVal, maxVal;
getMinMax(arr, 5, minVal, maxVal);
std::cout << "Min: " << minVal << ", Max: " << maxVal << "\n";
return 0;
}
重要: 对于内置类型(int, double 等),值传递和引用传递性能差异不大。对于大型对象,使用
const T&避免昂贵的拷贝操作。
易错点: 数组作为函数参数时会退化为指针,丢失大小信息。务必同时传递大小参数,或使用引用语法
T (&arr)[N]或std::array/std::vector。
内联函数
#include <iostream>
// 普通函数:调用时有函数调用的开销(压栈、跳转、返回)
int max(int a, int b) {
return (a > b) ? a : b;
}
// 内联函数:建议编译器将函数体直接插入调用处,减少调用开销
// inline 只是建议,编译器可能拒绝
inline int maxInline(int a, int b) {
return (a > b) ? a : b;
}
// 在类定义中直接定义的函数自动成为内联函数
class Calculator {
public:
// 隐式内联
int add(int a, int b) {
return a + b;
}
// 显式声明内联
inline int subtract(int a, int b);
};
inline int Calculator::subtract(int a, int b) {
return a - b;
}
// constexpr 函数(C++11):编译期可计算的函数,更强版本的"内联"
constexpr int square(int x) {
return x * x;
}
// 编译期计算
constexpr int result = square(5); // 编译时计算为 25
// constexpr 函数的限制(C++11/14)
// C++14 放宽了限制,允许变量声明和循环
constexpr int factorial(int n) {
int result = 1; // C++14 起允许
for (int i = 1; i <= n; ++i) { // C++14 起允许循环
result *= i;
}
return result;
}
// C++17 if constexpr:编译期条件
// template<typename T>
// auto getValue(T t) {
// if constexpr (std::is_pointer_v<T>) {
// return *t;
// } else {
// return t;
// }
// }
int main() {
int a = 3, b = 5;
// 内联函数调用
int m = maxInline(a, b);
// 编译期计算
int arr[square(5)]; // 数组大小必须是编译期常量
constexpr int fact5 = factorial(5);
std::cout << "5! = " << fact5 << "\n";
return 0;
}
重要:
inline关键字在现代 C++ 中的主要作用不是建议内联优化,而是允许函数在多个翻译单元中定义(通常放在头文件中)。实际的函数内联决策由编译器根据优化级别决定。
默认参数
#include <iostream>
#include <string>
// 默认参数从右向左指定
void printInfo(const std::string& name,
int age = 0,
const std::string& country = "Unknown") {
std::cout << "Name: " << name
<< ", Age: " << age
<< ", Country: " << country << "\n";
}
// ❌ 错误:默认参数不能左边有值而右边没有
// void bad(int a = 1, int b, int c = 3);
// ✅ 正确:从右向左依次设置默认值
void good(int a, int b = 2, int c = 3);
// 函数声明和定义分离时,默认参数只能在声明中指定
void setup(int width, int height, int depth = 1);
void setup(int width, int height, int depth) {
std::cout << width << "x" << height << "x" << depth << "\n";
}
// 默认参数可以是表达式
int getDefaultId() {
return 999;
}
void registerUser(const std::string& name, int id = getDefaultId()) {
std::cout << "User: " << name << ", ID: " << id << "\n";
}
// C++ 函数重载与默认参数的结合
class Rectangle {
public:
// 使用默认参数的版本
void draw(int x = 0, int y = 0, int width = 100, int height = 100);
// 重载版本
void draw(const std::string& style);
};
void Rectangle::draw(int x, int y, int width, int height) {
std::cout << "Draw rect at (" << x << "," << y << ") "
<< "size " << width << "x" << height << "\n";
}
void Rectangle::draw(const std::string& style) {
std::cout << "Draw rect with style: " << style << "\n";
}
int main() {
// 使用不同数量的参数
printInfo("Alice"); // 使用所有默认值
printInfo("Bob", 25); // country 使用默认值
printInfo("Charlie", 30, "USA"); // 不使用默认值
// 指定部分参数
setup(1920, 1080); // depth = 1
setup(800, 600, 32); // 指定所有参数
// 默认参数表达式
registerUser("David");
registerUser("Eve", 123);
// 类成员函数
Rectangle rect;
rect.draw(); // 全部默认
rect.draw(10, 20); // 部分指定
rect.draw("dashed"); // 调用重载版本
return 0;
}
易错点: 默认参数在函数声明和定义分离时,只能在声明中指定,否则会导致编译错误。此外,避免默认参数和函数重载同时使用,容易产生歧义调用。
指针和引用
指针的定义和使用
#include <iostream>
int main() {
int num = 42;
// 1. 指针的定义
int* ptr = # // ptr 存储 num 的地址
// 2. 解引用:获取指针指向的值
std::cout << "Value: " << *ptr << "\n"; // 42
std::cout << "Address: " << ptr << "\n"; // 地址(十六进制)
std::cout << "Num address: " << &num << "\n"; // 同上
// 3. 通过指针修改值
*ptr = 100;
std::cout << "New value: " << num << "\n"; // 100
// 4. 空指针
int* nullPtr = nullptr; // C++11 推荐
// int* oldNull = NULL; // C 风格,不推荐
// int* worstNull = 0; // 不推荐使用 0
if (nullPtr == nullptr) {
std::cout << "Pointer is null\n";
}
// 5. 野指针(未初始化的指针,危险!)
// int* wildPtr; // ❌ 未初始化
// *wildPtr = 10; // ❌ 可能导致程序崩溃
// 6. 指针的指针
int** ptrToPtr = &ptr;
std::cout << "Value via ptrToPtr: " << **ptrToPtr << "\n"; // 100
// 7. 常量指针 vs 指针常量
int a = 1, b = 2;
// 指向常量的指针:可以改指向,不能改值
const int* p1 = &a;
// *p1 = 10; // 错误!不能通过 p1 修改值
p1 = &b; // OK,可以指向其他变量
// 常量指针:不能改指向,可以改值
int* const p2 = &a;
*p2 = 10; // OK
// p2 = &b; // 错误!不能改指向
// 指向常量的常量指针:都不能改
const int* const p3 = &a;
// *p3 = 10; // 错误
// p3 = &b; // 错误
// 8. 指针运算
int arr[] = {10, 20, 30, 40, 50};
int* p = arr; // 指向数组首元素
std::cout << *p << "\n"; // 10
std::cout << *(p + 1) << "\n"; // 20(p 指向下一个 int)
std::cout << *(p + 2) << "\n"; // 30
// 指针减法(计算元素间隔)
int* end = &arr[4];
std::cout << "Elements between: " << (end - p) << "\n"; // 4
// 9. void 指针(通用指针)
void* generic = #
// 必须先转换为具体类型才能解引用
int* specific = static_cast<int*>(generic);
std::cout << *specific << "\n";
return 0;
}
重要:
const int*和int* const的区别是 C++ 中的经典面试题。记忆口诀:“const在*左边,值不能变;const在*右边,指向不能变”。
函数指针
#include <iostream>
// 普通函数
int add(int a, int b) {
return a + b;
}
int subtract(int a, int b) {
return a - b;
}
int multiply(int a, int b) {
return a * b;
}
// 函数指针作为参数
typedef int (*Operation)(int, int); // 使用 typedef
using Operation2 = int (*)(int, int); // C++11 using 别名
int calculate(int a, int b, Operation op) {
return op(a, b);
}
// 函数返回函数指针
Operation getOperation(char op) {
switch (op) {
case '+': return add;
case '-': return subtract;
case '*': return multiply;
default: return nullptr;
}
}
// 更复杂的例子:回调函数
void processArray(int arr[], int size, void (*callback)(int)) {
for (int i = 0; i < size; ++i) {
callback(arr[i]);
}
}
void printInt(int x) {
std::cout << x << " ";
}
void printSquare(int x) {
std::cout << x * x << " ";
}
// 成员函数指针
class Calculator {
public:
int value = 0;
void add(int x) { value += x; }
void subtract(int x) { value -= x; }
};
// 使用 std::function(C++11,更灵活)
#include <functional>
int main() {
// 1. 定义函数指针
int (*funcPtr)(int, int) = add;
// 使用函数指针调用
std::cout << funcPtr(3, 4) << "\n"; // 7
// 2. 指向不同函数
funcPtr = subtract;
std::cout << funcPtr(7, 4) << "\n"; // 3
// 3. 函数指针数组
int (*opArray[])(int, int) = {add, subtract, multiply};
std::cout << opArray[0](2, 3) << "\n"; // 5 (add)
std::cout << opArray[2](2, 3) << "\n"; // 6 (multiply)
// 4. 作为参数传递
std::cout << calculate(5, 3, add) << "\n"; // 8
std::cout << calculate(5, 3, multiply) << "\n"; // 15
// 5. 使用 typedef/using 简化
Operation op = add;
std::cout << op(10, 20) << "\n"; // 30
// 6. 回调函数
int arr[] = {1, 2, 3, 4, 5};
processArray(arr, 5, printInt); // 1 2 3 4 5
std::cout << "\n";
processArray(arr, 5, printSquare); // 1 4 9 16 25
std::cout << "\n";
// 7. 成员函数指针
void (Calculator::*memberPtr)(int) = &Calculator::add;
Calculator calc;
(calc.*memberPtr)(10); // 调用 add(10)
std::cout << "Value: " << calc.value << "\n"; // 10
memberPtr = &Calculator::subtract;
(calc.*memberPtr)(3); // 调用 subtract(3)
std::cout << "Value: " << calc.value << "\n"; // 7
// 8. std::function(更现代的方式)
std::function<int(int, int)> f = add;
std::cout << f(2, 3) << "\n"; // 5
// 可以存储 lambda
f = [](int a, int b) { return a * b; };
std::cout << f(4, 5) << "\n"; // 20
return 0;
}
重要: 现代 C++ 推荐使用
std::function和 lambda 表达式替代原始函数指针,它们更灵活,可以捕获变量,支持更多类型的可调用对象。
内存管理简介
C++ 允许程序员直接管理内存,这提供了灵活性但也带来了责任。
Alloc和Free
#include <iostream>
#include <cstdlib> // malloc, free
int main() {
// C 风格内存管理:malloc / free
// 分配单个 int
int* p1 = (int*)malloc(sizeof(int));
if (p1 == nullptr) {
std::cerr << "Memory allocation failed\n";
return 1;
}
*p1 = 42;
std::cout << *p1 << "\n";
free(p1); // 释放内存
// 分配数组
int* arr = (int*)malloc(5 * sizeof(int));
if (arr != nullptr) {
for (int i = 0; i < 5; ++i) {
arr[i] = i * 10;
}
free(arr);
}
// calloc:分配并初始化为 0
int* zeroed = (int*)calloc(5, sizeof(int)); // 5 个 int,全部初始化为 0
free(zeroed);
// realloc:重新分配大小
int* resized = (int*)malloc(3 * sizeof(int));
resized = (int*)realloc(resized, 10 * sizeof(int)); // 扩展到 10 个 int
free(resized);
return 0;
}
重要: 在 C++ 中,不推荐使用
malloc/free,因为它们不会调用构造函数和析构函数。使用new/delete或智能指针。
New和Delete
#include <iostream>
#include <string>
class MyClass {
public:
int value;
MyClass(int v) : value(v) {
std::cout << "Constructor: " << value << "\n";
}
~MyClass() {
std::cout << "Destructor: " << value << "\n";
}
};
int main() {
// 1. 分配单个对象
int* p1 = new int; // 未初始化
int* p2 = new int(); // 值初始化(0)
int* p3 = new int(42); // 初始化为 42
std::cout << *p1 << " " << *p2 << " " << *p3 << "\n";
delete p1;
delete p2;
delete p3;
// 2. 分配数组
int* arr1 = new int[5]; // 5 个未初始化的 int
int* arr2 = new int[5](); // 5 个初始化为 0 的 int
int* arr3 = new int[5]{1,2,3}; // {1,2,3,0,0}
delete[] arr1; // 数组必须用 delete[]
delete[] arr2;
delete[] arr3;
// 3. 分配对象(会调用构造函数)
MyClass* obj = new MyClass(100);
delete obj; // 会调用析构函数
// 4. 分配对象数组
MyClass* objArr = new MyClass[3]{1, 2, 3};
delete[] objArr; // 会调用每个对象的析构函数
// 5. 分配多维数组
int** matrix = new int*[3]; // 3 行
for (int i = 0; i < 3; ++i) {
matrix[i] = new int[4]; // 每行 4 列
}
// 使用...
matrix[1][2] = 42;
// 释放多维数组
for (int i = 0; i < 3; ++i) {
delete[] matrix[i];
}
delete[] matrix;
// 6. 异常安全分配(C++11 nothrow)
int* safe = new (std::nothrow) int[1000000000000]; // 可能失败
if (safe == nullptr) {
std::cerr << "Allocation failed\n";
} else {
delete[] safe;
}
// 7. 定位 new(在已分配的内存上构造对象)
char buffer[sizeof(MyClass)];
MyClass* placed = new (buffer) MyClass(200); // placement new
placed->~MyClass(); // 必须手动调用析构函数
return 0;
}
重要:
new和delete必须配对使用,new[]和delete[]必须配对使用。混用会导致未定义行为(通常是程序崩溃或内存泄漏)。
易错点:
delete后没有将指针设为nullptr,形成悬空指针- 多次
delete同一指针(双重释放)delete不是new分配的内存- 内存泄漏:分配了内存但没有释放
左值和右值
#include <iostream>
#include <string>
#include <vector>
// 左值引用
void process(int& x) {
std::cout << "Lvalue: " << x << "\n";
}
// 右值引用(C++11)
void process(int&& x) {
std::cout << "Rvalue: " << x << "\n";
}
// 万能引用模板
template<typename T>
void universal(T&& x) { // 万能引用
std::cout << "Universal\n";
}
int getValue() {
return 42;
}
int main() {
int a = 10; // a 是左值,10 是右值
// 左值:有名字,可取地址,可赋值(大部分情况下)
// 右值:临时值,不能取地址,不能赋值
int& ref = a; // OK:左值引用绑定左值
// int& ref2 = 10; // 错误:不能将左值引用绑定到右值
const int& cref = 10; // OK:const 左值引用可以绑定右值
int&& rref = 10; // OK:右值引用绑定右值
// int&& rref2 = a; // 错误:不能将右值引用绑定到左值
// std::move:将左值转为右值引用
int&& rref3 = std::move(a); // OK,但 a 现在处于"被移动"状态
// 函数重载选择
process(a); // 调用左值版本
process(10); // 调用右值版本
process(getValue()); // 调用右值版本
// 移动语义示例
std::vector<std::string> v1;
v1.push_back("Hello");
v1.push_back("World");
std::vector<std::string> v2 = v1; // 拷贝构造(深拷贝)
std::vector<std::string> v3 = std::move(v1); // 移动构造(转移所有权)
// v1 现在处于有效但未指定状态,不应再使用
std::cout << "v2 size: " << v2.size() << "\n"; // 2
std::cout << "v3 size: " << v3.size() << "\n"; // 2
std::cout << "v1 size: " << v1.size() << "\n"; // 0(被移动后)
return 0;
}
重要: 右值引用(
&&)和移动语义是 C++11 的重要特性,允许资源的高效转移而非昂贵的拷贝。使用std::move可以将左值转换为右值引用,触发移动操作。
智能指针
智能指针是 RAII(资源获取即初始化)原则的应用,自动管理内存生命周期。
Unique Pointer
#include <iostream>
#include <memory>
#include <vector>
class Resource {
public:
Resource() { std::cout << "Resource acquired\n"; }
~Resource() { std::cout << "Resource released\n"; }
void use() { std::cout << "Using resource\n"; }
};
int main() {
// 1. 创建 unique_ptr
std::unique_ptr<int> p1(new int(42));
auto p2 = std::make_unique<int>(100); // C++14 推荐方式
// 2. 使用
std::cout << *p1 << "\n"; // 解引用
// 3. unique_ptr 独占所有权,不能复制
// std::unique_ptr<int> p3 = p1; // 错误!不能拷贝
// 但可以转移所有权(移动语义)
std::unique_ptr<int> p3 = std::move(p1);
// p1 现在是 nullptr
if (p1 == nullptr) {
std::cout << "p1 is null\n";
}
// 4. 自定义删除器
auto deleter = [](int* p) {
std::cout << "Custom delete: " << *p << "\n";
delete p;
};
std::unique_ptr<int, decltype(deleter)> p4(new int(50), deleter);
// 5. 管理动态数组
std::unique_ptr<int[]> arr(new int[5]{1, 2, 3, 4, 5});
std::cout << arr[2] << "\n"; // 3
// C++14 make_unique 数组
auto arr2 = std::make_unique<int[]>(10); // 10 个 int,值初始化
// 6. 容器中的 unique_ptr
std::vector<std::unique_ptr<Resource>> resources;
resources.push_back(std::make_unique<Resource>());
resources.push_back(std::make_unique<Resource>());
// 自动释放所有资源
resources.clear();
// 7. 释放所有权
std::unique_ptr<Resource> res = std::make_unique<Resource>();
Resource* raw = res.release(); // 释放所有权,返回原始指针
delete raw; // 手动释放
// 8. 重置
res.reset(new Resource()); // 释放旧对象,接管新对象
res.reset(); // 释放对象,设为 nullptr
return 0;
} // 所有 unique_ptr 自动释放
重要:
std::unique_ptr是零开销抽象,运行时性能与原始指针相同。它明确表示”独占所有权”的语义,应该作为默认的智能指针选择。
Shared Pointer
#include <iostream>
#include <memory>
#include <vector>
class Node;
// 注意:如果可能形成循环引用,考虑使用 weak_ptr
class Node {
public:
int value;
std::shared_ptr<Node> next;
// std::weak_ptr<Node> parent; // 打破循环引用
Node(int v) : value(v) {
std::cout << "Node " << value << " created\n";
}
~Node() {
std::cout << "Node " << value << " destroyed\n";
}
};
int main() {
// 1. 创建 shared_ptr
std::shared_ptr<int> p1(new int(42));
auto p2 = std::make_shared<int>(100); // 推荐方式,更高效
// 2. 引用计数
std::cout << "p1 count: " << p1.use_count() << "\n"; // 1
{
std::shared_ptr<int> p3 = p1; // 共享所有权
std::cout << "p1 count: " << p1.use_count() << "\n"; // 2
std::cout << "p3 count: " << p3.use_count() << "\n"; // 2
} // p3 销毁,引用计数 -1
std::cout << "p1 count: " << p1.use_count() << "\n"; // 1
// 3. 可以拷贝
std::shared_ptr<int> p4 = p1;
std::cout << "p1 count: " << p1.use_count() << "\n"; // 2
// 4. 自定义删除器
auto fileDeleter = [](FILE* file) {
if (file) {
std::cout << "Closing file\n";
fclose(file);
}
};
// 5. 从 this 创建 shared_ptr(需要继承 enable_shared_from_this)
struct SharedFromThis : std::enable_shared_from_this<SharedFromThis> {
std::shared_ptr<SharedFromThis> getShared() {
return shared_from_this();
}
};
auto obj = std::make_shared<SharedFromThis>();
auto another = obj->getShared(); // 正确的做法
std::cout << "Count: " << obj.use_count() << "\n"; // 2
// 6. 循环引用问题
{
auto node1 = std::make_shared<Node>(1);
auto node2 = std::make_shared<Node>(2);
node1->next = node2;
node2->next = node1; // 循环引用!两个节点都不会被销毁
std::cout << "node1 count: " << node1.use_count() << "\n"; // 2
std::cout << "node2 count: " << node2.use_count() << "\n"; // 2
} // 内存泄漏!
// 7. shared_ptr 和原始指针
int* raw = new int(10);
{
std::shared_ptr<int> sp1(raw);
// std::shared_ptr<int> sp2(raw); // 错误!重复管理同一内存
}
// delete raw; // 错误!已经释放
// 8. 数组支持(C++17)
// std::shared_ptr<int[]> arr(new int[5]{1,2,3,4,5});
return 0;
}
重要:
std::shared_ptr使用引用计数管理共享所有权,但它有额外的内存开销(控制块)。避免循环引用,必要时使用std::weak_ptr。
Weak Pointer
#include <iostream>
#include <memory>
class Person;
class Team {
public:
std::string name;
std::weak_ptr<Person> leader; // 使用 weak_ptr 避免循环引用
Team(const std::string& n) : name(n) {
std::cout << "Team " << name << " created\n";
}
~Team() {
std::cout << "Team " << name << " destroyed\n";
}
};
class Person {
public:
std::string name;
std::shared_ptr<Team> team;
Person(const std::string& n) : name(n) {
std::cout << "Person " << name << " created\n";
}
~Person() {
std::cout << "Person " << name << " destroyed\n";
}
};
int main() {
// 1. weak_ptr 不增加引用计数
std::shared_ptr<int> sp = std::make_shared<int>(42);
std::weak_ptr<int> wp = sp;
std::cout << "shared_ptr count: " << sp.use_count() << "\n"; // 1
std::cout << "weak_ptr count: " << wp.use_count() << "\n"; // 1
// 2. 使用前先检查是否有效
if (auto locked = wp.lock()) { // lock() 返回 shared_ptr
std::cout << "Value: " << *locked << "\n";
std::cout << "shared_ptr count: " << sp.use_count() << "\n"; // 2
} // locked 销毁,计数 -1
// 3. 检查是否过期
std::cout << "Expired: " << wp.expired() << "\n"; // 0 (false)
sp.reset(); // 释放资源
std::cout << "Expired: " << wp.expired() << "\n"; // 1 (true)
// lock() 返回空的 shared_ptr
if (auto locked = wp.lock()) {
std::cout << "Won't execute\n";
} else {
std::cout << "Resource already released\n";
}
// 4. 实际应用:缓存观察者模式
{
auto team = std::make_shared<Team>("Engineering");
auto person = std::make_shared<Person>("Alice");
person->team = team;
team->leader = person; // weak_ptr,不增加引用计数
std::cout << "Person count: " << person.use_count() << "\n"; // 1
std::cout << "Team count: " << team.use_count() << "\n"; // 2
// 使用 weak_ptr
if (auto leader = team->leader.lock()) {
std::cout << "Team leader: " << leader->name << "\n";
}
} // 都能正确销毁
return 0;
}
重要:
std::weak_ptr用于”观察”但不拥有对象的场景,典型应用是打破循环引用和实现缓存。
头文件与命名空间
include <> or ""
// main.cpp
// 1. #include <...> - 用于标准库和第三方库头文件
// 编译器在系统包含路径中搜索
#include <iostream>
#include <vector>
#include <string>
#include <algorithm>
// 2. #include "..." - 用于项目自身的头文件
// 编译器先在当前目录搜索,再到系统路径
#include "myheader.h"
#include "utils/math_utils.h"
// 3. 现代 C++ 模块(C++20)- 替代头文件
// import std.core; // 导入标准库模块
int main() {
// 使用标准库
std::vector<int> vec = {1, 2, 3};
return 0;
}
头文件保护示例:
// math_utils.h
#ifndef MATH_UTILS_H // 如果没有定义这个宏
#define MATH_UTILS_H // 定义它
namespace math {
int add(int a, int b);
int subtract(int a, int b);
}
#endif // MATH_UTILS_H
重要:
- 使用
< >包含系统头文件和标准库- 使用
" "包含项目自己的头文件- 避免在头文件中使用
using namespace,这会污染包含该头文件的所有文件的命名空间
using
#include <iostream>
#include <vector>
#include <string>
// 1. using 声明:引入特定名称
using std::cout;
using std::endl;
// 2. using 指令:引入整个命名空间(不推荐在头文件中使用)
// using namespace std;
// 3. 命名空间别名
namespace fs = std::filesystem; // C++17
namespace mu = my::utility::math;
// 4. 类型别名(C++11 using)
using IntVec = std::vector<int>;
using StringPair = std::pair<std::string, std::string>;
// 对比 typedef
typedef std::vector<int> IntVecOld; // 旧方式
// 5. 函数指针别名
typedef int (*FuncPtr)(int, int); // typedef 方式
using FuncPtr2 = int (*)(int, int); // using 方式(更清晰)
// 6. 模板别名(C++11,typedef 无法做到)
template<typename T>
using Vec = std::vector<T>;
Vec<int> v1; // std::vector<int>
Vec<double> v2; // std::vector<double>
// 7. 在函数内部使用 using
void demo() {
using namespace std; // 只在这个函数内有效
cout << "Hello" << endl;
}
// 8. 匿名命名空间(只在当前文件可见,替代 static)
namespace {
int internalVar = 42; // 只在当前翻译单元可见
void internalFunc() {}
}
// 9. 嵌套命名空间(C++17 简化语法)
namespace A::B::C { // C++17
void func() {}
}
// 等价于
namespace A {
namespace B {
namespace C {
// void func() {}
}
}
}
// 10. inline 命名空间(C++11)
namespace lib {
inline namespace v2 { // 默认使用 v2
void api() { std::cout << "v2 API\n"; }
}
namespace v1 {
void api() { std::cout << "v1 API\n"; }
}
}
int main() {
// 使用 using 声明
cout << "Hello" << endl;
// 使用类型别名
IntVec numbers = {1, 2, 3};
for (const auto& n : numbers) {
cout << n << " ";
}
cout << endl;
// 调用 inline 命名空间
lib::api(); // 调用 v2::api()
lib::v1::api(); // 调用 v1::api()
return 0;
}
重要: 在头文件中永远不要使用
using namespace!这会导致命名空间污染,可能引发难以调试的名字冲突问题。
Pragma once和include guards
// ============ 方式 1:传统 Include Guards(标准兼容)============
// myheader.h
#ifndef MYHEADER_H
#define MYHEADER_H
// 头文件内容
class MyClass {
public:
void doSomething();
};
#endif // MYHEADER_H
// ============ 方式 2:#pragma once(简洁,大部分编译器支持)============
// myheader.h
#pragma once
// 头文件内容
class MyClass {
public:
void doSomething();
};
// ============ 方式 3:两者结合(最佳实践)============
// myheader.h
#pragma once
#ifndef MYHEADER_H
#define MYHEADER_H
// 头文件内容
#endif // MYHEADER_H
对比:
| 特性 | Include Guards | #pragma once |
|---|---|---|
| 标准性 | C/C++ 标准 | 编译器扩展(但广泛支持) |
| 简洁性 | 较冗长 | 简洁 |
| 冲突风险 | 宏名可能冲突 | 无冲突风险 |
| 编译速度 | 需要打开文件检查 | 更快(编译器可优化) |
重要: 现代 C++ 项目推荐使用
#pragma once,因为它更简洁且编译速度略快。但对于需要最大兼容性的库,使用传统的 include guards。
预处理与宏定义
// ============ 1. 简单宏定义 ============
#define PI 3.14159
#define MAX_SIZE 100
#define GREETING "Hello, World!"
// ============ 2. 带参数的宏(函数宏)============
#define SQUARE(x) ((x) * (x)) // 注意括号!
#define MAX(a, b) ((a) > (b) ? (a) : (b))
#define DEBUG_PRINT(msg) std::cout << "[DEBUG] " << msg << std::endl
// 多行宏(使用反斜杠续行)
#define SWAP(a, b) do { \
typeof(a) temp = a; \
a = b; \
b = temp; \
} while(0)
// ============ 3. 条件编译 ============
#define DEBUG
#ifdef DEBUG
#define LOG(msg) std::cout << "[DEBUG] " << msg << std::endl
#else
#define LOG(msg)
#endif
// 更复杂的条件
#if defined(DEBUG) && defined(VERBOSE)
#define DETAIL_LOG(msg) std::cout << msg << std::endl
#elif defined(DEBUG)
#define DETAIL_LOG(msg)
#else
#define DETAIL_LOG(msg)
#endif
// 根据平台选择代码
#ifdef _WIN32
#define PLATFORM "Windows"
#include <windows.h>
#elif defined(__linux__)
#define PLATFORM "Linux"
#include <unistd.h>
#elif defined(__APPLE__)
#define PLATFORM "macOS"
#endif
// ============ 4. 预定义宏 ============
void showPredefinedMacros() {
std::cout << "File: " << __FILE__ << "\n"; // 当前文件名
std::cout << "Line: " << __LINE__ << "\n"; // 当前行号
std::cout << "Date: " << __DATE__ << "\n"; // 编译日期
std::cout << "Time: " << __TIME__ << "\n"; // 编译时间
std::cout << "Function: " << __func__ << "\n"; // 当前函数名(C++11)
std::cout << "C++ version: " << __cplusplus << "\n"; // C++ 版本
}
// ============ 5. 宏在调试中的应用 ============
#define ASSERT(cond) \
do { \
if (!(cond)) { \
std::cerr << "Assertion failed: " << #cond \
<< " at " << __FILE__ << ":" << __LINE__ << "\n"; \
std::abort(); \
} \
} while(0)
// # 操作符:将参数转为字符串
#define TO_STRING(x) #x
// ## 操作符:连接
#define CONCAT(a, b) a##b
// ============ 6. 可变参数宏(C++11)============
#define ERROR(fmt, ...) printf("[ERROR] " fmt "\n", ##__VA_ARGS__)
// 更现代的用法
#define LOG_FORMAT(level, fmt, ...) \
printf("[" level "] %s:%d " fmt "\n", __FILE__, __LINE__, ##__VA_ARGS__)
// ============ 7. 取消宏定义 ============
#undef PI // 之后 PI 不再可用
// ============ 8. 实际代码示例 ============
#include <iostream>
#include <cassert>
// 使用 constexpr 替代宏(推荐)
constexpr double PI_CONST = 3.14159;
constexpr int MAX_SIZE_CONST = 100;
// 使用模板函数替代宏函数(类型安全,可调试)
template<typename T>
constexpr T square(T x) {
return x * x;
}
template<typename T>
constexpr T max_val(T a, T b) {
return (a > b) ? a : b;
}
// 断言宏(实际项目风格)
#ifdef NDEBUG
#define MY_ASSERT(cond) ((void)0)
#else
#define MY_ASSERT(cond) \
((cond) ? (void)0 : \
(std::cerr << "Assertion failed: " #cond \
<< " at " << __FILE__ << ":" << __LINE__ << "\n", \
std::abort()))
#endif
int main() {
// 宏使用
std::cout << "PI = " << PI << "\n";
std::cout << "Square of 5 = " << SQUARE(5) << "\n";
std::cout << "Max of 3 and 7 = " << MAX(3, 7) << "\n";
// 现代替代
std::cout << "Const PI = " << PI_CONST << "\n";
std::cout << "Template square = " << square(5) << "\n";
// 调试用宏
LOG("Starting program");
// 预定义宏
showPredefinedMacros();
// 字符串化
std::cout << TO_STRING(Hello World) << "\n"; // "Hello World"
// 连接
int xy = 10;
std::cout << CONCAT(x, y) << "\n"; // xy,即 10
// 断言
MY_ASSERT(2 + 2 == 4);
// MY_ASSERT(2 + 2 == 5); // 会触发断言失败
return 0;
}
重要: 现代 C++ 中,优先使用
const/constexpr、内联函数和模板替代宏。宏没有类型检查、不受命名空间限制、难以调试,且可能有副作用。
易错点: 宏定义中的参数必须加括号,否则可能出现意外的运算顺序问题。例如
#define SQUARE(x) x * x,SQUARE(1+2)会展开为1+2*1+2 = 5而非9。
Comments