一、继承的定义
1. 基本概念
- 继承:允许派生类(子类)复用基类(父类)的成员,并添加或覆盖成员。
- 语法:
class 派生类名 : 访问权限 基类名 { ... }; - 示例:
class Base { public: int a; void func() { cout << "Base::func()" << endl; } }; class Derived : public Base { // 公有继承 public: int b; void derivedFunc() { cout << "Derived::func()" << endl; } };
二、继承基类成员访问方式的变化
1. 访问权限规则
- 公有继承(
public):基类的public和protected成员在派生类中保持原有权限。 - 保护继承(
protected):基类的public和protected成员在派生类中变为protected。 - 私有继承(
private):基类的所有成员在派生类中变为private。
2. 示例
class Base {
public:
int pub;
protected:
int pro;
private:
int pri;
};
// 公有继承
class DerivedPublic : public Base {
public:
void test() {
pub = 1; // 可访问(public)
pro = 2; // 可访问(protected)
// pri = 3; // 错误:private不可访问
}
};
// 私有继承
class DerivedPrivate : private Base {
public:
void test() {
pub = 1; // 变为private
pro = 2; // 变为private
}
};
int main() {
DerivedPublic d;
d.pub = 1; // 可访问(public)
// d.pro = 2; // 错误:protected
}
三、派生类对基类的切片赋值(对象切片)
1. 定义
- 对象切片(Object Slicing):将派生类对象赋值给基类对象时,派生类特有的部分被“切掉”,仅保留基类部分。
- 示例:
class Base { public: int a; }; class Derived : public Base { public: int b; }; int main() { Derived d; d.a = 1; d.b = 2; Base b = d; // 对象切片,b仅保留a的值,b的值丢失 // b.b = 2; // 错误:Base类没有b成员 }
四、派生类的默认成员函数
1. 派生类的构造函数必须调用基类的构造函数初始化基类的那一部分成员。如果基类没有默认
的构造函数,则必须在派生类构造函数的初始化列表阶段显示调用。
2. 派生类的拷贝构造函数必须调用基类的拷贝构造完成基类的拷贝初始化。
3. 派生类的operator=必须要调用基类的operator=完成基类的复制。
4. 派生类的析构函数会在被调用完成后自动调用基类的析构函数清理基类成员。因为这样才能
保证派生类对象先清理派生类成员再清理基类成员的顺序。
5. 派生类对象初始化先调用基类构造再调派生类构造。
6. 派生类对象析构清理先调用派生类析构再调基类的析构。
7. 因为后续一些场景析构函数需要构成重写,重写的条件之一是函数名相同(这个我们后面会讲
解)。那么编译器会对析构函数名进行特殊处理,处理成destrutor(),所以父类析构函数不加
virtual的情况下,子类析构函数和父类析构函数构成隐藏关系。
五、继承中的作用域
- 规则:在继承体系中基类和派生类都有独立的作用域,派生类成员会隐藏基类的同名成员。
- 定义:子类和父类中有同名成员,子类成员将屏蔽父类对同名成员的直接访问,这种情况叫隐藏,也叫重定义。(在子类成员函数中,可以使用 基类::基类成员 来显示访问)
- 注意:需要注意的是如果是成员函数的隐藏,只需要函数名相同就构成隐藏。注意在实际中在继承体系里面最好不要定义同名的成员。
- 示例:
class Base { public: void func() { cout << "Base::func()" << endl; } }; class Derived : public Base { public: void func(int x) { cout << "Derived::func(int)" << endl; } void test() { func(1); // 调用Derived::func(int) Base::func(); // 调用Base::func() } };
六、继承与友元
1. 友元不可继承
- 规则:不能继承友元关系,也就是说基类友元不能访问子类私有和保护成员
- 示例:
class Base { friend void friendFunc(Base& b); private: int a; }; class Derived : public Base { private: int b; }; void friendFunc(Base& b) { b.a = 1; // 可访问 // Derived d; d.b = 2; // 错误:friendFunc不是Derived的友元 }
七、继承与静态成员
1. 静态成员共享
- 规则:基类的静态成员被所有派生类共享,即整个继承体系里面只有一个这样的成员。
- 示例:
class Base { public: static int count; }; int Base::count = 0; class Derived : public Base {}; int main() { Base::count++; Derived::count++; // 共享静态成员 cout << Base::count; // 输出2 }
八、菱形继承问题
1. 问题描述
- 菱形继承:派生类通过多条路径继承同一个基类,导致基类成员重复。
- 示例:
class A { public: int a; }; class B : public A {}; class C : public A {}; class D : public B, public C {}; int main() { D d; // d.a = 1; // 错误:二义性(B::a 还是 C::a?) d.B::a = 1; // 明确指定路径 d.C::a = 2; }
九、虚继承(解决菱形继承)
1. 虚基类
- 语法:在继承时添加
virtual关键字。 - 作用:确保派生类中只包含一个基类子对象。
- 注意:virtual加在直接继承父类的第一个子类上,父类出现了数据冗余,且第一个子类离父类最近。只有出现菱形继承问题的时候才能应用虚继承,其他情况下不要滥用虚继承
- 示例:
class A { public: int a; }; class B : virtual public A {}; // 虚继承 class C : virtual public A {}; // 虚继承 class D : public B, public C {}; int main() { D d; d.a = 1; // 无二义性,A的实例仅一份 }
十、组合(替代继承)
1. 定义
- 组合:将其他类的对象作为成员变量,而非继承。
- 优点:降低耦合,灵活性更高。
- 对比继承:public继承是一种is-a的关系。也就是说每个派生类对象都是一个基类对象。例如一辆车,我们用车类继承了引擎类。组合是一种has-a的关系。假设B组合了A,每个B对象中都有一个A对象。例如一辆车,我们在车类里定义了一个引擎类。
- 注意:实际尽量多去用组合。组合的耦合度低,代码维护性好。虽是这么说,但大家还是习惯用继承。
- 示例:
class Engine { public: void start() { cout << "Engine started." << endl; } }; class Car { private: Engine engine; // 组合 public: void startCar() { engine.start(); } }; int main() { Car car; car.startCar(); // 输出:Engine started. }
1931

被折叠的 条评论
为什么被折叠?



