🏠 返回首页 📝 前往题库

📘 第1章 C++ 入门知识

从零开始,先看懂最简单的 C++ 程序长什么样

1.1 最简单的 C++ 程序 常考

🎯 学习目标:看完这段代码,你要知道每一行是干什么的。
// 这是我的第一个 C++ 程序
#include <iostream>     // ① 导入输入输出工具
using namespace std;       // ② 告诉编译器用标准命名空间

int main() {                 // ③ 主函数——程序的入口
    cout << "Hello World!";  // ④ 输出一句话
    return 0;                 // ⑤ 告诉系统程序正常结束
}
1
#include <iostream> — 就像做菜前先拿工具。iostream 是"输入输出流"头文件,你要用 cout(输出)和 cin(输入)就必须先写这行。
2
using namespace std; — 标准库的东西都装在 std 这个"大盒子"里,写了这行就不用每次加 std:: 前缀了。
3
int main() { } — main 是程序的大门,程序从这里开始执行。int 表示它最后要返回一个整数给操作系统。
4
cout << "Hello" — cout 是"屏幕输出"的水龙头,<< 是"把内容推出去"的管道。
5
return 0; — 告诉电脑"一切正常,我结束了"。如果出错了就返回非零数字。
C++ 输入输出流程 键盘输入 cin >> 数据流向 内存变量 int a; 存储数据 屏幕输出 cout << 输入: 20 回车 a = 20 输出: a=20
💡 记法:cin >> 箭头方向——从键盘进入变量(人→电脑);cout << 箭头方向——从变量输出到屏幕(电脑→人)。

📖 逐行详解:求两数之和

常考 例题:输入两个数,输出它们的和
#include <iostream>
using namespace std;
int main() {
    int a, b, sum;          // ① 声明3个装整数的盒子
    cin >> a >> b;           // ② 从键盘取两个数,存入a和b
    sum = a + b;             // ③ 计算a+b,结果放sum里
    cout << "a+b=" << sum;   // ④ 输出 "a+b=" 然后输出 sum 的值
    return 0;
}
🔢 分步走:

假设输入:3 5(两个数之间用空格隔开,然后按回车)

int a, b, sum; → 在内存里画了 3 个小格子,分别叫 a、b、sum。此时它们还没值。

cin >> a >> b; → 你输入 "3 5" 回车,系统把 3 放到 a 格子里,5 放到 b 格子里。

sum = a + b; → 从 a 格子里拿 3,从 b 格子里拿 5,加起来得到 8,存到 sum 格子里。

cout << "a+b=" << sum; → 屏幕上显示:a+b=8

函数原型声明 常考

一句话:C++ 规定,如果函数调用写在函数定义之前,调用前必须先声明函数的原型——告诉编译器"这个函数长什么样子"。
// 例1.3 求两个数中的大数
#include <iostream>
using namespace std;

int main() {
    int a, b, c;
    cin >> a >> b;
    c = max(a, b);               // ⚠️ 调用时 max 还没定义,所以要提前声明
    cout << "max=" << c;
    return 0;
}

int max(int x, int y) {       // 函数定义在调用之后
    return (x > y) ? x : y;
}

必须在 main 之前加一行:int max(int x, int y); 或简写 int max(int, int);(形参名可以省略)。C++ 编译时只检查参数的数据类型,不检查形参名。

1.2 函数的重载 必考重点

一句话:同一个函数名,根据你给的参数不同,自动调用对应的版本。就像你去柜台说"我要一杯"——收银员会根据你说的"咖啡"还是"奶茶"给你不同的东西。
函数重载图解:同名函数,不同参数 max(3, 7, 5) ← 3个整数 int max(int a, int b, int c) ← 系统匹配这个 float max(float a, float b, float c) ← 不匹配
必考重点 例题:函数重载——求最大值
// 版本一:3个整数比大小
int max(int a, int b, int c) {
    if (b > a) a = b;
    if (c > a) a = c;
    return a;
}

// 版本二:3个浮点数比大小
float max(float a, float b, float c) {
    if (b > a) a = b;
    if (c > a) a = c;
    return a;
}
🔢 运行过程:

int m = max(3, 7, 5); → 编译器一看:传了3个 int,好,我调 int 版本 → 返回 7

float n = max(4.5, 9.1, 6.3); → 编译器一看:传了3个 float,调 float 版本 → 返回 9.1

③ 如果只写 int max(int a, int b) { }float max(int a, int b, int c) { },参数个数不同也算重载。

⚠ 禁止:不允许两个函数参数完全一样、仅返回值不同(比如一个返回 int,一个返回 float),因为编译器不知道调哪个!

1.3 变量的引用 必考重点

