C++ 中的面向对象三大特性是:封装(Encapsulation)、继承(Inheritance)、多态(Polymorphism)。这三者构成了面向对象编程(OOP)的核心理念。
封装
封装是将数据和操作数据的函数绑定在一起,并对外部隐藏实现细节,只暴露必要的接口。这种机制有助于提高代码的安全性和可维护性。实际上之前的学习,基本上都属于封装的体现。
通过 private、protected、public 控制访问权限,以限制外部直接访问内部状态,并提供 getter/setter 函数访问或修改私有数据。
代码如下:
class Student {
private:
string _name;
int _age;
public:
Student(const string& name, int age);
void set_name(const string& name);
void set_age(int age);
string get_name();
int get_age();
};
Student::Student(const std::string &name, int age) {
this->_name = name;
this->_age = age;
}
void Student::set_name(const std::string &name) {
this->_name = name;
}
string Student::get_name() {
return this->_name;
}
void Student::set_age(int age) {
this->_age = age;
}
int Student::get_age() {
return this->_age;
}
封装的核心是 隐藏细节,对外提供接口,有助于防止非法访问,提高模块独立性。
继承
C++ 中的继承是类与类之间的关系,是一个很简单很直观的概念,与现实世界中的继承类似,例如儿子继承父亲的财产。
继承是指在已存在类的基础上创建新类,这个新类就被称为 派生类。被继承的类称为 父类 或 基类,继承的类称为 子类 或 派生类。子类 和 父类 通常放在一起称呼,基类 和 派生类 通常放在一起称呼。
在 C++ 中,派生(Derive)和继承是一个概念,只是站的角度不同。继承是儿子接收父亲的产业,派生是父亲把产业传承给儿子。
例如,人和狗都属于动物,因此,我们可以定义一个 Animal 类作为父类,Human 与 Dog 作为子类;同理,学生和老师都属于人的职业,因此,我们可以定义 Human 类作为父类,Student 与 Teacher 作为子类,如下图所示:

