c++总复习
1. 什么是继承性以及 C++ 中如何实现继承
(1)继承性的概念
继承(Inheritance)是面向对象编程中的一个重要特性,它允许创建新的类(称为派生类或子类)从已有的类(称为基类或父类)那里获取成员变量和成员函数。派生类继承了基类的特性,并且可以在此基础上进行扩展,添加新的成员或者重写基类中的某些函数,以满足特定的应用需求。形象地说,就好比子女继承父母的某些特征,并在此基础上发展出自己独特的个性一样。
例如,有一个基类Animal
,它具有成员变量表示动物的年龄、体重等通用属性,以及成员函数如eat()
表示吃东西这个行为。然后可以创建派生类Dog
(狗类)从Animal
类继承,Dog
类除了拥有从Animal
类继承来的这些基本特征外,还可以添加自己特有的成员,比如成员变量表示狗的品种,成员函数bark()
表示狗叫的行为。
(2)C++ 中实现继承的语法
在 C++ 中,通过以下语法来实现继承:
class 派生类名 : 访问限定符 基类名 {
// 派生类新添加的成员声明
};
其中,访问限定符可以是public
(公有继承)、private
(私有继承)或protected
(保护继承),不同的访问限定符决定了基类成员在派生类中的访问权限以及通过派生类对象在外部对基类继承成员的访问情况,具体如下:
- 公有继承(
public
):- 基类的
public
成员在派生类中仍然是public
成员,可以被派生类的对象以及外部代码通过派生类对象直接访问(前提是在合适的访问范围内)。 - 基类的
protected
成员在派生类中依然是protected
成员,可在派生类内部访问,但不能被外部代码通过派生类对象访问。 - 基类的
private
成员在派生类中不可直接访问,只能通过基类提供的public
或protected
的接口(如成员函数)来间接访问。
例如:
- 基类的
class Animal {
public:
int age;
void eat() {
std::cout << "Animal is eating." << std::endl;
}
};
class Dog : public Animal {
public:
void bark() {
std::cout << "Dog is barking." << std::endl;
}
};
在上述代码中,Dog
类公有继承了Animal
类,Dog
类的对象可以直接访问从Animal
类继承来的public
成员变量age
以及调用public
成员函数eat()
,同时Dog
类自身又新增了成员函数bark()
。
- 私有继承(
private
):- 基类的
public
和protected
成员在派生类中都变为private
成员,只能在派生类内部访问,外部代码不能通过派生类对象访问这些从基类继承来的成员。
例如:
- 基类的
class Animal {
public:
int age;
void eat() {
std::cout << "Animal is eating." << std::endl;
}
};
class Cat : private Animal {
public:
void meow() {
// 在派生类内部可以访问从基类继承来的成员
age = 5;
eat();
std::cout << "Cat is meowing." << stdendl;
}
};
这里Cat
类私有继承了Animal
类,虽然在Cat
类内部能访问Animal
类的age
和eat()
,但外部代码不能通过Cat
类对象去访问它们,比如下面这样的代码就是错误的:
Cat myCat;
myCat.age; // 编译错误,因为在私有继承下,基类的public成员在派生类中变为private,外部不可访问
myCat.eat(); // 编译错误,同理
- 保护继承(
protected
):- 基类的
public
和protected
成员在派生类中都变为protected
成员,它们能在派生类内部访问,也能在派生类的派生类(即孙子类等更深层次的派生类)中访问,但外部代码不能通过派生类对象访问这些成员。
- 基类的
例如:
class Animal {
public:
int age;
void eat() {
std::cout << "Animal is eating." << std::endl;
}
};
class Tiger : protected Animal {
public:
void roar() {
age = 10;
eat();
std::cout << "Tiger is roaring." << std::endl;
}
};
在Tiger
类中可以访问从Animal
类继承来的成员,不过外部代码不能直接通过Tiger
类对象去访问它们,而且如果再有从Tiger
类派生的类,依然可以访问这些变为protected
的从Animal
类继承来的成员(如果在合适的访问控制规则下)。
2. 继承的好处和注意事项
(1)继承的好处
- 代码复用:
这是继承最显著的好处之一。通过继承基类,派生类无需重新编写基类中已有的代码,就能直接使用那些继承过来的成员变量和成员函数,大大减少了代码的重复编写,提高了开发效率。例如,在开发一个图形绘制系统时,如果已经有了一个表示基本图形的基类Shape
,里面有计算面积、周长等通用的函数以及表示图形位置等基本属性的成员变量,后续创建如Circle
(圆形)、Rectangle
(矩形)等具体图形类时,通过继承Shape
类,就不用再重新写那些通用的代码逻辑了,只需关注各自图形特有的属性和行为的实现即可。 - 层次化建模:
便于对现实世界中的对象关系进行建模,构建出清晰的类层次结构。以动物世界为例,可以先创建一个抽象的Animal
基类来表示所有动物共有的特征,然后派生出各种具体的动物类,如Mammal
(哺乳动物类)、Bird
(鸟类)等,这些类又可以进一步派生出更具体的动物子类,像Dog
、Cat
从Mammal
类派生,Eagle
、Sparrow
从Bird
类派生,这样的层次结构使得代码的组织和理解更加直观,符合人们对现实世界分类概念的认知。 - 多态性的基础:
继承是实现多态(Polymorphism)的前提条件之一。多态允许不同派生类的对象对同一函数调用做出不同的响应,增强了程序的灵活性和可扩展性。比如,定义一个基类指针或引用,可以指向不同派生类的对象,当通过这个指针或引用调用虚函数(虚函数是实现多态的关键语法元素,常和继承结合使用)时,会根据对象的实际类型来决定调用哪个派生类中重写后的函数版本,这在很多需要灵活处理不同类型对象但又有统一接口调用的场景中非常有用,例如图形绘制系统中,统一通过基类指针调用绘制函数,不同图形类(派生类)各自实现绘制逻辑,就能方便地绘制出各种不同的图形。
(2)继承的注意事项
- 继承层次不宜过深过复杂:
如果继承层次过多、过于复杂,代码的可读性和可维护性会大大降低。理解一个深层次继承关系下派生类的行为可能需要追溯多个基类的实现,容易造成混乱,也使得调试代码变得困难。例如,一个类经过七八层甚至更多层的派生,要弄清楚某个成员变量的最终来源以及其访问权限的变化情况等就会很麻烦,所以在设计类层次结构时,应尽量保持合理的继承深度,遵循简单清晰的原则。 - 注意访问权限控制:
要谨慎选择继承时的访问限定符(public
、private
、protected
),因为不同的限定符决定了基类成员在派生类以及外部的可访问性,错误的选择可能导致代码出现逻辑错误或者不符合预期的封装性。比如,如果希望派生类能向外暴露基类的某些公共接口,却错误地使用了私有继承,就会使得这些接口无法被外部正常访问,破坏了原本的设计意图。 - 避免不合理的继承关系:
不是所有类之间都适合建立继承关系,要遵循 “is-a”(是一种)的语义原则来确定继承关系是否合理。例如,不能仅仅因为某个类和另一个类有一些共同的属性或方法就建立继承关系,如果不符合现实世界或逻辑上的分类概念,会让代码结构变得混乱且难以理解。比如,有一个Car
(汽车)类和一个Wheel
(车轮)类,虽然汽车包含车轮,但不能说汽车 “是一种” 车轮,所以它们之间不适合用继承关系来表示,而用组合(Car
类可以有成员变量是Wheel
类型的对象,表示汽车包含车轮这种组成关系)等其他方式来建模更合适。