一句话:引用就是给变量取个小名/别名。你叫"张三",你妈叫你"三儿",叫的是同一个人。代码里 int &b = a; 就是 "b 是 a 的小名"。
引用图解:两个名字,同一个内存格子 int a = 10 int &b = a ✅ a 改了 → b 也跟着变;b 改了 → a 也跟着变
必考重点 引用做参数——两数交换
void swap(int &a, int &b) {  // int &a 的意思是 "a是实参的别名"
    int temp = a;               // 把 a 的值保存到临时变量
    a = b;                       // 把 b 的值赋给 a(其实改的是实参本身)
    b = temp;                    // 把保存的值赋给 b
}

int main() {
    int i = 3, j = 5;
    swap(i, j);
    // 现在 i=5, j=3 ✅ 真的交换了
    return 0;
}
🔢 逐行分析(小白版):

swap(i, j) 被调用时,参数传递相当于 int &a = i; int &b = j;(a 是小 i,b 是小 j)

int temp = a; → temp = 3(把 i 的当前值 3 存起来)

a = b; → a = 5,但 a 是 i 的别名 → 所以 i 也变成了 5

b = temp; → b = 3,b 是 j 的别名 → j 变成了 3

结果:i=5, j=3 ✅ 交换成功!

1.4 函数模板 常考

一句话:当你有一堆函数,逻辑一模一样,只是数据类型不同,就用模板。T 像一个"万能占位符",你传什么类型它就成了什么类型。

❌ 不用模板(写3遍)

int max(int a, int b) { return a>b?a:b; }
float max(float a, float b) { return a>b?a:b; }
char max(char a, char b) { return a>b?a:b; }

✅ 用模板(写1次)

template <typename T>
T max(T a, T b) {
    return a > b ? a : b;
}
🔑
理解模板的"魔法":编译器看到你写 max(3, 7),自动把 T 替换成 int;看到 max(4.5, 9.1),把 T 替换成 float。就像盖房子用同一个图纸,换不同的材料。
⚠ 限制:函数模板只适用于参数个数相同、仅类型不同的情况。如果参数个数不同(一个要 2 个参数,一个要 3 个参数),函数模板不行,必须用函数重载。

1.5 有默认参数的函数 常考

一句话:给函数的形参定一个"默认值"。如果你调用时没给这个参数,系统就用默认值。
// 定义:r 的默认值是 12.5
float volume(float h, float r = 12.5) {
    return 3.14 * r * r * h;
}

volume(45.6);              // r 取默认值 12.5 → 计算半径12.5的圆柱
volume(32.5, 10.5);         // r 取 10.5 → 覆盖默认值
⚠ 必须遵守的规则(考试常考):
① 有默认值的形参必须放在形参表最右边,不能交错。如 void func(int a, int b=2, int c) ❌ 错误!
② 一个函数名不能同时用于重载函数和带默认参数的函数。如 int max(int a, int b, int c=100) 同时又有一个 int max(int a, int b) — 调用 max(5, 23) 时编译器不知道调哪个 ❌

1.6 三种参数传递方式对比 必考重点

核心:函数参数传递有三种方式。区别在于——形参改了,实参变不变?
传递方式写法形参改实参?本质
值传递void f(int x)❌ 不变复制一份副本,形参实参各自独立
指针传递void f(int *p)✅ 能改传的是地址,通过地址改原值
引用传递void f(int &x)✅ 能改形参是实参的别名,就是同一个
必考 三版 swap 函数对比
// ① 值传递 — 交换失败 ❌
void swap1(int a, int b) { int t = a; a = b; b = t; }
// swap1(i, j) → i=3, j=5 没变!因为 a,b 是 i,j 的副本

// ② 指针传递 — 交换成功 ✅
void swap2(int *a, int *b) { int t = *a; *a = *b; *b = t; }
// swap2(&i, &j) → i=5, j=3 需要取地址

// ③ 引用传递 — 交换成功 ✅(最简洁直观)
void swap3(int &a, int &b) { int t = a; a = b; b = t; }
// swap3(i, j) → i=5, j=3 不用取地址,直接传变量

1.7 动态内存分配:new / delete 常考

一句话:程序运行时需要多少内存就new多少,用完用delete归还。就像去图书馆借书——用 new 借出来,用 delete 还回去。
// 分配一个整数
int *p = new int(5);      // new 返回指针,分配一个 int 并赋值为 5
cout << *p;                   // 输出 5
delete p;                      // 释放内存(还书)

// 分配一个数组
int *arr = new int[10];    // 分配10个int的数组
arr[0] = 1;
delete[] arr;                 // 释放数组(记得加 [])
⚠ 常见错误:new 了没 delete → 内存泄漏;② delete 了又用 → 野指针;③ 数组 new[] 但用了 delete(没加 [])→ 只释放了第一个元素

1.8 inline 内置函数 基础了解

一句话:函数前面加 inline,编译器会把函数代码直接"复制粘贴"到调用处,省去调用的开销。适合小函数(几行),大函数用了反而更慢。
inline int max(int a, int b) { return a > b ? a : b; }
// 调用 max(3, 5) → 编译时直接替换为 3>5?3:5,没有函数调用的时间

类内定义的成员函数默认是 inline 的。

