📘 第1章 C++ 入门知识
从零开始,先看懂最简单的 C++ 程序长什么样
1.1 最简单的 C++ 程序 常考
// 这是我的第一个 C++ 程序 #include <iostream> // ① 导入输入输出工具 using namespace std; // ② 告诉编译器用标准命名空间 int main() { // ③ 主函数——程序的入口 cout << "Hello World!"; // ④ 输出一句话 return 0; // ⑤ 告诉系统程序正常结束 }
int 表示它最后要返回一个整数给操作系统。<< 是"把内容推出去"的管道。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
函数原型声明 常考
// 例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 函数的重载 必考重点
// 版本一: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) { },参数个数不同也算重载。
1.3 变量的引用 必考重点
int &b = a; 就是 "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 函数模板 常考
❌ 不用模板(写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。就像盖房子用同一个图纸,换不同的材料。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) | ✅ 能改 | 形参是实参的别名,就是同一个 |
// ① 值传递 — 交换失败 ❌ 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 什么是面向对象?必考
左边:所有函数都能动所有数据,乱成一团。右边:数据和操作它自己的函数打包在一起,互不干扰。
面向对象四大特征 必考重点
🔮 抽象
把现实中的事物简化成程序里的"类"。
比如:学生类 = 学号 + 姓名 + 成绩 + 学习行为
📦 封装
把数据藏起来,只开放必要的接口。
就像手机——你只按按钮,不用管内部电路
🧬 继承
子类"复制"父类的全部能力,还能加新的。
学生继承人的特征,再增加学号、考试
🔄 多态
同样的命令,不同对象做出不同反应。
"叫" → 狗汪汪,猫喵喵
2.2 类的声明 必考重点
标准类定义格式
class Student { // class 关键字 + 类名 private: // 私有:只有类内部能访问 char name[20]; float score; public: // 公有:类内外都能访问 void setdata(); void display(); }; // ⚠️ 这里的分号不能忘记!!!
; 分号!编译报错找不到原因时,先检查这里。画不下了… 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-> 来访问的。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 动态数组 基础了解
#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 类声明和成员函数定义分离 基础了解
// 文件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 构造函数 必考重点
构造函数的几种形式
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 析构函数 必考重点
~类名,有点像"取关/注销账号"。构造函数
同名,无返回值
可重载
创建对象时自动调用
析构函数
~同名,无返回值
不能重载(一个类只有一个)
对象销毁时自动调用
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 静态成员 常考
int 类名::静态成员 = 值;3.4 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 友元类 常考
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 };
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 类模板 常考
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');
类模板名<实际类型> 对象名; 的格式。📕 第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) = 8,imag(4) + c2.imag(-10) = -6
⑤ 返回新创建的 Complex(8, -6),赋值给 c3
结果:c3 = (8, -6i) ✅
前置 ++ 与 后置 ++ 必考
重载前置++:
Time operator++(); —— 无参数重载后置++:
Time operator++(int); —— 一个 int 参数(不传值,仅用于区分)后置++的实现技巧:先保存当前状态(
Time temp = *this;),然后调前置++(++(*this);),最后返回保存的旧值(return temp;)。
流插入 << 和流提取 >> 重载 常考
cout << 你的对象 和 cin >> 你的对象 来输入输出,就得重载 << 和 >>。它们必须重载为友元函数(因为第一个参数是 ostream& 或 istream&,不是你的类类型)。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)
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 成员 在派生类中变成 | 基类 protected 成员 在派生类中变成 | 基类 private 成员 在派生类中 |
|---|---|---|---|
public 继承 | public | protected | 不可访问 |
protected 继承 | protected | protected | 不可访问 |
private 继承 | private | private | 不可访问 |
派生类构造函数执行顺序 必考
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种特殊情况 常考
派生类的析构函数 常考
// 构造函数顺序: 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 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 人(学生从人派生) class Student: public Person { }; // 组合:班级 has-a 学生(班级里包含学生对象) class Class { Student stud[50]; // 组合关系 };
📗 第6章 多态性与虚函数
静态关联 vs 动态关联 常考
静态关联(编译时确定)
函数重载、
用对象名调用虚函数
编译时就确定调用哪个函数
速度快
动态关联(运行时确定)
用基类指针或引用
调用虚函数
运行时根据对象的实际类型确定
灵活性高
什么情况下应当声明虚函数 常考
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
抽象类的设计意图:定一套标准(接口),所有子类必须遵守。就像驾照考试规则全国统一,但每个考场具体执行。
虚析构函数 常考
Base *p = new Circle(...); → 然后用 delete p; 释放。如果基类的析构函数不是虚函数 → 只会调用 Base 的析构函数,Circle 的析构函数不会执行!可能导致内存泄漏。
解决办法:把基类的析构函数声明为
virtual ~Base()
综合应用实例:Shape 类族 常考
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++ 课堂课件整理 · 零基础学习版 · 配合题库练习效果更佳
📝 去做练习题 →