1. 基础cpp语法
├── 1.1 基本语法
│ ├── 程序结构
│ ├── 注释
│ ├── 关键字
│ └── 标识符命名规则
├── 1.2 数据类型
│ ├── 基本数据类型
│ │ ├── 整型(int, short, long, long long)
│ │ ├── 字符型(char, wchar_t)
│ │ ├── 浮点型(float, double)
│ │ ├── 布尔型(bool)
│ │ └── void类型
│ ├── 类型修饰符
│ │ ├── signed/unsigned
│ │ ├── const
│ │ └── volatile
│ └── 类型转换
│ ├── 隐式类型转换
│ ├── 显式类型转换(static_cast等)
│ └── 类型别名(typedef, using)
├── 1.3 变量与常量
│ ├── 变量声明与定义
│ ├── 常量(const, constexpr)
│ └── 作用域与生命周期
├── 1.4 运算符
│ ├── 算术运算符
│ ├── 关系运算符
│ ├── 逻辑运算符
│ ├── 位运算符
│ ├── 赋值运算符
│ ├── 条件运算符(?:)
│ ├── 逗号运算符
│ ├── sizeof运算符
│ └── 运算符优先级与结合性
└── 1.5 输入输出
├── cin/cout/cerr
├── 格式控制
└── 文件流基础
1.1 程序基本结构
// 示例1.1:第一个C++程序
#include <iostream> // 预处理指令,包含头文件
// 命名空间声明
using namespace std;
/*
* 多行注释
* 主函数是程序的入口
*/
int main() { // 程序从这里开始执行
// 输出语句
cout << "Hello, World!" << endl;
// 返回0表示程序正常结束
return 0;
}
易错点提示:
#include <iostream>不是语句,末尾没有分号main()函数必须返回int类型(C++标准)- 使用
endl或"\n"换行,两者有细微区别(endl会刷新缓冲区)
1.2 基本数据类型
// 示例1.2:基本数据类型
#include <iostream>
#include <iomanip> // 用于格式化输出
using namespace std;
int main() {
// 1. 整型
int a = 10; // 4字节,通常范围:-2,147,483,648 ~ 2,147,483,647
short b = 100; // 2字节,范围:-32,768 ~ 32,767
long c = 1000L; // 4字节或8字节,取决于平台
long long d = 1000000LL; // 8字节
// 2. 字符型
char ch1 = 'A'; // 单引号表示字符
char ch2 = 65; // ASCII码值,同样表示'A'
wchar_t wch = L'中'; // 宽字符,用于Unicode
// 3. 浮点型
float f = 3.14f; // 单精度,4字节,f后缀表示float
double df = 3.14159; // 双精度,8字节
long double ldf = 3.141592653589793L; // 扩展精度
// 4. 布尔型
bool flag1 = true; // 实际存储为1
bool flag2 = false; // 实际存储为0
// 5. void类型 - 无类型,不能定义void变量
// void v; // 错误!
// 输出各类型大小
cout << "sizeof(int) = " << sizeof(int) << " bytes" << endl;
cout << "sizeof(char) = " << sizeof(char) << " bytes" << endl;
cout << "sizeof(bool) = " << sizeof(bool) << " bytes" << endl;
return 0;
}
易错点提示:
- 整数溢出:
short s = 32767;
s = s + 1; // 溢出!变为-32768
- 浮点数比较:
float f1 = 0.1f;
float f2 = 0.2f;
float f3 = 0.3f;
// 错误写法
if (f1 + f2 == f3) { // 可能不成立!浮点数有精度误差
cout << "Equal" << endl;
}
// 正确写法
if (abs(f1 + f2 - f3) < 0.00001) {
cout << "Approximately equal" << endl;
}
1.3 类型修饰符与类型转换
// 示例1.3:类型修饰符与转换
#include <iostream>
using namespace std;
int main() {
// 1. signed/unsigned
signed int s1 = -10; // 有符号,可正可负
unsigned int u1 = 10; // 无符号,只能非负
// 危险!负数赋给无符号数
unsigned int u2 = -10; // 实际值为4294967286(32位系统)
cout << "u2 = " << u2 << endl;
// 2. const - 常量
const int MAX_SIZE = 100;
// MAX_SIZE = 200; // 错误!常量不能修改
// 3. 类型转换
// 隐式转换(自动转换)
int i = 10;
double d = i; // int → double,安全
double d2 = 3.14;
int i2 = d2; // double → int,丢失小数部分,警告!
// 显式转换(强制转换)
// C风格转换(不推荐)
int i3 = (int)d2;
// C++风格转换
int i4 = static_cast<int>(d2); // 静态转换,常用
const int ci = 10;
int* pi = const_cast<int*>(&ci); // 去掉const属性,危险!
// 4. 类型别名
typedef int INTEGER; // C风格
INTEGER num = 100;
using LL = long long; // C++11风格
LL big_num = 1000000000LL;
return 0;
}
易混淆点:
- 四种C++风格转换的区别:
static_cast:常规转换,如数值类型转换、void*转换dynamic_cast:用于多态类型的向下转换,运行时检查const_cast:去掉const/volatile属性reinterpret_cast:低级别重新解释,最危险
- signed与unsigned比较的陷阱:
int a = -1;
unsigned int b = 10;
if (a < b) { // a被转换为unsigned,变成很大的正数,条件不成立!
cout << "a < b" << endl;
} else {
cout << "a >= b" << endl; // 输出这个!
}
1.4 变量与常量
// 示例1.4:变量与常量
#include <iostream>
using namespace std;
// 全局变量
int global_var = 100; // 初始化为100
const int GLOBAL_CONST = 200;
int main() {
// 1. 变量声明与定义
int a; // 声明并定义,未初始化(值随机)
int b = 10; // 声明、定义并初始化
int c(20); // C++风格的初始化
int d{30}; // C++11列表初始化
int e = {40}; // C++11列表初始化
// 2. 常量
const int LOCAL_CONST = 50;
// LOCAL_CONST = 60; // 错误!
// const与指针(难点!)
int x = 10;
int y = 20;
const int* p1 = &x; // p1指向常量,不能通过p1修改x
// *p1 = 30; // 错误!
p1 = &y; // 正确,p1本身可以指向别的变量
int* const p2 = &x; // p2是常量指针,不能指向别的变量
*p2 = 30; // 正确,可以通过p2修改x
// p2 = &y; // 错误!
const int* const p3 = &x; // p3是指向常量的常量指针
// *p3 = 40; // 错误!
// p3 = &y; // 错误!
// 3. 作用域
{
int block_var = 100;
cout << "block_var = " << block_var << endl;
}
// cout << block_var << endl; // 错误!超出作用域
// 4. 变量的生命周期
static int static_var = 0; // 静态局部变量,只初始化一次
static_var++;
cout << "static_var = " << static_var << endl;
return 0;
}
易错点提示:
- 未初始化变量的值:
int x; // 值随机,可能是任意值
cout << x << endl; // 危险!
- const位置不同含义不同:
const int* p; // 指向const int的指针(指针可变,指向的内容不可变)
int const* p; // 同上,等价写法
int* const p; // const指针(指针不可变,指向的内容可变)
const int* const p; // 指向const int的const指针
1.5 运算符
// 示例1.5:运算符
#include <iostream>
using namespace std;
int main() {
// 1. 算术运算符
int a = 10, b = 3;
cout << "a + b = " << a + b << endl; // 13
cout << "a - b = " << a - b << endl; // 7
cout << "a * b = " << a * b << endl; // 30
cout << "a / b = " << a / b << endl; // 3(整数除法)
cout << "a % b = " << a % b << endl; // 1(取余)
// 2. 关系运算符
cout << "a > b: " << (a > b) << endl; // 1(true)
cout << "a == b: " << (a == b) << endl; // 0(false)
// 3. 逻辑运算符
bool x = true, y = false;
cout << "x && y: " << (x && y) << endl; // 0(与)
cout << "x || y: " << (x || y) << endl; // 1(或)
cout << "!x: " << (!x) << endl; // 0(非)
// 短路求值
int i = 0;
if (i != 0 && 10 / i > 0) { // 短路,不会除以0
cout << "This won't execute" << endl;
}
// 4. 位运算符
unsigned char m = 0b11001100; // 204
unsigned char n = 0b10101010; // 170
cout << "m & n = " << (m & n) << endl; // 136(0b10001000)
cout << "m | n = " << (m | n) << endl; // 238(0b11101110)
cout << "m ^ n = " << (m ^ n) << endl; // 102(0b01100110)
cout << "~m = " << (~m) << endl; // 51(0b00110011)
cout << "m << 2 = " << (m << 2) << endl; // 48(0b00110000)
// 5. 赋值运算符
int k = 10;
k += 5; // k = 15
k -= 3; // k = 12
k *= 2; // k = 24
k /= 4; // k = 6
k %= 4; // k = 2
// 6. 自增自减运算符
int p = 5;
int q = ++p; // 前置:先自增,再赋值
cout << "p = " << p << ", q = " << q << endl; // p=6, q=6
int r = 5;
int s = r++; // 后置:先赋值,再自增
cout << "r = " << r << ", s = " << s << endl; // r=6, s=5
// 7. 条件运算符(三目运算符)
int max = (a > b) ? a : b;
cout << "max = " << max << endl;
// 8. 逗号运算符
int t = (a = 5, b = 10, a + b); // t = 15
cout << "t = " << t << endl;
// 9. sizeof运算符
cout << "sizeof(int) = " << sizeof(int) << endl;
cout << "sizeof(a) = " << sizeof(a) << endl;
return 0;
}
易错点提示:
- 自增自减运算符的优先级:
int i = 5;
int j = i++ + ++i; // 未定义行为!不同编译器结果不同
- 整数除法的陷阱:
int a = 5, b = 2;
double c = a / b; // c = 2.0,不是2.5!
// 正确写法:
double d = static_cast<double>(a) / b; // d = 2.5
- 运算符优先级(从高到低部分):
::作用域()[].->++--(后缀)++--(前缀)+-(正负)!~*&(取地址)sizeof*/%+-<<>><<=>>===!=&(位与)^|&&||?:=+=-=等赋值运算符,
1.6 输入输出
// 示例1.6:输入输出
#include <iostream>
#include <iomanip> // 格式化输出
#include <string> // 字符串类型
using namespace std;
int main() {
// 1. 标准输出
cout << "Hello, ";
cout << "World!" << endl; // endl会刷新缓冲区
// 2. 标准输入
int age;
string name;
cout << "Enter your name: ";
// cin >> name; // 遇到空格会停止
getline(cin, name); // 读取整行,包括空格
cout << "Enter your age: ";
cin >> age;
// 注意:混合使用>>和getline时,需要清除缓冲区
// 因为在输入年龄后按回车,换行符还在缓冲区
// getline会读取到空行
cin.ignore(); // 忽略一个字符(换行符)
cout << "Name: " << name << ", Age: " << age << endl;
// 3. 格式化输出
double pi = 3.141592653589793;
cout << fixed << setprecision(2); // 固定小数位数,保留2位
cout << "pi = " << pi << endl; // 输出: 3.14
cout << scientific; // 科学计数法
cout << "pi = " << pi << endl; // 输出: 3.14e+00
// 设置宽度和对齐
cout << setw(10) << left << "Name"
<< setw(10) << right << "Age" << endl;
cout << setw(10) << left << "Alice"
<< setw(10) << right << 25 << endl;
cout << setw(10) << left << "Bob"
<< setw(10) << right << 30 << endl;
// 4. 错误输出
cerr << "This is an error message!" << endl;
return 0;
}
易错点提示:
- cin和getline混合使用的坑:
int n;
string s;
cin >> n; // 输入: 10[回车]
getline(cin, s); // 会立即读取到空字符串!
// 解决方法:
cin >> n;
cin.ignore(); // 忽略换行符
getline(cin, s);
- 输入类型不匹配:
int num;
cin >> num; // 如果输入"abc",会失败但不会报错
if (cin.fail()) {
cin.clear(); // 清除错误状态
cin.ignore(1000, '\n'); // 忽略错误输入
}
综合例题与解析
// 例题1:类型转换的陷阱
#include <iostream>
using namespace std;
int main() {
// 题目:下面代码的输出是什么?
unsigned int a = 10;
int b = -20;
if (a + b > 0) {
cout << "a + b > 0" << endl;
} else {
cout << "a + b <= 0" << endl;
}
cout << "a + b = " << a + b << endl;
return 0;
}
/*
解析:
当有符号和无符号数混合运算时,有符号数会被转换为无符号数。
b = -20,转换为无符号数后变成很大的正数(2^32 - 20)
所以 a + b = 10 + (2^32 - 20) = 2^32 - 10 > 0
输出:
a + b > 0
a + b = 4294967286
*/
// 例题2:运算符优先级
#include <iostream>
using namespace std;
int main() {
int a = 5, b = 3, c = 2;
// 题目:下面表达式的值是多少?
int result1 = a + b * c; // 1. 5 + (3 * 2) = 11
int result2 = a = b = c; // 2. 从右向左赋值,a = b = c = 2
bool result3 = a == b == c; // 3. (a == b) == c → true == 2 → false
cout << "result1 = " << result1 << endl;
cout << "result2 = " << result2 << endl;
cout << "result3 = " << result3 << endl;
// 更复杂的例子
int x = 1, y = 2, z = 3;
x += y += z; // 等价于: y = y + z; x = x + y;
cout << "x = " << x << ", y = " << y << endl;
return 0;
}
// 例题3:自增自减的陷阱
#include <iostream>
using namespace std;
int main() {
int i = 5;
// 题目:下面代码的输出是什么?
cout << i++ << endl; // 输出5,然后i=6
cout << ++i << endl; // i先变为7,输出7
cout << i-- << endl; // 输出7,然后i=6
cout << --i << endl; // i先变为5,输出5
// 更复杂的例子(考试常见)
int a = 5;
int b = a++ + ++a; // 未定义行为!避免这样写
// 正确的理解方式应该是:
// 表达式求值顺序不确定,不同编译器结果不同
// GCC可能:a++返回5(a=6),++a返回7(a=7),b=12
// 其他编译器可能不同
return 0;
}
本章小结
- 基础语法:掌握程序结构、注释、标识符命名规则
- 数据类型:理解各类型的大小、范围,特别注意整数溢出和浮点数精度
- 类型转换:区分隐式和显式转换,避免signed/unsigned混合运算
- 变量与常量:理解作用域、生命周期,掌握const的各种用法
- 运算符:牢记优先级和结合性,避免复杂表达式中的未定义行为
- 输入输出:掌握格式化输出,注意输入时的缓冲区问题
第2章:流程控制
├── 2.1 选择结构
│ ├── if语句
│ ├── if-else语句
│ ├── if-else if-else语句
│ ├── switch语句
│ └── 嵌套选择结构
├── 2.2 循环结构
│ ├── while循环
│ ├── do-while循环
│ ├── for循环
│ ├── 范围for循环(C++11)
│ └── 循环控制语句
│ ├── break
│ ├── continue
│ └── goto(了解)
└── 2.3 跳转语句
└── return语句
2.1 选择结构
2.1.1 if语句
// 示例2.1:基本if语句
#include <iostream>
using namespace std;
int main() {
int score;
cout << "请输入成绩:";
cin >> score;
// 1. 基本if语句
if (score >= 60) {
cout << "及格" << endl;
}
// 2. if-else语句
if (score >= 60) {
cout << "及格" << endl;
} else {
cout << "不及格" << endl;
}
// 3. if-else if-else语句
if (score >= 90) {
cout << "优秀" << endl;
} else if (score >= 80) {
cout << "良好" << endl;
} else if (score >= 70) {
cout << "中等" << endl;
} else if (score >= 60) {
cout << "及格" << endl;
} else {
cout << "不及格" << endl;
}
// 4. 嵌套if语句
if (score >= 60) {
if (score >= 90) {
cout << "你很优秀!" << endl;
}
cout << "通过了考试" << endl;
}
return 0;
}
易错点提示:
- 常见错误:将
==误写为=
int a = 5;
// 错误写法
if (a = 10) { // 总是为真,因为赋值表达式返回10
cout << "a等于10" << endl;
}
// 正确写法
if (a == 10) {
cout << "a等于10" << endl;
}
- else悬空问题:
int x = 10, y = 20;
// 代码1
if (x > 5)
if (y > 15)
cout << "条件1" << endl;
else
cout << "条件2" << endl; // 这个else属于哪个if?
// 等价于
if (x > 5) {
if (y > 15) {
cout << "条件1" << endl;
} else {
cout << "条件2" << endl; // 属于内层if
}
}
// 建议:总是使用大括号
if (x > 5) {
if (y > 15) {
cout << "条件1" << endl;
}
} else {
cout << "条件2" << endl;
}
2.1.2 条件运算符(三目运算符)
// 示例2.2:条件运算符
#include <iostream>
using namespace std;
int main() {
int a = 10, b = 20;
// 基本用法
int max = (a > b) ? a : b;
cout << "最大值是:" << max << endl;
// 嵌套使用(可读性差,慎用)
int c = 30;
int max3 = (a > b) ? ((a > c) ? a : c) : ((b > c) ? b : c);
cout << "三个数的最大值是:" << max3 << endl;
// 作为右值
bool result = (a > b) ? true : false;
cout << "a > b 吗?" << (result ? "是" : "否") << endl;
// 注意:返回类型
// 条件运算符的返回类型由两个操作数的类型决定
int x = 10;
double y = 3.14;
auto z = (x > 5) ? x : y; // z的类型是double
cout << "z的类型:" << typeid(z).name() << endl;
return 0;
}
2.1.3 switch语句
// 示例2.3:switch语句
#include <iostream>
using namespace std;
int main() {
int day;
cout << "请输入星期几(1-7):";
cin >> day;
switch (day) {
case 1:
cout << "星期一" << endl;
break; // 必须使用break,否则会继续执行下一个case
case 2:
cout << "星期二" << endl;
break;
case 3:
cout << "星期三" << endl;
break;
case 4:
cout << "星期四" << endl;
break;
case 5:
cout << "星期五" << endl;
break;
case 6:
cout << "星期六" << endl;
break;
case 7:
cout << "星期日" << endl;
break;
default: // 可选,处理其他情况
cout << "输入错误!" << endl;
break;
}
// switch的高级用法:多个case共享代码
char grade;
cout << "请输入等级(A-D):";
cin >> grade;
switch (grade) {
case 'A':
case 'a':
cout << "优秀" << endl;
break;
case 'B':
case 'b':
cout << "良好" << endl;
break;
case 'C':
case 'c':
cout << "及格" << endl;
break;
case 'D':
case 'd':
cout << "不及格" << endl;
break;
default:
cout << "无效等级" << endl;
}
return 0;
}
易错点提示:
- break的重要性:
int x = 2;
switch (x) {
case 1:
cout << "1" << endl;
case 2:
cout << "2" << endl; // 从这里开始执行
case 3:
cout << "3" << endl; // 继续执行
default:
cout << "default" << endl; // 继续执行
}
// 输出:2 3 default(因为没有break)
- switch的限制:
- case值必须是整型常量表达式(整数、字符、枚举)
- 不能使用浮点数、字符串作为case值
// 错误示例
double d = 1.0;
switch (d) { // 错误!必须是整型
case 1.0: // 错误!必须是整型常量
// ...
}
2.2 循环结构
2.2.1 while循环
// 示例2.4:while循环
#include <iostream>
using namespace std;
int main() {
// 1. 基本while循环
int i = 1;
while (i <= 5) {
cout << i << " ";
i++;
}
cout << endl;
// 2. 计算1到100的和
int sum = 0, count = 1;
while (count <= 100) {
sum += count;
count++;
}
cout << "1到100的和是:" << sum << endl;
// 3. 输入验证循环
int score;
cout << "请输入成绩(0-100):";
cin >> score;
while (score < 0 || score > 100) {
cout << "输入错误!请重新输入(0-100):";
cin >> score;
}
cout << "输入的成绩是:" << score << endl;
// 4. 无限循环(慎用)
// while (true) {
// // 循环体
// // 必须有退出条件,否则死循环
// }
return 0;
}
2.2.2 do-while循环
// 示例2.5:do-while循环
#include <iostream>
using namespace std;
int main() {
// 1. 基本do-while循环
int i = 1;
do {
cout << i << " ";
i++;
} while (i <= 5);
cout << endl;
// 2. do-while至少执行一次
int x = 10;
do {
cout << "x = " << x << endl; // 会执行一次
x++;
} while (x < 5);
// 3. 菜单选择示例
char choice;
do {
cout << "\n===== 菜单 =====\n";
cout << "1. 选项一\n";
cout << "2. 选项二\n";
cout << "3. 退出\n";
cout << "请选择:";
cin >> choice;
switch (choice) {
case '1':
cout << "执行选项一" << endl;
break;
case '2':
cout << "执行选项二" << endl;
break;
case '3':
cout << "退出程序" << endl;
break;
default:
cout << "无效选择,请重新输入" << endl;
}
} while (choice != '3');
return 0;
}
易错点提示:
- while与do-while的区别:
int i = 10;
// while循环:先判断条件
while (i < 5) {
cout << "while循环" << endl; // 不会执行
}
// do-while循环:先执行一次再判断条件
do {
cout << "do-while循环" << endl; // 会执行一次
} while (i < 5);
2.2.3 for循环
// 示例2.6:for循环
#include <iostream>
#include <vector>
using namespace std;
int main() {
// 1. 基本for循环
for (int i = 1; i <= 5; i++) {
cout << i << " ";
}
cout << endl;
// 2. 灵活的for循环
int j = 1;
for (; j <= 5; ) { // 省略初始化和迭代部分
cout << j << " ";
j++;
}
cout << endl;
// 3. 多个控制变量
for (int i = 0, j = 10; i < j; i++, j--) {
cout << "i=" << i << ", j=" << j << endl;
}
// 4. 无限循环
// for (;;) {
// // 循环体
// // 需要break退出
// }
// 5. 计算阶乘
int n = 5;
long long factorial = 1;
for (int i = 1; i <= n; i++) {
factorial *= i;
}
cout << n << "! = " << factorial << endl;
// 6. 循环嵌套(打印乘法表)
cout << "\n乘法表:" << endl;
for (int i = 1; i <= 9; i++) {
for (int j = 1; j <= i; j++) {
cout << j << "×" << i << "=" << i * j << "\t";
}
cout << endl;
}
return 0;
}
2.2.4 范围for循环(C++11)
// 示例2.7:范围for循环
#include <iostream>
#include <vector>
#include <array>
using namespace std;
int main() {
// 1. 遍历数组
int arr[] = {1, 2, 3, 4, 5};
// 传统for循环
cout << "传统for循环:";
for (int i = 0; i < 5; i++) {
cout << arr[i] << " ";
}
cout << endl;
// 范围for循环
cout << "范围for循环:";
for (int num : arr) { // 遍历数组的每个元素
cout << num << " ";
}
cout << endl;
// 2. 使用auto自动推断类型
cout << "使用auto:";
for (auto num : arr) {
cout << num << " ";
}
cout << endl;
// 3. 使用引用修改元素
cout << "修改前:";
for (auto num : arr) {
cout << num << " ";
}
cout << endl;
for (auto &num : arr) { // 使用引用
num *= 2; // 每个元素乘以2
}
cout << "修改后:";
for (auto num : arr) {
cout << num << " ";
}
cout << endl;
// 4. 遍历vector
vector<int> vec = {10, 20, 30, 40, 50};
cout << "vector元素:";
for (int val : vec) {
cout << val << " ";
}
cout << endl;
// 5. 遍历string
string str = "Hello";
cout << "字符串字符:";
for (char ch : str) {
cout << ch << " ";
}
cout << endl;
return 0;
}
2.3 循环控制语句
2.3.1 break语句
// 示例2.8:break语句
#include <iostream>
using namespace std;
int main() {
// 1. break在循环中的使用
cout << "break示例:" << endl;
for (int i = 1; i <= 10; i++) {
if (i == 5) {
break; // 跳出整个循环
}
cout << i << " ";
}
cout << "\n循环结束" << endl;
// 2. break在switch中的使用(前面已讲)
// 3. break只能跳出一层循环
cout << "\n嵌套循环中的break:" << endl;
for (int i = 1; i <= 3; i++) {
for (int j = 1; j <= 3; j++) {
if (j == 2) {
break; // 只跳出内层循环
}
cout << "i=" << i << ", j=" << j << endl;
}
}
// 4. 使用flag跳出多层循环
cout << "\n跳出多层循环:" << endl;
bool shouldBreak = false;
for (int i = 1; i <= 3 && !shouldBreak; i++) {
for (int j = 1; j <= 3; j++) {
if (i == 2 && j == 2) {
shouldBreak = true;
break; // 跳出内层循环
}
cout << "i=" << i << ", j=" << j << endl;
}
}
return 0;
}
2.3.2 continue语句
// 示例2.9:continue语句
#include <iostream>
using namespace std;
int main() {
// 1. continue的基本使用
cout << "continue示例(跳过奇数):" << endl;
for (int i = 1; i <= 10; i++) {
if (i % 2 != 0) { // 如果是奇数
continue; // 跳过本次循环剩余部分
}
cout << i << " "; // 只输出偶数
}
cout << endl;
// 2. continue与while循环
cout << "while循环中的continue:" << endl;
int i = 0;
while (i < 10) {
i++;
if (i % 3 == 0) {
continue; // 跳过能被3整除的数
}
cout << i << " ";
}
cout << endl;
// 3. 注意无限循环陷阱
cout << "注意:错误的continue使用可能导致死循环" << endl;
int j = 0;
while (j < 10) {
if (j % 2 == 0) {
continue; // 死循环!j永远不会增加
}
cout << j << " ";
j++; // 这行永远不会执行到
}
return 0;
}
2.3.3 goto语句(了解即可)
// 示例2.10:goto语句(一般不推荐使用)
#include <iostream>
using namespace std;
int main() {
// goto语句:无条件跳转
int i = 0;
loop: // 标签
if (i >= 5) {
goto end; // 跳转到end标签
}
cout << i << " ";
i++;
goto loop; // 跳转到loop标签
end: // 标签
cout << "\n循环结束" << endl;
// goto的合理使用场景:跳出多层嵌套
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 3; j++) {
for (int k = 0; k < 3; k++) {
if (i == 1 && j == 1 && k == 1) {
goto exit_all; // 一次性跳出三层循环
}
cout << i << j << k << " ";
}
}
}
exit_all:
cout << "\n已跳出所有循环" << endl;
return 0;
}
重要提示:goto语句会破坏程序的结构,使代码难以理解和维护。现代编程中应尽量避免使用goto,可以使用函数、break、return等替代。
综合例题与解析
// 例题1:选择结构嵌套
#include <iostream>
using namespace std;
int main() {
int year;
cout << "请输入年份:";
cin >> year;
// 判断闰年
// 闰年条件:能被4整除但不能被100整除,或者能被400整除
if ((year % 4 == 0 && year % 100 != 0) || (year % 400 == 0)) {
cout << year << "年是闰年" << endl;
} else {
cout << year << "年不是闰年" << endl;
}
// 等价的三目运算符写法
string result = ((year % 4 == 0 && year % 100 != 0) || (year % 400 == 0))
? "是闰年" : "不是闰年";
cout << year << "年" << result << endl;
return 0;
}
// 例题2:循环与选择结构结合
#include <iostream>
#include <iomanip>
using namespace std;
int main() {
// 统计各种成绩的人数
int score;
int countA = 0, countB = 0, countC = 0, countD = 0, countF = 0;
int total = 0;
cout << "请输入成绩(输入-1结束):" << endl;
while (true) {
cout << "成绩 " << total + 1 << ": ";
cin >> score;
if (score == -1) {
break; // 输入-1结束
}
if (score < 0 || score > 100) {
cout << "成绩无效,请重新输入" << endl;
continue; // 跳过本次循环
}
// 统计成绩分布
if (score >= 90) {
countA++;
} else if (score >= 80) {
countB++;
} else if (score >= 70) {
countC++;
} else if (score >= 60) {
countD++;
} else {
countF++;
}
total++;
}
// 输出统计结果
cout << "\n====== 成绩统计 ======" << endl;
cout << "总人数: " << total << endl;
cout << "A(90-100): " << countA << endl;
cout << "B(80-89): " << countB << endl;
cout << "C(70-79): " << countC << endl;
cout << "D(60-69): " << countD << endl;
cout << "F(0-59): " << countF << endl;
// 计算百分比
if (total > 0) {
cout << fixed << setprecision(2);
cout << "\n====== 百分比分布 ======" << endl;
cout << "A: " << (countA * 100.0 / total) << "%" << endl;
cout << "B: " << (countB * 100.0 / total) << "%" << endl;
cout << "C: " << (countC * 100.0 / total) << "%" << endl;
cout << "D: " << (countD * 100.0 / total) << "%" << endl;
cout << "F: " << (countF * 100.0 / total) << "%" << endl;
}
return 0;
}
// 例题3:循环嵌套(打印图形)
#include <iostream>
using namespace std;
int main() {
int n;
cout << "请输入行数:";
cin >> n;
// 打印直角三角形
cout << "\n直角三角形:" << endl;
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= i; j++) {
cout << "* ";
}
cout << endl;
}
// 打印等腰三角形
cout << "\n等腰三角形:" << endl;
for (int i = 1; i <= n; i++) {
// 打印空格
for (int j = 1; j <= n - i; j++) {
cout << " ";
}
// 打印星号
for (int j = 1; j <= 2 * i - 1; j++) {
cout << "*";
}
cout << endl;
}
// 打印菱形
cout << "\n菱形:" << endl;
// 上半部分
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= n - i; j++) {
cout << " ";
}
for (int j = 1; j <= 2 * i - 1; j++) {
cout << "*";
}
cout << endl;
}
// 下半部分
for (int i = n - 1; i >= 1; i--) {
for (int j = 1; j <= n - i; j++) {
cout << " ";
}
for (int j = 1; j <= 2 * i - 1; j++) {
cout << "*";
}
cout << endl;
}
return 0;
}
// 例题4:素数判断(综合应用)
#include <iostream>
#include <cmath>
using namespace std;
int main() {
// 方法1:判断单个数字是否为素数
int num;
cout << "请输入一个正整数:";
cin >> num;
if (num <= 1) {
cout << num << "不是素数" << endl;
} else {
bool isPrime = true;
// 优化:只需要检查到sqrt(num)
for (int i = 2; i <= sqrt(num); i++) {
if (num % i == 0) {
isPrime = false;
break;
}
}
if (isPrime) {
cout << num << "是素数" << endl;
} else {
cout << num << "不是素数" << endl;
}
}
// 方法2:输出1-100之间的所有素数
cout << "\n1-100之间的素数:" << endl;
int count = 0;
for (int i = 2; i <= 100; i++) {
bool isPrime = true;
// 检查i是否为素数
for (int j = 2; j <= sqrt(i); j++) {
if (i % j == 0) {
isPrime = false;
break;
}
}
if (isPrime) {
cout << i << " ";
count++;
if (count % 10 == 0) { // 每10个换行
cout << endl;
}
}
}
cout << "\n共有" << count << "个素数" << endl;
return 0;
}
本章小结
重点回顾
- 选择结构:if、if-else、switch,注意break在switch中的使用
- 循环结构:while、do-while、for、范围for,理解各自适用场景
- 循环控制:break(跳出循环)、continue(跳过本次循环)、goto(了解即可)
- 嵌套结构:循环嵌套、选择与循环嵌套,注意代码可读性
易错易混淆点
- if条件中的赋值:
if (a = b)vsif (a == b) - switch中的break:忘记break会导致穿透执行
- while与do-while:至少执行一次的区别
- 循环变量的作用域:for循环中定义的变量只在该循环内有效
- 无限循环陷阱:while循环中使用continue可能导致死循环
- break的作用范围:只能跳出当前一层循环
3. 复合数据类型
├── 3.1 数组
│ ├── 一维数组
│ ├── 多维数组
│ ├── 字符数组(C风格字符串)
│ └── 数组作为函数参数
├── 3.2 结构体(struct)
│ ├── 结构体定义与使用
│ ├── 结构体数组
│ ├── 结构体指针
│ └── 结构体嵌套
├── 3.3 共用体(union)
│ └── 匿名共用体
├── 3.4 枚举(enum)
│ ├── 传统枚举
│ └── 强类型枚举(C++11)
└── 3.5 类型推导
├── auto关键字(C++11)
└── decltype关键字(C++11)
3.1 数组
3.1.1 一维数组
// 示例3.1:一维数组
#include <iostream>
#include <algorithm> // 用于sort函数
using namespace std;
int main() {
// 1. 数组的声明和初始化
int arr1[5]; // 声明一个包含5个整数的数组(未初始化)
// 初始化方式
int arr2[5] = {1, 2, 3, 4, 5}; // 完全初始化
int arr3[] = {1, 2, 3, 4, 5}; // 自动推断大小
int arr4[5] = {1, 2}; // 部分初始化,其余为0
int arr5[5] = {0}; // 全部初始化为0
// C++11统一初始化语法
int arr6[]{1, 2, 3, 4, 5};
int arr7[5]{1, 2, 3, 4, 5};
// 2. 访问数组元素
cout << "arr2的元素:" << endl;
for (int i = 0; i < 5; i++) {
cout << "arr2[" << i << "] = " << arr2[i] << endl;
}
// 3. 数组大小计算
int arr8[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
int size = sizeof(arr8) / sizeof(arr8[0]); // 计算元素个数
cout << "arr8的大小: " << size << endl;
// 4. 数组与指针的关系(重要!)
// 数组名在大多数情况下会被转换为指向第一个元素的指针
cout << "\n数组名和指针的关系:" << endl;
cout << "arr8 = " << arr8 << endl; // 数组首地址
cout << "&arr8[0] = " << &arr8[0] << endl; // 第一个元素的地址
cout << "*arr8 = " << *arr8 << endl; // 第一个元素的值
// 5. 数组遍历的几种方式
cout << "\n数组遍历方式:" << endl;
// 方式1:下标遍历
cout << "下标遍历:";
for (int i = 0; i < size; i++) {
cout << arr8[i] << " ";
}
cout << endl;
// 方式2:指针遍历
cout << "指针遍历:";
for (int* p = arr8; p < arr8 + size; p++) {
cout << *p << " ";
}
cout << endl;
// 方式3:范围for循环(C++11)
cout << "范围for循环:";
for (int num : arr8) {
cout << num << " ";
}
cout << endl;
// 6. 数组的常见操作
int scores[10] = {85, 90, 78, 92, 88, 76, 95, 81, 89, 73};
// 求和
int sum = 0;
for (int i = 0; i < 10; i++) {
sum += scores[i];
}
cout << "平均分: " << sum / 10.0 << endl;
// 找最大值
int max_score = scores[0];
for (int i = 1; i < 10; i++) {
if (scores[i] > max_score) {
max_score = scores[i];
}
}
cout << "最高分: " << max_score << endl;
// 排序(冒泡排序)
int nums[] = {5, 3, 8, 1, 9, 2, 7, 4, 6};
int n = sizeof(nums) / sizeof(nums[0]);
for (int i = 0; i < n - 1; i++) {
for (int j = 0; j < n - i - 1; j++) {
if (nums[j] > nums[j + 1]) {
// 交换
int temp = nums[j];
nums[j] = nums[j + 1];
nums[j + 1] = temp;
}
}
}
cout << "排序后:";
for (int num : nums) {
cout << num << " ";
}
cout << endl;
return 0;
}
易错点提示:
- 数组越界访问:
int arr[5] = {1, 2, 3, 4, 5};
cout << arr[5]; // 错误!索引0-4,arr[5]越界
// 越界访问可能不报错,但结果是未定义的,可能访问到其他内存
- 数组不能直接赋值:
int a[3] = {1, 2, 3};
int b[3];
// b = a; // 错误!数组不能直接赋值
// 必须逐个元素复制
for (int i = 0; i < 3; i++) {
b[i] = a[i];
}
- 数组大小必须是编译时常量:
int n;
cin >> n;
// int arr[n]; // 错误!C++标准不支持变长数组(但某些编译器扩展支持)
// 正确做法:使用动态内存分配或vector
3.1.2 多维数组
// 示例3.2:多维数组
#include <iostream>
#include <iomanip>
using namespace std;
int main() {
// 1. 二维数组的声明和初始化
// 方式1:指定所有维度
int matrix1[3][4] = {
{1, 2, 3, 4},
{5, 6, 7, 8},
{9, 10, 11, 12}
};
// 方式2:省略第一维(编译器可以推断)
int matrix2[][4] = {
{1, 2, 3, 4},
{5, 6, 7, 8},
{9, 10, 11, 12}
};
// 方式3:线性初始化(按内存顺序)
int matrix3[3][4] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12};
// 2. 访问二维数组元素
cout << "二维数组元素:" << endl;
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 4; j++) {
cout << setw(3) << matrix1[i][j] << " ";
}
cout << endl;
}
// 3. 计算行数和列数
int rows = sizeof(matrix1) / sizeof(matrix1[0]);
int cols = sizeof(matrix1[0]) / sizeof(matrix1[0][0]);
cout << "\n矩阵大小: " << rows << "行 × " << cols << "列" << endl;
// 4. 二维数组在内存中的布局
// 二维数组实际上是"数组的数组",在内存中是连续存储的
cout << "\n内存地址验证:" << endl;
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 4; j++) {
cout << "&matrix1[" << i << "][" << j << "] = "
<< &matrix1[i][j] << endl;
}
}
// 5. 实用示例:矩阵运算
int A[2][3] = {
{1, 2, 3},
{4, 5, 6}
};
int B[2][3] = {
{6, 5, 4},
{3, 2, 1}
};
int C[2][3]; // 结果矩阵
// 矩阵相加
cout << "\n矩阵加法:" << endl;
for (int i = 0; i < 2; i++) {
for (int j = 0; j < 3; j++) {
C[i][j] = A[i][j] + B[i][j];
cout << setw(3) << C[i][j] << " ";
}
cout << endl;
}
// 6. 三维数组
int cube[2][3][4] = {
{
{1, 2, 3, 4},
{5, 6, 7, 8},
{9, 10, 11, 12}
},
{
{13, 14, 15, 16},
{17, 18, 19, 20},
{21, 22, 23, 24}
}
};
cout << "\n三维数组访问:" << endl;
for (int i = 0; i < 2; i++) {
cout << "第" << i << "层:" << endl;
for (int j = 0; j < 3; j++) {
for (int k = 0; k < 4; k++) {
cout << setw(3) << cube[i][j][k] << " ";
}
cout << endl;
}
cout << endl;
}
return 0;
}
易错点提示:
- 多维数组初始化必须指定除第一维外的所有维度:
// int arr[][] = {{1,2}, {3,4}}; // 错误!必须指定列数
int arr[][2] = {{1,2}, {3,4}}; // 正确
- 二维数组作为函数参数的特殊语法:
// 错误声明
// void func(int arr[][]);
// 正确声明(必须指定列数)
void func(int arr[][4]);
// 或者使用指针
void func(int (*arr)[4]);
3.1.3 字符数组和C风格字符串
// 示例3.3:字符数组和C风格字符串
#include <iostream>
#include <cstring> // C风格字符串函数
using namespace std;
int main() {
// 1. 字符数组的不同初始化方式
char str1[6] = {'H', 'e', 'l', 'l', 'o', '\0'}; // 手动添加\0
char str2[] = {'H', 'e', 'l', 'l', 'o', '\0'}; // 自动计算大小
char str3[] = "Hello"; // 字符串字面量,自动添加\0
char str4[10] = "Hello"; // 指定大小,剩余部分用\0填充
// 2. C风格字符串的输出
cout << "str1: " << str1 << endl;
cout << "str3: " << str3 << endl;
// 3. C风格字符串的长度
cout << "\n字符串长度(不包括\\0):" << endl;
cout << "strlen(str3) = " << strlen(str3) << endl; // 5
cout << "sizeof(str3) = " << sizeof(str3) << endl; // 6(包含\0)
// 4. 字符串操作函数
char src[20] = "Hello";
char dest[20];
// 复制字符串
strcpy(dest, src);
cout << "\nstrcpy后,dest: " << dest << endl;
// 连接字符串
strcat(dest, " World!");
cout << "strcat后,dest: " << dest << endl;
// 比较字符串
char s1[] = "apple";
char s2[] = "banana";
int cmp = strcmp(s1, s2);
if (cmp < 0) {
cout << "\"" << s1 << "\" < \"" << s2 << "\"" << endl;
} else if (cmp > 0) {
cout << "\"" << s1 << "\" > \"" << s2 << "\"" << endl;
} else {
cout << "字符串相等" << endl;
}
// 5. 字符串查找
char text[] = "This is a test string.";
char *found = strstr(text, "test");
if (found != nullptr) {
cout << "\n找到子串,位置: " << (found - text) << endl;
cout << "子串内容: " << found << endl;
}
// 6. 字符串输入
char name[50];
cout << "\n请输入你的名字(最多49个字符): ";
// cin >> name; // 遇到空格会停止
cin.getline(name, 50); // 读取整行,包括空格
cout << "你好, " << name << "!" << endl;
// 7. 常见错误:缓冲区溢出
char small[5] = "Test";
// strcat(small, " Too Long"); // 危险!缓冲区溢出
// 安全版本:使用strn系列函数
char safe[10] = "Hello";
strncat(safe, " World!", sizeof(safe) - strlen(safe) - 1);
cout << "\n安全连接: " << safe << endl;
// 8. 字符数组与字符串字面量
char arr[] = "C++"; // 可以修改
// arr[0] = 'c'; // 正确,可以修改
const char* ptr = "C++"; // 字符串字面量在只读内存
// ptr[0] = 'c'; // 错误!试图修改只读内存
// 正确:使用const char*指向字符串字面量
const char* greeting = "Hello, World!";
cout << greeting << endl;
return 0;
}
重要概念:
- 字符串字面量:如”Hello”是常量,存储在只读内存区
- ‘\0’:空字符,ASCII值为0,表示字符串结束
- sizeof vs strlen:
sizeof:数组的总大小(包含\0)strlen:字符串的实际长度(不包括\0)
3.2 结构体(struct)
3.2.1 结构体的基本使用
// 示例3.4:结构体的基本使用
#include <iostream>
#include <cstring>
using namespace std;
// 1. 结构体定义
struct Student {
// 成员变量
char name[50];
int id;
int age;
float score;
// C++中结构体可以包含成员函数(与类的区别很小)
void display() {
cout << "姓名: " << name << endl;
cout << "学号: " << id << endl;
cout << "年龄: " << age << endl;
cout << "成绩: " << score << endl;
}
void setScore(float newScore) {
if (newScore >= 0 && newScore <= 100) {
score = newScore;
}
}
};
// 也可以先声明后定义
struct Point; // 前向声明
struct Point {
int x;
int y;
};
int main() {
// 2. 结构体变量的声明和初始化
// 方式1:逐个成员初始化
Student stu1;
strcpy(stu1.name, "张三");
stu1.id = 1001;
stu1.age = 20;
stu1.score = 85.5;
// 方式2:聚合初始化(C++11之前)
Student stu2 = {"李四", 1002, 21, 90.0};
// 方式3:C++11统一初始化
Student stu3{"王五", 1003, 19, 88.5};
// 方式4:指定成员初始化(C++20)
// Student stu4{.name="赵六", .id=1004, .age=22, .score=92.0};
// 3. 访问结构体成员
cout << "学生信息:" << endl;
cout << "姓名: " << stu1.name << endl;
cout << "学号: " << stu1.id << endl;
// 4. 使用成员函数
cout << "\n使用成员函数显示信息:" << endl;
stu1.display();
// 5. 结构体赋值(逐字节复制)
Student stu5 = stu1; // 复制整个结构体
stu5.id = 1005;
cout << "\n复制后的学生:" << endl;
stu5.display();
// 6. 结构体数组
Student class1[3] = {
{"小明", 2001, 18, 78.5},
{"小红", 2002, 19, 92.0},
{"小刚", 2003, 20, 85.0}
};
cout << "\n班级学生信息:" << endl;
for (int i = 0; i < 3; i++) {
class1[i].display();
cout << "---" << endl;
}
// 7. 结构体指针
Student* pStu = &stu1;
// 访问指针指向的结构体成员
// 方式1:解引用然后使用点运算符
(*pStu).age = 21;
// 方式2:使用箭头运算符(更常用)
pStu->score = 88.0;
cout << "\n通过指针修改后:" << endl;
stu1.display();
// 8. 结构体嵌套
struct Date {
int year;
int month;
int day;
};
struct Employee {
char name[50];
int id;
Date hireDate; // 嵌套结构体
double salary;
};
Employee emp = {
"张经理",
5001,
{2020, 3, 15}, // 嵌套初始化
8000.0
};
cout << "\n员工信息:" << endl;
cout << "姓名: " << emp.name << endl;
cout << "入职日期: " << emp.hireDate.year << "-"
<< emp.hireDate.month << "-" << emp.hireDate.day << endl;
// 9. 结构体大小和内存对齐
cout << "\n结构体大小:" << endl;
cout << "sizeof(Student) = " << sizeof(Student) << endl;
cout << "sizeof(stu1) = " << sizeof(stu1) << endl;
// 查看成员偏移量
cout << "\n成员偏移量:" << endl;
cout << "offsetof(Student, name) = " << offsetof(Student, name) << endl;
cout << "offsetof(Student, id) = " << offsetof(Student, id) << endl;
cout << "offsetof(Student, age) = " << offsetof(Student, age) << endl;
cout << "offsetof(Student, score) = " << offsetof(Student, score) << endl;
return 0;
}
重要概念:
- 结构体与类的区别:在C++中,struct默认成员是public,class默认是private
- 内存对齐:为了提高访问效率,编译器会对结构体成员进行内存对齐
- 结构体赋值:支持直接赋值,是浅拷贝(逐字节复制)
3.2.2 结构体中的位字段
// 示例3.5:结构体位字段
#include <iostream>
using namespace std;
int main() {
// 位字段:节省内存空间,用于硬件编程、网络协议等
struct PackedData {
// 冒号后的数字表示该字段占用的位数
unsigned int flag1 : 1; // 1位
unsigned int flag2 : 1; // 1位
unsigned int type : 4; // 4位,可以表示0-15
unsigned int value : 10; // 10位,可以表示0-1023
unsigned int : 0; // 匿名位字段,强制对齐到下一个字边界
unsigned int extra : 16; // 16位
};
PackedData data;
data.flag1 = 1;
data.flag2 = 0;
data.type = 5;
data.value = 255;
data.extra = 65535;
cout << "位字段示例:" << endl;
cout << "flag1: " << data.flag1 << endl;
cout << "flag2: " << data.flag2 << endl;
cout << "type: " << data.type << endl;
cout << "value: " << data.value << endl;
cout << "extra: " << data.extra << endl;
cout << "\n结构体大小: " << sizeof(PackedData) << " bytes" << endl;
// 注意:位字段的地址不可取
// unsigned int* p = &data.flag1; // 错误!
return 0;
}
3.3 共用体(union)
// 示例3.6:共用体
#include <iostream>
#include <cstring>
using namespace std;
int main() {
// 1. 共用体定义
// 共用体的所有成员共享同一块内存
union Data {
int i;
float f;
char str[20];
};
// 2. 共用体变量的声明
Data data;
cout << "共用体大小: " << sizeof(data) << " bytes" << endl;
// 3. 使用共用体
data.i = 10;
cout << "data.i = " << data.i << endl;
data.f = 3.14f;
cout << "data.f = " << data.f << endl;
cout << "注意:data.i现在是未定义的: " << data.i << endl;
strcpy(data.str, "Hello");
cout << "data.str = " << data.str << endl;
// 4. 匿名共用体(C++11)
struct Variant {
enum Type { INT, FLOAT, STRING } type;
union {
int intValue;
float floatValue;
char stringValue[20];
}; // 匿名共用体,成员可以直接访问
void setInt(int val) {
type = INT;
intValue = val;
}
void display() {
switch (type) {
case INT:
cout << "整数: " << intValue << endl;
break;
case FLOAT:
cout << "浮点数: " << floatValue << endl;
break;
case STRING:
cout << "字符串: " << stringValue << endl;
break;
}
}
};
Variant var;
var.setInt(100);
var.display();
// 5. 共用体的实际应用:类型转换
union Converter {
int i;
float f;
float intToFloat(int val) {
i = val;
return f;
}
int floatToInt(float val) {
f = val;
return i;
}
};
Converter conv;
int intVal = 1092616192; // 对应的float是10.5
float floatVal = conv.intToFloat(intVal);
cout << "\n类型转换:" << endl;
cout << "整数 " << intVal << " 对应的浮点数: " << floatVal << endl;
// 6. 带构造函数的共用体(C++11)
union ComplexUnion {
int x;
double y;
char z;
// C++11允许共用体有构造函数
ComplexUnion() : x(0) {} // 默认初始化x为0
};
ComplexUnion cu;
cout << "初始化后 cu.x = " << cu.x << endl;
return 0;
}
重要概念:
- 共用体大小:等于最大成员的大小
- 内存共享:所有成员共享同一块内存,修改一个成员会影响其他成员
- 用途:节省内存、类型转换、实现变体类型
3.4 枚举(enum)
// 示例3.7:枚举
#include <iostream>
using namespace std;
int main() {
// 1. 传统枚举(C风格)
enum Color {
RED, // 0
GREEN, // 1
BLUE, // 2
YELLOW = 10, // 可以指定值
PURPLE // 11(前一个值+1)
};
Color c1 = RED;
Color c2 = BLUE;
cout << "传统枚举:" << endl;
cout << "RED = " << RED << endl;
cout << "GREEN = " << GREEN << endl;
cout << "BLUE = " << BLUE << endl;
cout << "YELLOW = " << YELLOW << endl;
cout << "PURPLE = " << PURPLE << endl;
// 枚举可以隐式转换为整数
int colorValue = c1;
cout << "c1的值: " << colorValue << endl;
// 但整数不能隐式转换为枚举(需要强制转换)
// Color c3 = 2; // 错误!
Color c3 = static_cast<Color>(2); // 正确
// 2. 强类型枚举(C++11)
enum class Day {
MONDAY, // 0
TUESDAY, // 1
WEDNESDAY, // 2
THURSDAY, // 3
FRIDAY, // 4
SATURDAY, // 5
SUNDAY // 6
};
enum class Month : unsigned char { // 指定底层类型
JAN = 1, FEB, MAR, APR, MAY, JUN,
JUL, AUG, SEP, OCT, NOV, DEC
};
Day today = Day::MONDAY;
Month thisMonth = Month::APR;
cout << "\n强类型枚举:" << endl;
// 必须使用作用域解析运算符
// cout << today << endl; // 错误!不能隐式转换
// 可以显式转换
cout << "today: " << static_cast<int>(today) << endl;
cout << "thisMonth: " << static_cast<int>(thisMonth) << endl;
// 3. 枚举的大小
cout << "\n枚举大小:" << endl;
cout << "sizeof(Color) = " << sizeof(Color) << endl;
cout << "sizeof(Day) = " << sizeof(Day) << endl;
cout << "sizeof(Month) = " << sizeof(Month) << endl;
// 4. 枚举的switch用法
cout << "\n枚举在switch中的使用:" << endl;
switch (c1) {
case RED:
cout << "红色" << endl;
break;
case GREEN:
cout << "绿色" << endl;
break;
case BLUE:
cout << "蓝色" << endl;
break;
default:
cout << "其他颜色" << endl;
}
// 5. 枚举与整数的比较
Color myColor = GREEN;
if (myColor == GREEN) {
cout << "颜色是绿色" << endl;
}
// 6. 枚举的应用:状态机
enum class State {
IDLE,
RUNNING,
PAUSED,
STOPPED
};
State currentState = State::IDLE;
// 模拟状态转换
currentState = State::RUNNING;
cout << "\n当前状态: " << static_cast<int>(currentState) << endl;
// 7. 遍历枚举值(需要额外处理)
cout << "\n遍历Month枚举:" << endl;
for (int i = static_cast<int>(Month::JAN);
i <= static_cast<int>(Month::DEC);
i++) {
cout << i << " ";
}
cout << endl;
return 0;
}
重要概念:
- 传统枚举的缺点:
- 枚举值会污染外层命名空间
- 可以隐式转换为整数
- 可以与其他枚举类型比较(类型不安全)
- 强类型枚举的优点:
- 必须使用
枚举类型::枚举值访问 - 不会污染外层命名空间
- 不能隐式转换为整数
- 类型安全
3.5 类型推导(C++11)
// 示例3.8:类型推导
#include <iostream>
#include <vector>
#include <map>
#include <typeinfo> // typeid运算符
using namespace std;
int main() {
// 1. auto关键字(自动类型推断)
auto x = 5; // x的类型是int
auto y = 3.14; // y的类型是double
auto z = "Hello"; // z的类型是const char*
cout << "auto类型推导:" << endl;
cout << "x的类型: " << typeid(x).name() << endl;
cout << "y的类型: " << typeid(y).name() << endl;
cout << "z的类型: " << typeid(z).name() << endl;
// auto在循环中的应用
vector<int> numbers = {1, 2, 3, 4, 5};
cout << "\n传统for循环:" << endl;
for (vector<int>::iterator it = numbers.begin();
it != numbers.end(); ++it) {
cout << *it << " ";
}
cout << endl;
cout << "使用auto的for循环:" << endl;
for (auto it = numbers.begin(); it != numbers.end(); ++it) {
cout << *it << " ";
}
cout << endl;
cout << "范围for循环 + auto:" << endl;
for (auto num : numbers) {
cout << num << " ";
}
cout << endl;
// 2. decltype关键字(获取表达式的类型)
int a = 10;
decltype(a) b = 20; // b的类型与a相同,即int
const int c = 30;
decltype(c) d = 40; // d的类型是const int
decltype(a + 3.14) e; // e的类型是double
cout << "\ndecltype示例:" << endl;
cout << "sizeof(b) = " << sizeof(b) << endl;
cout << "sizeof(e) = " << sizeof(e) << endl;
// 3. auto和decltype的区别
// auto会去掉引用和const修饰,decltype会保留
int num = 10;
int& ref = num;
const int const_num = 20;
auto auto1 = ref; // auto1是int,去掉了引用
decltype(ref) decl1 = num; // decl1是int&,保留了引用
auto auto2 = const_num; // auto2是int,去掉了const
decltype(const_num) decl2 = 30; // decl2是const int
// 4. 后置返回类型(trailing return type)
// 使用auto和decltype结合,可以推导函数返回类型
auto add(int x, int y) -> int {
return x + y;
}
// 更复杂的例子:模板函数
template<typename T, typename U>
auto multiply(T x, U y) -> decltype(x * y) {
return x * y;
}
// 实际使用
auto result1 = add(3, 4);
auto result2 = multiply(3, 4.5); // 返回double
cout << "add(3, 4) = " << result1 << endl;
cout << "multiply(3, 4.5) = " << result2 << endl;
// 5. 在复杂数据结构中使用auto
map<string, vector<int>> data = {
{"Alice", {85, 90, 88}},
{"Bob", {78, 82, 80}},
{"Charlie", {92, 95, 90}}
};
cout << "\n复杂数据结构遍历:" << endl;
// 不使用auto(代码冗长)
for (map<string, vector<int>>::iterator it = data.begin();
it != data.end(); ++it) {
cout << it->first << ": ";
for (vector<int>::iterator vit = it->second.begin();
vit != it->second.end(); ++vit) {
cout << *vit << " ";
}
cout << endl;
}
// 使用auto(代码简洁)
cout << "\n使用auto:" << endl;
for (auto& entry : data) {
cout << entry.first << ": ";
for (auto score : entry.second) {
cout << score << " ";
}
cout << endl;
}
// 6. 注意事项
// auto不能用于函数参数(C++20之前)
// void func(auto x) {} // C++20允许
// auto不能推导数组类型
int arr[] = {1, 2, 3};
auto arr_auto = arr; // arr_auto是int*,不是int[3]
// 使用decltype可以保留数组类型
decltype(arr) arr_copy = {4, 5, 6}; // arr_copy是int[3]
return 0;
}
重要概念:
- auto推导规则:
- 忽略顶层const和引用
- 数组退化为指针
- 函数退化为函数指针
- decltype推导规则:
- 如果表达式是变量,则保留变量的类型(包括const和引用)
- 如果表达式不是变量,则推导出表达式的类型
- auto vs decltype:
- 使用
auto想要推断类型时 - 使用
decltype想要精确知道表达式的类型时
综合例题与解析
// 例题1:学生成绩管理系统
#include <iostream>
#include <cstring>
#include <iomanip>
using namespace std;
struct Student {
int id;
char name[20];
float scores[3]; // 三门课程的成绩
float average;
char grade; // 等级:A(>=90), B(>=80), C(>=70), D(>=60), F(<60)
};
void calculateGrade(Student& stu) {
stu.average = (stu.scores[0] + stu.scores[1] + stu.scores[2]) / 3.0;
if (stu.average >= 90) stu.grade = 'A';
else if (stu.average >= 80) stu.grade = 'B';
else if (stu.average >= 70) stu.grade = 'C';
else if (stu.average >= 60) stu.grade = 'D';
else stu.grade = 'F';
}
void displayStudent(const Student& stu) {
cout << left << setw(6) << stu.id
<< setw(15) << stu.name
<< fixed << setprecision(1);
for (int i = 0; i < 3; i++) {
cout << setw(8) << stu.scores[i];
}
cout << setw(10) << stu.average
<< setw(6) << stu.grade << endl;
}
int main() {
const int NUM_STUDENTS = 3;
Student students[NUM_STUDENTS];
// 输入学生信息
for (int i = 0; i < NUM_STUDENTS; i++) {
cout << "\n输入第" << i + 1 << "个学生的信息:" << endl;
cout << "学号:";
cin >> students[i].id;
cout << "姓名:";
cin >> students[i].name;
cout << "三门课程的成绩:";
for (int j = 0; j < 3; j++) {
cin >> students[i].scores[j];
}
calculateGrade(students[i]);
}
// 显示学生信息
cout << "\n===== 学生成绩单 =====" << endl;
cout << left << setw(6) << "学号"
<< setw(15) << "姓名"
<< setw(8) << "成绩1"
<< setw(8) << "成绩2"
<< setw(8) << "成绩3"
<< setw(10) << "平均分"
<< setw(6) << "等级" << endl;
cout << string(60, '-') << endl;
for (int i = 0; i < NUM_STUDENTS; i++) {
displayStudent(students[i]);
}
// 统计各等级人数
int gradeCount[5] = {0}; // A,B,C,D,F
for (int i = 0; i < NUM_STUDENTS; i++) {
switch (students[i].grade) {
case 'A': gradeCount[0]++; break;
case 'B': gradeCount[1]++; break;
case 'C': gradeCount[2]++; break;
case 'D': gradeCount[3]++; break;
case 'F': gradeCount[4]++; break;
}
}
cout << "\n===== 等级统计 =====" << endl;
char grades[] = {'A', 'B', 'C', 'D', 'F'};
for (int i = 0; i < 5; i++) {
cout << grades[i] << ": " << gradeCount[i] << "人" << endl;
}
return 0;
}
// 例题2:矩阵运算
#include <iostream>
#include <iomanip>
using namespace std;
const int ROWS = 3;
const int COLS = 3;
// 使用typedef定义矩阵类型
typedef int Matrix[ROWS][COLS];
void printMatrix(const Matrix mat, const string& name) {
cout << "\n" << name << " = " << endl;
for (int i = 0; i < ROWS; i++) {
for (int j = 0; j < COLS; j++) {
cout << setw(4) << mat[i][j];
}
cout << endl;
}
}
void addMatrix(const Matrix a, const Matrix b, Matrix result) {
for (int i = 0; i < ROWS; i++) {
for (int j = 0; j < COLS; j++) {
result[i][j] = a[i][j] + b[i][j];
}
}
}
void multiplyMatrix(const Matrix a, const Matrix b, Matrix result) {
for (int i = 0; i < ROWS; i++) {
for (int j = 0; j < COLS; j++) {
result[i][j] = 0;
for (int k = 0; k < COLS; k++) {
result[i][j] += a[i][k] * b[k][j];
}
}
}
}
void transposeMatrix(const Matrix mat, Matrix result) {
for (int i = 0; i < ROWS; i++) {
for (int j = 0; j < COLS; j++) {
result[j][i] = mat[i][j];
}
}
}
int main() {
Matrix A = {
{1, 2, 3},
{4, 5, 6},
{7, 8, 9}
};
Matrix B = {
{9, 8, 7},
{6, 5, 4},
{3, 2, 1}
};
Matrix C; // 用于存储结果
printMatrix(A, "A");
printMatrix(B, "B");
// 矩阵加法
addMatrix(A, B, C);
printMatrix(C, "A + B");
// 矩阵乘法
multiplyMatrix(A, B, C);
printMatrix(C, "A × B");
// 矩阵转置
transposeMatrix(A, C);
printMatrix(C, "A的转置");
return 0;
}
// 例题3:联合体的实际应用 - IP地址转换
#include <iostream>
#include <cstdint> // 固定宽度整数类型
using namespace std;
// 使用联合体将32位IP地址分解为4个字节
union IPAddress {
uint32_t address; // 32位无符号整数
struct {
uint8_t octet4; // 最高位字节
uint8_t octet3;
uint8_t octet2;
uint8_t octet1; // 最低位字节
} octets;
};
int main() {
IPAddress ip;
// 设置IP地址为 192.168.1.100
// 注意:网络字节序通常是大端序,这里假设是小端序系统
ip.octets.octet1 = 100; // 最后一位
ip.octets.octet2 = 1;
ip.octets.octet3 = 168;
ip.octets.octet4 = 192; // 第一位
cout << "IP地址的32位值: " << ip.address << endl;
cout << "IP地址点分十进制: "
<< (int)ip.octets.octet4 << "."
<< (int)ip.octets.octet3 << "."
<< (int)ip.octets.octet2 << "."
<< (int)ip.octets.octet1 << endl;
// 另一种方式:直接设置32位值
ip.address = 0xC0A80164; // 192.168.1.100的十六进制
cout << "\n直接设置32位值后:" << endl;
cout << "IP地址点分十进制: "
<< (int)ip.octets.octet4 << "."
<< (int)ip.octets.octet3 << "."
<< (int)ip.octets.octet2 << "."
<< (int)ip.octets.octet1 << endl;
return 0;
}
本章小结
重点回顾
- 数组:一维、多维数组的声明、初始化和使用,特别注意数组与指针的关系
- 结构体:自定义数据类型,包含成员变量和函数,理解内存对齐
- 共用体:所有成员共享内存,用于节省空间和类型转换
- 枚举:传统枚举和强类型枚举的区别和应用场景
- 类型推导:auto和decltype的使用场景和区别
易错易混淆点
- 数组越界:C++不检查数组边界,需要程序员自己保证
- 数组名与指针:数组名在大多数情况下会退化为指针,但
sizeof(数组名)例外 - 结构体赋值:直接赋值是浅拷贝,对于指针成员需要特别小心
- 枚举作用域:传统枚举会污染命名空间,强类型枚举不会
- auto推导:会去掉顶层const和引用,decltype会保留
考研笔试常见题型
- 选择题:考察复合数据类型的基本概念和特性
- 程序阅读题:分析涉及数组、结构体的复杂程序输出
- 程序填空题:补全结构体定义、数组操作等代码
- 编程题:实现特定功能,如学生管理系统、矩阵运算等
编程实践建议
- 数组安全:总是检查数组边界,使用std::array或std::vector更安全
- 结构体设计:合理设计结构体,注意内存对齐对性能的影响
- 类型选择:根据需求选择合适的复合数据类型
- 代码可读性:为结构体和枚举使用有意义的名称
- 现代C++:优先使用C++11的特性,如范围for循环、auto、强类型枚举
学习检查
- 你能解释数组名在什么情况下不会退化为指针吗?
- 结构体和类在C++中的主要区别是什么?
- 什么情况下应该使用共用体?
- 为什么C++11要引入强类型枚举?
- auto和decltype的主要区别是什么?
1) 数组名在什么情况下不会退化为指针?
“数组退化(decay)”指:数组表达式在大多数场合会隐式转换成指向首元素的指针(T*)。不退化的典型场合主要有:
sizeof运算符
int a[10];
sizeof(a); // 得到整个数组字节数,不是指针大小
decltype
int a[10];
decltype(a) x = a; // x 的类型是 int[10](数组类型)
- 取地址
&a
int a[10];
auto p = &a; // p 的类型是 int (*)[10](指向“数组”的指针),不是 int*
- 用引用接收数组(不会退化成指针)
template <size_t N>
void f(int (&arr)[N]) { /* arr 是数组引用 */ }
int a[10];
f(a); // N=10,没退化
- 范围 for(range-based for)遍历数组
这里编译器用数组的边界信息来生成遍历逻辑,并不把它当成普通int*参数传进去(内部会用begin/end语义)。
反例:作为函数参数时,
void g(int arr[])/void g(int arr[10])都会调整为void g(int* arr),这就是退化最常见的地方。
2) 结构体(struct)和类(class)在 C++ 中的主要区别
在 C++ 里,struct 和 class 几乎一样,主要差别只有默认规则:
- 默认成员访问权限
struct:默认publicclass:默认private
- 默认继承方式
struct Derived : Base默认是public继承class Derived : Base默认是private继承
除此之外(构造/析构、成员函数、虚函数、模板、重载等)完全相同。
惯例上:struct 常用于“数据聚合/简单载体”,class 常用于“封装+不变式”。
3) 什么情况下应该使用共用体(union)?
union 让多个成员共享同一块内存,同一时刻只能“有效地”表示其中一种类型。
适用场景(现代 C++ 更强调谨慎使用):
- 节省内存:大型数据结构里多个字段互斥出现(例如协议包、AST 节点的多种形态)。
- 与底层二进制/硬件/协议对接:需要按不同视角解释同一段字节(但要注意严格别名/未定义行为问题)。
- 实现“变体类型”:一种对象可能是 int 或 double 或 string 等。
现代 C++ 的更推荐方案:
- 需要“安全的变体类型”优先用
std::variant(带类型标签、析构正确、访问安全)。 union更适合非常底层、极致内存布局控制的场景,且通常要自己维护“当前活跃成员”的判别字段。
4) 为什么 C++11 要引入强类型枚举(enum class)?
传统 enum 的痛点:
- 会隐式转换成整数,容易混用/误用:
enum Color { Red };
int x = Red; // 允许(容易出错)
- 枚举名污染外层作用域(名字冲突)
enum Color { Red };
enum Stop { Red }; // 冲突
- 底层类型/大小不够可控(跨平台 ABI、内存布局、序列化不稳定)
enum class(强类型枚举)解决:
- 不再隐式转 int:类型更安全
- 作用域限定:
Color::Red - 可指定底层类型:
enum class Color : uint8_t { ... }
更利于二进制协议、节省空间、稳定布局
5) auto 和 decltype 的主要区别
两者都用于“推导类型”,但推导规则和使用场景不同。
auto
- 用于从初始化表达式推导变量类型
- 推导时更像“模板类型推导”,会丢掉一些顶层属性:
- 通常会丢顶层
const - 引用需要写
auto&/auto&&才保留
例:
int x = 1;
int& r = x;
auto a = r; // a 是 int(引用被丢掉)
auto& b = r; // b 是 int&(保留引用)
decltype
- 用于获取某个表达式的精确类型(包括引用性)
- 特别规则:
decltype((x))(注意双括号)会得到引用类型
例:
int x = 1;
decltype(x) a = x; // int
decltype((x)) b = x; // int& (因为 (x) 是左值表达式)
常见搭配
decltype(auto):让返回类型/变量类型“完全按表达式精确推导”,常用于完美转发/返回引用:
decltype(auto) f() { return (x); } // 可能返回引用
4. 函数
├── 4.1 函数基础
│ ├── 函数定义与声明
│ ├── 函数调用
│ ├── 函数参数
│ │ ├── 值传递
│ │ ├── 引用传递
│ │ └── 指针传递
│ └── 函数返回
│ ├── 返回类型
│ ├── 返回引用
│ └── 返回指针
├── 4.2 函数特性
│ ├── 内联函数(inline)
│ ├── 默认参数
│ ├── 函数重载
│ ├── 函数模板
│ └── 递归函数
├── 4.3 函数指针
│ ├── 函数指针定义
│ ├── 函数指针作为参数
│ └── 函数指针数组
└── 4.4 Lambda表达式(C++11)
├── Lambda语法
├── 捕获列表
└── mutable与异常规范
函数是C++程序的基本构建块,是实现模块化和代码重用的核心。本章将深入讲解函数的各个方面。
4.1 函数基础
4.1.1 函数定义与声明
// 示例4.1:函数的基本使用
#include <iostream>
using namespace std;
// 1. 函数声明(原型) - 告诉编译器函数的存在
// 函数声明只需要:返回类型、函数名、参数列表
int add(int a, int b); // 函数声明
void printMessage(); // 无参数函数声明
double calculate(double x, double y); // 声明可以没有参数名
// 2. 函数定义 - 实现函数功能
// 函数定义必须提供函数体
int add(int a, int b) { // 函数头:返回类型 函数名(参数列表)
// 函数体
int sum = a + b; // 局部变量
return sum; // 返回值
}
// 3. 无参数函数
void printMessage() {
cout << "Hello from function!" << endl;
// void函数可以不写return,或写 return; 表示返回
}
// 4. 无返回值函数
void printSum(int a, int b) {
int sum = a + b;
cout << "Sum = " << sum << endl;
// 没有return语句,或使用 return;(可选)
}
// 5. 主函数 - 程序入口
int main() {
// 函数调用
int result = add(5, 3); // 实际参数(实参):5和3
cout << "5 + 3 = " << result << endl;
printMessage();
printSum(10, 20);
// 调用已声明的函数
double calcResult = calculate(3.14, 2.5);
cout << "calculate result = " << calcResult << endl;
return 0;
}
// 6. 函数可以在声明之后定义
double calculate(double x, double y) {
return x * y + x / y;
}
重要概念:
- 函数声明:告诉编译器函数的存在,包括返回类型、函数名和参数列表
- 函数定义:提供函数的具体实现
- 函数调用:使用函数执行特定任务
- 实参与形参:
- 形参:函数定义时的参数(如
int a, int b) - 实参:函数调用时传递的参数(如
5, 3)
4.1.2 参数传递的三种方式
// 示例4.2:参数传递方式
#include <iostream>
using namespace std;
// 1. 值传递(传值) - 默认方式
// 创建实参的副本,函数内修改不影响原始数据
void valuePass(int x) {
x = x * 2; // 修改的是副本
cout << "函数内: x = " << x << endl;
}
// 2. 引用传递(传引用)
// 传递变量的引用(别名),函数内修改会影响原始数据
void referencePass(int &x) { // &表示引用
x = x * 2; // 修改的是原始变量
cout << "函数内: x = " << x << endl;
}
// 3. 指针传递(传地址)
// 传递变量的地址,通过指针访问和修改原始数据
void pointerPass(int *x) { // *表示指针
*x = *x * 2; // 通过指针修改原始变量
cout << "函数内: *x = " << *x << endl;
}
// 4. 对比示例
void demonstratePassing() {
int a = 5;
cout << "原始值: a = " << a << endl;
// 值传递
valuePass(a);
cout << "值传递后: a = " << a << endl; // a不变
// 引用传递
referencePass(a);
cout << "引用传递后: a = " << a << endl; // a改变
// 指针传递
pointerPass(&a); // 传递地址
cout << "指针传递后: a = " << a << endl; // a改变
cout << endl;
}
// 5. 选择参数传递方式的准则
// 小对象(如基本类型):值传递或引用传递
// 大对象(如结构体、类):引用传递,避免拷贝开销
// 需要修改原始数据:引用传递或指针传递
// 不希望修改原始数据:值传递或const引用传递
// 6. 常量引用传递(重要!)
// 避免拷贝开销,同时防止修改原始数据
void printLargeObject(const string &str) { // const引用
// str[0] = 'A'; // 错误!不能修改const引用
cout << str << endl;
}
// 7. 数组作为参数(特殊)
// 数组作为参数时,实际上传递的是数组首元素的指针
void printArray(int arr[], int size) { // 等价于 int* arr
for (int i = 0; i < size; i++) {
cout << arr[i] << " ";
}
cout << endl;
}
// 传递多维数组必须指定除第一维外的所有维度
void printMatrix(int matrix[][3], int rows) {
for (int i = 0; i < rows; i++) {
for (int j = 0; j < 3; j++) {
cout << matrix[i][j] << " ";
}
cout << endl;
}
}
int main() {
demonstratePassing();
// 常量引用示例
string longString = "This is a very long string...";
printLargeObject(longString);
// 数组参数示例
int arr[] = {1, 2, 3, 4, 5};
printArray(arr, 5);
// 多维数组参数示例
int matrix[2][3] = {{1, 2, 3}, {4, 5, 6}};
printMatrix(matrix, 2);
return 0;
}
易错点提示:
- 值传递的局限性:
void swap(int a, int b) { // 错误!无法交换原始值
int temp = a;
a = b;
b = temp;
}
// 正确写法:使用引用或指针
void swap(int &a, int &b) { // 正确
int temp = a;
a = b;
b = temp;
}
- 返回局部变量的引用或指针:
int& badFunction() {
int x = 10; // 局部变量
return x; // 危险!返回局部变量的引用
} // x被销毁,返回的引用无效(悬空引用)
4.1.3 返回类型与返回值
// 示例4.3:返回类型与返回值
#include <iostream>
#include <vector>
using namespace std;
// 1. 基本返回类型
int getSum(int a, int b) {
return a + b; // 返回int类型
}
// 2. 返回引用
// 可以返回全局变量、静态变量、动态分配的内存或传入的引用参数的引用
int& getElement(int arr[], int index) {
return arr[index]; // 返回数组元素的引用
}
// 3. 返回指针
int* createArray(int size) {
int* arr = new int[size]; // 动态分配
for (int i = 0; i < size; i++) {
arr[i] = i * 2;
}
return arr; // 返回动态分配的内存的指针
}
// 4. 返回结构体/类对象
struct Point {
int x, y;
};
Point createPoint(int x, int y) {
Point p;
p.x = x;
p.y = y;
return p; // 返回结构体对象(可能涉及拷贝)
}
// 5. 返回自动类型推导(C++14)
auto multiply(int a, double b) {
return a * b; // 返回类型自动推导为double
}
// 6. 无返回值(void)
void processData(int value) {
if (value < 0) {
return; // 提前返回
}
cout << "Processing: " << value << endl;
// 函数结束时自动返回,不需要return语句
}
// 7. 返回多个值的方法
// 方法1:通过引用参数返回
void getMinMax(int arr[], int size, int &min, int &max) {
min = arr[0];
max = arr[0];
for (int i = 1; i < size; i++) {
if (arr[i] < min) min = arr[i];
if (arr[i] > max) max = arr[i];
}
}
// 方法2:返回结构体
struct MinMax {
int min;
int max;
};
MinMax findMinMax(int arr[], int size) {
MinMax result;
result.min = arr[0];
result.max = arr[0];
for (int i = 1; i < size; i++) {
if (arr[i] < result.min) result.min = arr[i];
if (arr[i] > result.max) result.max = arr[i];
}
return result;
}
// 8. C++17结构化绑定(返回多个值的便捷方式)
#include <tuple>
tuple<int, int, double> calculateStats(int a, int b) {
int sum = a + b;
int product = a * b;
double average = (a + b) / 2.0;
return make_tuple(sum, product, average); // 返回tuple
}
int main() {
// 基本返回类型
int sum = getSum(5, 3);
cout << "Sum: " << sum << endl;
// 返回引用
int numbers[] = {10, 20, 30, 40, 50};
getElement(numbers, 2) = 100; // 修改数组元素
cout << "numbers[2] = " << numbers[2] << endl;
// 返回指针
int* arr = createArray(5);
cout << "Dynamic array: ";
for (int i = 0; i < 5; i++) {
cout << arr[i] << " ";
}
cout << endl;
delete[] arr; // 必须手动释放内存!
// 返回结构体
Point p = createPoint(3, 4);
cout << "Point: (" << p.x << ", " << p.y << ")" << endl;
// 自动类型推导
auto result = multiply(3, 2.5);
cout << "Multiply result: " << result
<< ", type: " << typeid(result).name() << endl;
// 通过引用参数返回多个值
int min, max;
getMinMax(numbers, 5, min, max);
cout << "Min: " << min << ", Max: " << max << endl;
// 返回结构体获取多个值
MinMax mm = findMinMax(numbers, 5);
cout << "MinMax struct - Min: " << mm.min << ", Max: " << mm.max << endl;
// 使用tuple返回多个值
auto stats = calculateStats(10, 20);
cout << "Tuple - Sum: " << get<0>(stats)
<< ", Product: " << get<1>(stats)
<< ", Average: " << get<2>(stats) << endl;
// C++17结构化绑定(更方便)
auto [s, pdt, avg] = calculateStats(30, 40);
cout << "Structured binding - Sum: " << s
<< ", Product: " << pdt
<< ", Average: " << avg << endl;
return 0;
}
重要概念:
- 返回引用:必须确保返回的引用在函数调用后仍然有效
- 返回值优化(RVO):编译器可能会优化返回对象的拷贝
- 返回类型推导:C++14允许使用auto推导返回类型
- 尾返回类型:C++11引入,用于复杂返回类型推导
4.2 函数特性
4.2.1 内联函数(inline)
// 示例4.4:内联函数
#include <iostream>
using namespace std;
// 1. 内联函数定义
// 内联函数建议编译器在调用处展开代码,避免函数调用开销
// 适用于短小、频繁调用的函数
inline int square(int x) {
return x * x;
}
// 2. 内联函数与宏的区别
// 宏是预处理器的文本替换,没有类型检查
#define SQUARE_MACRO(x) ((x) * (x))
// 内联函数有类型检查,更安全
inline double square(double x) {
return x * x;
}
// 3. 类内定义的成员函数自动为内联函数
class Calculator {
public:
// 类内定义的函数默认为inline
int add(int a, int b) { return a + b; }
// 类内声明,类外定义的函数需要显式指定inline
int multiply(int a, int b);
};
// 类外定义内联函数
inline int Calculator::multiply(int a, int b) {
return a * b;
}
// 4. 内联函数的限制
// - 不能包含循环、switch等复杂控制结构
// - 不能是递归函数
// - 函数体不能过大
// 注意:inline只是建议,编译器可能忽略
// 5. 正确使用内联函数的示例
inline int max(int a, int b) {
return (a > b) ? a : b;
}
inline bool isEven(int n) {
return (n % 2 == 0);
}
int main() {
// 内联函数调用
int a = 5;
cout << "square(5) = " << square(5) << endl;
cout << "square(3.14) = " << square(3.14) << endl;
// 宏调用
cout << "SQUARE_MACRO(5) = " << SQUARE_MACRO(5) << endl;
// 宏的陷阱
int x = 5;
cout << "SQUARE_MACRO(++x) = " << SQUARE_MACRO(++x) << endl; // 危险!x被增加两次
x = 5;
cout << "square(++x) = " << square(++x) << endl; // 安全,x只增加一次
// 类成员函数
Calculator calc;
cout << "calc.add(3, 4) = " << calc.add(3, 4) << endl;
cout << "calc.multiply(3, 4) = " << calc.multiply(3, 4) << endl;
// 使用内联函数
cout << "max(10, 20) = " << max(10, 20) << endl;
cout << "isEven(7) = " << isEven(7) << endl;
return 0;
}
4.2.2 默认参数
// 示例4.5:默认参数
#include <iostream>
#include <string>
using namespace std;
// 1. 基本默认参数
// 默认参数必须在函数声明中指定,通常在头文件中
void display(string message, int times = 1) {
for (int i = 0; i < times; i++) {
cout << message << endl;
}
}
// 2. 多个默认参数
// 默认参数必须从右向左连续设置
void drawRectangle(int width = 10, int height = 5, char symbol = '*') {
for (int i = 0; i < height; i++) {
for (int j = 0; j < width; j++) {
cout << symbol;
}
cout << endl;
}
}
// 3. 默认参数与函数重载
// 默认参数可以减少函数重载的需要
void print(int value, string label = "Value") {
cout << label << ": " << value << endl;
}
// 4. 默认参数在指针/引用函数中的使用
void initArray(int* arr, int size, int initValue = 0) {
for (int i = 0; i < size; i++) {
arr[i] = initValue;
}
}
// 5. 默认参数与内联函数结合
inline void logMessage(const string& msg, bool withTimestamp = false) {
if (withTimestamp) {
// 这里简化处理,实际应用中会获取当前时间
cout << "[TIME] " << msg << endl;
} else {
cout << msg << endl;
}
}
// 6. 默认参数的声明与定义
// 通常默认参数在函数声明中指定
void setColor(int r, int g = 0, int b = 0);
int main() {
// 使用默认参数
display("Hello"); // 使用默认times=1
display("World", 3); // 指定times=3
cout << endl;
// 多个默认参数的使用
cout << "默认矩形:" << endl;
drawRectangle(); // 全部使用默认值
cout << "\n指定宽度:" << endl;
drawRectangle(20); // 只指定width
cout << "\n指定宽度和高度:" << endl;
drawRectangle(15, 8); // 指定width和height
cout << "\n指定所有参数:" << endl;
drawRectangle(12, 6, '#'); // 指定所有参数
// 默认参数与函数调用
print(42); // 使用默认label
print(100, "Number"); // 指定label
// 初始化数组
int arr[5];
initArray(arr, 5); // 使用默认initValue=0
cout << "初始化后的数组:";
for (int i = 0; i < 5; i++) {
cout << arr[i] << " ";
}
cout << endl;
initArray(arr, 5, -1); // 指定initValue=-1
cout << "重新初始化后的数组:";
for (int i = 0; i < 5; i++) {
cout << arr[i] << " ";
}
cout << endl;
// 内联函数与默认参数
logMessage("Info message");
logMessage("Important message", true);
return 0;
}
// 函数定义(声明中已有默认参数,定义中不应重复)
void setColor(int r, int g, int b) {
cout << "Color: RGB(" << r << ", " << g << ", " << b << ")" << endl;
}
易错点提示:
- 默认参数的位置:
// 错误!默认参数必须从右向左
// void func(int a = 1, int b, int c = 2);
// 正确
void func(int a, int b = 2, int c = 3);
- 默认参数与函数重载冲突:
void display(string msg, int times = 1);
void display(string msg); // 重载冲突!调用display("hello")有歧义
- 默认参数在声明和定义中的一致性:
// 头文件中声明
void func(int a, int b = 10);
// 源文件中定义(不能重复默认参数)
// void func(int a, int b = 10) { ... } // 错误!重复默认参数
void func(int a, int b) { ... } // 正确
4.2.3 函数重载
// 示例4.6:函数重载
#include <iostream>
#include <string>
#include <cmath>
using namespace std;
// 1. 函数重载:同一作用域内,函数名相同但参数列表不同
// 参数列表不同包括:参数类型不同、参数个数不同、参数顺序不同
// 2. 参数类型不同的重载
int add(int a, int b) {
cout << "调用 add(int, int)" << endl;
return a + b;
}
double add(double a, double b) {
cout << "调用 add(double, double)" << endl;
return a + b;
}
string add(const string& a, const string& b) {
cout << "调用 add(string, string)" << endl;
return a + b;
}
// 3. 参数个数不同的重载
int sum(int a) {
return a;
}
int sum(int a, int b) {
return a + b;
}
int sum(int a, int b, int c) {
return a + b + c;
}
// 4. 参数顺序不同的重载
void display(int id, string name) {
cout << "ID: " << id << ", Name: " << name << endl;
}
void display(string name, int id) {
cout << "Name: " << name << ", ID: " << id << endl;
}
// 5. 函数重载与默认参数的关系
// 注意:可能导致二义性
void print(int x) {
cout << "print(int): " << x << endl;
}
void print(int x, int y = 0) {
cout << "print(int, int): " << x << ", " << y << endl;
}
// print(5); // 错误!有歧义,可以调用print(int)或print(int, int=0)
// 6. 函数重载与const参数
// const可以用于区分重载函数
void process(int& x) {
cout << "process(int&): 可修改" << endl;
x++;
}
void process(const int& x) {
cout << "process(const int&): 不可修改" << endl;
// x++; // 错误!x是const
}
// 7. 函数重载与指针/引用参数
void swap(int* a, int* b) {
int temp = *a;
*a = *b;
*b = temp;
}
void swap(int& a, int& b) {
int temp = a;
a = b;
b = temp;
}
// 8. 重载解析过程(编译器如何选择重载函数)
// 1) 精确匹配
// 2) 类型提升(如char→int,float→double)
// 3) 标准转换(如int→double)
// 4) 用户定义转换
// 5) 可变参数匹配
void test(int x) {
cout << "test(int)" << endl;
}
void test(double x) {
cout << "test(double)" << endl;
}
void test(int x, double y) {
cout << "test(int, double)" << endl;
}
int main() {
// 参数类型不同的重载
cout << "add(3, 4) = " << add(3, 4) << endl;
cout << "add(3.14, 2.5) = " << add(3.14, 2.5) << endl;
cout << "add(\"Hello, \", \"World!\") = " << add("Hello, ", "World!") << endl;
// 参数个数不同的重载
cout << "\nsum(5) = " << sum(5) << endl;
cout << "sum(5, 6) = " << sum(5, 6) << endl;
cout << "sum(5, 6, 7) = " << sum(5, 6, 7) << endl;
// 参数顺序不同的重载
cout << endl;
display(101, "Alice");
display("Bob", 102);
// const参数重载
cout << endl;
int num = 10;
const int const_num = 20;
process(num); // 调用 process(int&)
process(const_num); // 调用 process(const int&)
process(30); // 调用 process(const int&),字面量是右值
// 指针/引用参数重载
cout << endl;
int x = 5, y = 10;
cout << "交换前: x = " << x << ", y = " << y << endl;
swap(&x, &y); // 调用 swap(int*, int*)
cout << "指针交换后: x = " << x << ", y = " << y << endl;
swap(x, y); // 调用 swap(int&, int&)
cout << "引用交换后: x = " << x << ", y = " << y << endl;
// 重载解析
cout << "\n重载解析测试:" << endl;
char c = 'A';
float f = 3.14f;
test(c); // char → int(类型提升),调用 test(int)
test(f); // float → double(标准转换),调用 test(double)
test(5, f); // 精确匹配 test(int, double)
// 9. 不能仅靠返回类型区分重载函数
// int getValue(); // 错误!与下面冲突
// double getValue(); // 仅返回类型不同,不能重载
return 0;
}
4.2.4 递归函数
// 示例4.7:递归函数
#include <iostream>
using namespace std;
// 1. 递归函数:函数直接或间接调用自身
// 必须有递归终止条件,否则会无限递归(栈溢出)
// 2. 阶乘计算(经典递归示例)
// n! = n × (n-1)!,其中0! = 1
long long factorial(int n) {
// 终止条件
if (n <= 1) {
return 1;
}
// 递归调用
return n * factorial(n - 1);
}
// 3. 斐波那契数列
// F(0) = 0, F(1) = 1, F(n) = F(n-1) + F(n-2)
int fibonacci(int n) {
if (n <= 0) return 0;
if (n == 1) return 1;
return fibonacci(n - 1) + fibonacci(n - 2);
}
// 4. 尾递归优化版本(效率更高)
// 尾递归:递归调用是函数体最后执行的操作
long long factorialTail(int n, long long result = 1) {
if (n <= 1) {
return result;
}
return factorialTail(n - 1, n * result); // 尾递归调用
}
// 5. 递归的优缺点
// 优点:代码简洁,适合解决分治、回溯等问题
// 缺点:效率较低(函数调用开销),可能栈溢出
// 6. 汉诺塔问题
void hanoi(int n, char from, char to, char aux) {
if (n == 1) {
cout << "移动盘子 1 从 " << from << " 到 " << to << endl;
return;
}
hanoi(n - 1, from, aux, to);
cout << "移动盘子 " << n << " 从 " << from << " 到 " << to << endl;
hanoi(n - 1, aux, to, from);
}
// 7. 递归实现二分查找
int binarySearch(int arr[], int left, int right, int target) {
if (left > right) {
return -1; // 未找到
}
int mid = left + (right - left) / 2;
if (arr[mid] == target) {
return mid; // 找到
} else if (arr[mid] > target) {
return binarySearch(arr, left, mid - 1, target); // 在左半部分查找
} else {
return binarySearch(arr, mid + 1, right, target); // 在右半部分查找
}
}
// 8. 递归实现数组求和
int arraySum(int arr[], int n) {
if (n <= 0) {
return 0;
}
return arraySum(arr, n - 1) + arr[n - 1];
}
// 9. 递归实现字符串反转
void reverseString(char str[], int start, int end) {
if (start >= end) {
return;
}
// 交换首尾字符
char temp = str[start];
str[start] = str[end];
str[end] = temp;
// 递归处理剩余部分
reverseString(str, start + 1, end - 1);
}
// 10. 递归深度和栈溢出
void deepRecursion(int depth) {
if (depth <= 0) {
return;
}
// 每次递归调用都会在栈上分配内存
// 如果递归太深,会导致栈溢出
cout << "深度: " << depth << endl;
deepRecursion(depth - 1);
}
int main() {
// 阶乘计算
cout << "5! = " << factorial(5) << endl;
cout << "10! = " << factorial(10) << endl;
cout << "20! = " << factorialTail(20) << " (尾递归)" << endl;
// 斐波那契数列
cout << "\n斐波那契数列前10项: ";
for (int i = 0; i < 10; i++) {
cout << fibonacci(i) << " ";
}
cout << endl;
// 汉诺塔问题
cout << "\n汉诺塔问题(3个盘子):" << endl;
hanoi(3, 'A', 'C', 'B');
// 二分查找
int sortedArr[] = {1, 3, 5, 7, 9, 11, 13, 15};
int size = sizeof(sortedArr) / sizeof(sortedArr[0]);
int target = 7;
int index = binarySearch(sortedArr, 0, size - 1, target);
cout << "\n二分查找 " << target << ": ";
if (index != -1) {
cout << "找到,索引为 " << index << endl;
} else {
cout << "未找到" << endl;
}
// 数组求和
int arr[] = {1, 2, 3, 4, 5};
int sum = arraySum(arr, 5);
cout << "\n数组求和: " << sum << endl;
// 字符串反转
char str[] = "Hello, World!";
cout << "原始字符串: " << str << endl;
reverseString(str, 0, strlen(str) - 1);
cout << "反转后字符串: " << str << endl;
// 注意:递归深度过大可能导致栈溢出
// 尝试调用 deepRecursion(10000); // 可能导致栈溢出
return 0;
}
递归重要概念:
- 递归三要素:
- 递归定义:问题的定义是递归的
- 递归终止条件:必须有明确的结束条件
- 递归调用:每次调用都向终止条件靠近
- 递归 vs 迭代:
- 递归:代码简洁,但效率低,可能栈溢出
- 迭代:代码可能复杂,但效率高,不会栈溢出
- 尾递归优化:
- 某些编译器可以将尾递归优化为迭代,避免栈溢出
- 尾递归要求递归调用是函数体中最后执行的操作
4.3 函数指针
// 示例4.8:函数指针
#include <iostream>
#include <vector>
#include <algorithm>
#include <cmath>
using namespace std;
// 1. 函数指针基础
// 函数指针:指向函数的指针,可以像函数一样调用
// 函数声明
int add(int a, int b);
int subtract(int a, int b);
int multiply(int a, int b);
int divide(int a, int b);
// 函数定义
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; }
int divide(int a, int b) {
if (b != 0) return a / b;
return 0;
}
// 2. 函数指针类型定义
// 方式1:直接定义函数指针
int (*funcPtr1)(int, int); // 指向返回int,接受两个int参数的函数
// 方式2:使用typedef定义函数指针类型
typedef int (*MathFunc)(int, int); // MathFunc是函数指针类型
// 方式3:使用using(C++11)
using MathFunc2 = int (*)(int, int);
// 3. 函数指针的使用
void demonstrateFunctionPointers() {
cout << "函数指针演示:" << endl;
// 将函数地址赋给函数指针
MathFunc func = add;
cout << "5 + 3 = " << func(5, 3) << endl;
func = subtract;
cout << "5 - 3 = " << func(5, 3) << endl;
// 直接使用函数指针
int (*ptr)(int, int) = multiply;
cout << "5 * 3 = " << ptr(5, 3) << endl;
// 函数指针数组
MathFunc operations[] = {add, subtract, multiply, divide};
char opSymbols[] = {'+', '-', '*', '/'};
int a = 10, b = 5;
for (int i = 0; i < 4; i++) {
cout << a << " " << opSymbols[i] << " " << b
<< " = " << operations[i](a, b) << endl;
}
}
// 4. 函数指针作为参数(回调函数)
void processNumbers(int a, int b, MathFunc operation) {
int result = operation(a, b);
cout << "处理结果: " << result << endl;
}
// 5. 函数指针作为返回值
MathFunc getOperation(char op) {
switch (op) {
case '+': return add;
case '-': return subtract;
case '*': return multiply;
case '/': return divide;
default: return nullptr;
}
}
// 6. 标准库中的函数指针应用
bool compareDesc(int a, int b) {
return a > b; // 降序比较
}
bool isEven(int n) {
return n % 2 == 0;
}
void demonstrateSTLWithFunctionPointers() {
cout << "\nSTL中使用函数指针:" << endl;
vector<int> numbers = {5, 2, 8, 1, 9, 3};
// 使用函数指针作为比较函数
sort(numbers.begin(), numbers.end(), compareDesc);
cout << "降序排序: ";
for (int num : numbers) {
cout << num << " ";
}
cout << endl;
// 使用函数指针作为谓词
auto it = find_if(numbers.begin(), numbers.end(), isEven);
if (it != numbers.end()) {
cout << "找到第一个偶数: " << *it << endl;
}
}
// 7. 函数指针与函数对象的对比
// 函数对象(仿函数)通常比函数指针更灵活,可以被内联
// 8. C++11:使用std::function(更灵活)
#include <functional>
void demonstrateStdFunction() {
cout << "\n使用std::function:" << endl;
// std::function可以存储任何可调用对象
std::function<int(int, int)> func;
func = add;
cout << "add(7, 3) = " << func(7, 3) << endl;
// 可以存储lambda表达式
func = [](int a, int b) { return a * b; };
cout << "lambda multiply(7, 3) = " << func(7, 3) << endl;
// 可以存储函数对象
struct Power {
int operator()(int a, int b) {
return static_cast<int>(pow(a, b));
}
};
Power power;
func = power;
cout << "power(2, 3) = " << func(2, 3) << endl;
}
// 9. 成员函数指针(指向类成员函数的指针)
class Calculator {
public:
int add(int a, int b) { return a + b; }
int subtract(int a, int b) { return a - b; }
static int multiply(int a, int b) { return a * b; }
};
void demonstrateMemberFunctionPointers() {
cout << "\n成员函数指针:" << endl;
Calculator calc;
// 普通成员函数指针
int (Calculator::*memberFunc)(int, int) = &Calculator::add;
cout << "calc.add(5, 3) = " << (calc.*memberFunc)(5, 3) << endl;
memberFunc = &Calculator::subtract;
cout << "calc.subtract(5, 3) = " << (calc.*memberFunc)(5, 3) << endl;
// 静态成员函数指针(与普通函数指针类似)
int (*staticFunc)(int, int) = &Calculator::multiply;
cout << "Calculator::multiply(5, 3) = " << staticFunc(5, 3) << endl;
}
int main() {
demonstrateFunctionPointers();
// 回调函数示例
cout << "\n回调函数示例:" << endl;
processNumbers(20, 4, add);
processNumbers(20, 4, divide);
// 返回函数指针
MathFunc op = getOperation('*');
if (op != nullptr) {
cout << "\n返回的函数指针: 8 * 2 = " << op(8, 2) << endl;
}
demonstrateSTLWithFunctionPointers();
demonstrateStdFunction();
demonstrateMemberFunctionPointers();
return 0;
}
函数指针重要概念:
- 函数指针的声明语法:
// 返回类型 (*指针名)(参数列表)
int (*funcPtr)(int, int);
- 函数指针 vs 函数对象:
- 函数指针:指向函数的指针,语法较复杂
- 函数对象:重载了
()运算符的类对象,更灵活 std::function:C++11引入,可以存储任何可调用对象
- 成员函数指针的特殊语法:
// 类名::*指针名
int (ClassName::*memberPtr)(int);
// 调用语法
(object.*memberPtr)(args);
(ptr->*memberPtr)(args);
4.4 Lambda表达式(C++11)
// 示例4.9:Lambda表达式
#include <iostream>
#include <vector>
#include <algorithm>
#include <functional>
using namespace std;
int main() {
// 1. Lambda表达式基本语法
// [捕获列表](参数列表) -> 返回类型 { 函数体 }
// 最简单的lambda:没有参数,没有返回值
auto simpleLambda = []() {
cout << "Hello from lambda!" << endl;
};
simpleLambda();
// 2. 带参数的lambda
auto add = [](int a, int b) -> int {
return a + b;
};
cout << "add(3, 4) = " << add(3, 4) << endl;
// 返回类型可以自动推导(C++14)
auto multiply = [](auto a, auto b) { // C++14泛型lambda
return a * b;
};
cout << "multiply(3, 4.5) = " << multiply(3, 4.5) << endl;
// 3. 捕获列表
// []:不捕获任何外部变量
// [=]:以值方式捕获所有外部变量
// [&]:以引用方式捕获所有外部变量
// [x]:以值方式捕获x
// [&x]:以引用方式捕获x
// [=, &x]:以值方式捕获所有变量,但x以引用方式捕获
// [&, x]:以引用方式捕获所有变量,但x以值方式捕获
int x = 10, y = 20;
// 值捕获
auto valueCapture = [x, y]() {
cout << "值捕获: x = " << x << ", y = " << y << endl;
// x++; // 错误!值捕获的变量在lambda内是const(除非使用mutable)
};
valueCapture();
// 引用捕获
auto referenceCapture = [&x, &y]() {
x++;
y++;
cout << "引用捕获: x = " << x << ", y = " << y << endl;
};
referenceCapture();
cout << "修改后: x = " << x << ", y = " << y << endl;
// 混合捕获
auto mixedCapture = [x, &y]() {
// x是值捕获(副本),y是引用捕获
cout << "混合捕获: x = " << x << ", y = " << y << endl;
};
mixedCapture();
// 4. mutable关键字
// 允许修改值捕获的变量(修改的是副本)
int count = 0;
auto counter = [count]() mutable {
cout << "计数: " << count << endl;
count++; // 修改副本,不影响外部的count
};
for (int i = 0; i < 3; i++) {
counter();
}
cout << "外部count: " << count << endl; // 仍然是0
// 5. Lambda在STL算法中的应用
vector<int> numbers = {5, 2, 8, 1, 9, 3, 7, 4, 6};
cout << "\n原始数组: ";
for_each(numbers.begin(), numbers.end(), [](int n) {
cout << n << " ";
});
cout << endl;
// 排序(使用lambda作为比较函数)
sort(numbers.begin(), numbers.end(), [](int a, int b) {
return a > b; // 降序排序
});
cout << "降序排序后: ";
for_each(numbers.begin(), numbers.end(), [](int n) {
cout << n << " ";
});
cout << endl;
// 查找第一个大于5的元素
auto it = find_if(numbers.begin(), numbers.end(), [](int n) {
return n > 5;
});
if (it != numbers.end()) {
cout << "第一个大于5的元素: " << *it << endl;
}
// 统计偶数个数
int evenCount = count_if(numbers.begin(), numbers.end(), [](int n) {
return n % 2 == 0;
});
cout << "偶数个数: " << evenCount << endl;
// 6. 返回lambda的函数
auto makeMultiplier = [](int factor) {
// 返回一个lambda
return [factor](int value) {
return value * factor;
};
};
auto doubleIt = makeMultiplier(2);
auto tripleIt = makeMultiplier(3);
cout << "doubleIt(5) = " << doubleIt(5) << endl;
cout << "tripleIt(5) = " << tripleIt(5) << endl;
// 7. Lambda作为回调函数
auto processData = [](const vector<int>& data, function<void(int)> callback) {
for (int value : data) {
callback(value);
}
};
cout << "\n回调处理: ";
processData(numbers, [](int value) {
cout << value << " ";
});
cout << endl;
// 8. 泛型Lambda(C++14)
auto genericAdd = [](auto a, auto b) {
return a + b;
};
cout << "泛型lambda: " << endl;
cout << "genericAdd(3, 4) = " << genericAdd(3, 4) << endl;
cout << "genericAdd(3.14, 2.5) = " << genericAdd(3.14, 2.5) << endl;
cout << "genericAdd(string(\"Hello, \"), string(\"World!\")) = "
<< genericAdd(string("Hello, "), string("World!")) << endl;
// 9. 初始化捕获(C++14)
int base = 100;
auto addBase = [value = base + 10](int x) { // 初始化捕获
return x + value;
};
cout << "初始化捕获: addBase(5) = " << addBase(5) << endl;
// 10. 将lambda存储在std::function中
function<int(int, int)> func;
func = [](int a, int b) { return a + b; };
cout << "function存储lambda: " << func(10, 20) << endl;
// 11. Lambda表达式类型
// 每个lambda都有唯一的类型,可以使用auto或std::function存储
auto uniqueLambda = []() { return 42; };
// decltype(uniqueLambda) anotherLambda; // 错误!不能默认构造
return 0;
}
Lambda表达式重要概念:
- Lambda的组成:
- 捕获列表:指定如何捕获外部变量
- 参数列表:与普通函数参数类似
- 返回类型:可以自动推导或显式指定
- 函数体:实现功能的代码块
- 捕获方式:
- 值捕获:创建外部变量的副本
- 引用捕获:使用外部变量的引用
- 初始化捕获(C++14):可以初始化捕获的变量
- mutable关键字:
- 允许修改值捕获的变量(修改的是副本)
- 不影响外部变量
- Lambda的应用场景:
- STL算法的谓词和比较函数
- 回调函数
- 创建闭包(携带状态的函数对象)
- 简化一次性使用的函数
综合例题与解析
// 例题1:计算器程序(综合应用函数特性)
#include <iostream>
#include <functional>
#include <map>
#include <cmath>
using namespace std;
// 使用函数指针类型
using Operation = double (*)(double, double);
// 基本运算函数
double add(double a, double b) { return a + b; }
double subtract(double a, double b) { return a - b; }
double multiply(double a, double b) { return a * b; }
double divide(double a, double b) {
if (b == 0) throw runtime_error("除数不能为0");
return a / b;
}
// 使用函数对象(仿函数)
class Power {
public:
double operator()(double base, double exponent) const {
return pow(base, exponent);
}
};
// 使用lambda
auto modulo = [](double a, double b) -> double {
if (b == 0) throw runtime_error("除数不能为0");
return fmod(a, b);
};
// 计算器类
class Calculator {
private:
map<char, function<double(double, double)>> operations;
public:
Calculator() {
// 注册基本运算
operations['+'] = add;
operations['-'] = subtract;
operations['*'] = multiply;
operations['/'] = divide;
// 注册函数对象
operations['^'] = Power();
// 注册lambda
operations['%'] = modulo;
// 注册更多lambda
operations['@'] = [](double a, double b) { return sqrt(a*a + b*b); }; // 模长
}
double calculate(char op, double a, double b) {
auto it = operations.find(op);
if (it == operations.end()) {
throw runtime_error("未知的操作符");
}
try {
return it->second(a, b);
} catch (const exception& e) {
throw runtime_error(string("计算错误: ") + e.what());
}
}
void displayOperations() const {
cout << "支持的操作:" << endl;
cout << " + : 加法" << endl;
cout << " - : 减法" << endl;
cout << " * : 乘法" << endl;
cout << " / : 除法" << endl;
cout << " ^ : 幂运算" << endl;
cout << " % : 取模" << endl;
cout << " @ : 计算模长 sqrt(a^2 + b^2)" << endl;
}
};
// 递归函数:计算表达式树
struct Node {
char op; // 操作符,'#'表示数字
double value; // 如果是数字,存储值
Node* left;
Node* right;
Node(char o, double v = 0) : op(o), value(v), left(nullptr), right(nullptr) {}
};
double evaluateTree(Node* root, Calculator& calc) {
if (root == nullptr) {
return 0;
}
if (root->op == '#') {
return root->value; // 叶子节点,返回数字
}
// 递归计算左右子树
double leftVal = evaluateTree(root->left, calc);
double rightVal = evaluateTree(root->right, calc);
// 计算当前节点
return calc.calculate(root->op, leftVal, rightVal);
}
int main() {
Calculator calc;
calc.displayOperations();
cout << "\n简单计算测试:" << endl;
try {
// 测试各种运算
cout << "10 + 5 = " << calc.calculate('+', 10, 5) << endl;
cout << "10 - 5 = " << calc.calculate('-', 10, 5) << endl;
cout << "10 * 5 = " << calc.calculate('*', 10, 5) << endl;
cout << "10 / 5 = " << calc.calculate('/', 10, 5) << endl;
cout << "2 ^ 3 = " << calc.calculate('^', 2, 3) << endl;
cout << "10 % 3 = " << calc.calculate('%', 10, 3) << endl;
cout << "3 @ 4 = " << calc.calculate('@', 3, 4) << endl;
// 测试错误情况
// cout << "10 / 0 = " << calc.calculate('/', 10, 0) << endl; // 抛出异常
} catch (const exception& e) {
cerr << "错误: " << e.what() << endl;
}
cout << "\n表达式树计算测试:" << endl;
// 构建表达式树: (3 + 4) * (10 - 2)
// *
// / \
// + -
// / \ / \
// 3 4 10 2
Node* root = new Node('*');
root->left = new Node('+');
root->right = new Node('-');
root->left->left = new Node('#', 3);
root->left->right = new Node('#', 4);
root->right->left = new Node('#', 10);
root->right->right = new Node('#', 2);
double result = evaluateTree(root, calc);
cout << "(3 + 4) * (10 - 2) = " << result << endl;
// 清理内存
delete root->left->left;
delete root->left->right;
delete root->left;
delete root->right->left;
delete root->right->right;
delete root->right;
delete root;
return 0;
}
本章小结
重点回顾
- 函数基础:声明、定义、调用,参数传递的三种方式
- 返回值:返回类型、返回引用和指针的注意事项
- 函数特性:内联函数、默认参数、函数重载、递归函数
- 函数指针:指向函数的指针,回调函数的应用
- Lambda表达式:匿名函数,捕获列表,在STL中的应用
易错易混淆点
- 参数传递选择:
- 小对象:值传递或const引用传递
- 大对象:const引用传递
- 需要修改:引用传递或指针传递
- 函数重载二义性:
- 默认参数可能导致重载冲突
- 类型转换可能导致二义性
- 递归终止条件:
- 必须有明确的终止条件
- 递归深度过大会导致栈溢出
- 函数指针语法:
- 注意函数指针的声明语法
- 成员函数指针的特殊调用语法
- Lambda捕获:
- 值捕获的是副本,引用捕获的是引用
- 注意捕获的变量生命周期
考研笔试常见题型
- 选择题:考察函数的基本概念和特性
- 程序阅读题:分析函数调用、参数传递、返回值
- 程序填空题:补全函数定义、调用代码
- 编程题:实现特定功能的函数或算法
编程实践建议
- 函数设计原则:
- 单一职责:一个函数只做一件事
- 简洁明了:函数体不宜过长
- 好的命名:函数名应反映其功能
- 参数设计:
- 合理使用默认参数
- 避免使用输出参数,优先使用返回值
- 使用const保护不需要修改的参数
- 错误处理:
- 使用返回值或异常处理错误
- 检查参数的有效性
- 现代C++特性:
- 优先使用Lambda代替函数指针
- 使用auto简化返回类型
- 使用constexpr函数进行编译期计算
学习检查
- 你能解释值传递、引用传递和指针传递的区别吗?
- 什么情况下应该使用内联函数?
- 函数重载和默认参数可能导致什么问题?
- 递归函数必须满足什么条件?
- Lambda表达式有哪几种捕获方式?各有什么特点?
5. 指针与引用(重点)
├── 5.1 指针基础
│ ├── 指针概念与定义
│ ├── 指针运算符(*和&)
│ ├── 指针运算
│ │ ├── 指针算术运算
│ │ ├── 指针关系运算
│ │ └── 指针与数组
│ └── 指针常量与常量指针
├── 5.2 高级指针
│ ├── 指向指针的指针
│ ├── 指针数组
│ ├── 动态内存分配
│ │ ├── new/delete
│ │ ├── new[]/delete[]
│ │ └── malloc/free(对比)
│ └── 智能指针(C++11)
│ ├── unique_ptr
│ ├── shared_ptr
│ └── weak_ptr
├── 5.3 引用
│ ├── 引用概念与定义
│ ├── 引用作为函数参数
│ ├── 引用作为返回值
│ └── 引用与指针对比
└── 5.4 指针与引用常见考点
├── 悬空指针与野指针
├── 内存泄漏
├── const与指针/引用
└── 函数参数传递方式选择
指针和引用是C++中最核心、最重要、也最难掌握的概念。它们是理解C++内存管理、高效编程和面向对象设计的基础。
5.1 指针基础
5.1.1 指针的概念与定义
// 示例5.1:指针基础
#include <iostream>
using namespace std;
int main() {
int var = 10; // 定义一个整型变量
int *ptr; // 定义一个整型指针
ptr = &var; // 将var的地址赋给指针ptr
cout << "变量var的值: " << var << endl;
cout << "变量var的地址: " << &var << endl;
cout << "指针ptr存储的地址: " << ptr << endl;
cout << "通过指针访问var的值: " << *ptr << endl;
// 修改指针所指向的值
*ptr = 20;
cout << "修改后,变量var的值: " << var << endl;
// 指针的大小(与指向的类型无关,只与系统架构有关)
cout << "指针ptr的大小: " << sizeof(ptr) << " bytes" << endl;
// 不同类型的指针
double d = 3.14;
double *dptr = &d;
cout << "double指针的大小: " << sizeof(dptr) << " bytes" << endl;
// 空指针
int *nullPtr = nullptr; // C++11引入的空指针常量
// 在C++11之前,使用NULL(实际上是0)
// int *nullPtr = NULL;
if (nullPtr == nullptr) {
cout << "这是一个空指针" << endl;
}
// 野指针(未初始化的指针)是危险的
// int *wildPtr; // 野指针,指向不确定的内存地址
// *wildPtr = 10; // 可能导致程序崩溃
return 0;
}
重要概念:
&:取地址运算符,获取变量的内存地址*:解引用运算符,获取指针所指向地址的值- 指针类型:指针的类型必须与其指向的变量类型一致(void指针除外)
5.1.2 指针运算
// 示例5.2:指针运算
#include <iostream>
using namespace std;
int main() {
int arr[] = {10, 20, 30, 40, 50};
int *ptr = arr; // 数组名就是数组首元素的地址
cout << "数组元素地址和值:" << endl;
for (int i = 0; i < 5; i++) {
cout << "地址: " << (ptr + i) << ", 值: " << *(ptr + i) << endl;
}
// 指针加法
ptr = arr; // 重新指向数组开头
cout << "\n指针加法:" << endl;
cout << "ptr = " << ptr << endl;
cout << "ptr + 1 = " << ptr + 1 << endl; // 移动4个字节(int的大小)
cout << "*(ptr + 1) = " << *(ptr + 1) << endl;
// 指针减法
int *ptr2 = &arr[4];
cout << "\n指针减法:" << endl;
cout << "ptr2 = " << ptr2 << endl;
cout << "ptr2 - 1 = " << ptr2 - 1 << endl;
cout << "*(ptr2 - 1) = " << *(ptr2 - 1) << endl;
// 指针差值(同一数组内)
int diff = ptr2 - ptr;
cout << "\n指针差值(元素个数): " << diff << endl;
// 指针比较
if (ptr < ptr2) {
cout << "ptr 在 ptr2 之前" << endl;
}
// 递增和递减
ptr = arr;
cout << "\n指针递增:" << endl;
cout << "初始: *ptr = " << *ptr << endl;
ptr++;
cout << "递增后: *ptr = " << *ptr << endl;
// 注意:指针运算的步长取决于指针指向的类型
char charArr[] = {'a', 'b', 'c', 'd'};
char *charPtr = charArr;
cout << "\nchar指针运算:" << endl;
cout << "charPtr = " << (void*)charPtr << endl; // 强制转换为void*以打印地址
cout << "charPtr + 1 = " << (void*)(charPtr + 1) << endl; // 移动1个字节
return 0;
}
易错点提示:
- 指针运算不是数学运算:
int arr[5];
int *p1 = &arr[1];
int *p2 = &arr[4];
// 错误理解:p2 - p1 = 3(地址差值)
// 正确理解:p2 - p1 = 3(元素个数差)
// 指针运算的单位是指针指向类型的大小
cout << p2 - p1 << endl; // 输出3,不是地址的字节差
- 指针比较的局限性:
int a = 10, b = 20;
int *p1 = &a;
int *p2 = &b;
// 比较两个不相关变量的指针是未定义行为
// if (p1 < p2) // 结果不确定
5.1.3 指针常量与常量指针
这是一个容易混淆的概念,需要特别注意。
// 示例5.3:指针常量与常量指针
#include <iostream>
using namespace std;
int main() {
int a = 10, b = 20;
// 1. 常量指针(pointer to constant)
// 指针指向的内容是常量,不能通过指针修改,但指针本身可以指向其他地址
const int *ptr1 = &a; // 或者 int const *ptr1 = &a;
// *ptr1 = 30; // 错误!不能通过ptr1修改a的值
a = 30; // 正确,a本身不是常量,可以直接修改
ptr1 = &b; // 正确,指针本身可以指向其他变量
cout << "*ptr1 = " << *ptr1 << endl;
// 2. 指针常量(constant pointer)
// 指针本身是常量,不能指向其他地址,但可以通过指针修改指向的内容
int *const ptr2 = &a;
*ptr2 = 40; // 正确,可以通过ptr2修改a的值
// ptr2 = &b; // 错误!指针本身是常量,不能指向其他变量
// 3. 指向常量的指针常量
const int *const ptr3 = &a;
// *ptr3 = 50; // 错误!不能通过ptr3修改a的值
// ptr3 = &b; // 错误!指针本身是常量
// 记忆技巧:
// const在*左边,表示指向的内容是常量(常量指针)
// const在*右边,表示指针本身是常量(指针常量)
// const在*两边都有,表示两者都是常量
return 0;
}
记忆口诀:
- “左定值,右定向”:const在左边,值不能改;const在右边,指向不能改
- “常量指针”指向常量,”指针常量”是常量指针
5.2 高级指针
5.2.1 指向指针的指针(二级指针)
// 示例5.4:二级指针
#include <iostream>
using namespace std;
int main() {
int var = 100;
int *ptr = &var; // 一级指针,指向int
int **pptr = &ptr; // 二级指针,指向int*
cout << "var = " << var << endl;
cout << "*ptr = " << *ptr << endl;
cout << "**pptr = " << **pptr << endl;
// 通过二级指针修改变量的值
**pptr = 200;
cout << "修改后,var = " << var << endl;
// 二级指针在动态二维数组中的应用
int rows = 3, cols = 4;
int **matrix = new int*[rows]; // 分配一个指针数组
for (int i = 0; i < rows; i++) {
matrix[i] = new int[cols]; // 为每一行分配数组
}
// 初始化二维数组
int count = 1;
for (int i = 0; i < rows; i++) {
for (int j = 0; j < cols; j++) {
matrix[i][j] = count++;
}
}
// 打印二维数组
cout << "\n动态二维数组:" << endl;
for (int i = 0; i < rows; i++) {
for (int j = 0; j < cols; j++) {
cout << matrix[i][j] << "\t";
}
cout << endl;
}
// 释放内存
for (int i = 0; i < rows; i++) {
delete[] matrix[i];
}
delete[] matrix;
return 0;
}
5.2.2 指针数组与数组指针
// 示例5.5:指针数组与数组指针
#include <iostream>
using namespace std;
int main() {
// 指针数组:一个数组,其元素都是指针
int a = 10, b = 20, c = 30;
int *ptrArr[3]; // 指针数组,包含3个int*类型的元素
ptrArr[0] = &a;
ptrArr[1] = &b;
ptrArr[2] = &c;
cout << "指针数组:" << endl;
for (int i = 0; i < 3; i++) {
cout << "*ptrArr[" << i << "] = " << *ptrArr[i] << endl;
}
// 数组指针:一个指针,指向一个数组
int arr[5] = {1, 2, 3, 4, 5};
int (*arrPtr)[5]; // 数组指针,指向一个包含5个int的数组
arrPtr = &arr; // 取整个数组的地址
cout << "\n数组指针:" << endl;
for (int i = 0; i < 5; i++) {
cout << "(*arrPtr)[" << i << "] = " << (*arrPtr)[i] << endl;
}
// 数组指针与二维数组
int matrix[3][4] = {
{1, 2, 3, 4},
{5, 6, 7, 8},
{9, 10, 11, 12}
};
int (*rowPtr)[4]; // 指向包含4个int的数组的指针
rowPtr = matrix; // 二维数组名转换为指向第一行的指针
cout << "\n通过数组指针访问二维数组:" << endl;
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 4; j++) {
cout << rowPtr[i][j] << "\t";
}
cout << endl;
}
return 0;
}
重要区别:
- 指针数组:
int *arr[5]– arr是数组,每个元素是int* - 数组指针:
int (*arr)[5]– arr是指针,指向包含5个int的数组
5.2.3 动态内存分配
// 示例5.6:动态内存分配
#include <iostream>
using namespace std;
int main() {
// 1. 使用new和delete分配单个对象
int *p = new int; // 分配一个int
*p = 100;
cout << "*p = " << *p << endl;
delete p; // 释放内存
// 2. 使用new[]和delete[]分配数组
int n;
cout << "请输入数组大小: ";
cin >> n;
int *arr = new int[n]; // 分配n个int的数组
cout << "请输入" << n << "个整数: ";
for (int i = 0; i < n; i++) {
cin >> arr[i];
}
cout << "数组元素: ";
for (int i = 0; i < n; i++) {
cout << arr[i] << " ";
}
cout << endl;
delete[] arr; // 释放数组内存
// 3. 动态分配结构体
struct Point {
int x, y;
};
Point *pt = new Point;
pt->x = 10;
pt->y = 20;
cout << "Point: (" << pt->x << ", " << pt->y << ")" << endl;
delete pt;
// 4. 内存分配失败的处理
int *bigArray = new (nothrow) int[1000000000]; // 可能分配失败
if (bigArray == nullptr) {
cout << "内存分配失败!" << endl;
} else {
delete[] bigArray;
}
// 5. C风格动态内存分配(malloc/free)与new/delete的区别
// malloc/free是函数,new/delete是运算符
// malloc/free不会调用构造函数和析构函数,new/delete会
// 使用malloc分配内存
int *mptr = (int*)malloc(sizeof(int));
*mptr = 50;
cout << "*mptr = " << *mptr << endl;
free(mptr);
// 使用malloc分配数组
int *marr = (int*)malloc(n * sizeof(int));
for (int i = 0; i < n; i++) {
marr[i] = i * 2;
}
free(marr);
return 0;
}
重要区别:
- new/delete vs malloc/free:
new调用构造函数,delete调用析构函数malloc/free只是分配/释放内存new返回类型安全的指针,malloc返回void*需要强制转换new可以重载,malloc不能
- 配对使用:
int *p1 = new int; delete p1; // 正确
int *p2 = new int[10]; delete[] p2; // 正确
int *p3 = new int; delete[] p3; // 错误!未定义行为
int *p4 = new int[10]; delete p4; // 错误!未定义行为
5.2.4 智能指针(C++11)
智能指针是C++11引入的,用于自动管理动态内存,避免内存泄漏。
// 示例5.7:智能指针
#include <iostream>
#include <memory> // 智能指针头文件
using namespace std;
class MyClass {
public:
MyClass() { cout << "MyClass构造函数" << endl; }
~MyClass() { cout << "MyClass析构函数" << endl; }
void sayHello() { cout << "Hello from MyClass!" << endl; }
};
int main() {
// 1. unique_ptr:独占所有权的智能指针
// 同一时间只能有一个unique_ptr指向一个对象
unique_ptr<int> up1(new int(10));
cout << "*up1 = " << *up1 << endl;
// unique_ptr不能复制,只能移动
unique_ptr<int> up2 = move(up1); // up1转移给up2,up1变为空
if (up1) {
cout << "up1不为空" << endl;
} else {
cout << "up1为空" << endl;
}
cout << "*up2 = " << *up2 << endl;
// 使用make_unique(C++14)更安全
unique_ptr<int> up3 = make_unique<int>(20);
cout << "*up3 = " << *up3 << endl;
// 2. shared_ptr:共享所有权的智能指针
// 多个shared_ptr可以指向同一个对象,使用引用计数
shared_ptr<MyClass> sp1(new MyClass());
{
shared_ptr<MyClass> sp2 = sp1; // 引用计数加1
sp1->sayHello();
sp2->sayHello();
cout << "引用计数: " << sp1.use_count() << endl; // 2
} // sp2离开作用域,引用计数减1
cout << "引用计数: " << sp1.use_count() << endl; // 1
// 使用make_shared更高效
shared_ptr<MyClass> sp3 = make_shared<MyClass>();
// 3. weak_ptr:弱引用,不增加引用计数
// 用于解决shared_ptr的循环引用问题
weak_ptr<MyClass> wp = sp3;
cout << "weak_ptr引用计数: " << wp.use_count() << endl;
// 使用weak_ptr访问对象,需要先转换为shared_ptr
if (shared_ptr<MyClass> sp = wp.lock()) {
sp->sayHello();
} else {
cout << "对象已被释放" << endl;
}
// 4. 智能指针与数组
unique_ptr<int[]> upArray(new int[5]);
for (int i = 0; i < 5; i++) {
upArray[i] = i * 10;
}
// shared_ptr默认不支持数组,需要自定义删除器
shared_ptr<int> spArray(new int[5], [](int *p) { delete[] p; });
// 或者使用C++17的shared_ptr支持数组
// shared_ptr<int[]> spArray(new int[5]);
return 0;
}
// 注意:智能指针会在离开作用域时自动释放内存,无需手动delete
智能指针总结:
unique_ptr:独占所有权,轻量级,性能好shared_ptr:共享所有权,使用引用计数,有开销weak_ptr:弱引用,配合shared_ptr使用,解决循环引用
5.3 引用
5.3.1 引用的基本概念
引用是变量的别名,必须在定义时初始化,且不能改变指向。
// 示例5.8:引用的基本概念
#include <iostream>
using namespace std;
int main() {
int var = 10;
int &ref = var; // ref是var的引用(别名)
cout << "var = " << var << endl;
cout << "ref = " << ref << endl;
// 通过引用修改变量的值
ref = 20;
cout << "修改后,var = " << var << endl;
// 引用必须初始化
// int &ref2; // 错误!引用必须初始化
// 引用一旦初始化,就不能再引用其他变量
int another = 30;
// ref = another; // 这不是让ref引用another,而是将another的值赋给ref(即var)
// 引用的引用
int &ref2 = ref; // ref2也是var的引用
ref2 = 40;
cout << "通过ref2修改后,var = " << var << endl;
// 常量引用
const int &cref = var; // 常量引用,不能通过cref修改var
// cref = 50; // 错误!
var = 50; // 可以直接修改var
// 常量引用可以绑定到常量
const int constant = 100;
const int &cref2 = constant;
// 常量引用可以绑定到字面量
const int &cref3 = 200; // 合法
return 0;
}
5.3.2 引用与指针对比
// 示例5.9:引用与指针对比
#include <iostream>
using namespace std;
void swapByPointer(int *a, int *b) {
int temp = *a;
*a = *b;
*b = temp;
}
void swapByReference(int &a, int &b) {
int temp = a;
a = b;
b = temp;
}
int main() {
int x = 10, y = 20;
cout << "交换前: x = " << x << ", y = " << y << endl;
// 使用指针交换
swapByPointer(&x, &y);
cout << "指针交换后: x = " << x << ", y = " << y << endl;
// 使用引用交换
swapByReference(x, y);
cout << "引用交换后: x = " << x << ", y = " << y << endl;
// 引用与指针的区别:
// 1. 引用必须初始化,指针可以不初始化(但危险)
// 2. 引用不能改变指向,指针可以
// 3. 引用不能为空,指针可以为空
// 4. 引用更安全,指针更灵活
// 5. 引用在语法上更简洁
// 引用作为返回值
int arr[5] = {1, 2, 3, 4, 5};
int &getElement(int index); // 函数声明,返回引用
getElement(2) = 100; // 修改数组元素
cout << "arr[2] = " << arr[2] << endl;
// 注意:不能返回局部变量的引用
// int& badFunction() {
// int local = 10;
// return local; // 错误!局部变量在函数结束后被销毁
// }
return 0;
}
int &getElement(int index) {
static int arr[5] = {1, 2, 3, 4, 5}; // 静态数组,生命周期持续到程序结束
return arr[index];
}
引用 vs 指针:
| 特性 | 引用 | 指针 |
|---|---|---|
| 初始化 | 必须初始化 | 可以不初始化 |
| 指向 | 不能改变指向 | 可以改变指向 |
| 空值 | 不能为空 | 可以为空 |
| 语法 | 简洁(直接使用) | 复杂(需要*和&) |
| 安全性 | 更安全 | 更灵活但也更危险 |
| 内存占用 | 通常不占用额外内存 | 占用指针大小的内存 |
5.4 指针与引用常见考点
5.4.1 悬空指针与野指针
// 示例5.10:悬空指针与野指针
#include <iostream>
using namespace std;
int* createArray(int size) {
int *arr = new int[size];
for (int i = 0; i < size; i++) {
arr[i] = i + 1;
}
return arr;
}
int main() {
// 野指针:未初始化的指针
// int *wildPtr; // 野指针,指向不确定的内存
// *wildPtr = 10; // 可能导致程序崩溃
// 悬空指针:指向已被释放的内存的指针
int *ptr = new int(100);
cout << "*ptr = " << *ptr << endl;
delete ptr; // 释放内存
// ptr现在成为悬空指针,指向的内存已被释放
// *ptr = 200; // 危险!访问已释放的内存
// 避免悬空指针:释放后立即置空
ptr = nullptr;
// 返回局部变量的指针
int* badPtr = createArray(5);
// 使用badPtr...
delete[] badPtr; // 必须释放内存,否则内存泄漏
// 使用智能指针避免这些问题
unique_ptr<int[]> safePtr = make_unique<int[]>(5);
// 无需手动释放,自动管理内存
return 0;
}
5.4.2 内存泄漏
// 示例5.11:内存泄漏
#include <iostream>
using namespace std;
void memoryLeak() {
int *ptr = new int(10); // 分配内存
// 忘记delete ptr; // 内存泄漏!
// 函数结束,ptr被销毁,但分配的内存没有释放
}
void noLeak() {
int *ptr = new int(10);
delete ptr; // 正确释放
}
void exceptionLeak() {
int *ptr = new int(10);
// 如果这里抛出异常,delete可能不会执行,导致内存泄漏
// delete ptr;
}
void safeWithSmartPointer() {
unique_ptr<int> ptr(new int(10));
// 即使抛出异常,智能指针也会自动释放内存
}
int main() {
memoryLeak(); // 每次调用都会泄漏一个int的内存
// 循环中的内存泄漏
for (int i = 0; i < 100; i++) {
int *ptr = new int[1000]; // 每次循环分配内存
// 没有释放,内存泄漏累积
// delete[] ptr; // 应该在这里释放
}
return 0;
}
5.4.3 const与指针/引用
// 示例5.12:const与指针/引用
#include <iostream>
using namespace std;
int main() {
int a = 10;
const int b = 20;
// 1. const与指针
const int *p1 = &a; // 指向const int的指针
int const *p2 = &a; // 同上,等价写法
int *const p3 = &a; // const指针,指向int
const int *const p4 = &a; // const指针,指向const int
// 2. const与引用
const int &r1 = a; // const引用,不能通过r1修改a
// int &const r2 = a; // 错误!引用本身就是不可变的,不需要const
// 3. 函数参数中的const
// void func(const int* ptr); // 保护指针指向的内容不被修改
// void func(int* const ptr); // 保护指针本身不被修改(很少用)
// void func(const int& ref); // 保护引用参数不被修改
// 4. const在函数返回中的应用
// const int* getPointer(); // 返回的指针不能用于修改其指向的内容
// 5. 类型转换
const int *pc = &a;
// int *p = pc; // 错误!不能去掉const
int *p = const_cast<int*>(pc); // 使用const_cast去掉const(危险!)
return 0;
}
综合例题与解析
// 例题1:复杂指针声明解析
#include <iostream>
using namespace std;
int main() {
// 解析复杂指针声明的方法:从变量名开始,从内向外,从左向右
int var = 10;
// 1. int *p1;
// p1是一个指针,指向int
// 2. int **p2;
// p2是一个指针,指向int*(二级指针)
// 3. int arr[5];
// arr是一个数组,包含5个int
// 4. int *arrPtr[5];
// arrPtr是一个数组,包含5个int*(指针数组)
// 5. int (*ptrArr)[5];
// ptrArr是一个指针,指向一个包含5个int的数组(数组指针)
// 6. int (*funcPtr)(int, int);
// funcPtr是一个指针,指向一个函数,该函数接受两个int参数,返回int
// 7. int &ref = var;
// ref是一个引用,引用int
// 8. int *&refPtr = p1;
// refPtr是一个引用,引用int*(指针的引用)
// 9. const int *ptr1 = &var;
// ptr1是一个指针,指向const int(常量指针)
// 10. int *const ptr2 = &var;
// ptr2是一个const指针,指向int(指针常量)
// 11. const int *const ptr3 = &var;
// ptr3是一个const指针,指向const int
// 右左法则:从变量名开始,先向右看,再向左看
// 例如:int (*ptrArr)[5];
// 1. ptrArr是一个指针(*ptrArr)
// 2. 指向一个数组((*ptrArr)[5])
// 3. 数组元素是int
return 0;
}
// 例题2:实现字符串操作函数
#include <iostream>
#include <cstring>
using namespace std;
// 使用指针实现字符串操作
int myStrlen(const char *str) {
const char *p = str;
while (*p != '\0') {
p++;
}
return p - str;
}
void myStrcpy(char *dest, const char *src) {
while ((*dest++ = *src++) != '\0');
}
int myStrcmp(const char *str1, const char *str2) {
while (*str1 && (*str1 == *str2)) {
str1++;
str2++;
}
return *(unsigned char*)str1 - *(unsigned char*)str2;
}
void myStrcat(char *dest, const char *src) {
// 移动到dest的末尾
while (*dest) dest++;
// 复制src
while ((*dest++ = *src++) != '\0');
}
int main() {
char str1[100] = "Hello";
char str2[100] = "World";
cout << "str1长度: " << myStrlen(str1) << endl;
myStrcpy(str1, str2);
cout << "复制后str1: " << str1 << endl;
cout << "比较str1和str2: " << myStrcmp(str1, str2) << endl;
myStrcat(str1, "!!!");
cout << "连接后str1: " << str1 << endl;
return 0;
}
// 例题3:动态数组类(模拟vector)
#include <iostream>
using namespace std;
class DynamicArray {
private:
int *data;
int size;
int capacity;
void resize(int newCapacity) {
int *newData = new int[newCapacity];
for (int i = 0; i < size; i++) {
newData[i] = data[i];
}
delete[] data;
data = newData;
capacity = newCapacity;
}
public:
DynamicArray(int initialCapacity = 10) {
capacity = initialCapacity;
size = 0;
data = new int[capacity];
}
~DynamicArray() {
delete[] data;
}
// 拷贝构造函数(深拷贝)
DynamicArray(const DynamicArray &other) {
capacity = other.capacity;
size = other.size;
data = new int[capacity];
for (int i = 0; i < size; i++) {
data[i] = other.data[i];
}
}
// 拷贝赋值运算符(深拷贝)
DynamicArray& operator=(const DynamicArray &other) {
if (this != &other) {
delete[] data;
capacity = other.capacity;
size = other.size;
data = new int[capacity];
for (int i = 0; i < size; i++) {
data[i] = other.data[i];
}
}
return *this;
}
void push_back(int value) {
if (size == capacity) {
resize(capacity * 2);
}
data[size++] = value;
}
int& operator[](int index) {
if (index < 0 || index >= size) {
throw out_of_range("索引越界");
}
return data[index];
}
const int& operator[](int index) const {
if (index < 0 || index >= size) {
throw out_of_range("索引越界");
}
return data[index];
}
int getSize() const { return size; }
int getCapacity() const { return capacity; }
void print() const {
for (int i = 0; i < size; i++) {
cout << data[i] << " ";
}
cout << endl;
}
};
int main() {
DynamicArray arr;
for (int i = 0; i < 20; i++) {
arr.push_back(i * 2);
}
cout << "数组元素: ";
arr.print();
cout << "大小: " << arr.getSize() << endl;
cout << "容量: " << arr.getCapacity() << endl;
// 测试拷贝
DynamicArray arr2 = arr;
arr2[0] = 100;
cout << "原数组: ";
arr.print();
cout << "拷贝数组: ";
arr2.print();
return 0;
}
本章小结
重点回顾
- 指针基础:指针的概念、运算、常量指针与指针常量
- 高级指针:二级指针、指针数组、数组指针、动态内存分配
- 智能指针:unique_ptr、shared_ptr、weak_ptr的使用
- 引用:引用的概念、与指针对比、引用传递
- 常见问题:悬空指针、野指针、内存泄漏
易错易混淆点
- 指针与引用的区别:引用必须初始化、不能为空、不能改变指向
- 指针常量与常量指针:const在*左边还是右边
- 数组指针与指针数组:
int (*ptr)[5]vsint *ptr[5] - 动态内存管理:new/delete必须配对,避免内存泄漏
- 智能指针所有权:unique_ptr独占,shared_ptr共享
考研笔试常见题型
- 选择题:考察指针和引用的基本概念和区别
- 程序阅读题:分析指针运算、动态内存管理的代码
- 程序填空题:补全指针操作、内存分配的代码
- 编程题:实现字符串操作、动态数据结构等
学习建议
- 多画内存图:理解指针指向和内存布局
- 多写代码:实践指针和引用的各种用法
- 使用智能指针:现代C++中优先使用智能指针
- 注意安全:避免悬空指针、野指针和内存泄漏
6. 类与对象(面向对象)
├── 6.1 类的基本概念
│ ├── 类定义
│ ├── 对象创建与使用
│ ├── 访问控制
│ │ ├── public
│ │ ├── private
│ │ └── protected
│ └── 类与结构体对比
├── 6.2 成员函数
│ ├── 成员函数定义
│ ├── const成员函数
│ ├── 内联成员函数
│ ├── 静态成员函数
│ └── 友元函数
├── 6.3 构造函数与析构函数
│ ├── 构造函数
│ │ ├── 默认构造函数
│ │ ├── 带参构造函数
│ │ ├── 拷贝构造函数(深/浅拷贝)
│ │ ├── 委托构造函数(C++11)
│ │ └── 转换构造函数
│ ├── 析构函数
│ ├── 构造函数初始化列表
│ └── explicit关键字
├── 6.4 特殊成员
│ ├── 静态成员变量
│ ├── const成员变量
│ ├── mutable成员变量
│ └── 友元类
├── 6.5 this指针
│ └── this指针的应用
└── 6.6 对象数组与对象指针
类是C++面向对象编程的核心,理解类的概念和用法是掌握C++的关键。本章将深入讲解类与对象的所有重要概念。
6.1 类的基本概念
6.1.1 类与结构体的区别
// 示例6.1:类的基本概念
#include <iostream>
#include <string>
using namespace std;
// 1. 使用class关键字定义类
class Student {
// 访问控制修饰符
private: // 私有成员,只能被类内部的成员函数访问
string name;
int age;
float score;
public: // 公有成员,可以在类外部访问
// 成员函数(方法)
void setInfo(string n, int a, float s) {
name = n;
age = a;
score = s;
}
void display() {
cout << "姓名: " << name << endl;
cout << "年龄: " << age << endl;
cout << "成绩: " << score << endl;
}
// 内联成员函数
float getScore() { return score; }
// 常量成员函数:不会修改对象的状态
void printInfo() const {
// name = "张三"; // 错误!const成员函数不能修改成员变量
cout << "姓名: " << name << ", 成绩: " << score << endl;
}
};
// 2. 使用struct关键字定义类(默认成员是public)
struct Point {
// struct成员默认是public
int x;
int y;
void set(int xVal, int yVal) {
x = xVal;
y = yVal;
}
};
int main() {
// 创建对象
Student stu1; // 栈上分配
// 访问公有成员函数
stu1.setInfo("张三", 20, 85.5);
stu1.display();
// 不能直接访问私有成员
// stu1.name = "李四"; // 错误!name是私有成员
// 使用struct创建对象
Point p1;
p1.x = 10; // struct成员默认public,可以直接访问
p1.y = 20;
cout << "点坐标: (" << p1.x << ", " << p1.y << ")" << endl;
// 3. 动态创建对象
Student* stu2 = new Student(); // 堆上分配
stu2->setInfo("李四", 22, 90.0);
stu2->display();
delete stu2; // 必须手动释放
return 0;
}
重要概念:
- class vs struct:
- class默认成员是private,struct默认成员是public
- 除此之外,两者在C++中几乎没有区别
- 习惯上:class用于数据+操作,struct用于纯数据
- 访问控制:
- private:只能被类的成员函数访问
- public:可以被任何代码访问
- protected:在继承中使用,后面会讲
6.1.2 类的封装
// 示例6.2:类的封装
#include <iostream>
#include <string>
using namespace std;
// 封装的银行账户类
class BankAccount {
private:
string accountNumber; // 账号
string ownerName; // 户主姓名
double balance; // 余额
// 私有辅助函数
bool isValidAmount(double amount) {
return amount > 0;
}
public:
// 设置账户信息
void setAccount(string accNum, string owner, double initialBalance) {
if (initialBalance < 0) {
cout << "初始余额不能为负数!" << endl;
return;
}
accountNumber = accNum;
ownerName = owner;
balance = initialBalance;
}
// 存款
bool deposit(double amount) {
if (!isValidAmount(amount)) {
cout << "存款金额必须为正数!" << endl;
return false;
}
balance += amount;
cout << "成功存款 " << amount << " 元" << endl;
return true;
}
// 取款
bool withdraw(double amount) {
if (!isValidAmount(amount)) {
cout << "取款金额必须为正数!" << endl;
return false;
}
if (amount > balance) {
cout << "余额不足!" << endl;
return false;
}
balance -= amount;
cout << "成功取款 " << amount << " 元" << endl;
return true;
}
// 获取余额(只读)
double getBalance() const {
return balance;
}
// 显示账户信息
void displayAccount() const {
cout << "账户信息:" << endl;
cout << "账号: " << accountNumber << endl;
cout << "户主: " << ownerName << endl;
cout << "余额: " << balance << " 元" << endl;
}
};
int main() {
BankAccount account;
// 初始化账户
account.setAccount("6228480010123456789", "张三", 1000.0);
// 存款和取款
account.deposit(500.0);
account.withdraw(200.0);
// 尝试取款超过余额
account.withdraw(2000.0);
// 显示账户信息
account.displayAccount();
// 不能直接访问私有成员
// account.balance = 1000000; // 错误!
return 0;
}
封装的好处:
- 数据保护:防止外部代码直接修改数据
- 接口稳定:可以修改内部实现而不影响外部代码
- 易于维护:数据验证和业务逻辑集中在类内部
6.2 构造函数与析构函数
6.2.1 构造函数
// 示例6.3:构造函数
#include <iostream>
#include <string>
using namespace std;
class Person {
private:
string name;
int age;
string gender;
public:
// 1. 默认构造函数(无参数)
Person() {
name = "未知";
age = 0;
gender = "未知";
cout << "默认构造函数被调用" << endl;
}
// 2. 带参数的构造函数
Person(string n, int a, string g) {
name = n;
age = a;
gender = g;
cout << "带参数的构造函数被调用" << endl;
}
// 3. 构造函数初始化列表(推荐方式)
Person(string n, int a) : name(n), age(a), gender("未知") {
// 成员初始化已经在初始化列表中完成
cout << "使用初始化列表的构造函数被调用" << endl;
}
// 4. 拷贝构造函数
Person(const Person& other) {
name = other.name;
age = other.age;
gender = other.gender;
cout << "拷贝构造函数被调用" << endl;
}
// 5. 委托构造函数(C++11)
Person(string n) : Person(n, 0, "未知") {
cout << "委托构造函数被调用" << endl;
}
// 成员函数
void display() const {
cout << "姓名: " << name << ", 年龄: " << age << ", 性别: " << gender << endl;
}
// 设置年龄,包含数据验证
void setAge(int a) {
if (a >= 0 && a <= 150) {
age = a;
} else {
cout << "无效的年龄!" << endl;
}
}
};
int main() {
cout << "=== 构造函数演示 ===" << endl;
// 调用默认构造函数
Person p1;
p1.display();
// 调用带参数的构造函数
Person p2("张三", 25, "男");
p2.display();
// 调用使用初始化列表的构造函数
Person p3("李四", 30);
p3.display();
// 调用拷贝构造函数
Person p4 = p2; // 等价于 Person p4(p2);
p4.display();
// 调用委托构造函数
Person p5("王五");
p5.display();
// 动态创建对象
Person* p6 = new Person("赵六", 35, "男");
p6->display();
delete p6;
// 2. 构造函数与explicit关键字
cout << "\n=== explicit关键字演示 ===" << endl;
class Test {
int value;
public:
// 没有explicit,允许隐式转换
Test(int v) : value(v) {
cout << "Test构造函数: " << value << endl;
}
};
Test t1 = 100; // 隐式转换:100 -> Test(100)
class TestExplicit {
int value;
public:
// 使用explicit,禁止隐式转换
explicit TestExplicit(int v) : value(v) {
cout << "TestExplicit构造函数: " << value << endl;
}
};
TestExplicit t2(200); // 正确,显式调用
// TestExplicit t3 = 300; // 错误!不能隐式转换
return 0;
}
构造函数的重要特点:
- 与类同名,没有返回类型
- 可以重载:可以有多个构造函数
- 创建对象时自动调用,不能显式调用
- 初始化列表:更高效,某些情况必须使用
6.2.2 析构函数
// 示例6.4:析构函数
#include <iostream>
#include <cstring>
using namespace std;
class String {
private:
char* data;
int length;
public:
// 构造函数
String(const char* str = "") {
length = strlen(str);
data = new char[length + 1]; // +1 用于存储'\0'
strcpy(data, str);
cout << "构造函数: " << data << endl;
}
// 拷贝构造函数(深拷贝)
String(const String& other) {
length = other.length;
data = new char[length + 1];
strcpy(data, other.data);
cout << "拷贝构造函数: " << data << endl;
}
// 析构函数
~String() {
cout << "析构函数: " << data << endl;
delete[] data; // 释放动态分配的内存
}
// 显示字符串
void display() const {
cout << "字符串: " << data << " (长度: " << length << ")" << endl;
}
// 获取长度
int getLength() const { return length; }
};
void testFunction() {
cout << "\n进入testFunction..." << endl;
String localStr("局部字符串");
localStr.display();
cout << "离开testFunction..." << endl;
// localStr的析构函数会被自动调用
}
int main() {
cout << "=== 析构函数演示 ===" << endl;
// 1. 栈上对象的析构
{
cout << "\n进入代码块..." << endl;
String str1("Hello");
str1.display();
cout << "离开代码块..." << endl;
// str1的析构函数会被调用
}
// 2. 函数中对象的析构
testFunction();
// 3. 动态分配对象的析构
cout << "\n动态分配对象..." << endl;
String* pStr = new String("动态字符串");
pStr->display();
delete pStr; // 必须手动调用delete,否则析构函数不会被调用
// 4. 对象数组的析构
cout << "\n对象数组..." << endl;
String arr[3] = {String("第一个"), String("第二个"), String("第三个")};
// 5. 深拷贝与浅拷贝问题
cout << "\n=== 深拷贝与浅拷贝 ===" << endl;
String s1("原始字符串");
// 如果没有定义拷贝构造函数,编译器会生成默认的拷贝构造函数(浅拷贝)
// 浅拷贝:只复制指针,不复制指向的内容
// 深拷贝:复制指针指向的内容
String s2 = s1; // 使用我们定义的拷贝构造函数(深拷贝)
s2.display();
return 0;
// main函数结束时,所有栈上对象的析构函数会被调用
}
析构函数的重要特点:
- ~类名(),没有参数,没有返回类型
- 对象销毁时自动调用,不能显式调用
- 用于释放资源:如动态内存、文件句柄、网络连接等
- 可以虚析构函数:用于多态,后面会讲
6.2.3 构造函数初始化列表
// 示例6.5:构造函数初始化列表
#include <iostream>
using namespace std;
class Point {
private:
const int id; // const成员,必须在初始化列表中初始化
int& ref; // 引用成员,必须在初始化列表中初始化
int x;
int y;
public:
// 初始化列表是唯一初始化const和引用成员的地方
Point(int i, int& r, int xVal, int yVal)
: id(i), ref(r), x(xVal), y(yVal) {
// 构造函数体
cout << "Point构造函数" << endl;
}
// 错误:不能在构造函数体内初始化const和引用成员
// Point(int i, int& r, int xVal, int yVal) {
// id = i; // 错误!const成员不能赋值
// ref = r; // 错误!引用必须在定义时初始化
// x = xVal;
// y = yVal;
// }
void display() const {
cout << "ID: " << id << ", 引用: " << ref
<< ", 坐标: (" << x << ", " << y << ")" << endl;
}
// 初始化列表中的初始化顺序
// 注意:初始化顺序只与成员声明的顺序有关,与初始化列表中的顺序无关
// 以下示例展示初始化顺序问题
class BadExample {
int a;
int b;
public:
// 虽然初始化列表中b在a前面,但实际初始化顺序是a先于b
BadExample(int val) : b(val), a(b * 2) { // 危险!a使用未初始化的b
cout << "a = " << a << ", b = " << b << endl;
}
};
// 正确做法:按声明顺序初始化
class GoodExample {
int a;
int b;
public:
GoodExample(int val) : a(val * 2), b(val) { // 正确
cout << "a = " << a << ", b = " << b << endl;
}
};
};
class Complex {
private:
double real;
double imag;
public:
// 多个构造函数使用初始化列表
Complex() : real(0), imag(0) {} // 默认构造函数
Complex(double r) : real(r), imag(0) {} // 转换构造函数
Complex(double r, double i) : real(r), imag(i) {} // 带两个参数
// 使用初始化列表调用成员对象的构造函数
class Line {
Point start;
Point end;
public:
Line(const Point& s, const Point& e)
: start(s), end(e) { // 调用Point的拷贝构造函数
cout << "Line构造函数" << endl;
}
};
void display() const {
cout << real << " + " << imag << "i" << endl;
}
};
int main() {
cout << "=== 构造函数初始化列表演示 ===" << endl;
int value = 10;
Point p(1, value, 3, 4);
p.display();
// 修改引用的原始值
value = 20;
p.display(); // ref也会改变
// 初始化顺序问题演示
cout << "\n初始化顺序问题:" << endl;
cout << "BadExample:" << endl;
Point::BadExample bad(10); // 输出未定义的值
cout << "\nGoodExample:" << endl;
Point::GoodExample good(10); // 输出正确的值
// Complex类演示
cout << "\nComplex类演示:" << endl;
Complex c1;
Complex c2(3.5);
Complex c3(2.0, 4.5);
c1.display();
c2.display();
c3.display();
return 0;
}
初始化列表的重要规则:
- 必须使用初始化列表:
- const成员
- 引用成员
- 没有默认构造函数的类类型成员
- 初始化顺序:
- 按照成员声明的顺序初始化
- 与初始化列表中的顺序无关
- 初始化列表的优势:
- 效率更高(避免先默认构造再赋值)
- 某些情况必须使用
6.3 特殊成员
6.3.1 静态成员
// 示例6.6:静态成员
#include <iostream>
using namespace std;
class Student {
private:
string name;
int score;
// 静态成员变量:属于类,不属于任何一个对象
static int totalStudents; // 静态成员变量声明
static float totalScore; // 所有学生的总成绩
public:
Student(string n, int s) : name(n), score(s) {
totalStudents++; // 每创建一个学生,计数加1
totalScore += score; // 累加成绩
}
~Student() {
totalStudents--; // 学生对象销毁时计数减1
totalScore -= score; // 减去成绩
}
// 静态成员函数:只能访问静态成员
static int getTotalStudents() {
// name = "test"; // 错误!不能访问非静态成员
return totalStudents;
}
static float getAverageScore() {
if (totalStudents == 0) return 0;
return totalScore / totalStudents;
}
// 显示学生信息
void display() const {
cout << "姓名: " << name << ", 成绩: " << score << endl;
}
// 静态常量成员
static const int MAX_SCORE = 100; // 可以在类内初始化
static const int MIN_SCORE; // 也可以在类外初始化
};
// 静态成员变量必须在类外定义和初始化
int Student::totalStudents = 0; // 定义并初始化
float Student::totalScore = 0.0f; // 定义并初始化
const int Student::MIN_SCORE = 0; // 静态常量成员在类外初始化
// 静态成员的应用:单例模式
class Singleton {
private:
static Singleton* instance; // 静态成员,指向唯一实例
int data;
// 私有构造函数,防止外部创建对象
Singleton() : data(0) {
cout << "Singleton创建" << endl;
}
// 私有拷贝构造函数和赋值运算符,防止拷贝
Singleton(const Singleton&) = delete;
Singleton& operator=(const Singleton&) = delete;
public:
// 静态成员函数获取唯一实例
static Singleton* getInstance() {
if (instance == nullptr) {
instance = new Singleton();
}
return instance;
}
// 静态成员函数释放实例
static void destroyInstance() {
if (instance != nullptr) {
delete instance;
instance = nullptr;
}
}
void setData(int value) { data = value; }
int getData() const { return data; }
void display() const {
cout << "Singleton data: " << data << endl;
}
};
// 初始化静态成员
Singleton* Singleton::instance = nullptr;
// 静态成员在模板类中的应用
template<typename T>
class Container {
private:
T value;
public:
// 每个模板实例化都有自己的静态成员
static int count; // 每种类型的Container有自己的count
Container(T v) : value(v) {
count++;
}
~Container() {
count--;
}
static int getCount() {
return count;
}
T getValue() const { return value; }
};
// 模板类的静态成员初始化
template<typename T>
int Container<T>::count = 0;
int main() {
cout << "=== 静态成员演示 ===" << endl;
// 访问静态成员
cout << "最大成绩: " << Student::MAX_SCORE << endl;
cout << "最小成绩: " << Student::MIN_SCORE << endl;
cout << "初始学生数: " << Student::getTotalStudents() << endl;
// 创建学生对象
Student s1("张三", 85);
Student s2("李四", 92);
Student s3("王五", 78);
s1.display();
s2.display();
s3.display();
// 通过对象访问静态成员函数
cout << "\n当前学生数: " << s1.getTotalStudents() << endl;
cout << "平均成绩: " << Student::getAverageScore() << endl;
// 通过类名访问静态成员函数
cout << "通过类名访问 - 学生数: " << Student::getTotalStudents() << endl;
// 销毁一个学生
{
Student s4("赵六", 88);
cout << "创建赵六后学生数: " << Student::getTotalStudents() << endl;
} // s4离开作用域,析构函数被调用
cout << "赵六销毁后学生数: " << Student::getTotalStudents() << endl;
// 单例模式演示
cout << "\n=== 单例模式演示 ===" << endl;
Singleton* singleton = Singleton::getInstance();
singleton->setData(100);
singleton->display();
// 再次获取实例,应该是同一个
Singleton* sameInstance = Singleton::getInstance();
sameInstance->setData(200);
singleton->display(); // data变为200
// 释放实例
Singleton::destroyInstance();
// 模板类的静态成员演示
cout << "\n=== 模板类的静态成员 ===" << endl;
Container<int> intContainer1(10);
Container<int> intContainer2(20);
Container<double> doubleContainer1(3.14);
Container<double> doubleContainer2(2.71);
cout << "int Container数量: " << Container<int>::getCount() << endl;
cout << "double Container数量: " << Container<double>::getCount() << endl;
return 0;
}
静态成员的特点:
- 属于类,不属于对象:所有对象共享同一份静态成员
- 必须在类外定义和初始化(除了静态常量整数成员)
- 静态成员函数:
- 不能访问非静态成员
- 不能使用this指针
- 可以通过对象或类名调用
6.3.2 const和mutable成员
// 示例6.7:const和mutable成员
#include <iostream>
using namespace std;
class Account {
private:
string owner;
mutable double balance; // mutable成员,即使在const成员函数中也可以修改
const int accountId; // const成员,创建后不能修改
double interestRate;
public:
Account(string o, int id, double initial)
: owner(o), accountId(id), balance(initial), interestRate(0.03) {
}
// const成员函数:承诺不修改对象状态
void display() const {
cout << "账户ID: " << accountId << endl;
cout << "户主: " << owner << endl;
cout << "余额: " << balance << endl;
// owner = "test"; // 错误!const成员函数不能修改成员
}
// 非const成员函数
void deposit(double amount) {
balance += amount;
cout << "存款 " << amount << " 成功" << endl;
}
// 重载:const和非const版本
// const版本
double getBalance() const {
cout << "调用const版本的getBalance" << endl;
return balance;
}
// 非const版本
double getBalance() {
cout << "调用非const版本的getBalance" << endl;
return balance;
}
// mutable成员可以在const函数中修改
void addInterest() const {
// 虽然这是const函数,但可以修改mutable成员
double interest = balance * interestRate;
balance += interest; // 可以修改mutable成员
cout << "添加利息: " << interest << endl;
}
// 不能修改const成员
// void changeId(int newId) {
// accountId = newId; // 错误!const成员不能修改
// }
};
// const对象
void processConstAccount(const Account& acc) {
// acc是const引用,只能调用const成员函数
acc.display();
cout << "余额: " << acc.getBalance() << endl; // 调用const版本
// acc.deposit(100); // 错误!不能调用非const成员函数
acc.addInterest(); // 可以调用,虽然它修改了mutable成员
}
int main() {
cout << "=== const和mutable成员演示 ===" << endl;
Account acc("张三", 1001, 1000.0);
// 调用非const版本的getBalance
cout << "初始余额: " << acc.getBalance() << endl;
// 存款
acc.deposit(500.0);
// 处理const引用
cout << "\n处理const引用:" << endl;
processConstAccount(acc);
// 调用const版本的getBalance
cout << "\n通过const对象调用:" << endl;
const Account& constRef = acc;
cout << "余额: " << constRef.getBalance() << endl; // 调用const版本
// const对象
const Account constAcc("李四", 1002, 2000.0);
constAcc.display();
// constAcc.deposit(100); // 错误!const对象不能调用非const成员函数
return 0;
}
const成员的重要规则:
- const成员函数:
- 不能修改成员变量(mutable除外)
- 只能调用其他const成员函数
- const对象只能调用const成员函数
- mutable成员:
- 可以在const成员函数中被修改
- 用于表示与对象逻辑状态无关的物理状态
- 函数重载:
- const和非const版本的成员函数可以重载
- 根据对象的const性质调用相应版本
6.3.3 友元
// 示例6.8:友元
#include <iostream>
#include <cmath>
using namespace std;
class Point; // 前向声明
// 友元函数:计算两点距离
double distance(const Point& p1, const Point& p2);
class Point {
private:
double x, y;
// 私有辅助函数
double square(double val) const {
return val * val;
}
public:
Point(double xVal = 0, double yVal = 0) : x(xVal), y(yVal) {}
// 友元函数声明
friend double distance(const Point& p1, const Point& p2);
// 友元类声明
friend class PointTransformer;
// 友元成员函数
friend void displayPoint(const Point& p);
// 普通成员函数
void display() const {
cout << "(" << x << ", " << y << ")";
}
// 获取私有成员(通常不需要友元,用getter即可)
double getX() const { return x; }
double getY() const { return y; }
};
// 友元类
class PointTransformer {
public:
// 可以访问Point的私有成员
static void translate(Point& p, double dx, double dy) {
p.x += dx; // 直接访问私有成员
p.y += dy;
}
static void rotate(Point& p, double angle) {
double newX = p.x * cos(angle) - p.y * sin(angle);
double newY = p.x * sin(angle) + p.y * cos(angle);
p.x = newX;
p.y = newY;
}
};
// 友元函数定义
double distance(const Point& p1, const Point& p2) {
// 可以直接访问Point的私有成员
return sqrt(p1.square(p1.x - p2.x) + p1.square(p1.y - p2.y));
}
// 友元成员函数
void displayPoint(const Point& p) {
// 可以访问Point的私有成员
cout << "点坐标: (" << p.x << ", " << p.y << ")" << endl;
}
// 非友元函数,不能访问私有成员
// double badDistance(const Point& p1, const Point& p2) {
// return sqrt((p1.x - p2.x)*(p1.x - p2.x) +
// (p1.y - p2.y)*(p1.y - p2.y)); // 错误!不能访问私有成员
// }
int main() {
cout << "=== 友元演示 ===" << endl;
Point p1(3, 4);
Point p2(0, 0);
cout << "点p1: ";
p1.display();
cout << endl;
cout << "点p2: ";
p2.display();
cout << endl;
// 使用友元函数计算距离
cout << "两点距离: " << distance(p1, p2) << endl;
// 使用友元成员函数
displayPoint(p1);
// 使用友元类
cout << "\n平移点p1 (dx=2, dy=3):" << endl;
PointTransformer::translate(p1, 2, 3);
p1.display();
cout << endl;
cout << "\n旋转点p1 90度:" << endl;
PointTransformer::rotate(p1, 3.14159/2); // 90度
p1.display();
cout << endl;
// 通过getter访问私有成员
cout << "\n通过getter访问:" << endl;
cout << "p1.x = " << p1.getX() << ", p1.y = " << p1.getY() << endl;
return 0;
}
友元的重要规则:
- 友元不是成员:友元函数或友元类不是类的成员
- 破坏封装性:友元可以访问类的私有成员,谨慎使用
- 单向性:友元关系不是双向的,也不是传递的
- 使用场景:
- 运算符重载(特别是需要访问私有成员的运算符)
- 某些需要高效访问私有成员的函数
- 紧密协作的类之间
6.4 this指针
// 示例6.9:this指针
#include <iostream>
#include <string>
using namespace std;
class Person {
private:
string name;
int age;
public:
// 构造函数中使用this区分参数和成员变量
Person(string name, int age) {
this->name = name; // this->name是成员变量,name是参数
this->age = age;
}
// 返回对象自身的引用,支持链式调用
Person& setName(string name) {
this->name = name;
return *this; // 返回当前对象的引用
}
Person& setAge(int age) {
this->age = age;
return *this; // 返回当前对象的引用
}
// 链式调用
Person& birthday() {
age++;
return *this;
}
void display() const {
cout << "姓名: " << name << ", 年龄: " << age << endl;
}
// 比较两个对象
bool isSamePerson(const Person& other) const {
// this指针指向调用该成员函数的对象
return (this == &other); // 比较地址
}
// 返回当前对象的指针
Person* getThis() {
return this;
}
// 静态成员函数没有this指针
static void test() {
// cout << name << endl; // 错误!不能访问非静态成员
// this->name = "test"; // 错误!静态函数没有this指针
}
};
int main() {
cout << "=== this指针演示 ===" << endl;
Person p1("张三", 20);
Person p2("李四", 25);
p1.display();
p2.display();
// 链式调用
cout << "\n链式调用:" << endl;
p1.setName("张三丰").setAge(100).birthday();
p1.display();
// 比较对象
cout << "\n比较对象:" << endl;
cout << "p1和p2是同一个对象吗?"
<< (p1.isSamePerson(p2) ? "是" : "否") << endl;
Person& ref = p1;
cout << "p1和ref是同一个对象吗?"
<< (p1.isSamePerson(ref) ? "是" : "否") << endl;
// 获取this指针
cout << "\n获取this指针:" << endl;
Person* p1Ptr = p1.getThis();
cout << "p1的地址: " << &p1 << endl;
cout << "p1.getThis(): " << p1Ptr << endl;
// this指针在运算符重载中的应用(后面会详细讲)
return 0;
}
this指针的重要特点:
- 隐含参数:每个非静态成员函数都有一个隐含的this指针参数
- 指向当前对象:指向调用该成员函数的对象
- 不能修改:this是常量指针(如
Person* const this) - 不能用于静态函数:静态函数没有this指针
6.5 对象数组与对象指针
// 示例6.10:对象数组与对象指针
#include <iostream>
#include <vector>
using namespace std;
class Student {
private:
string name;
int score;
public:
// 默认构造函数(必须有,用于创建数组)
Student() : name("未命名"), score(0) {
cout << "调用默认构造函数" << endl;
}
Student(string n, int s) : name(n), score(s) {
cout << "调用带参数构造函数" << endl;
}
// 拷贝构造函数
Student(const Student& other) : name(other.name), score(other.score) {
cout << "调用拷贝构造函数" << endl;
}
void display() const {
cout << "学生: " << name << ", 成绩: " << score << endl;
}
void setName(string n) { name = n; }
void setScore(int s) { score = s; }
};
int main() {
cout << "=== 对象数组演示 ===" << endl;
// 1. 对象数组
// 调用默认构造函数3次
Student students1[3];
cout << "\nstudents1数组:" << endl;
for (int i = 0; i < 3; i++) {
students1[i].display();
}
// 2. 使用初始化列表
Student students2[3] = {
Student("张三", 85), // 调用带参数构造函数
Student("李四", 92), // 调用带参数构造函数
Student() // 调用默认构造函数
};
cout << "\nstudents2数组:" << endl;
for (int i = 0; i < 3; i++) {
students2[i].display();
}
// 3. 动态对象数组
cout << "\n动态对象数组:" << endl;
int n = 3;
Student* dynamicStudents = new Student[n]; // 调用默认构造函数n次
// 初始化动态数组
for (int i = 0; i < n; i++) {
dynamicStudents[i].setName("学生" + to_string(i+1));
dynamicStudents[i].setScore(70 + i * 5);
}
for (int i = 0; i < n; i++) {
dynamicStudents[i].display();
}
delete[] dynamicStudents; // 调用每个对象的析构函数
// 4. 对象指针数组
cout << "\n对象指针数组:" << endl;
Student* ptrArray[3];
// 动态创建对象
ptrArray[0] = new Student("王五", 88);
ptrArray[1] = new Student("赵六", 95);
ptrArray[2] = new Student("孙七", 76);
for (int i = 0; i < 3; i++) {
ptrArray[i]->display();
}
// 释放内存
for (int i = 0; i < 3; i++) {
delete ptrArray[i];
}
// 5. vector存储对象
cout << "\nvector存储对象:" << endl;
vector<Student> studentVector;
// 添加对象(会调用拷贝构造函数)
studentVector.push_back(Student("周八", 82));
studentVector.push_back(Student("吴九", 91));
cout << "vector中的学生:" << endl;
for (const auto& stu : studentVector) {
stu.display();
}
// 6. 指向对象的指针
cout << "\n指向对象的指针:" << endl;
Student stu("郑十", 79);
Student* pStu = &stu;
// 通过指针访问成员
pStu->display(); // 等价于 (*pStu).display()
(*pStu).display(); // 等价于 pStu->display()
// 修改对象
pStu->setScore(85);
stu.display();
return 0;
}
综合例题与解析
// 例题1:实现一个简单的字符串类
#include <iostream>
#include <cstring>
using namespace std;
class MyString {
private:
char* data;
int length;
// 私有辅助函数
void copyString(const char* str) {
if (str == nullptr) {
data = new char[1];
data[0] = '\0';
length = 0;
} else {
length = strlen(str);
data = new char[length + 1];
strcpy(data, str);
}
}
public:
// 构造函数
MyString() {
copyString("");
}
MyString(const char* str) {
copyString(str);
}
// 拷贝构造函数
MyString(const MyString& other) {
copyString(other.data);
}
// 析构函数
~MyString() {
delete[] data;
}
// 拷贝赋值运算符
MyString& operator=(const MyString& other) {
if (this != &other) { // 防止自我赋值
delete[] data; // 释放原有内存
copyString(other.data);
}
return *this;
}
// 获取长度
int getLength() const {
return length;
}
// 获取C风格字符串
const char* c_str() const {
return data;
}
// 拼接字符串
MyString concat(const MyString& other) const {
char* newStr = new char[length + other.length + 1];
strcpy(newStr, data);
strcat(newStr, other.data);
MyString result(newStr);
delete[] newStr;
return result;
}
// 重载+运算符(简洁写法)
MyString operator+(const MyString& other) const {
return concat(other);
}
// 重载+=运算符
MyString& operator+=(const MyString& other) {
char* newStr = new char[length + other.length + 1];
strcpy(newStr, data);
strcat(newStr, other.data);
delete[] data;
data = newStr;
length += other.length;
return *this;
}
// 重载==运算符
bool operator==(const MyString& other) const {
return strcmp(data, other.data) == 0;
}
// 重载[]运算符
char& operator[](int index) {
if (index < 0 || index >= length) {
throw out_of_range("索引越界");
}
return data[index];
}
// const版本的[]运算符
const char& operator[](int index) const {
if (index < 0 || index >= length) {
throw out_of_range("索引越界");
}
return data[index];
}
// 输出运算符重载(需要友元)
friend ostream& operator<<(ostream& os, const MyString& str);
// 输入运算符重载(需要友元)
friend istream& operator>>(istream& is, MyString& str);
};
// 输出运算符重载实现
ostream& operator<<(ostream& os, const MyString& str) {
os << str.data;
return os;
}
// 输入运算符重载实现
istream& operator>>(istream& is, MyString& str) {
char buffer[1024];
is >> buffer;
str = MyString(buffer);
return is;
}
int main() {
cout << "=== 自定义字符串类演示 ===" << endl;
// 测试构造函数
MyString s1;
MyString s2("Hello");
MyString s3 = "World"; // 隐式转换
cout << "s1: " << s1 << " (长度: " << s1.getLength() << ")" << endl;
cout << "s2: " << s2 << " (长度: " << s2.getLength() << ")" << endl;
cout << "s3: " << s3 << " (长度: " << s3.getLength() << ")" << endl;
// 测试拷贝构造函数
MyString s4 = s2; // 调用拷贝构造函数
cout << "\n拷贝后 s4: " << s4 << endl;
// 测试赋值运算符
MyString s5;
s5 = s3; // 调用赋值运算符
cout << "赋值后 s5: " << s5 << endl;
// 测试自我赋值
s5 = s5;
cout << "自我赋值后 s5: " << s5 << endl;
// 测试+运算符
MyString s6 = s2 + " " + s3 + "!";
cout << "\ns2 + \" \" + s3 + \"!\": " << s6 << endl;
// 测试+=运算符
s2 += " ";
s2 += s3;
s2 += "!";
cout << "s2 += \" \" += s3 += \"!\": " << s2 << endl;
// 测试==运算符
cout << "\n比较字符串:" << endl;
cout << "s2 == s6: " << (s2 == s6 ? "true" : "false") << endl;
cout << "s2 == \"Hello World!\": " << (s2 == "Hello World!" ? "true" : "false") << endl;
// 测试[]运算符
cout << "\n访问字符:" << endl;
cout << "s2[0] = " << s2[0] << endl;
cout << "s2[6] = " << s2[6] << endl;
// 修改字符
s2[0] = 'h';
cout << "修改后 s2: " << s2 << endl;
// 测试输入
// cout << "\n请输入一个字符串: ";
// MyString s7;
// cin >> s7;
// cout << "你输入的是: " << s7 << endl;
return 0;
}
本章小结
重点回顾
- 类的基本概念:class vs struct,访问控制,封装
- 构造函数与析构函数:各种构造函数,初始化列表,深拷贝
- 特殊成员:静态成员,const成员,mutable成员,友元
- this指针:指向当前对象,支持链式调用
- 对象数组与指针:创建和使用对象数组,对象指针
易错易混淆点
- 构造函数初始化顺序:只与成员声明顺序有关
- 深拷贝与浅拷贝:需要动态分配内存时必须定义拷贝构造函数
- const成员函数:不能修改成员(mutable除外)
- 静态成员初始化:必须在类外定义和初始化
- this指针:静态函数没有this指针
考研笔试常见题型
- 选择题:考察类的基本概念和特性
- 程序阅读题:分析类的设计,构造函数调用顺序
- 程序填空题:补全类的成员函数实现
- 编程题:设计并实现类,如字符串类、日期类等
编程实践建议
- 合理设计类:遵循单一职责原则
- 使用初始化列表:提高效率,避免重复初始化
- 管理资源:如果有动态内存,必须定义拷贝构造函数和赋值运算符
- 使用const:尽量将不修改对象的成员函数声明为const
- 限制友元:谨慎使用友元,保持封装性
7. 继承与多态(面向对象的核心)
├── 7.1 继承基础
│ ├── 继承概念
│ ├── 派生类定义
│ ├── 访问控制
│ │ ├── 公有继承
│ │ ├── 保护继承
│ │ └── 私有继承
│ └── 继承中的构造与析构
├── 7.2 继承的扩展
│ ├── 多继承
│ ├── 虚继承
│ ├── 菱形继承问题
│ └── 继承与组合对比
├── 7.3 多态
│ ├── 虚函数
│ ├── 纯虚函数与抽象类
│ ├── 虚函数表(vtable)
│ ├── 重写(override)与重载
│ └── 虚析构函数
├── 7.4 运行时类型识别(RTTI)
│ ├── typeid运算符
│ └── dynamic_cast
└── 7.5 访问控制与继承
├── using改变访问权限
└── final与override(C++11)
下面先完善你的目录(按学习/面试高频点补齐),然后给一份可直接用来背诵与自测的复习文档(标准 Markdown)。
继承与多态复习大纲(完善版)
7.1 继承基础
- 7.1.1 继承概念:is-a / LSP(里氏替换)
- 7.1.2 派生类定义与语法:
class D : public B - 7.1.3 访问控制与继承方式
- 7.1.3.1 成员访问控制:
public/protected/private - 7.1.3.2 继承方式:公有/保护/私有继承对基类成员可见性的影响
- 7.1.3.3
protected的工程含义与风险 - 7.1.4 名字查找与隐藏(Name Hiding)
- 7.1.4.1 派生类同名函数隐藏基类重载集合
- 7.1.4.2
using Base::foo;引入基类重载 - 7.1.5 继承中的构造与析构
- 7.1.5.1 构造顺序:Base→成员→Derived
- 7.1.5.2 析构顺序:Derived→成员→Base
- 7.1.5.3 初始化列表与基类构造选择
- 7.1.5.4 禁止/默认特殊成员:
=delete/=default
7.2 继承的扩展
- 7.2.1 多继承:接口隔离 vs 实现复用
- 7.2.2 歧义与作用域限定:
A::f,B::f - 7.2.3 虚继承(virtual inheritance)
- 7.2.4 菱形继承问题与虚基类初始化规则
- 7.2.5 继承与组合对比(组合优先)
- 7.2.5.1 “能组合就组合”的判断准则
- 7.2.5.2 依赖倒置/接口类(抽象基类)与组合
7.3 多态(运行时多态)
- 7.3.1 多态的条件:基类指针/引用 + 虚函数
- 7.3.2 虚函数(virtual)与动态绑定
- 7.3.3 纯虚函数与抽象类
- 7.3.4 override / final(C++11)
- 7.3.5 重写 vs 重载 vs 隐藏(必考三分法)
- 7.3.6 虚析构函数(virtual destructor)
- 7.3.6.1 为什么必须:
Base* p = new Derived; delete p; - 7.3.6.2 纯虚析构也要有定义
- 7.3.7 对象切片(slicing)与多态拷贝(clone)
- 7.3.8 默认参数与虚函数(默认参数静态绑定的坑)
7.4 运行时类型识别(RTTI)
- 7.4.1
typeid与std::type_info - 7.4.2
dynamic_cast(安全向下转型) - 7.4.2.1 指针失败返回
nullptr - 7.4.2.2 引用失败抛异常
- 7.4.2.3 多继承下的指针调整
- 7.4.3 RTTI 的使用边界:能用虚函数就别用 RTTI 분支
7.5 工程实践与设计建议(总结章)
- 7.5.1 Rule of 0/3/5 与多态类资源管理
- 7.5.2 多态对象的所有权:
unique_ptr<Base> - 7.5.3 接口类(纯抽象类)写法建议
- 7.5.4 禁止误用:基类析构
protected的场景 - 7.5.5 单元测试建议:利用多态注入 mock(了解即可)
继承与多态复习文档(Markdown)
1. 继承基础速记
1.1 is-a 与 LSP
- 公有继承表示 is-a:
Derived能在需要Base的地方使用。 - LSP(里氏替换):子类替换父类不能破坏父类对外承诺。
1.2 继承方式对可见性影响(核心表)
假设基类成员在基类中是:
| 基类成员 | public 继承后 | protected 继承后 | private 继承后 |
|---|---|---|---|
| public | public | protected | private |
| protected | protected | protected | private |
| private | 不可直接访问(始终不可) | 不可直接访问 | 不可直接访问 |
重点:基类的 private 成员无论如何派生类都不能直接访问。
1.3 构造/析构顺序
- 构造:Base → 成员 → Derived
- 析构:Derived → 成员 → Base
构造时基类怎么构造?
- 必须在初始化列表里指定:
Derived() : Base(args...) {}
否则走Base()(如果存在)。
1.4 名字隐藏(Name Hiding)
派生类声明同名函数,会隐藏基类同名函数的所有重载。
struct Base { void f(int); void f(double); };
struct Derived : Base { void f(int); }; // Base::f(double) 被隐藏
修复方式:
struct Derived : Base {
using Base::f; // 引入基类重载集合
void f(int);
};
2. 多态速记
2.1 多态的必要条件
- 用 基类指针/引用 指向派生类对象
- 通过 virtual 函数 调用
Base* p = new Derived;
p->virt(); // 动态绑定,调用 Derived::virt
2.2 override / final
override:要求“确实重写了基类虚函数”,写错签名会编译报错。final:禁止继续被重写/继承。
struct Base { virtual void f() = 0; virtual ~Base() = default; };
struct D final : Base { void f() override {} };
2.3 重写 vs 重载 vs 隐藏(必考)
- 重写 override:发生在继承层次 + 虚函数 + 签名一致
- 重载 overload:同一作用域,同名不同参
- 隐藏 hiding:派生类同名声明导致基类同名集合不可见(与 virtual 无关)
3. 虚析构(最重要的工程规则之一)
3.1 什么时候必须虚析构?
只要你可能这样写:
Base* p = new Derived;
delete p;
那么 Base 必须:
virtual ~Base() = default;
否则 delete p 只会调用 ~Base(),派生类析构不执行 → 资源泄漏/未定义行为风险。
3.2 纯虚析构也要有定义
struct Base {
virtual ~Base() = 0;
};
Base::~Base() = default; // 仍然必须提供定义
4. 对象切片与多态拷贝
4.1 对象切片(slicing)
Base b = Derived{}; // Derived 的那部分被切掉
4.2 多态拷贝(clone 模式)
基类提供:
virtual std::unique_ptr<Base> clone() const = 0;
派生类实现:
std::unique_ptr<Base> clone() const override { return std::make_unique<Derived>(*this); }
5. vtable(虚函数表)你要会说到什么程度
- 有虚函数的类通常会有 vptr(虚表指针)
- 动态绑定通过 vtable 查到实际函数地址
- 这解释了为什么 virtual 析构 能在
delete basePtr时正确找到派生析构
面试时:说“原理大概如此”,别强行绑定具体编译器实现细节。
6. RTTI:typeid 与 dynamic_cast
6.1 typeid
if (typeid(*p) == typeid(Derived)) { ... }
- 需要多态类型(通常基类要有虚函数)才能得到“动态类型”。
6.2 dynamic_cast
if (auto* d = dynamic_cast<Derived*>(p)) {
d->onlyDerived();
}
- 指针转换失败返回
nullptr - 引用转换失败抛
std::bad_cast
6.3 工程建议
- 优先用虚函数表达行为多态
- RTTI 多用于:解析外部数据、插件边界、调试/日志、确实需要类型分支时
7. 多继承 / 虚继承 / 菱形继承要点
7.1 多继承风险点
- 二义性:同名成员来自多个基类
- 指针调整:
Base1*/Base2*指针值可能不同(同一对象不同子对象地址)
7.2 菱形继承(Diamond)
A
/ \
B C
\ /
D
- 如果不虚继承:D 里会有两份 A 子对象
- 虚继承:保证只有一份 A 子对象,但构造规则更复杂(最派生类负责初始化虚基类)
8. 工程实践总结(你可以当作“答题模板”)
- 多态基类一定 virtual 析构
- 资源管理优先 RAII(Rule of 0),少写裸
new/delete - 用
override/final降低维护成本 - 警惕:名字隐藏、对象切片、默认参数静态绑定
- “能组合就组合”,继承用于稳定抽象与多态接口
9. 自测题(快速检查是否掌握)
Base* p = new Derived; delete p;哪个条件必需?为什么?Derived写了void f(int),Base有void f(double),为什么d.f(3.14)编译不过?怎么修?- 为什么
Base b = Derived{}会切片?如何避免? dynamic_cast用在什么前提上?失败行为是什么?- 虚继承解决了什么问题?代价是什么?
第7章:继承与多态(面向对象核心)
继承和多态是面向对象编程的两个核心概念。继承允许我们定义新的类来继承已有类的属性和行为,多态则允许我们使用统一的接口来处理不同类型的对象。
7.1 继承基础
7.1.1 继承的基本概念
cpp
// 示例7.1:继承的基本概念
#include <iostream>
#include <string>
using namespace std;
// 基类(父类)
class Animal {
protected: // 保护成员,派生类可以访问,但外部不能访问
string name;
int age;
public:
Animal(const string& n, int a) : name(n), age(a) {
cout << "Animal构造函数" << endl;
}
// 虚析构函数(多态需要)
virtual ~Animal() {
cout << "Animal析构函数" << endl;
}
void eat() const {
cout << name << "正在吃东西" << endl;
}
void sleep() const {
cout << name << "正在睡觉" << endl;
}
// 虚函数,可以在派生类中重写
virtual void makeSound() const {
cout << name << "发出声音" << endl;
}
// 非虚函数,派生类可以继承但不能重写(除非重新定义,但不会有多态行为)
void displayInfo() const {
cout << "名称: " << name << ", 年龄: " << age << "岁" << endl;
}
};
// 派生类(子类)Dog,公有继承Animal
class Dog : public Animal {
private:
string breed; // 品种
public:
// 派生类构造函数需要初始化基类部分和新增成员
Dog(const string& n, int a, const string& b)
: Animal(n, a), breed(b) {
cout << "Dog构造函数" << endl;
}
~Dog() {
cout << "Dog析构函数" << endl;
}
// 重写基类的虚函数
void makeSound() const override {
cout << name << "汪汪叫" << endl;
}
// 新增函数
void fetch() const {
cout << name << "正在捡球" << endl;
}
// 使用基类的保护成员
void setAge(int a) {
if (a > 0) age = a;
}
void displayBreed() const {
cout << name << "的品种是: " << breed << endl;
}
};
// 派生类Cat,公有继承Animal
class Cat : public Animal {
private:
string color;
public:
Cat(const string& n, int a, const string& c)
: Animal(n, a), color(c) {
cout << "Cat构造函数" << endl;
}
~Cat() {
cout << "Cat析构函数" << endl;
}
// 重写基类的虚函数
void makeSound() const override {
cout << name << "喵喵叫" << endl;
}
// 新增函数
void climb() const {
cout << name << "正在爬树" << endl;
}
void displayColor() const {
cout << name << "的颜色是: " << color << endl;
}
};
int main() {
cout << "=== 继承基本概念演示 ===" << endl;
// 创建基类对象
cout << "\n1. 创建基类对象:" << endl;
Animal animal("普通动物", 2);
animal.eat();
animal.makeSound();
// 创建派生类对象
cout << "\n2. 创建派生类对象:" << endl;
Dog dog("旺财", 3, "金毛");
dog.eat(); // 继承自Animal
dog.makeSound(); // 调用Dog重写的版本
dog.fetch(); // Dog自己的函数
Cat cat("咪咪", 2, "白色");
cat.sleep(); // 继承自Animal
cat.makeSound(); // 调用Cat重写的版本
cat.climb(); // Cat自己的函数
// 通过基类指针调用派生类对象
cout << "\n3. 通过基类指针调用:" << endl;
Animal* ptr = &dog;
ptr->makeSound(); // 多态:调用Dog的makeSound
ptr = &cat;
ptr->makeSound(); // 多态:调用Cat的makeSound
// 注意:不能通过基类指针调用派生类特有的函数
// ptr->fetch(); // 错误!Animal没有fetch函数
return 0;
// 对象销毁时,析构函数调用顺序:先派生类,后基类
}
7.1.2 不同的继承方式
C++有三种继承方式:public、protected和private。
cpp
// 示例7.2:不同的继承方式
#include <iostream>
using namespace std;
class Base {
public:
int publicVar;
protected:
int protectedVar;
private:
int privateVar;
public:
Base() : publicVar(1), protectedVar(2), privateVar(3) {}
void publicFunc() {
cout << "Base::publicFunc()" << endl;
}
void accessMembers() {
cout << "Base内部访问: ";
cout << "publicVar=" << publicVar << ", ";
cout << "protectedVar=" << protectedVar << ", ";
cout << "privateVar=" << privateVar << endl;
}
};
// 公有继承
class PublicDerived : public Base {
public:
void accessBaseMembers() {
cout << "PublicDerived访问Base成员: ";
cout << "publicVar=" << publicVar << ", "; // OK
cout << "protectedVar=" << protectedVar << ", "; // OK
// cout << "privateVar=" << privateVar << endl; // 错误!不能访问private
}
};
// 保护继承
class ProtectedDerived : protected Base {
public:
void accessBaseMembers() {
cout << "ProtectedDerived访问Base成员: ";
cout << "publicVar=" << publicVar << ", "; // OK,但现在是protected
cout << "protectedVar=" << protectedVar << endl; // OK
// privateVar不可访问
}
// 重新暴露publicFunc为public
using Base::publicFunc;
};
// 私有继承
class PrivateDerived : private Base {
public:
void accessBaseMembers() {
cout << "PrivateDerived访问Base成员: ";
cout << "publicVar=" << publicVar << ", "; // OK,但现在是private
cout << "protectedVar=" << protectedVar << endl; // OK,但现在是private
// privateVar不可访问
}
// 重新暴露publicFunc为public
using Base::publicFunc;
};
int main() {
cout << "=== 不同继承方式演示 ===" << endl;
Base base;
cout << "\n1. Base对象直接访问:" << endl;
cout << "base.publicVar = " << base.publicVar << endl; // OK
base.publicFunc(); // OK
// cout << base.protectedVar << endl; // 错误!protected不能外部访问
// cout << base.privateVar << endl; // 错误!private不能外部访问
cout << "\n2. 公有继承:" << endl;
PublicDerived pubDerived;
cout << "pubDerived.publicVar = " << pubDerived.publicVar << endl; // OK,仍然是public
pubDerived.publicFunc(); // OK,仍然是public
pubDerived.accessBaseMembers();
cout << "\n3. 保护继承:" << endl;
ProtectedDerived protDerived;
// cout << protDerived.publicVar << endl; // 错误!现在是protected
protDerived.publicFunc(); // 通过using声明重新暴露,可以访问
protDerived.accessBaseMembers();
cout << "\n4. 私有继承:" << endl;
PrivateDerived privDerived;
// cout << privDerived.publicVar << endl; // 错误!现在是private
privDerived.publicFunc(); // 通过using声明重新暴露,可以访问
privDerived.accessBaseMembers();
// 继承方式总结:
// 公有继承:基类public->派生类public,protected->protected
// 保护继承:基类public和protected->派生类protected
// 私有继承:基类public和protected->派生类private
return 0;
}
7.1.3 继承中的构造和析构函数
cpp
// 示例7.3:继承中的构造和析构函数
#include <iostream>
using namespace std;
class Base {
private:
int baseValue;
public:
// 默认构造函数
Base() : baseValue(0) {
cout << "Base默认构造函数,baseValue = " << baseValue << endl;
}
// 带参数构造函数
Base(int val) : baseValue(val) {
cout << "Base带参数构造函数,baseValue = " << baseValue << endl;
}
// 拷贝构造函数
Base(const Base& other) : baseValue(other.baseValue) {
cout << "Base拷贝构造函数,baseValue = " << baseValue << endl;
}
// 析构函数
virtual ~Base() {
cout << "Base析构函数,baseValue = " << baseValue << endl;
}
int getValue() const { return baseValue; }
void setValue(int val) { baseValue = val; }
};
class Derived : public Base {
private:
int derivedValue;
public:
// 默认构造函数
Derived() : Base(), derivedValue(0) {
cout << "Derived默认构造函数,derivedValue = " << derivedValue << endl;
}
// 带参数构造函数
Derived(int baseVal, int derivedVal) : Base(baseVal), derivedValue(derivedVal) {
cout << "Derived带参数构造函数,derivedValue = " << derivedValue << endl;
}
// 拷贝构造函数
Derived(const Derived& other) : Base(other), derivedValue(other.derivedValue) {
cout << "Derived拷贝构造函数,derivedValue = " << derivedValue << endl;
}
// 析构函数
~Derived() {
cout << "Derived析构函数,derivedValue = " << derivedValue << endl;
}
int getDerivedValue() const { return derivedValue; }
};
int main() {
cout << "=== 继承中的构造和析构函数 ===" << endl;
cout << "\n1. 创建Derived默认对象:" << endl;
Derived d1;
cout << "\n2. 创建Derived带参数对象:" << endl;
Derived d2(10, 20);
cout << "\n3. 创建Derived拷贝对象:" << endl;
Derived d3 = d2;
cout << "\n4. 动态创建和销毁对象:" << endl;
Derived* ptr = new Derived(100, 200);
delete ptr; // 注意析构顺序
cout << "\n5. 对象数组:" << endl;
Derived arr[2]; // 调用默认构造函数
cout << "\n6. 作用域结束,自动销毁:" << endl;
// 注意:析构顺序与构造顺序相反
return 0;
}
7.2 继承的扩展
7.2.1 多继承
cpp
// 示例7.4:多继承
#include <iostream>
#include <string>
using namespace std;
// 基类1
class Printer {
public:
void print(const string& text) const {
cout << "打印: " << text << endl;
}
};
// 基类2
class Scanner {
public:
void scan() const {
cout << "正在扫描..." << endl;
}
};
// 基类3
class Fax {
public:
void sendFax(const string& document) const {
cout << "发送传真: " << document << endl;
}
void receiveFax() const {
cout << "接收传真..." << endl;
}
};
// 多继承:同时继承Printer、Scanner和Fax
class MultifunctionMachine : public Printer, public Scanner, public Fax {
public:
void copy(const string& document) {
cout << "开始复印..." << endl;
scan(); // 调用Scanner的scan
print(document); // 调用Printer的print
cout << "复印完成" << endl;
}
void multifunctionTask(const string& doc) {
cout << "执行多功能任务:" << endl;
scan();
print(doc);
sendFax(doc);
}
};
// 多继承中的问题:菱形继承(钻石问题)
class A {
public:
int data;
A() : data(0) { cout << "A构造函数" << endl; }
~A() { cout << "A析构函数" << endl; }
};
class B : public A {
public:
B() { cout << "B构造函数" << endl; }
~B() { cout << "B析构函数" << endl; }
};
class C : public A {
public:
C() { cout << "C构造函数" << endl; }
~C() { cout << "C析构函数" << endl; }
};
class D : public B, public C {
public:
D() {
cout << "D构造函数" << endl;
// 问题:data来自B还是C?实际上有两份data
B::data = 10; // 需要指定作用域
C::data = 20;
}
~D() { cout << "D析构函数" << endl; }
void showData() {
cout << "B::data = " << B::data << endl;
cout << "C::data = " << C::data << endl;
}
};
// 使用虚继承解决菱形继承问题
class VirtualA {
public:
int data;
VirtualA() : data(0) { cout << "VirtualA构造函数" << endl; }
~VirtualA() { cout << "VirtualA析构函数" << endl; }
};
class VirtualB : virtual public VirtualA {
public:
VirtualB() { cout << "VirtualB构造函数" << endl; }
~VirtualB() { cout << "VirtualB析构函数" << endl; }
};
class VirtualC : virtual public VirtualA {
public:
VirtualC() { cout << "VirtualC构造函数" << endl; }
~VirtualC() { cout << "VirtualC析构函数" << endl; }
};
class VirtualD : public VirtualB, public VirtualC {
public:
VirtualD() {
cout << "VirtualD构造函数" << endl;
data = 100; // 现在只有一份data,不需要指定作用域
}
~VirtualD() { cout << "VirtualD析构函数" << endl; }
void showData() {
cout << "data = " << data << endl;
// 也可以通过虚基类访问
cout << "VirtualA::data = " << VirtualA::data << endl;
cout << "VirtualB::data = " << VirtualB::data << endl;
cout << "VirtualC::data = " << VirtualC::data << endl;
}
};
int main() {
cout << "=== 多继承演示 ===" << endl;
cout << "\n1. 多功能机:" << endl;
MultifunctionMachine mfm;
mfm.print("测试文档");
mfm.scan();
mfm.sendFax("重要文件");
mfm.copy("身份证复印件");
mfm.multifunctionTask("报告");
cout << "\n2. 菱形继承问题:" << endl;
D d;
d.showData();
cout << "sizeof(D) = " << sizeof(d) << endl;
cout << "\n3. 虚继承解决菱形继承:" << endl;
VirtualD vd;
vd.showData();
cout << "sizeof(VirtualD) = " << sizeof(vd) << endl;
return 0;
}
7.2.2 继承与组合
cpp
// 示例7.5:继承与组合
#include <iostream>
#include <string>
#include <vector>
using namespace std;
// 继承:"is-a"关系
class Engine {
private:
int horsepower;
public:
Engine(int hp) : horsepower(hp) {
cout << "Engine构造函数: " << horsepower << "马力" << endl;
}
void start() const {
cout << "引擎启动,马力: " << horsepower << endl;
}
void stop() const {
cout << "引擎停止" << endl;
}
};
class Wheel {
private:
int size; // 英寸
public:
Wheel(int s) : size(s) {
cout << "Wheel构造函数: " << size << "寸" << endl;
}
void rotate() const {
cout << size << "寸轮子旋转" << endl;
}
};
// 组合:"has-a"关系
class Car {
private:
string brand;
Engine engine; // 组合:Car有一个Engine
Wheel wheels[4]; // 组合:Car有四个Wheel
public:
Car(const string& b, int engineHP, int wheelSize)
: brand(b), engine(engineHP), wheels{Wheel(wheelSize), Wheel(wheelSize),
Wheel(wheelSize), Wheel(wheelSize)} {
cout << "Car构造函数: " << brand << endl;
}
void drive() const {
cout << brand << "启动:" << endl;
engine.start();
for (int i = 0; i < 4; i++) {
wheels[i].rotate();
}
cout << brand << "行驶中..." << endl;
}
};
// 继承:"is-a"关系
class ElectricEngine : public Engine {
private:
int batteryCapacity; // 电池容量
public:
ElectricEngine(int hp, int capacity)
: Engine(hp), batteryCapacity(capacity) {
cout << "ElectricEngine构造函数,电池容量: " << batteryCapacity << "kWh" << endl;
}
void charge() const {
cout << "充电中,电池容量: " << batteryCapacity << "kWh" << endl;
}
// 重写基类函数
void start() const {
cout << "电动引擎启动,安静无声" << endl;
}
};
// 继承与组合结合
class ElectricCar : public Car {
private:
ElectricEngine electricEngine; // 组合:ElectricCar有一个ElectricEngine
int range; // 续航里程
public:
ElectricCar(const string& b, int engineHP, int wheelSize, int batteryCap, int r)
: Car(b, engineHP, wheelSize),
electricEngine(engineHP, batteryCap),
range(r) {
cout << "ElectricCar构造函数,续航里程: " << range << "公里" << endl;
}
void driveElectric() const {
cout << "电动车模式:" << endl;
electricEngine.start();
electricEngine.charge();
cout << "续航里程: " << range << "公里" << endl;
}
};
// 继承 vs 组合的选择
class Person {
protected:
string name;
int age;
public:
Person(const string& n, int a) : name(n), age(a) {}
void introduce() const {
cout << "我叫" << name << ",今年" << age << "岁" << endl;
}
};
// 继承:Student "is-a" Person
class Student : public Person {
private:
string studentId;
public:
Student(const string& n, int a, const string& id)
: Person(n, a), studentId(id) {}
void study() const {
cout << name << "(学号: " << studentId << ")正在学习" << endl;
}
};
// 组合:University "has-a" Person
class University {
private:
string name;
vector<Person*> people; // 组合:大学有多个人员
public:
University(const string& n) : name(n) {}
void addPerson(Person* person) {
people.push_back(person);
}
void showAllPeople() const {
cout << name << "的所有人员:" << endl;
for (const auto& person : people) {
person->introduce();
}
}
};
int main() {
cout << "=== 继承与组合演示 ===" << endl;
cout << "\n1. 组合示例(Car has Engine and Wheels):" << endl;
Car car("Toyota", 150, 18);
car.drive();
cout << "\n2. 继承示例(ElectricCar is a Car):" << endl;
ElectricCar tesla("Tesla", 300, 20, 100, 500);
tesla.drive(); // 继承自Car
tesla.driveElectric(); // ElectricCar自己的方法
cout << "\n3. 继承与组合比较:" << endl;
Student student("张三", 20, "2023001");
student.introduce(); // 继承自Person
student.study(); // Student自己的方法
University university("清华大学");
university.addPerson(&student);
Person teacher("李老师", 35);
university.addPerson(&teacher);
university.showAllPeople();
return 0;
}
7.3 多态
7.3.1 虚函数与纯虚函数
cpp
// 示例7.6:虚函数与纯虚函数
#include <iostream>
#include <vector>
#include <memory>
using namespace std;
// 抽象基类:包含纯虚函数
class Shape {
protected:
string name;
string color;
public:
Shape(const string& n, const string& c) : name(n), color(c) {}
virtual ~Shape() {
cout << "Shape析构函数: " << name << endl;
}
// 纯虚函数:没有实现,派生类必须实现
virtual double area() const = 0;
// 纯虚函数:派生类必须实现
virtual double perimeter() const = 0;
// 虚函数:有默认实现,派生类可以重写
virtual void draw() const {
cout << "绘制" << color << "的" << name << endl;
}
// 非虚函数:派生类可以继承但不能重写(除非重新定义,但不会有多态)
void displayInfo() const {
cout << name << " (" << color << "): ";
cout << "面积 = " << area() << ", 周长 = " << perimeter() << endl;
}
// 虚函数:有默认实现
virtual void scale(double factor) {
cout << name << "缩放 " << factor << " 倍" << endl;
}
};
// 派生类1:必须实现所有纯虚函数
class Circle : public Shape {
private:
double radius;
public:
Circle(double r, const string& c = "红色")
: Shape("圆形", c), radius(r) {}
// 实现纯虚函数
double area() const override {
return 3.14159 * radius * radius;
}
double perimeter() const override {
return 2 * 3.14159 * radius;
}
// 重写虚函数
void draw() const override {
cout << "绘制" << color << "的圆形,半径" << radius << endl;
}
void scale(double factor) override {
radius *= factor;
cout << "圆形半径变为 " << radius << endl;
}
// 新增函数
double getRadius() const { return radius; }
};
// 派生类2
class Rectangle : public Shape {
private:
double width;
double height;
public:
Rectangle(double w, double h, const string& c = "蓝色")
: Shape("矩形", c), width(w), height(h) {}
// 实现纯虚函数
double area() const override {
return width * height;
}
double perimeter() const override {
return 2 * (width + height);
}
// 重写虚函数
void draw() const override {
cout << "绘制" << color << "的矩形,"
<< width << "×" << height << endl;
}
void scale(double factor) override {
width *= factor;
height *= factor;
cout << "矩形尺寸变为 " << width << "×" << height << endl;
}
// 新增函数
bool isSquare() const {
return width == height;
}
};
// 派生类3:继承自Rectangle
class Square : public Rectangle {
public:
Square(double side, const string& c = "绿色")
: Rectangle(side, side, "正方形") { // 注意:调用基类构造函数
name = "正方形"; // 可以修改基类的保护成员
color = c;
}
// 重写虚函数
void draw() const override {
cout << "绘制" << color << "的正方形,边长" << getWidth() << endl;
}
// 获取宽度(矩形有宽度和高度,正方形相同)
double getWidth() const {
// 注意:width是Rectangle的private成员,不能直接访问
// 需要通过Rectangle的公共接口访问
// 这里简化处理,实际应该提供getter
return area() / height; // 通过面积和高度计算
}
};
// 工厂函数:返回基类指针
Shape* createShape(const string& type) {
if (type == "circle") {
return new Circle(10.0);
} else if (type == "rectangle") {
return new Rectangle(5.0, 8.0);
} else if (type == "square") {
return new Square(6.0);
}
return nullptr;
}
int main() {
cout << "=== 虚函数与纯虚函数演示 ===" << endl;
// 不能创建抽象类的对象
// Shape shape; // 错误!Shape是抽象类
cout << "\n1. 创建具体形状对象:" << endl;
Circle circle(5.0, "红色");
Rectangle rectangle(4.0, 6.0, "蓝色");
Square square(5.0, "绿色");
circle.displayInfo();
rectangle.displayInfo();
square.displayInfo();
cout << "\n2. 通过基类指针调用多态函数:" << endl;
Shape* shapes[3];
shapes[0] = &circle;
shapes[1] = &rectangle;
shapes[2] = □
for (int i = 0; i < 3; i++) {
shapes[i]->draw(); // 多态调用
shapes[i]->displayInfo(); // 非多态调用
shapes[i]->scale(1.5); // 多态调用
cout << endl;
}
cout << "\n3. 使用动态内存和智能指针:" << endl;
vector<unique_ptr<Shape>> shapeList;
shapeList.push_back(make_unique<Circle>(3.0));
shapeList.push_back(make_unique<Rectangle>(2.0, 4.0));
shapeList.push_back(make_unique<Square>(5.0));
for (const auto& shape : shapeList) {
shape->draw();
shape->displayInfo();
cout << endl;
}
cout << "\n4. 工厂函数创建对象:" << endl;
Shape* s1 = createShape("circle");
Shape* s2 = createShape("square");
if (s1) {
s1->displayInfo();
delete s1;
}
if (s2) {
s2->displayInfo();
delete s2;
}
return 0;
}
7.3.2 虚函数表与动态绑定
cpp
// 示例7.7:虚函数表与动态绑定
#include <iostream>
using namespace std;
class Base {
public:
virtual void func1() {
cout << "Base::func1()" << endl;
}
virtual void func2() {
cout << "Base::func2()" << endl;
}
void nonVirtual() {
cout << "Base::nonVirtual()" << endl;
}
virtual ~Base() {}
};
class Derived : public Base {
public:
void func1() override {
cout << "Derived::func1()" << endl;
}
// 不重写func2,继承Base的func2
void nonVirtual() {
cout << "Derived::nonVirtual()" << endl;
}
virtual void func3() {
cout << "Derived::func3()" << endl;
}
};
// 演示动态绑定的工作原理
void demonstrateDynamicBinding() {
cout << "=== 动态绑定演示 ===" << endl;
Base base;
Derived derived;
cout << "\n1. 通过对象直接调用:" << endl;
base.func1(); // 静态绑定:调用Base::func1
derived.func1(); // 静态绑定:调用Derived::func1
derived.func2(); // 静态绑定:调用Base::func2(继承)
derived.nonVirtual(); // 静态绑定:调用Derived::nonVirtual
cout << "\n2. 通过基类指针调用(多态):" << endl;
Base* ptr = &derived;
ptr->func1(); // 动态绑定:调用Derived::func1
ptr->func2(); // 动态绑定:调用Base::func2
ptr->nonVirtual(); // 静态绑定:调用Base::nonVirtual(非虚函数)
cout << "\n3. 通过基类引用调用:" << endl;
Base& ref = derived;
ref.func1(); // 动态绑定:调用Derived::func1
ref.nonVirtual(); // 静态绑定:调用Base::nonVirtual
cout << "\n4. 切片问题(对象切片):" << endl;
Base sliced = derived; // 切片:只复制Base部分
sliced.func1(); // 静态绑定:调用Base::func1(不是Derived的!)
sliced.nonVirtual(); // 静态绑定:调用Base::nonVirtual
}
// 演示虚函数表的存在
void demonstrateVTable() {
cout << "\n=== 虚函数表存在性演示 ===" << endl;
Base base;
Derived derived;
// 获取对象大小(包含虚函数表指针)
cout << "sizeof(Base) = " << sizeof(base) << endl;
cout << "sizeof(Derived) = " << sizeof(derived) << endl;
// 对于32位系统,指针大小是4字节
// 对于64位系统,指针大小是8字节
// 虚函数表指针通常是一个指针的大小
cout << "\n对象布局:" << endl;
cout << "Base对象包含:虚函数表指针" << endl;
cout << "Derived对象包含:Base部分(虚函数表指针) + Derived新增部分" << endl;
}
int main() {
demonstrateDynamicBinding();
demonstrateVTable();
return 0;
}
7.4 运行时类型识别(RTTI)
cpp
// 示例7.8:运行时类型识别(RTTI)
#include <iostream>
#include <typeinfo>
#include <vector>
using namespace std;
class Animal {
public:
virtual ~Animal() {}
virtual void speak() const = 0;
};
class Dog : public Animal {
public:
void speak() const override {
cout << "汪汪!" << endl;
}
void fetch() const {
cout << "捡球" << endl;
}
};
class Cat : public Animal {
public:
void speak() const override {
cout << "喵喵!" << endl;
}
void climb() const {
cout << "爬树" << endl;
}
};
class Bird : public Animal {
public:
void speak() const override {
cout << "叽叽喳喳!" << endl;
}
void fly() const {
cout << "飞翔" << endl;
}
};
void demonstrateTypeid() {
cout << "=== typeid运算符演示 ===" << endl;
Animal* animals[] = {
new Dog(),
new Cat(),
new Bird(),
new Dog()
};
for (int i = 0; i < 4; i++) {
// 使用typeid获取类型信息
const type_info& ti = typeid(*animals[i]);
cout << "animal[" << i << "] 的类型是: " << ti.name() << endl;
// 比较类型
if (ti == typeid(Dog)) {
cout << " -> 这是一只狗" << endl;
} else if (ti == typeid(Cat)) {
cout << " -> 这是一只猫" << endl;
} else if (ti == typeid(Bird)) {
cout << " -> 这是一只鸟" << endl;
}
animals[i]->speak();
cout << endl;
}
// 清理内存
for (int i = 0; i < 4; i++) {
delete animals[i];
}
}
void demonstrateDynamicCast() {
cout << "\n=== dynamic_cast运算符演示 ===" << endl;
vector<Animal*> animals;
animals.push_back(new Dog());
animals.push_back(new Cat());
animals.push_back(new Bird());
animals.push_back(new Dog());
animals.push_back(new Cat());
int dogCount = 0, catCount = 0, birdCount = 0;
for (size_t i = 0; i < animals.size(); i++) {
animals[i]->speak();
// 尝试向下转型
if (Dog* dog = dynamic_cast<Dog*>(animals[i])) {
cout << " 转换为Dog成功,调用fetch()" << endl;
dog->fetch();
dogCount++;
}
else if (Cat* cat = dynamic_cast<Cat*>(animals[i])) {
cout << " 转换为Cat成功,调用climb()" << endl;
cat->climb();
catCount++;
}
else if (Bird* bird = dynamic_cast<Bird*>(animals[i])) {
cout << " 转换为Bird成功,调用fly()" << endl;
bird->fly();
birdCount++;
}
else {
cout << " 转换失败" << endl;
}
cout << endl;
}
cout << "统计结果:" << endl;
cout << "狗: " << dogCount << " 只" << endl;
cout << "猫: " << catCount << " 只" << endl;
cout << "鸟: " << birdCount << " 只" << endl;
// 清理内存
for (auto animal : animals) {
delete animal;
}
cout << "\n=== dynamic_cast失败情况 ===" << endl;
// 失败的dynamic_cast
Animal* animal = new Dog();
// 正确转型
Dog* dog = dynamic_cast<Dog*>(animal);
if (dog) {
cout << "Animal* -> Dog* 成功" << endl;
}
// 错误转型
Cat* cat = dynamic_cast<Cat*>(animal);
if (!cat) {
cout << "Animal* -> Cat* 失败,返回nullptr" << endl;
}
delete animal;
// 引用转型(失败会抛出异常)
try {
Dog d;
Animal& a = d;
// 成功转型
Dog& rd = dynamic_cast<Dog&>(a);
cout << "Animal& -> Dog& 成功" << endl;
// 尝试错误转型(会抛出bad_cast异常)
// Cat& rc = dynamic_cast<Cat&>(a); // 抛出异常
} catch (const bad_cast& e) {
cout << "引用转型失败: " << e.what() << endl;
}
}
int main() {
demonstrateTypeid();
demonstrateDynamicCast();
return 0;
}
7.5 访问控制与继承(C++11)
cpp
// 示例7.9:C++11中的final与override
#include <iostream>
using namespace std;
// final类:不能被继承
class Base final { // 添加final关键字
private:
int value;
public:
Base(int v) : value(v) {}
int getValue() const { return value; }
};
// 错误:不能继承final类
// class Derived : public Base {};
// override和final在成员函数中的使用
class Animal {
public:
virtual ~Animal() {}
// 虚函数
virtual void speak() const {
cout << "动物发出声音" << endl;
}
// 虚函数,希望派生类重写
virtual void move() const {
cout << "动物移动" << endl;
}
// 虚函数,不希望派生类重写(但可以被重写)
virtual void eat() const {
cout << "动物吃东西" << endl;
}
// 非虚函数
void sleep() const {
cout << "动物睡觉" << endl;
}
};
class Dog : public Animal {
public:
// 正确:重写基类虚函数
void speak() const override {
cout << "狗汪汪叫" << endl;
}
// 正确:使用override确保重写的是虚函数
void move() const override {
cout << "狗跑动" << endl;
}
// 正确:可以重写,但使用final禁止进一步重写
virtual void eat() const override final {
cout << "狗吃骨头" << endl;
}
// 错误:尝试重写非虚函数
// void sleep() const override {} // 错误!基类sleep不是虚函数
// 新增虚函数
virtual void fetch() {
cout << "狗捡球" << endl;
}
};
class SpecialDog : public Dog {
public:
// 正确:重写Dog的speak
void speak() const override {
cout << "特殊狗叫" << endl;
}
// 错误:不能重写final函数
// void eat() const override {} // 错误!Dog::eat是final
// 可以重写fetch
void fetch() override {
cout << "特殊狗捡飞盘" << endl;
}
};
// final成员函数
class Base2 {
public:
virtual void func() final { // 声明为final
cout << "Base2::func()" << endl;
}
virtual void func2() {
cout << "Base2::func2()" << endl;
}
};
class Derived2 : public Base2 {
public:
// 错误:不能重写final函数
// void func() override {}
// 正确:可以重写func2
void func2() override {
cout << "Derived2::func2()" << endl;
}
};
// using改变访问权限
class Base3 {
protected:
void protectedFunc() {
cout << "Base3::protectedFunc()" << endl;
}
public:
void publicFunc() {
cout << "Base3::publicFunc()" << endl;
}
};
class Derived3 : private Base3 { // 私有继承
public:
// 使用using将基类的protected成员提升为public
using Base3::protectedFunc;
// 使用using将基类的public成员保持为public(否则会变成private)
using Base3::publicFunc;
void test() {
protectedFunc(); // 可以访问,因为using声明
publicFunc(); // 可以访问
}
};
int main() {
cout << "=== final与override演示 ===" << endl;
cout << "\n1. override关键字:" << endl;
Dog dog;
dog.speak(); // 调用Dog::speak
dog.move(); // 调用Dog::move
dog.eat(); // 调用Dog::eat
dog.sleep(); // 调用Animal::sleep(继承)
cout << "\n2. final类:" << endl;
Base base(10);
cout << "base.getValue() = " << base.getValue() << endl;
cout << "\n3. 进一步继承:" << endl;
SpecialDog specialDog;
specialDog.speak(); // 调用SpecialDog::speak
specialDog.fetch(); // 调用SpecialDog::fetch
specialDog.eat(); // 调用Dog::eat(final,不能重写)
cout << "\n4. using改变访问权限:" << endl;
Derived3 d3;
d3.publicFunc(); // 可以访问
d3.protectedFunc(); // 可以访问(通过using声明)
d3.test();
return 0;
}
综合例题与解析
cpp
// 例题:实现一个简单的图形编辑器
#include <iostream>
#include <vector>
#include <memory>
#include <cmath>
#include <algorithm>
using namespace std;
// 抽象图形类
class Graphic {
protected:
int x, y; // 位置
string color;
public:
Graphic(int xPos, int yPos, const string& c)
: x(xPos), y(yPos), color(c) {}
virtual ~Graphic() {}
// 纯虚函数
virtual void draw() const = 0;
virtual double area() const = 0;
virtual Graphic* clone() const = 0; // 克隆模式
// 虚函数
virtual void move(int dx, int dy) {
x += dx;
y += dy;
cout << "图形移动到 (" << x << ", " << y << ")" << endl;
}
virtual void scale(double factor) {
cout << "图形缩放 " << factor << " 倍" << endl;
}
// 非虚函数
void setColor(const string& c) {
color = c;
cout << "颜色改为: " << color << endl;
}
string getColor() const { return color; }
int getX() const { return x; }
int getY() const { return y; }
// 静态成员
static int getGraphicCount() {
return graphicCount;
}
protected:
static int graphicCount; // 统计创建的图形数量
};
int Graphic::graphicCount = 0;
// 圆形类
class Circle : public Graphic {
private:
double radius;
public:
Circle(int x, int y, double r, const string& c = "黑色")
: Graphic(x, y, c), radius(r) {
graphicCount++;
}
Circle(const Circle& other)
: Graphic(other.x, other.y, other.color), radius(other.radius) {
graphicCount++;
}
~Circle() {
graphicCount--;
}
void draw() const override {
cout << "在 (" << x << ", " << y << ") 绘制 "
<< color << " 的圆形,半径 " << radius << endl;
}
double area() const override {
return 3.14159 * radius * radius;
}
void scale(double factor) override {
radius *= factor;
cout << "圆形半径变为 " << radius << endl;
}
Graphic* clone() const override {
return new Circle(*this);
}
double getRadius() const { return radius; }
};
// 矩形类
class Rectangle : public Graphic {
private:
double width, height;
public:
Rectangle(int x, int y, double w, double h, const string& c = "黑色")
: Graphic(x, y, c), width(w), height(h) {
graphicCount++;
}
Rectangle(const Rectangle& other)
: Graphic(other.x, other.y, other.color),
width(other.width), height(other.height) {
graphicCount++;
}
~Rectangle() {
graphicCount--;
}
void draw() const override {
cout << "在 (" << x << ", " << y << ") 绘制 "
<< color << " 的矩形,尺寸 "
<< width << "×" << height << endl;
}
double area() const override {
return width * height;
}
void scale(double factor) override {
width *= factor;
height *= factor;
cout << "矩形尺寸变为 " << width << "×" << height << endl;
}
Graphic* clone() const override {
return new Rectangle(*this);
}
bool isSquare() const {
return abs(width - height) < 0.0001;
}
};
// 三角形类
class Triangle : public Graphic {
private:
double base, height;
public:
Triangle(int x, int y, double b, double h, const string& c = "黑色")
: Graphic(x, y, c), base(b), height(h) {
graphicCount++;
}
Triangle(const Triangle& other)
: Graphic(other.x, other.y, other.color),
base(other.base), height(other.height) {
graphicCount++;
}
~Triangle() {
graphicCount--;
}
void draw() const override {
cout << "在 (" << x << ", " << y << ") 绘制 "
<< color << " 的三角形,底 "
<< base << ",高 " << height << endl;
}
double area() const override {
return 0.5 * base * height;
}
void scale(double factor) override {
base *= factor;
height *= factor;
cout << "三角形尺寸变为 底" << base << ",高" << height << endl;
}
Graphic* clone() const override {
return new Triangle(*this);
}
};
// 图形编辑器
class GraphicEditor {
private:
vector<unique_ptr<Graphic>> graphics;
public:
void addGraphic(unique_ptr<Graphic> graphic) {
graphics.push_back(move(graphic));
}
void drawAll() const {
cout << "\n=== 绘制所有图形 ===" << endl;
for (const auto& graphic : graphics) {
graphic->draw();
}
}
void scaleAll(double factor) {
cout << "\n=== 缩放所有图形 ===" << endl;
for (const auto& graphic : graphics) {
graphic->scale(factor);
}
}
void moveAll(int dx, int dy) {
cout << "\n=== 移动所有图形 ===" << endl;
for (const auto& graphic : graphics) {
graphic->move(dx, dy);
}
}
double totalArea() const {
double total = 0;
for (const auto& graphic : graphics) {
total += graphic->area();
}
return total;
}
// 复制所有图形
GraphicEditor clone() const {
GraphicEditor editor;
for (const auto& graphic : graphics) {
editor.addGraphic(unique_ptr<Graphic>(graphic->clone()));
}
return editor;
}
// 按面积排序
void sortByArea() {
sort(graphics.begin(), graphics.end(),
[](const unique_ptr<Graphic>& a, const unique_ptr<Graphic>& b) {
return a->area() < b->area();
});
cout << "\n图形已按面积排序" << endl;
}
void displayInfo() const {
cout << "\n=== 图形信息 ===" << endl;
for (size_t i = 0; i < graphics.size(); i++) {
cout << "图形 " << i << ": ";
graphics[i]->draw();
cout << " 面积: " << graphics[i]->area() << endl;
}
cout << "总图形数: " << graphics.size() << endl;
cout << "总面积: " << totalArea() << endl;
cout << "全局图形计数: " << Graphic::getGraphicCount() << endl;
}
};
int main() {
cout << "=== 图形编辑器演示 ===" << endl;
GraphicEditor editor;
// 添加图形
editor.addGraphic(make_unique<Circle>(100, 100, 50, "红色"));
editor.addGraphic(make_unique<Rectangle>(200, 200, 80, 60, "蓝色"));
editor.addGraphic(make_unique<Triangle>(300, 300, 100, 50, "绿色"));
editor.addGraphic(make_unique<Circle>(400, 400, 30, "黄色"));
// 操作图形
editor.drawAll();
editor.displayInfo();
editor.scaleAll(1.5);
editor.moveAll(10, 10);
editor.displayInfo();
// 排序
editor.sortByArea();
editor.displayInfo();
// 克隆编辑器
cout << "\n=== 克隆编辑器 ===" << endl;
GraphicEditor clonedEditor = editor.clone();
cout << "克隆后的编辑器:" << endl;
clonedEditor.displayInfo();
// 修改克隆后的编辑器
clonedEditor.moveAll(-20, -20);
clonedEditor.scaleAll(0.5);
cout << "\n修改后的克隆编辑器:" << endl;
clonedEditor.displayInfo();
cout << "\n原始编辑器:" << endl;
editor.displayInfo();
return 0;
}
本章小结
重点回顾
- 继承的基本概念:公有、保护、私有继承
- 构造和析构顺序:先基类后派生类的构造,先派生类后基类的析构
- 多继承与虚继承:解决菱形继承问题
- 多态的实现:虚函数、纯虚函数、抽象类
- 虚函数表与动态绑定:运行时多态的实现机制
- RTTI:typeid和dynamic_cast的使用
- C++11新特性:final、override、using改变访问权限
易错易混淆点
- 访问控制:不同继承方式对基类成员访问权限的影响
- 切片问题:派生类对象赋值给基类对象时丢失派生类特有信息
- 虚析构函数:基类指针删除派生类对象时必须使用虚析构函数
- 纯虚函数与抽象类:含有纯虚函数的类是抽象类,不能实例化
- 重载、隐藏与重写:
- 重载:同一作用域,函数名相同但参数不同
- 隐藏:派生类函数屏蔽基类同名函数
- 重写(覆盖):派生类重写基类的虚函数
考研笔试常见题型
- 选择题:考察继承和多态的基本概念
- 程序阅读题:分析继承关系、函数调用、对象构造析构顺序
- 程序填空题:补全继承相关的代码
- 编程题:设计类的继承体系,实现多态
编程实践建议
- 合理设计继承层次:遵循”is-a”关系使用继承,”has-a”关系使用组合
- 使用虚析构函数:基类通常应该声明虚析构函数
- 使用override关键字:明确表示重写虚函数,避免错误
- 使用final关键字:防止不必要的继承或重写
- 避免过度使用继承:优先使用组合,继承会带来紧耦合
- 理解虚函数开销:虚函数调用有额外开销,不要滥用
8. 运算符重载
├── 8.1 运算符重载基础
│ ├── 可重载运算符
│ ├── 不可重载运算符
│ ├── 成员函数重载
│ └── 友元函数重载
├── 8.2 常用运算符重载
│ ├── 算术运算符重载
│ ├── 关系运算符重载
│ ├── 赋值运算符重载
│ ├── 下标运算符重载([])
│ ├── 函数调用运算符重载(())
│ ├── 自增自减运算符重载
│ ├── 输入输出运算符重载(<<, >>)
│ └── 类型转换运算符重载
└── 8.3 特殊运算符重载
├── 拷贝赋值运算符
├── 移动赋值运算符(C++11)
└── new/delete运算符重载
第 8 章:C++ 运算符重载笔记(标准版)
目标:掌握“哪些能/不能重载、成员 vs 非成员/友元怎么选、常见运算符的推荐签名、返回值/返回引用规则、易错点”。
8.1 运算符重载基础
8.1.1 可重载运算符(常见)
- 算术:
+ - * / % - 复合赋值:
+= -= *= /= %= &= |= ^= <<= >>= - 关系:
== != < <= > >= - 位运算:
& | ^ ~ << >>(位移) - 逻辑:
!(&& ||也可重载但一般不推荐) - 自增自减:
++ -- - 其他:
[] () -> * , - 内存:
new delete new[] delete[] - 类型转换:
operator T()
规律:能重载 ≠ 应该重载,重载要遵循“直觉语义”。
8.1.2 不可重载运算符(必背)
.(成员访问).*(成员指针访问)::(作用域)?:(三目)sizeoftypeidalignofdecltype
并且:
- 不能改变运算符的优先级、结合性、操作数个数。
8.1.3 成员函数重载(Member)
表达式:
a += b等价于a.operator+=(b)a[i]等价于a.operator[](i)
特点:
- 左操作数必须是该类对象
- 可直接访问私有成员
- 常用于“修改自身”的运算符
必须是成员函数的运算符:
operator=operator[]operator()operator->
8.1.4 非成员/友元函数重载(non-member / friend)
表达式:
a + b等价于operator+(a, b)cout << x等价于operator<<(cout, x)
特点:
- 对称性更好(两侧都能参与隐式转换)
<< >>几乎必须是非成员(左操作数是流类型)- 访问私有成员可用
friend(或提供 public 接口)
8.2 常用运算符重载
8.2.1 算术运算符(+ - * / %)
推荐套路:
- 复合运算(如
+=)写成员:修改自身并返回引用 +写非成员:按值接收lhs,复用+=
常见模板:
T& T::operator+=(const T& rhs); // 成员,修改自身
T operator+(T lhs, const T& rhs) { // 非成员,产生新值
lhs += rhs;
return lhs;
}
要点:
operator+不能写成return lhs + rhs;(会递归调用自己)lhs按值是刻意设计:得到副本,避免改到原左操作数
8.2.2 关系运算符(== != < <= > >=)
模板:
bool operator==(const T& a, const T& b);
bool operator<(const T& a, const T& b); // 若用于排序容器,必须满足严格弱序
要点:
- 返回
bool(值) - 参数通常用
const T&(不拷贝且不修改)
8.2.3 赋值运算符(operator=)
标准签名:
T& T::operator=(const T& rhs);
要点:
- 返回
*this(引用),支持链式:a = b = c; - 若类管理资源(如裸指针),要遵守 Rule of 3/5(见 8.3)
8.2.4 下标运算符 operator[](必须成员)
模板(强烈建议提供 const / 非 const 两个版本):
T& operator[](size_t i);
const T& operator[](size_t i) const;
要点:
- 非 const 返回
T&允许修改:a[i] = ... - const 返回
const T&保证只读
8.2.5 函数调用运算符 operator()(必须成员)
模板:
R operator()(Args... args) const;
用途:
- 函数对象(仿函数)、自定义回调、策略类、比较器等
8.2.6 自增自减(++ --)
区分前置与后置(必考):
- 前置:先改再返回自身引用
T& operator++(); // ++a
T& operator--(); // --a
- 后置:返回旧值(副本),再修改自身
T operator++(int); // a++
T operator--(int); // a--
要点:
- 后置的
int只是占位用于区分签名 - 后置返回值(按值)是合理的:语义要求“旧值”
8.2.7 输入输出(<< >>)
几乎总是非成员(常为 friend):
std::ostream& operator<<(std::ostream& os, const T& obj);
std::istream& operator>>(std::istream& is, T& obj);
要点:
- 返回流引用支持链式:
cout << a << b;、cin >> a >> b; >>的对象参数必须是T&(要写入对象)ostream/istream不可拷贝,因此不能按值返回/按值接收
8.2.8 类型转换运算符 operator T()
模板:
explicit operator bool() const; // 常用且建议 explicit
operator int() const; // 视需求
要点:
- 易引发隐式转换歧义,很多转换应加
explicit
8.3 特殊运算符重载
8.3.1 拷贝赋值运算符(Copy Assignment)
签名:
T& operator=(const T& rhs);
当你手动管理资源时要考虑:
- 深拷贝
- 自赋值
- 异常安全(常见 copy-and-swap)
8.3.2 移动赋值运算符(Move Assignment,C++11)
签名:
T& operator=(T&& rhs) noexcept;
目的:
- “窃取”资源,避免深拷贝
- 对容器性能影响巨大(扩容、返回值等)
8.3.3 new/delete 运算符重载
常见用途:
- 内存池、调试统计、对齐需求
- 需要非常谨慎(容易造成内存管理问题)
模板概念:
void* operator new(std::size_t);
void operator delete(void*) noexcept;
高频规则总结(背这个)
1) 返回引用(多):修改自身 + 支持链式
通常返回 T&:
- 复合赋值:
+= -= *= ... - 赋值:
= - 前置
++a --a operator[](非 const 版本)- 流:
ostream& operator<<、istream& operator>>
2) 返回值(少):产生新结果 / 返回旧值副本
通常返回 T 或 bool:
+ - * / %(非复合)- 一元:
- + ~ ! - 关系运算:返回
bool - 后置
a++ a--(旧值副本) - 类型转换:返回目标类型值
3) “该返回值却返回引用”会出事吗?
通常会:
- 悬空引用:返回局部临时变量的引用
- 语义错误:把本应不修改操作数的
+变成类似+=
成员 vs 非成员怎么选(最实用判断)
- 语法规定必须成员:
= [] () -> - 修改左操作数(复合赋值):优先成员(
+=等) - 需要对称性/隐式转换(
+ == <等):优先非成员 - 左操作数不是你的类型(流
<< >>):必须非成员(通常 friend)
易错点清单
operator+内部写return lhs + rhs;会无限递归- 忘记
const:T operator+(...) const(成员版+应是 const) >>的对象参数误写成const T&(输入必须改对象)operator[]只写一个版本导致 const 对象无法下标访问- 返回类型写错:
+=返回值会破坏链式语义/增加拷贝
9. 模板与泛型编程
├── 9.1 函数模板
│ ├── 模板定义
│ ├── 模板实例化
│ ├── 模板参数
│ ├── 模板特化
│ └── 模板重载
├── 9.2 类模板
│ ├── 类模板定义
│ ├── 模板成员函数
│ ├── 模板参数默认值
│ └── 模板特化与偏特化
└── 9.3 模板高级特性
├── 可变参数模板(C++11)
├── 模板元编程基础
└── 类型 traits
第 9 章 模板记忆手册(以 template <typename R, typename... Ts> 为核心)
记忆锚点:
R= 我额外想指定/控制的类型(常见:返回类型/策略类型);Ts...= 从实参推导出来的一串类型。
一句话:模板=把“类型/常量/行为”提到编译期参数,让编译器生成对应版本代码。
9.1 函数模板
9.1.1 模板定义
- 语法骨架:
template <typename T> ...:单类型参数template <typename T, typename U> ...:多类型参数template <typename R, typename... Ts> ...:一个“独立类型” + 可变参数包typename与class在模板参数处等价。
模板参数的三种常见形态
- 类型参数:
typename T - 非类型参数:
int N(编译期常量) - 参数包:
typename... Ts(可变数量的类型)
9.1.2 模板实例化(编译器生成代码)
- 模板本身不是“已生成的函数”,被调用时才实例化。
- 两种触发方式:
- 隐式实例化:
f(1, 2.5)编译器推导类型并生成版本 - 显式实例化/指定:
f<double>(1, 2.5)
记忆点
- 推导失败常见原因:同一个模板参数要求类型一致(如
template<typename T> f(T,T)传入int+double)
9.1.3 模板参数(为何要 R + Ts...)
template <typename R, typename... Ts> 的意义
R:你要“明确指定”的类型(常见:返回类型、策略类型、目标类型)Ts...:由调用处实参自动推导的一串类型(数量可变)
典型用途(必须会)
- 强制/控制返回类型(避免溢出、提升精度、统一输出)
- 让调用者注入“策略类型”(比较器、分配器、格式化器)
可变参数 + 折叠(C++17 重点)
- 折叠表达式:
(expr op ...)/(... op expr) - 常见:
((cout << xs), ...)
9.1.4 模板特化(Specialization)
函数模板
- 允许:全特化
- 不允许:偏特化(用重载替代)
记忆句:
- 函数模板:特化少用,重载更常用;偏特化不存在。
9.1.5 模板重载(Overload)
- 可以同时存在:
- 普通函数重载
- 函数模板重载
- 选择规则(记忆版):
- 能匹配更“精确”的版本优先(常见:普通函数可能优先于模板)
- 模板之间看更特化/更匹配的那个
9.2 类模板
9.2.1 类模板定义
- 目的:把“数据结构/类”做成按类型生成的版本(容器、智能指针、工具类)。
- 典型形态:
template<typename T> class Box;template<typename T, int N> class Array;(N 为编译期常量)
记忆句:
- 类模板 = 同一份类设计,按类型/编译期参数生成不同类。
9.2.2 模板成员函数
- 类模板的成员函数也依赖模板参数。
- 实战高频坑:
- 定义常放在头文件/同一翻译单元,否则可能链接不到实例化代码。
9.2.3 模板参数默认值
- 形态:
template<typename T=int> class Box;- 用法:
Box<>等价于Box<int>
9.2.4 特化与偏特化
类模板
- 支持:全特化、偏特化(函数模板做不到)
记忆句:
- 偏特化是类模板的专属武器。
9.3 模板高级特性
9.3.1 可变参数模板(C++11)
template<typename... Ts>:接收任意数量类型Ts... xs:接收任意数量实参- 展开方式:
- C++11/14:递归展开
- C++17:折叠表达式(更简洁、优先掌握)
与 template <typename R, typename... Ts> 的结合点:
R用来指定结果/策略,Ts...用来接收任意实参
9.3.2 模板元编程基础
- 思想:编译期计算/编译期分支(
constexpr、模板递归、static_assert) - 记忆点:很多“检查”可以提前到编译期完成。
9.3.3 类型 traits
- 作用:在编译期判断类型性质、选择实现或限制类型
- 常用:
std::is_integral_v<T>std::is_arithmetic_v<T>std::is_same_v<A,B>std::common_type_t<T,U>
与 R, Ts... 的关系(记忆句):
- traits 帮你“约束 Ts… 是否可用”,
common_type帮你“推导合理的返回类型”。
速背:什么时候要 template <typename R, typename... Ts>?
- 你希望调用者显式指定一个“结果/策略类型”:
R - 同时还要接收任意多个、类型可能各不相同的参数:
Ts...
典型题型(背下来)
- “把任意参数统一转换成 R 再处理”
- “日志/打印/格式化:参数个数不定”
- “构造转发:把 Ts… 完整转发给某个构造函数”(更偏工程)
终极对照表(最常考的三种模板头)
template <typename T>:一类参数,要求同型template <typename T, typename U>:两类参数,允许不同型(常配common_type_t)template <typename R, typename... Ts>:一个“指定类型” + 一串“推导类型”(最灵活)
10. 异常处理
├── 10.1 异常处理基础
│ ├── try-catch块-/
│ ├── throw语句
│ └── 异常类型
├── 10.2 异常规范
│ ├── 异常说明
│ └── noexcept(C++11)
└── 10.3 标准异常类
└── 异常类层次结构
11. 文件操作
├── 11.1 文件流类
│ ├── ifstream
│ ├── ofstream
│ └── fstream
├── 11.2 文件操作
│ ├── 打开与关闭文件
│ ├── 文本文件读写
│ └── 二进制文件读写
└── 11.3 文件指针操作
├── seekg/seekp
└── tellg/tellp
include
include
include
using namespace std;
struct Data { int a; double b; };
int main() {
// 文本写
{
ofstream out(“a.txt”);
out << “hello\n” << 123 << “\n”;
}
// 文本读(按行)
{
ifstream in("a.txt");
string line;
while (getline(in, line)) cout << line << "\n";
}
// 二进制写/读
{
Data w{1, 2.5};
ofstream out("data.bin", ios::binary);
out.write(reinterpret_cast<const char*>(&w), sizeof(w));
ifstream in("data.bin", ios::binary);
Data r{};
in.read(reinterpret_cast<char*>(&r), sizeof(r));
cout << r.a << " " << r.b << "\n";
}
// 文件大小
{
ifstream in("data.bin", ios::binary);
in.seekg(0, ios::end);
cout << "size=" << in.tellg() << " bytes\n";
}
}
12. 标准库STL
├── 12.1 容器
│ ├── 序列容器
│ │ ├── vector
│ │ ├── deque
│ │ ├── list
│ │ ├── forward_list(C++11)
│ │ └── array(C++11)
│ ├── 关联容器
│ │ ├── set/multiset
│ │ ├── map/multimap
│ │ ├── unordered_set(C++11)
│ │ └── unordered_map(C++11)
│ └── 容器适配器
│ ├── stack
│ ├── queue
│ └── priority_queue
├── 12.2 迭代器
│ ├── 输入/输出迭代器
│ ├── 前向迭代器
│ ├── 双向迭代器
│ ├── 随机访问迭代器
│ └── 迭代器适配器
├── 12.3 算法
│ ├── 非修改序列算法
│ ├── 修改序列算法
│ ├── 排序及相关操作
│ ├── 数值算法
│ └── 函数对象
├── 12.4 函数对象与Lambda
│ ├── 内置函数对象
│ ├── 函数适配器
│ └── bind(C++11)
└── 12.5 字符串类(string)
├── 字符串操作
├── 字符串与C风格字符串
└── 字符串流
13. cpp11/14/17新特性
├── 13.1 自动类型推导
│ ├── auto
│ └── decltype
├── 13.2 智能指针
├── 13.3 右值引用与移动语义
│ ├── 右值引用
│ ├── 移动构造函数
│ └── 移动赋值运算符
├── 13.4 Lambda表达式
├── 13.5 范围for循环
├── 13.6 初始化列表
│ └── 统一初始化语法
├── 13.7 类型相关
│ ├── nullptr
│ ├── 强类型枚举
│ └── 类型别名模板
├── 13.8 并发支持(了解)
│ ├── thread
│ ├── mutex
│ └── atomic
└── 13.9 其他特性
├── constexpr
├── static_assert
├── 委托构造函数
└── 继承构造函数
14. 内存管理
├── 14.1 内存分区
│ ├── 栈区
│ ├── 堆区
│ ├── 全局/静态存储区
│ ├── 常量存储区
│ └── 代码区
├── 14.2 内存分配方式
│ ├── 静态分配
│ ├── 栈分配
│ └── 堆分配
└── 14.3 常见内存问题
├── 内存泄漏
├── 缓冲区溢出
├── 野指针
└── 重复释放