1.9 作用域运算符 :: 常考

一句话::: 像是一个"指路牌",告诉编译器"这个成员是哪个类的"。比如 Student::display() 表示"Student类的display函数"。
// ① 类外定义成员函数时必用
void Student::display() { ... }   // 告诉编译器这是 Student 类的函数

// ② 访问静态成员
cout << Student::count;              // 不用创建对象就能访问

// ③ 解决重名冲突
c1.A::f();   // 明确调用基类 A 的 f()

📗 第2章 类和对象

这是 C++ 最关键的概念,理解了类和对象,才算入了面向对象的大门。

2.1 什么是面向对象?必考

面向过程 vs 面向对象 面向过程:按步骤做事 数据1 数据2 函数A 操作数据1,2 函数B 操作数据1,2 VS 面向对象:封装成整体 对象A 数据A1, A2 操作A的函数 对象B 数据B1, B2 操作B的函数

左边:所有函数都能动所有数据,乱成一团。右边:数据和操作它自己的函数打包在一起,互不干扰。

面向对象四大特征 必考重点

🔮 抽象

把现实中的事物简化成程序里的"类"。

比如:学生类 = 学号 + 姓名 + 成绩 + 学习行为

📦 封装

把数据藏起来,只开放必要的接口。

就像手机——你只按按钮,不用管内部电路

🧬 继承

子类"复制"父类的全部能力,还能加新的。

学生继承人的特征,再增加学号、考试

🔄 多态

同样的命令,不同对象做出不同反应。

"叫" → 狗汪汪,猫喵喵

2.2 类的声明 必考重点

📐 类比:类 = 设计蓝图(房子图纸),对象 = 按蓝图建出来的真实房子。蓝图不占地方,房子占地方。
类 与 对象 的关系 class Student(蓝图) private: char name[20]; float score; public: void display(); 实例化 (按图建房) Student stud1 name="张三" score=87 Student stud2 name="李四" score=92 占用内存 ✅ 占用内存 ✅

标准类定义格式

class Student {      // class 关键字 + 类名
private:              // 私有:只有类内部能访问
    char name[20];
    float score;
public:               // 公有:类内外都能访问
    void setdata();
    void display();
};  // ⚠️ 这里的分号不能忘记!!!
⚠️ 新手最容易犯的错:类定义最后忘了写; 分号!编译报错找不到原因时,先检查这里。
三种访问权限对比图 private 私有 ✅ 本类成员函数可访问 ❌ 类外代码不可访问 ❌ 派生类不可访问 protected 保护 ✅ 本类成员函数可访问 ❌ 类外代码不可访问 ✅ 派生类可访问

画不下了… public 公有:所有地方都能访问(✅ ✅ ✅)

2.2.4 class vs struct 异同 常考

class

默认访问权限:private

可以包含成员函数

面向对象编程的主流

struct

默认访问权限:public

也可以包含成员函数

C++中用法与 class 基本相同

课件重点:C++ 中 struct 和 class 用法基本相同,都可以加函数。唯一的区别是默认权限——struct 默认 public,class 默认 private。

常考 例题:完整类定义 + 使用
class Array_max {         // 声明一个"找数组最大值"的类
public:
    void set_value();      // 输入10个数
    void max_value();      // 找最大值
    void show_value();     // 输出结果
private:
    int array[10];          // 存放10个数的数组
    int max;                 // 存最大值
};

// ---- 类外定义成员函数 ----
void Array_max::set_value() {
    for (int i = 0; i < 10; i++)
        cin >> array[i];
}
void Array_max::max_value() {
    max = array[0];           // 先假设第一个最大
    for (int i = 1; i < 10; i++)  // 从第二个开始比
        if (array[i] > max) max = array[i];
}
void Array_max::show_value() {
    cout << "max=" << max;
}

int main() {
    Array_max arrmax;            // 创建对象
    arrmax.set_value();          // 调用方法:对象名.函数名
    arrmax.max_value();
    arrmax.show_value();
    return 0;
}

2.3 this 指针 必考重点

一句话:每个对象都有一个隐含的指针 this,它指向对象自己。就像每个人都知道"我自己"是谁一样。当你在成员函数里访问成员变量时,其实都是通过 this-> 来访问的。
必考 this 指针的隐含用法
class A {
    int x, y;
public:
    void Setxy(int x, int y) {
        // this->x 表示"我这个对象的x",后面的 x 是参数 x
        this->x = x;    // 相当于 对象.x = 传入的x
        this->y = y;
    }
    A* getThis() { return this; }  // 返回自己的地址
};
🔢 关键理解:

① 当你写 a.Setxy(3, 5),编译器偷偷传了对象的地址 → 相当于 A::Setxy(&a, 3, 5)

② 函数内部 this 就是 &a

this->x = x 等价于 a.x = 3

④ 在成员函数里直接写 x = 3 时,x 前面其实隐含着 this->

2.4 vector 动态数组 基础了解