继承的基本语法
子类(派生类)的声明方法如下:
class 子类名: 继承方式 父类名 {
派生类新增的成员
};
继承方式包括:
public:公有继承;private:私有继承(默认);protected:保护继承;
C++ 中继承关系非常复杂,3 种继承方式下又有 3 种访问限定符,那一共有 9 种继承方式,这也是 C++ 设计的一大缺陷。其实我们在使用中 99% 都使用的公有继承,几乎很少使用 protected/private 继承,也不提倡使用 protected/private 继承,因为 protected/private 继承下来的成员都只能在派生类的类中使用,实际中继承维护性不强。因此,为了降低学习难度,我们只讲 public 公有继承的方式。
继承(Inheritance)可以理解为一个类从另一个类获取成员变量和成员函数的过程。例如,Human 与 Dog 类继承于 Animal 类,也就是 Animal 类的子类,那么 Human 类与 Dog 类就拥有 Animal 的成员变量和成员函数,示例如下:
#include <iostream>
using namespace std;
// 父类
class Animal {
public:
int eyes;
int legs;
void eat() {
cout << "吃饭" << endl;
}
void sleep() {
cout << "睡觉" << endl;
}
};
// Animal 子类 1
class Human: public Animal {};
// Animal 子类 2
class Dog: public Animal {};
int main() {
Animal obj_1;
obj_1.eat();
Human obj_2;
obj_2.sleep();
Dog obj_3;
obj_3.legs = 4;
cout << "狗有 " << obj_3.legs << " 条腿" << endl;
return 0;
}
由上述代码可知,子类可以继承父类的全部成员变量和方法,除了构造函数与析构函数
派生类除了拥有基类的成员,还可以定义自己的新成员,以增强类的功能。例如:狗会汪汪叫,人会工作,代码如下:
// Animal 子类 1
class Human: public Animal {
public:
// 在子类中定义新成员
void work() {
cout << "工作" << endl;
}
};
// Animal 子类 2
class Dog: public Animal {
public:
// 在子类中定义新成员
void bark() {
cout << "汪汪汪" << endl;
}
};
protected 受保护权限
以上两段代码中,所有的成员都是写在 public 中的,如果 Animal 父类的成员属性是写在 private 权限下,虽然可以调用公共成员,但是父类的私有属性在子类内部是无法直接访问的,代码如下:
// 父类
class Animal {
private:
int _legs = 0;
public:
// 私有属性 _legs 的 getter 函数
int get_legs() {
return this->_legs;
}
};
// 子类
class Dog: public Animal {
public:
void set_legs(int legs) {
_legs = legs;
}
};
此时可以发现,尽管我们可以在子类 Dog 中调用父类的公共成员 getter/setter 函数来获取或设置父类中的私有属性 legs,但是无法在子类 Dog 的内部调用父类 Animal 的私有属性。
这就涉及到了之前学习的访问控制权限,如下表所示:
| 访问控制关键字 | 权限 | 类与对象内部 | 类与对象外部 | 子类 |
|---|---|---|---|---|
| public | 公共 | 可以访问 | 可以访问 | 可以访问 |
| private | 私有 | 可以访问 | 不可以访问 | 不可以访问 |
| protected | 保护 | 可以访问 | 不可以访问 | 可以访问 |
这里,我们可以看到,私有成员在子类中是无法访问的,因此,可以将父类的访问权限,从私有权限 private 改成保护权限 protected。
代码如下:
#include <iostream>
using namespace std;
// 父类
class Animal {
protected:
int _legs = 0;
public:
// 私有属性 _legs 的 getter 函数
int get_legs() {
return this->_legs;
}
};
// 子类
class Dog: public Animal {
public:
void set_legs(int legs) {
_legs = legs;
}
};
int main() {
Animal obj_1;
Dog obj_2;
cout << "obj_1: " << obj_1.get_legs() << endl;
obj_2.set_legs(2);
cout << "狗有 " << obj_2.get_legs() << " 条腿" << endl;
return 0;
}
继承机制下的构造函数与析构函数执行顺序
子类继承父类后,当创建子类对象时,也会调用父类的构造函数与析构函数。那么,如果子类也创建了自己的构造函数,此时父类与子类的构造和析构函数的执行顺序是谁先谁后呢?
示例如下:
// 父类
class Father {
public:
Father() {
cout << "调用 父类 构造函数" << endl;
}
~Father() {
cout << "调用 子类 析构函数" << endl;
}
};
// 子类
class Son: public Father {
public:
Son() {
cout << "调用 父类 构造函数" << endl;
}
~Son() {
cout << "调用 子类 析构函数" << endl;
}
};
int main() {
// 创建对象
Son obj_1;
return 0;
}
由以上代码运行结果可以得出:继承中先调用父类构造函数,再调用子类构造函数,析构顺序与构造相反。
同名成员
如果子类中的成员(包括成员变量与成员函数)和父类中的成员出现同名的情况,程序是如何处理的呢?
先说结论:
- 子类中的同名成员可以直接访问;
- 父类中的同名成员需要加上作用域前缀,才可以访问。
示例如下:
#include <iostream>
using namespace std;
// 父类
class Father {
public:
int a = 1;
void func() {
cout << "调用 父类 func 函数" << endl;
}
};
// 子类
class Son: public Father {
public:
int a = 2;
void func() {
cout << "调用 子类 func 函数" << endl;
}
};
int main() {
// 创建子类对象
Son obj_1;
// 调用同名属性
cout << "子类中 a: " << obj_1.a << endl;
cout << "父类中 a: " << obj_1.Father::a << endl;
// 修改 a 的值
obj_1.a = 5;
cout << "子类中 a: " << obj_1.a << endl;
cout << "父类中 a: " << obj_1.Father::a << endl;
// 调用同名方法
obj_1.func();
obj_1.Father::func();
return 0;
}
还有一点需要注意的是,子类中定义的函数是不能重载父类中的同名函数的。这是因为父类与子类属于两个不同的作用域,而函数重载要求必须发生在同一作用域中。