本文最后更新于 18 天前,其中的信息可能已经有所发展或是发生改变。
用实例来介绍面向对象
类(Class)
事物的“模板”
假设你是家具设计师,设计了一款“沙发”。在生产前,你需要创建一份设计图纸,这份图纸定义了沙发的结构(如尺寸、材质、颜色)和功能(如可坐、可躺、可收纳)。
代码中的类:
#include <iostream>
#include <string>
using namespace std;
class Sofa {
private: // 私有访问权限,隐藏内部属性
string color;
string material;
int seats; // 座位数,私有属性
public:
// 构造函数,初始化属性
// 笔者注:我在构造函数的参数变量名前添加 p_ 前缀,用作区分成员变量和参数变量
Sofa(string p_color, string p_material, int p_seats) : color(p_color), material(p_material), seats(p_seats) {}
// 公共方法:获取座位数(访问私有属性)
int getSeats() const {
return seats;
}
// 公共方法:坐的行为
void sit() const {
cout << "坐在" << color << "的" << material << "沙发上" << endl;
}
// 公共方法:躺的行为
void lieDown() const {
if (getSeats() >= 2) {
cout << "可以躺下" << endl;
} else {
cout << "单人沙发无法躺下" << endl;
}
}
};
关键点:
- 类是抽象的:它不是具体的沙发,而是描述沙发“长什么样”和“能做什么”的抽象模板。
- 属性(Attribute):类的“特征”(如颜色
color
、材质material
)。 - 方法(Method):类的“行为”(如坐
sit()
、躺liedown()
)。 - 访问控制:通过
private
关键字隐藏属性(如seats
),通过公共方法(如getSeats()
)控制访问,实现封装。
对象(Object)
类的“实例”
根据设计图纸,工厂生产出具体的沙发。比如:
- 沙发A:红色、皮质、3座位
- 沙发B:蓝色、布质、1座位
创建对象:
int main() {
// 创建两个沙发对象(实例化)
Sofa sofaA("红色", "皮质", 3);
Sofa sofaB("蓝色", "布质", 1);
// 调用对象的方法
sofaA.sit(); // 输出:坐在红色的皮质沙发上
sofaB.lieDown(); // 输出:单人沙发无法躺下
return 0;
}
关键点:
- 对象是具体的:每个对象有自己的属性值(如沙发A
sofaA
是红色,沙发BsofaB
是蓝色)。 - 实例化:从类创建对象的过程,通过调用构造函数(如
Sofa(...)
)实现。
封装(Encapsulation)
信息隐藏
沙发的内部结构(如弹簧、填充物)被包裹在布料或皮革下,用户只需知道如何使用沙发(坐、躺),而无需了解内部工作原理。
代码中的封装:
#include <iostream>
#include <string>
using namespace std;
class Sofa {
private: // 私有访问权限,隐藏内部属性
string color;
string material;
int seats; // 座位数,私有属性
public:
// 构造函数,初始化属性
// 笔者注:我在构造函数的参数变量名前添加 p_ 前缀,用作区分成员变量和参数变量
Sofa(string p_color, string p_material, int p_seats) : color(p_color), material(p_material), seats(p_seats) {}
// 公共方法:获取座位数(访问私有属性)
int getSeats() const {
return seats;
}
// 公共方法:坐的行为
void sit() const {
cout << "坐在" << color << "的" << material << "沙发上" << endl;
}
// 公共方法:躺的行为
void lieDown() const {
if (getSeats() >= 2) {
cout << "可以躺下" << endl;
} else {
cout << "单人沙发无法躺下" << endl;
}
}
};
关键点:
- 私有属性:用
private
关键字修饰属性(如color
、material
、seats
),禁止外部直接访问。 - 公共接口:通过
public
方法(如getSeats()
、sit()
)提供对外交互的途径,保护数据安全。
核心作用:隐藏内部细节,控制数据访问,避免外部代码直接修改对象状态。
继承(Inheritance)
复用与扩展
你基于“沙发”图纸设计了一款多功能沙发床,它保留了沙发的所有特性(颜色、材质、坐的功能),并新增了展开成床的功能。
代码中的继承:
class SofaBed : public Sofa { // 公有继承自 Sofa 类
private:
double bedLength; // 新增属性:床的长度
public:
// 构造函数:复用父类初始化逻辑,并添加可指定的bedLength初始化
SofaBed(string p_color, string p_material, int p_seats, double p_bedLength) : Sofa(p_color, p_material, p_seats), bedLength(p_bedLength) {}
// 提供另一个构造函数,默认bedLength值为1.8
SofaBed(string p_color, string p_material, int p_seats) : Sofa(p_color, p_material, p_seats), bedLength(1.8) {}
// 新增方法:展开成床
void unfold() const {
cout << "展开沙发,变成" << bedLength << "米的床" << endl;
}
};
创建和使用子类对象:
int main() {
SofaBed sofaBedA("白色", "亚麻", 2, 2.0); // 自动调用构造函数1,将私有成员变量bedLength设为2.0
SofaBed sofaBedB("灰色", "亚麻", 2); // 自动调用构造函数2,成员变量bedLength使用默认值1.8
sofaBedA.sit(); // 继承自父类的方法,输出:坐在白色的亚麻沙发上
sofaBedA.unfold(); // 子类新增的方法,输出:展开沙发,变成2.0米的床
sofaBedB.sit(); // 继承自父类的方法,输出:坐在灰色的亚麻沙发上
sofaBedB.unfold(); // 子类新增的方法,输出:展开沙发,变成1.8米的床
return 0;
}
关键点:
- 代码复用:子类(
SofaBed
)继承父类(Sofa
)的属性和方法,避免重复编写。 - 功能扩展:子类可添加新属性(如
bedLength
)和新方法(如unfold()
),或重写父类方法(后续章节可深入学习虚函数实现重写)。
多态(Polymorphism)
同一接口,不同实现
你设计了多种类型的家具(沙发、椅子、桌子),它们都有“清洁”的功能,但清洁方式不同:
- 沙发:用吸尘器吸
- 椅子:用湿布擦
- 桌子:先用清洁剂再擦
代码中的多态:
class Furniture {
public:
virtual void clean() const = 0; // 纯虚函数,定义通用接口(抽象类)
};
class Sofa : public Furniture {
public:
void clean() const override { // C++11 及以上支持 override 关键字
cout << "用吸尘器清洁沙发" << endl;
}
};
class Chair : public Furniture {
public:
void clean() const override {
cout << "用湿布擦拭椅子" << endl;
}
};
class Table : public Furniture {
public:
void clean() const override {
cout << "先用清洁剂喷洒,再用干布擦拭桌子" << endl;
}
};
统一调用不同对象的方法:
int main() {
// 创建家具对象数组(通过基类指针实现多态)
Furniture* furnitureList[] = {new Sofa(), new Chair(), new Table()};
// 遍历调用清洁方法
for (auto* item : furnitureList) {
item->clean(); // 同一接口,根据对象实际类型执行不同实现
}
// 释放内存(避免内存泄漏)
for (auto* item : furnitureList) {
delete item;
}
return 0;
}
以上代码将会输出:
用吸尘器清洁沙发
用湿布擦拭椅子
先用清洁剂喷洒,再用干布擦拭桌子
关键点:
- 接口统一:基类(
Furniture
)通过纯虚函数clean()
定义统一接口。 - 动态绑定:子类(
Sofa
、Chair
、Table
)重写clean()
方法,运行时根据对象实际类型调用对应实现(需通过基类指针/引用实现)。 - 抽象类:包含纯虚函数的类(如
Furniture
)无法实例化,仅作为基类被继承。
总结
通过家具的例子,带你理解 C++ 面向对象编程(OOP)的四大核心概念:
类与对象:用类组织代码(模板),用对象表示具体事物(实例)。
封装:隐藏内部细节,通过公共接口控制访问,保证数据安全。
继承:复用父类代码,扩展子类功能,实现代码重用与层次化设计。
多态:统一接口定义,不同类实现不同行为,提高代码灵活性和可维护性。
C++ 与 Python 的差异提示:
C++
需要显式声明访问权限(private
/public
),Python
通过双下划线(__
)模拟私有属性。C++
继承需指定继承方式(如public
),Python
直接在类名后括号中指定父类。C++
多态依赖虚函数(virtual
)和基类指针,Python
通过动态类型自动实现多态。C++
的构造函数必须与类同名,Python
的构造函数使用__init__
方法。
附:C++的三种继承方式
继承类型 | 基类public 成员 | 基类protected 成员 | 基类private 成员 |
---|---|---|---|
公有继承public | 子类中为public | 子类中为protected | 子类中不可见 |
保护继承protected | 子类中为protected | 子类中为protected | 子类中不可见 |
私有继承private | 子类中为private | 子类中为private | 子类中不可见 |
C++中,类的继承语法如下:
class [子类名字] : [继承类型] [父类名字] { /* ... */ };
// 例如:
class Mammal : public Animal { /* ... */ };
/*
* 其中,子类名字为Mammal,
* 继承类型为public(即公有继承),
* 父类名字为Animal
*/