💡 实用扩展(课件推荐):C++ 真正的灵活可变数组——不用事先定大小,可以随时 push_back 添加元素。
#include <vector>           // 使用 vector 需要包含此头文件
using namespace std;

vector<float> arr;              // 声明一个存 float 的动态数组
arr.push_back(3.14);             // 在末尾添加 3.14
arr.push_back(2.71);             // 再添加 2.71
cout << arr.size();                // 输出数组大小: 2
cout << arr[0];                  // 用下标访问第一个元素: 3.14
sort(arr.begin(), arr.end());      // 从小到大排序(需 #include <algorithm>)

2.5 类声明和成员函数定义分离 基础了解

一句话:实际开发中,通常把类拆成三个文件——头文件(.h)放声明、源文件(.cpp)放实现、另一个源文件放使用代码。这样结构清晰、方便复用。
// 文件1: student.h — 类的声明头文件
class Student {
private: int num; float score;
public: void display();
};

// 文件2: student.cpp — 类的实现文件
#include "student.h"
void Student::display() { cout << num << " " << score; }

// 文件3: main.cpp — 类的使用文件
#include "student.h"
int main() { Student s; s.display(); }

📙 第3章 构造/析构/静态/友元

3.1 构造函数 必考重点

👶 生孩子的比喻:构造函数就像一个自动"初始化"机器。每次创建对象(生孩子),系统就自动调用构造函数(给孩子取名字、打疫苗)。你不写构造函数?系统给你一个啥都不做的默认构造函数(孩子没名字)。
构造函数自动执行流程 创建对象 构造函数被调用 初始化数据 hour=0, minute=0, sec=0 对象可用 不再调用构造函数 整个过程自动完成,程序员不需要写调用语句

构造函数的几种形式

① 无参构造函数(默认)
class Time {
public:
    Time() {               // 构造函数名 = 类名,无返回值
        hour = 0;        // 初始化成员
        minute = 0;
        sec = 0;
    }
};
Time t1;                   // 自动调 Time(),t1的hour=0
必考 ② 带参数的构造函数 + 初始化表
class Box {
public:
    // 初始化表写法 :height(h) 相当于在体内写 height = h
    Box(int h, int w, int len): height(h), width(w), length(len) {}
// 等价于 ↓
//  Box(int h, int w, int len) { height = h; width = w; length = len; }

    int volume() { return height * width * length; }
private:
    int height, width, length;
};

Box box1(12, 25, 30);     // 传参,自动调 Box(12,25,30)
cout << box1.volume();    // 12×25×30 = 9000
🔢 运行过程:

① 执行 Box box1(12,25,30); → 在内存中创建了一个 Box 对象

② 自动调用构造函数 → 初始化表 : height(12), width(25), length(30) → 三个格子分别存入 12、25、30

box1.volume() → 计算 12 × 25 × 30 = 9000 ← 输出结果

常考 ③ 构造函数重载(一个类有多个构造函数)
class Box {
public:
    Box() { height=10; width=10; length=10; }           // 无参 → 默认10
    Box(int h, int w, int len): height(h), width(w), length(len) {}  // 有参
};

Box box1;                      // 调无参 → 10,10,10
Box box2(15, 30, 25);          // 调有参 → 15,30,25

一个类可以有多个构造函数,但创建对象时只执行其中一个,系统根据参数匹配。

3.2 析构函数 必考重点

💀 比喻:对象"死"的时候自动调用析构函数,做清理工作(比如释放 new 出来的内存)。名字是 ~类名,有点像"取关/注销账号"。

构造函数

同名,无返回值
可重载
创建对象时自动调用

析构函数

~同名,无返回值
不能重载(一个类只有一个)
对象销毁时自动调用

📋 调用顺序口诀:先构造的后析构,后构造的先析构(像叠碗一样,后叠上去的先拿下来)。
必考 例题:构造函数 + 析构函数完整示例
class Student {
public:
    Student(int n, string nam, char s) {     // 构造函数
        num = n;  name = new string(nam);  sex = s;
        cout << "Constructor called." << endl;
    }
    ~Student() {                                    // 析构函数
        delete name;      // 清理 new 出来的内存
        cout << "Destructor called." << endl;
    }
private:
    int num;  string *name;  char sex;
};

int main() {
    Student *s1 = new Student(10010, "Wang", 'f');
    delete s1;              // → 调用析构函数 ❗

    Student s2(10011, "Zhang", 'm');
    // main 结束 → 自动调 s2 的析构函数
}
🔢 输出顺序:

Constructor called. (s1 创建)→ Destructor called.(delete s1)→ Constructor called.(s2 创建)→ Destructor called.(main 结束,s2 销毁)

3.3 静态成员 常考

静态成员:被所有对象共享 static int count ← 这个被所有对象共用 int Box::count=0 (类外定义) 对象1 → count++ 对象2 → count++ 对象3 → count++ count = 3 (三个对象共享同一个计数器)
⚠ 关键:静态数据成员必须在类外定义和初始化!格式:int 类名::静态成员 = 值;

