在C++中,继承是面向对象编程的重要特性之一。它允许一个类(我们称之为子类或派生类)通过“继承”另一个类(我们称之为父类或基类)的成员(成员变量和成员函数),从而复用已有代码并扩展功能。想象一下,如果我们有一个“动物”类,里面包含“吃饭”“睡觉”这些通用行为,那么“狗”“猫”等具体动物类就可以直接继承“动物”类,无需重复编写这些通用代码。
一、如何定义父类和子类¶
首先,我们需要定义一个父类,再定义一个子类来继承它。以“动物”和“狗”为例:
// 父类:Animal(动物)
class Animal {
public:
string name; // 动物的名字(公开成员,子类可直接访问)
int age; // 动物的年龄(公开成员,子类可直接访问)
// 吃饭的方法(公开成员函数,子类可直接调用)
void eat() {
cout << name << " is eating." << endl;
}
// 睡觉的方法(公开成员函数,子类可直接调用)
void sleep() {
cout << name << " is sleeping." << endl;
}
private:
int weight; // 体重(私有成员,子类不可直接访问)
public:
// 通过公开接口设置体重(间接访问私有成员)
void setWeight(int w) {
weight = w;
}
int getWeight() {
return weight;
}
};
// 子类:Dog(狗),继承自Animal
class Dog : public Animal { // public继承:父类成员在子类中的访问权限保持原样
public:
// 狗特有的方法(子类新增的成员)
void bark() {
cout << name << " is barking!" << endl;
}
};
二、子类如何继承父类的成员变量¶
父类的成员变量分为 公开(public)、保护(protected) 和 私有(private) 三种,它们在子类中的访问规则不同:
- public 成员变量:子类可以直接访问。
例如:Dog类继承了Animal的name和age,所以在Dog中可以直接使用name和age。
Dog myDog;
myDog.name = "Buddy"; // 直接访问父类的public成员
myDog.age = 3; // 直接访问父类的public成员
-
private 成员变量:子类不能直接访问,但可以通过父类提供的公开接口(如
setWeight/getWeight)间接操作。
例如:Animal的weight是 private,子类Dog不能直接写myDog.weight,但可以调用myDog.setWeight(15)来设置体重。 -
protected 成员变量:仅子类和子类的子类可以直接访问(初学者暂时不用深入,了解即可)。
三、子类如何继承父类的成员函数¶
与成员变量类似,父类的成员函数也分不同权限,子类的访问规则如下:
-
public 成员函数:子类可以直接调用。
例如:Animal的eat()和sleep()是 public,子类Dog可以直接调用myDog.eat()或myDog.sleep()。 -
private 成员函数:子类不能直接调用,需通过父类的公开接口间接调用(同私有变量逻辑)。
-
protected 成员函数:仅子类可以直接调用(初学者了解即可)。
int main() {
Dog myDog;
myDog.name = "Buddy";
myDog.age = 3;
myDog.eat(); // 调用父类的public成员函数(正常输出)
myDog.bark(); // 调用子类新增的成员函数(输出 "Buddy is barking!")
myDog.sleep(); // 调用父类的public成员函数(正常输出)
myDog.setWeight(15); // 通过父类的接口设置private成员
cout << "Weight: " << myDog.getWeight() << endl; // 输出 15
return 0;
}
四、不同继承方式的影响¶
C++ 有三种继承方式,会影响子类对父类成员的访问权限:
| 继承方式 | 父类 public 成员在子类中的权限 | 父类 protected 成员在子类中的权限 | 父类 private 成员在子类中的权限 |
|---|---|---|---|
| public | public | protected | 不可见(无法直接访问) |
| private | private | private | 不可见 |
| protected | protected | protected | 不可见 |
初学者注意:
- 最常用的是 public 继承(默认不写时也可视为 public,但建议显式写出),此时父类的 public 和 protected 成员在子类中保持原权限,private 成员不可见。
- 其他两种方式(private/protected 继承)通常用于特殊场景(如隐藏父类接口),初学者可暂时忽略。
五、构造函数的继承与初始化¶
父类的构造函数不能被直接继承,但子类构造函数必须先初始化父类部分。例如,Animal 有一个构造函数:
class Animal {
public:
string name;
int age;
Animal(string n, int a) : name(n), age(a) { // 父类构造函数
cout << "Animal " << name << " created." << endl;
}
};
子类 Dog 构造时,需通过初始化列表调用父类构造函数:
class Dog : public Animal {
public:
// 子类构造函数:先调用父类构造函数,再初始化自己
Dog(string n, int a) : Animal(n, a) { // 必须先调用父类构造函数
cout << "Dog " << n << " created." << endl;
}
};
关键点:子类对象创建时,会先执行父类构造函数,再执行子类构造函数。
六、总结¶
继承的核心是 代码复用 和 扩展:
- 子类可以直接继承父类的 public/protected 成员(变量/函数),避免重复编写通用逻辑。
- 父类的 private 成员需通过公开接口间接访问,保证数据封装性。
- 构造函数需显式调用父类构造函数,通过初始化列表完成父类部分的初始化。
通过继承,我们可以用更少的代码实现更复杂的功能,比如“猫”“鸟”等类都可以继承“动物”类,只需添加各自特有的方法(如 Cat 的 catchMouse())。
小练习:尝试定义一个 Cat 类继承 Animal,并添加 catchMouse() 方法,然后创建 Cat 对象测试继承效果。