配套学习:《C++语言程序设计教程》第五章 ~ 第八章
适合人群:已掌握 C++ 基础语法,想系统学习 OOP 的同学
阅读时长:约 18 分钟
一、前言
如果说 C 语言是"面向过程"的菜刀,那 C++ 就是"在菜刀基础上,给你配了一套完整的厨房"。面向对象编程(OOP) 正是 C++ 区别于 C 的灵魂所在。本文围绕《C++语言程序设计教程》核心章节,把"类与对象、封装、继承、多态"四大支柱用通俗例子 + 完整代码讲透,建议先看我的第一篇基础篇再读本文。
掌握 OOP 之后,你才真正进入 C++ 的大门。
二、目录
面向对象的思想起源
类与对象:从"图纸"到"实物"
封装:用访问权限保护数据
构造函数与析构函数
继承:代码复用的艺术
多态:同一种行为的不同表现
综合案例:学生成绩管理系统
学习建议与避坑指南
三、面向对象的思想起源
面向过程(C) 关注的是"怎么做":先做哪一步,后做哪一步。
面向对象(C++) 关注的是"谁来做":把数据和操作数据的方法打包成一个整体。
举个例子:把大象装进冰箱
面向过程写法:
打开冰箱门
把大象塞进去
关上冰箱门
面向对象写法:
冰箱 类:有 开门()、关门() 方法
大象 类:有 进入(冰箱) 方法
主程序:冰箱.开门(); 大象.进入(冰箱); 冰箱.关门();
后者更贴近现实世界的思维方式,也更容易扩展(比如换成"把长颈鹿装进冰箱"只需要新增一个 长颈鹿 类)。
四、类与对象:从"图纸"到"实物"
类(Class) 是抽象的模板,对象(Object) 是类的具体实例。
cpp
#include
#include
using namespace std;
// 定义一个"学生"类
class Student {
public: // 公有成员(外部可访问)
string name; // 姓名
int age; // 年龄
int score; // 分数
void introduce() { // 成员函数 cout << "我叫" << name << ",今年" << age << "岁" << ",考了" << score << "分" << endl; }};
int main() {
Student stu1; // 创建对象(实例化)
stu1.name = “张三”;
stu1.age = 18;
stu1.score = 95;
stu1.introduce();
Student stu2; // 再创建一个对象 stu2.name = "李四"; stu2.age = 19; stu2.score = 88; stu2.introduce(); return 0;}
💡 类就像"学生信息登记表模板",对象就是"填写好的具体某位同学"。
五、封装:用访问权限保护数据
直接暴露数据很容易被误改,封装 提供了三种访问权限:
表格
关键字 权限 使用场景
public 公有 外部接口(成员函数)
private 私有 内部数据(成员变量)默认权限
protected 保护 给子类访问,外部不能访问
cpp
class Account {
private: // 私有,外部不能直接访问
string owner;
double balance;
public: // 公有接口
void setOwner(string name) {
if (name.empty()) {
cout << “姓名不能为空!” << endl;
return;
}
owner = name;
}
void deposit(double money) { if (money <= 0) { cout << "存款金额必须为正!" << endl; return; } balance += money; } void show() { cout << owner << " 的余额: " << balance << " 元" << endl; }};
int main() {
Account acc;
acc.setOwner(“王五”);
acc.deposit(1000);
acc.deposit(-500); // 不会执行,余额不变
acc.show();
// acc.balance = 99999; // ❌ 编译错误,私有成员不能直接访问
return 0;
}
封装的核心思想:把数据藏起来,对外只暴露"做什么"(方法),不暴露"怎么做"(细节)。这样数据更安全,修改内部实现也不会影响外部调用。
六、构造函数与析构函数
6.1 构造函数:对象"出生"时自动调用
cpp
class Person {
private:
string name;
int age;
public:
// 默认构造函数(无参)
Person() {
name = “未命名”;
age = 0;
cout << “默认构造被调用” << endl;
}
// 带参构造函数 Person(string n, int a) { name = n; age = a; cout << "带参构造被调用" << endl; } // 拷贝构造函数 Person(const Person &p) { name = p.name; age = p.age; cout << "拷贝构造被调用" << endl; } void show() { cout << name << " " << age << endl; }};
int main() {
Person p1; // 调用默认构造
Person p2(“张三”, 18); // 调用带参构造
Person p3 = p2; // 调用拷贝构造
p1.show(); // 未命名 0
p2.show(); // 张三 18
p3.show(); // 张三 18
return 0;
}
6.2 析构函数:对象"销毁"前自动调用
cpp
class Array {
private:
int *data;
int size;
public:
Array(int n) {
size = n;
data = new int[n]; // 申请堆内存
cout << “申请了 " << n << " 个 int 的空间” << endl;
}
~Array() { delete[] data; // 释放堆内存,防止内存泄漏 cout << "释放了 " << size << " 个 int 的空间" << endl; } void set(int i, int v) { if (i >= 0 && i < size) data[i] = v; } int get(int i) { return data[i]; }};
⚠️ 必考点:构造函数和析构函数成对出现。有 new 就要有 delete,否则会造成内存泄漏,这是 C++ 程序员最容易犯的错误之一。
七、继承:代码复用的艺术
继承 让子类直接拥有父类的成员,同时可以扩展自己的新功能。
cpp
// 父类(基类)
class Animal {
protected: // protected 让子类能访问
string name;
int age;
public:
Animal(string n, int a) : name(n), age(a) {}
void eat() { cout << name << " 在吃东西" << endl; } void sleep() { cout << name << " 在睡觉" << endl; }};
// 子类(派生类)
class Dog : public Animal { // 公有继承
private:
string breed; // 品种
public:
Dog(string n, int a, string b) : Animal(n, a), breed(b) {}
void bark() { // 子类自己的方法 cout << name << " 在汪汪叫" << endl; } void showInfo() { cout << "名字: " << name << ",年龄: " << age << ",品种: " << breed << endl; }};
int main() {
Dog dog(“旺财”, 3, “柴犬”);
dog.eat(); // 继承自父类
dog.sleep(); // 继承自父类
dog.bark(); // 子类自己的方法
dog.showInfo();
return 0;
}
三种继承方式对比:
表格
继承方式 父类 public 成员 父类 protected 成员 父类 private 成员
public 继承 仍是 public 仍是 protected 不可访问
protected 继承 变成 protected 仍是 protected 不可访问
private 继承 变成 private 变成 private 不可访问
💡 实战建议:99% 的情况下用 public 继承即可,其他两种方式会让代码关系复杂化,不建议初学者使用。
八、多态:同一种行为的不同表现
多态 指的是:父类指针/引用调用同一个方法,实际执行的是子类的方法。
实现多态的两个关键条件:
继承 + 虚函数(父类方法前加 virtual 关键字)
通过父类指针或引用调用
cpp
#include
using namespace std;
class Shape {
public:
virtual double area() { // 虚函数
return 0;
}
virtual ~Shape() {} // 虚析构,保证子类对象能被正确销毁};
class Circle : public Shape {
private:
double r;
public:
Circle(double radius) : r(radius) {}
double area() { // 重写父类的虚函数
return 3.14159 * r * r;
}
};
class Rectangle : public Shape {
private:
double w, h;
public:
Rectangle(double width, double height) : w(width), h(height) {}
double area() {
return w * h;
}
};
int main() {
Shape *p1 = new Circle(5);
Shape *p2 = new Rectangle(4, 6);
cout << "圆形面积: " << p1->area() << endl; // 调用 Circle::area cout << "矩形面积: " << p2->area() << endl; // 调用 Rectangle::area delete p1; delete p2; return 0;}
运行结果:
plaintext
圆形面积: 78.5398
矩形面积: 24
如果父类的 area() 不加 virtual,那么无论指针指向什么子类,调用的都是 Shape::area(),输出两个 0。这就是"静态绑定"和"动态绑定"的区别。
⚠️ 面试常考题:构造函数能不能是虚函数?不能。析构函数要不要写成虚函数?有继承时一定要,否则子类对象可能释放不完整。
九、综合案例:学生成绩管理系统
把上面学的"类 + 继承 + 多态"组合起来:
cpp
#include
#include
#include
using namespace std;
// 抽象基类
class Person {
protected:
string name;
int id;
public:
Person(string n, int i) : name(n), id(i) {}
virtual void showInfo() = 0; // 纯虚函数,Person 是抽象类
virtual ~Person() {}
};
// 学生类
class Student : public Person {
private:
double score;
public:
Student(string n, int i, double s) : Person(n, i), score(s) {}
void showInfo() override { cout << "学生 - 学号: " << id << ",姓名: " << name << ",成绩: " << score << endl; } double getScore() const { return score; }};
// 教师类
class Teacher : public Person {
private:
string title;
public:
Teacher(string n, int i, string t) : Person(n, i), title(t) {}
void showInfo() override { cout << "教师 - 工号: " << id << ",姓名: " << name << ",职称: " << title << endl; }};
int main() {
vector<Person*> people; // 用父类指针数组统一管理
people.push_back(new Student("张三", 1001, 92.5)); people.push_back(new Student("李四", 1002, 85.0)); people.push_back(new Teacher("王老师", 2001, "副教授")); // 多态调用:同一个 showInfo 表现不同 for (auto p : people) { p->showInfo(); } // 释放内存 for (auto p : people) delete p; return 0;}
运行结果:
plaintext
学生 - 学号: 1001,姓名: 张三,成绩: 92.5
学生 - 学号: 1002,姓名: 李四,成绩: 85
教师 - 工号: 2001,姓名: 王老师,职称: 副教授
十、学习建议与避坑指南
学习路线建议:
先把基础语法(变量、循环、函数)写熟练
再学类与对象、构造函数
然后是继承、访问控制
最后啃多态、虚函数、纯虚函数
学完基础再看模板(STL)和异常处理
常见坑:
表格
坑 解决方案
忘记写虚析构函数 有继承关系时,析构函数必加 virtual
构造函数中调用虚函数 不要这么做!子类的构造还没完成
重写虚函数忘记加 override 加上 override 关键字让编译器帮你检查
浅拷贝导致 double free 涉及指针成员时,手写拷贝构造函数和 operator=
抽象类想直接实例化 抽象类(含纯虚函数)不能创建对象,只能作为指针类型
面试高频题:
面向对象的三大特性是什么?封装、继承、多态
多态的实现原理?虚函数表(vtable)+ 虚表指针(vptr)
重载(overload)和重写(override)的区别?重载在同一作用域,重写在父子类之间
写在最后
《C++语言程序设计教程》这本书的精髓就是"用 C 的语法,写 Java 一样优雅的面向对象程序"。当你理解了"类就是图纸、对象就是实物、多态就是同一句话不同人听出不同意思"之后,C++ 在你眼中就不再可怕了。
下一篇文章我会写 C++ 模板与 STL 实战,包括 vector、map、算法的实际应用,敬请期待!
📚 推荐练习:
定义一个 Vehicle 父类,派生出 Car、Bike 子类,实现 run() 多态
写一个 Shape 抽象类,提供纯虚函数 area() 和 perimeter(),派生 Triangle
实现一个简单的 DynamicArray 类,包含构造、拷贝构造、析构、push_back
如果本文对你有帮助,欢迎 点赞 👍 收藏 ⭐ 关注 ➕,评论区留下你想看的内容~
本文基于《C++语言程序设计教程》整理总结,所有代码均经过编译运行验证。