3.4 const 保护共用数据 常考

一句话:用 const 声明"只读"的数据或函数,告诉编译器"这个不能改,改了报错"。
概念写法说明
常对象const Time t1(10,20,30);对象的所有成员都不能修改,只能调用常成员函数
常数据成员const int hour;(在类内声明)只能用初始化表初始化,不能赋值
常成员函数void show() const { ... }函数中不能修改任何数据成员
常引用const int &a = i;通过引用不能改值(但 i 本身可改)
常指针const int *p = &i;不能通过 p 修改 i 的值
常考 常成员函数示例
class Time {
public:
    Time(int h, int m, int s): hour(h), minute(m), sec(s) {}
    void show_time() const {     // const 放在函数后面,承诺不改数据
        cout << hour << ":" << minute << ":" << sec;
    }
private:
    int hour, minute, sec;
};

const Time t1(10, 20, 30);   // 常对象
t1.show_time();                  // ✅ 常对象只能调用常成员函数
// t1.hour = 12;               ❌ 常对象的成员不能修改

3.5 复制构造函数 必考重点

一句话:用一个已存在的对象来"拷贝"创建一个新对象。格式:类名(const 类名 &对象)。当你写 Box box2 = box1; 时自动调用。
class Box {
public:
    Box(int h, int w, int l): height(h), width(w), length(l) {}
    // 复制构造函数:参数必须是本类对象的常引用
    Box(const Box &b): height(b.height), width(b.width), length(b.length) {
        cout << "Copy constructor called" << endl;
    }
private:
    int height, width, length;
};

Box box1(12, 25, 30);
Box box2 = box1;     // → 调用复制构造函数,box2 跟 box1 一样
Box box3(box1);      // → 也调用复制构造函数
⚠ 什么时候自动调复制构造函数?① 用已存在的对象初始化新对象;② 函数参数是对象,传值调用时;③ 函数返回一个对象时。
如果你不写复制构造函数:系统自动生成一个,逐个成员复制(浅拷贝)。如果成员里有指针(new出来的),浅拷贝会导致两个对象指向同一块内存,析构时会出 bug!

3.6 友元类 常考

一句话:在 A 类里写 friend class B;,那么 B 类中的所有成员函数都可以访问 A 类的私有成员。就像你给了 B 一把"万能钥匙"。
class A {
private:
    int x;
public:
    A(): x(3) {}
    friend class B;     // B 是我的朋友,B 的所有函数都能访问我的私有成员
};

class B {
public:
    void disp(A &a) { cout << a.x; }  // ✅ 可以访问 A 的私有 x
};
⚠ 友元的两条重要性质(必考判断题):① 友元关系是单向的(A 声明 B 是朋友 ≠ B 也把 A 当朋友);② 友元关系不能传递(A 是 B 的朋友,B 是 C 的朋友 ≠ A 是 C 的朋友)

3.7 对象数组 常考

一句话:数组里的每个元素都是一个对象。就像 int a[5] 是 5 个整数,Box box[3] 是 3 个 Box 对象。
class Student {
public:
    Student(): num(0), score(0) {}        // 无参构造函数(对象数组需要)
    Student(int n, float s): num(n), score(s) {}
private:
    int num; float score;
};

Student stud[3] = {                    // 对象数组,3个学生
    Student(1001, 87.5),
    Student(1002, 79),
    Student(1005, 98)
};
cout << stud[0];                        // 像普通数组一样用下标访问
⚠ 注意:对象数组要求类必须有无参构造函数(或全部提供默认值),因为创建数组时会先为每个元素调用无参构造函数。

3.8 类模板 常考

一句话:和函数模板类似,T 是"万能类型"。比如你要写一个"比较大小"的类,可以比 int、float、char……不同数据类型,用类模板只写一次。
template <class T>                // T 是虚拟类型参数
class Compare {
private:
    T x, y;
public:
    Compare(T a, T b): x(a), y(b) {}
    T max() { return (x > y) ? x : y; }
    T min() { return (x < y) ? x : y; }
};

// 使用:指定实际类型
Compare<int> cmp1(3, 7);
cout << cmp1.max();         // 7

Compare<float> cmp2(45.78, 93.6);
Compare<char> cmp3('a', 'A');
💡 类模板 vs 函数模板:函数模板是函数的抽象,类模板是类的抽象。定义对象时用 类模板名<实际类型> 对象名; 的格式。

📕 第4章 运算符重载

一句话:让 +、-、++ 这些运算符也能用在你自己定义的类上。比如 "复数" 类型,需要能 c1 + c2 吧?编译器本来不知道,你用 operator+ 告诉它怎么做。

不能重载的 5 个运算符 必考

.

成员访问运算符

.*

成员指针运算符

::

作用域运算符

sizeof

长度运算符

?:

条件运算符(三目)

必考 例题:重载 + 实现复数相加(成员函数形式)
class Complex {
public:
    Complex(double r, double i): real(r), imag(i) {}
    Complex operator+(Complex &c2) {          // 重载 + 号
        return Complex(real + c2.real, imag + c2.imag);
    }
private:
    double real, imag;
};

// 使用:
Complex c1(3, 4), c2(5, -10), c3;
c3 = c1 + c2;      // 编译器解释为 c1.operator+(c2)
// 相当于 c3.real = 3+5 = 8, c3.imag = 4+(-10) = -6
🔢 逐行拆解:

c1 + c2 被系统翻译为 c1.operator+(c2)(成员函数形式,c1 是调用者,c2 是参数)

② 进入函数:return Complex(real + c2.real, imag + c2.imag);

real 是谁?就是 c1 的 real(因为 c1 调用的这个函数,函数的 real 就是 c1 的成员)

④ 计算:real(3) + c2.real(5) = 8imag(4) + c2.imag(-10) = -6

⑤ 返回新创建的 Complex(8, -6),赋值给 c3

结果:c3 = (8, -6i) ✅

前置 ++ 与 后置 ++ 必考

前置++ 与 后置++ 对比 前置++:先加后用 ++a ① a = a + 1 ② 用更新后的 a 参与运算 后置++:先用后加 a++ ① 用 a 的当前值参与运算 ② a = a + 1
⚠ 区分写法(高频考点):
重载前置++:Time operator++(); —— 无参数
重载后置++:Time operator++(int); —— 一个 int 参数(不传值,仅用于区分)
后置++的实现技巧:先保存当前状态(Time temp = *this;),然后调前置++(++(*this);),最后返回保存的旧值(return temp;)。

流插入 << 和流提取 >> 重载 常考

一句话:你想用 cout << 你的对象cin >> 你的对象 来输入输出,就得重载 << 和 >>。它们必须重载为友元函数(因为第一个参数是 ostream& 或 istream&,不是你的类类型)。
常考 重载 << 和 >> 完整示例(例4.7/4.8)
class Complex {
    friend ostream& operator<<(ostream &out, Complex &c);  // 声明为友元
    friend istream& operator>>(istream &in, Complex &c);
private:
    double real, imag;
};

// 输出:必须返回 ostream& 才能连续输出(如 cout << a << b)
ostream& operator<<(ostream &out, Complex &c) {
    out << "(" << c.real << "," << c.imag << "i)";
    return out;
}
// 输入
istream& operator>>(istream &in, Complex &c) {
    cout << "输入实部和虚部:";
    in >> c.real >> c.imag;
    return in;
}

// 使用:
Complex c1(3, 4);
cout << c1;              // 输出 (3,4i)
cin >> c1;               // 输入实部和虚部
🔢 关键点:

① 必须返回 ostream&(引用)而不是 ostream(值)—— 因为 ostream 不允许复制

② 必须返回 return out; — 才能实现 cout << a << b 链式调用(第一次返回的 out 接着给第二次用)

③ 为什么必须是友元?因为第一个参数是 ostream&,不是你的类,没法写成成员函数。

String 类重载关系运算符 常考(例4.4)

常考 例4.4 重载 > < == 比较两个字符串
class String {
    friend bool operator>(String &s1, String &s2);
    friend bool operator<(String &s1, String &s2);
    friend bool operator==(String &s1, String &s2);
private:
    char *p;
public:
    String(char *str): p(str) {}
};

bool operator>(String &s1, String &s2) {
    return strcmp(s1.p, s2.p) > 0;  // 用 strcmp 比较 C 风格字符串
}
// 使用:
String s1("Hello"), s2("Book");
cout << (s1 > s2);   // 1 (true),因为 'H' > 'B'

📘 第5章 继承与派生

🧬 比喻:儿子继承父亲的财产。派生类(儿子)继承基类(父亲)的全部成员,还能自己新增。儿子用父亲的东西不用重新买,但也能买自己的新东西。

三种继承方式对比 必考重点

核心:继承方式决定了基类成员在派生类中的访问权限。继承方式只有三种:public、private、protected。
继承方式基类 public 成员
在派生类中变成
基类 protected 成员
在派生类中变成
基类 private 成员
在派生类中
public 继承publicprotected不可访问
protected 继承protectedprotected不可访问
private 继承privateprivate不可访问
💡 记法:不管用什么方式继承,基类的 private 成员在派生类中永远不可直接访问。public 继承最常用——保持"is-a"关系(学生是一种人)。
继承层次图——多层派生 Student 基类:num, name, display() public 继承 Student1: num,name,display(), +age public 继承 Student2: ...age +score

派生类构造函数执行顺序 必考

1
先调基类构造函数 — 先初始化从父亲那里继承来的东西
2
再调子对象构造函数 — 类里如果有其他类的对象成员,初始化它们
3
最后执行派生类构造函数体 — 初始化自己新增的成员
必考 例题:派生类构造函数写法
class Student1: public Student {     // 继承 Student
private:
    int age;
    string addr;
public:
    // 总参数表: 先用一部分参数调基类构造函数,剩下的初始化自己的成员
    Student1(int n, string nam, int a, string ad):
        Student(n, nam),          // 调基类构造函数
        age(a),  addr(ad)         // 初始化自己的成员
    { }
};

// 多层派生时写的规则:只调用直接基类
Student2(int n, string nam, int a, int s):
    Student1(n, nam, a), score(s) { }         // ✅ 正确(只调直接基类 Student1)
// Student2(n, nam, a, s): Student(n, nam), Student1(n, nam, a), score(s) {}  ❌ 错误

派生类构造函数的5种特殊情况 常考

核心原则:写不写基类构造函数,取决于基类有没有带参数的构造函数
1
基类没有构造函数/有无参构造函数 → 派生类可以不写基类构造函数。调用时系统自动调基类默认构造函数。
2
基类有带参构造函数 → 派生类必须定义构造函数,并在初始化表中列出基类构造函数及参数。
3
不需要初始化派生类新增成员 → 派生类构造函数体可以为空(只写初始化表)。
4
基类既有无参又有有参构造函数 → 派生类可以包含也可以不包含基类构造函数。
5
基类和子对象都没有带参构造函数 → 可以不定义派生类构造函数。

派生类的析构函数 常考

一句话:派生类不能继承基类的析构函数。撤销派生类对象时,执行顺序与构造函数相反
1
先执行派生类自身的析构函数,清理新增成员
2
再调用子对象的析构函数,清理子对象
3
最后调用基类的析构函数,清理基类成员
// 构造函数顺序: B() → C()
// 析构函数顺序: ~C() → ~B()
class B {
    B() { cout << "B构造"; }
    ~B() { cout << "B析构"; }
};
class C: public B {
    C() { cout << "C构造"; }
    ~C() { cout << "C析构"; }
};
// C obj; 输出: B构造 C构造 C析构 B析构

多重继承的二义性 必考

菱形继承:同一份数据被继承两次 class N { int a; } virtual virtual class A: virtual public N class B: virtual public N class C: public A, public B ✅ C中只有一份 N::a
💡 虚基类口诀:第一级继承就加 virtual,最终派生类负责初始化虚基类。这样菱形继承的顶层基类只保留一个副本。

多重继承完整示例 必考

一句话:一个派生类有多个基类。比如一个"研究生"同时是"学生"和"教师"。
例5.8 多重继承——研究生类继承教师+学生
class Teacher {
protected:  string name; int age; string title;
};
class Student {
protected:  string name1; char sex; float score;
};

// 多重继承:Graduate 同时继承 Teacher 和 Student
class Graduate: public Teacher, public Student {
    float wage;
public:
    // 构造:初始化表中列出所有基类的构造函数
    Graduate(string nam, int a, char s, string t, float sco, float w):
        Teacher(nam, a, t), Student(nam, s, sco), wage(w) {}
};
🔢 构造函数调用顺序:

按照声明派生类时基类的出现顺序——先 Teacher 后 Student。所以:Teacher() → Student() → Graduate()

虚基类的初始化 必考

⚠ 关键规则:如果在虚基类中定义了带参数的构造函数,所有直接和间接的派生类都要通过构造函数的初始化表对虚基类进行初始化。但编译系统只执行最顶层的虚基类构造函数一次,其它的忽略。
class A { public: A(int k) { } };
class B: virtual public A { public: B(int n): A(n) { } };
class C: virtual public A { public: C(int n): A(n) { } };

// 最终派生类 D 必须负责初始化虚基类 A
class D: public B, public C {
public:
    D(int n): A(n), B(n), C(n) { }    // ✅ 正确写法
};

继承和组合 基础了解(自学)

一句话:继承是"is-a"关系(学生是一种人),组合是"has-a"关系(班级里有学生)。课件5.8-5.9为自学内容,简单了解概念即可。
// 继承:学生 is-a 人(学生从人派生)
class Student: public Person { };

// 组合:班级 has-a 学生(班级里包含学生对象)
class Class {
    Student stud[50];   // 组合关系
};

📗 第6章 多态性与虚函数

🎭 一句话:同一个命令(函数调用),不同对象做出不同反应。用基类指针指向不同派生类对象,执行不同版本的函数。

静态关联 vs 动态关联 常考

静态关联(编译时确定)

函数重载、
用对象名调用虚函数
编译时就确定调用哪个函数
速度快

动态关联(运行时确定)

用基类指针引用
调用虚函数
运行时根据对象的实际类型确定
灵活性高

什么情况下应当声明虚函数 常考

1
首先看这个成员函数所在的类是否会作为基类。如果不作为基类,不需要虚函数。
2
派生类中是否需要重新定义这个函数的功能。如果派生类和基类功能一样,不需要虚函数。
3
是否需要通过基类指针/引用来调用。如果不通过基类指针,用普通函数即可。
💡 注意:只能将类的成员函数声明为虚函数(普通函数不行)。基类的虚函数体可以是空的,具体功能留给派生类去添加。
虚函数工作原理:基类指针→动态绑定 Base *p; // 基类指针 p->display(); // 同一个调用语句,指针指向谁就调谁的版本 p=&b0 → 调Base版本 p=&b1 → 调B1版本 p=&d1 → 调D1版本 不加 virtual → 全程调 Base 版本;加 virtual → 动态关联,指向谁就调谁
必考 例题:虚函数完整示例
class Base {
public:
    virtual void display() { cout << "Base::display()" << endl; }
};
class B1: public Base {
public:
    virtual void display() { cout << "B1::display()" << endl; }  // 自动成为虚函数
};
class D1: public B1 {
public:
    virtual void display() { cout << "D1::display()" << endl; }
};

// 👇 核心:用一个函数,传不同的对象指针
void fun(Base *ptr) {
    ptr->display();     // 动态关联!运行的时候才知道调哪个
}

int main() {
    Base b0;  B1 b1;  D1 d1;
    fun(&b0);    // 输出: Base::display()
    fun(&b1);    // 输出: B1::display()
    fun(&d1);    // 输出: D1::display()
}
🔢 逐行分析:

virtual void display() — 加上 virtual,表示这个函数以后可能被派生类"覆盖"(override)

fun(Base *ptr) — 参数是基类指针,可以接受任何派生类对象的地址(因为派生类对象也是基类对象)

ptr->display() — 如果没加 virtual,不管 ptr 指向谁,永远调 Base 的 display

④ 加了 virtual 后 → 系统在运行时检查 ptr 实际指向的对象类型,调用对应版本的 display

这就是"一个接口,多种方法"的多态!

纯虚函数与抽象类 必考

虚函数

virtual void func();
有函数体(可以为空)
所在类可以实例化

纯虚函数

virtual void func() = 0;
没有函数体
所在类是抽象类,不能实例化

必考 抽象类示例
class Shape {               // 抽象基类
public:
    virtual void area() = 0;   // 纯虚函数,=0 表示"我不实现,子类去实现"
};

class Circle: public Shape {
    float r;
public:
    Circle(float rr): r(rr) {}
    void area() { cout << 3.14 * r * r; }  // 派生类必须实现
};

// Shape s;     ❌ 抽象类不能创建对象
Shape *p;          // ✅ 可以声明指针
Circle c(15);
p = &c;
p->area();         // 调 Circle 的 area() → 3.14×15×15 = 706.5

抽象类的设计意图:定一套标准(接口),所有子类必须遵守。就像驾照考试规则全国统一,但每个考场具体执行。

虚析构函数 常考

⚠ 一个隐蔽的 BUG:
Base *p = new Circle(...); → 然后用 delete p; 释放。
如果基类的析构函数不是虚函数 → 只会调用 Base 的析构函数,Circle 的析构函数不会执行!可能导致内存泄漏。
解决办法:把基类的析构函数声明为 virtual ~Base()

综合应用实例:Shape 类族 常考

例6.4 利用虚函数和抽象类实现"形状"类族
class Shape {                                      // 抽象基类
public:
    virtual float area() const { return 0.0; }    // 虚函数
    virtual float volume() const { return 0.0; }  // 虚函数
    virtual void shapeName() const = 0;          // 纯虚函数
};

class Point: public Shape {                         // 点 → 公有继承 Shape
protected: float x, y;
public:
    Point(float a = 0, float b = 0): x(a), y(b) {}
    virtual void shapeName() const { cout << "Point:"; }  // 实现纯虚函数
};

class Circle: public Point {                          // 圆 → 继承 Point
protected: float radius;
public:
    Circle(float x, float y, float r): Point(x, y), radius(r) {}
    virtual float area() const { return 3.14159 * radius * radius; }
    virtual void shapeName() const { cout << "Circle:"; }
};

class Cylinder: public Circle {                        // 圆柱 → 继承 Circle
    float height;
public:
    Cylinder(float x, float y, float r, float h): Circle(x, y, r), height(h) {}
    virtual float area() const { return 2 * Circle::area() + 2 * 3.14159 * radius * height; }
    virtual float volume() const { return Circle::area() * height; }
    virtual void shapeName() const { cout << "Cylinder:"; }
};

// 使用:基类指针 → 动态关联
Shape *pt;
pt = &point;  pt->shapeName();  // Point:
pt = &circle; pt->shapeName();  // Circle:
pt = &cyl;    pt->shapeName();  // Cylinder:
// area() 和 volume() 同样动态关联

这个例子完美展示了:抽象基类(Shape)定义统一接口 → 派生类逐层实现具体功能 → 基类指针实现多态。是理解虚函数+抽象类的最佳范例。

📚 基于 C++ 课堂课件整理 · 零基础学习版 · 配合题库练习效果更佳
📝 去做练习题 →