C++ 继承
面向对象程序设计中最重要的一个概念是继承。继承允许我们依据另一个类来定义一个类,这使得创建和维护一个应用程序变得更容易。这样做,也达到了重用代码功能和提高执行效率的效果。
当创建一个类时,您不需要重新编写新的数据成员和成员函数,只需指定新建的类继承了一个已有的类的成员即可。这个已有的类称为基类,新建的类称为派生类。
继承代表了 is a 关系。例如,哺乳动物是动物,狗是哺乳动物,因此,狗是动物,等等。

代码如下:
class Animal {
// eat() 函数
// sleep() 函数
};
//派生类
class Dog : public Animal {
// bark() 函数
};
基类 & 派生类
一个类可以派生自多个类,这意味着,它可以从多个基类继承数据和函数。定义一个派生类,我们使用一个类派生列表来指定基类。类派生列表以一个或多个基类命名,形式如下:
class derived-class: access-specifier base-class
其中,访问修饰符 access-specifier 是 public、protected 或 private 其中的一个,base-class 是之前定义过的某个类的名称。如果未使用访问修饰符 access-specifier,则默认为 private。
假设有一个基类 Shape,Rectangle 是它的派生类,如下所示:
实例
当上面的代码被编译和执行时,它会产生下列结果:
Total area: 35
访问控制和继承
派生类可以访问基类中所有的非私有成员。因此基类成员如果不想被派生类的成员函数访问,则应在基类中声明为 private。
我们可以根据访问权限总结出不同的访问类型,如下所示:
| 访问 | public | protected | private |
|---|---|---|---|
| 同一个类 | yes | yes | yes |
| 派生类 | yes | yes | no |
| 外部的类 | yes | no | no |
一个派生类继承了所有的基类方法,但下列情况除外:
- 基类的构造函数、析构函数和拷贝构造函数。
- 基类的重载运算符。
- 基类的友元函数。
继承类型
当一个类派生自基类,该基类可以被继承为 public、protected 或 private 几种类型。继承类型是通过上面讲解的访问修饰符 access-specifier 来指定的。
我们几乎不使用 protected 或 private 继承,通常使用 public 继承。当使用不同类型的继承时,遵循以下几个规则:
- 公有继承(public):当一个类派生自公有基类时,基类的公有成员也是派生类的公有成员,基类的保护成员也是派生类的保护成员,基类的私有成员不能直接被派生类访问,但是可以通过调用基类的公有和保护成员来访问。
- 保护继承(protected): 当一个类派生自保护基类时,基类的公有和保护成员将成为派生类的保护成员。
- 私有继承(private):当一个类派生自私有基类时,基类的公有和保护成员将成为派生类的私有成员。
多继承
多继承即一个子类可以有多个父类,它继承了多个父类的特性。
C++ 类可以从多个类继承成员,语法如下:
class <派生类名>:<继承方式1><基类名1>,<继承方式2><基类名2>,…
{
<派生类类体>
};
其中,访问修饰符继承方式是 public、protected 或 private 其中的一个,用来修饰每个基类,各个基类之间用逗号分隔,如上所示。现在让我们一起看看下面的实例:
实例
当上面的代码被编译和执行时,它会产生下列结果:
Total area: 35 Total paint cost: $2450
孙程耿
951***452@qq.com
另外多继承(环状继承),A->D, B->D, C->(A,B),例如:
class D{......}; class B: public D{......}; class A: public D{......}; class C: public B, public A{.....};这个继承会使D创建两个对象,要解决上面问题就要用虚拟继承格式
格式:class 类名: virtual 继承方式 父类名
class D{......}; class B: virtual public D{......}; class A: virtual public D{......}; class C: public B, public A{.....};虚继承--(在创建对象的时候会创建一个虚表)在创建父类对象的时候
实例:
#include <iostream> using namespace std; //基类 class D { public: D(){cout<<"D()"<<endl;} ~D(){cout<<"~D()"<<endl;} protected: int d; }; class B:virtual public D { public: B(){cout<<"B()"<<endl;} ~B(){cout<<"~B()"<<endl;} protected: int b; }; class A:virtual public D { public: A(){cout<<"A()"<<endl;} ~A(){cout<<"~A()"<<endl;} protected: int a; }; class C:public B, public A { public: C(){cout<<"C()"<<endl;} ~C(){cout<<"~C()"<<endl;} protected: int c; }; int main() { cout << "Hello World!" << endl; C c; //D, B, A ,C cout<<sizeof(c)<<endl; return 0; }孙程耿
951***452@qq.com
yyy
jas***an999@qq.com
参考地址
为什么子类的构造函数中会出现在初始化列表中呢?原因在于子类能够从基类继承的内容限制上。
我们知道,一个派生类继承了所有的基类方法,但下列情况除外:
因此,我们不能够在子类的成员函数体中调用基类的构造函数来为成员变量进行初始化。例如这样子是不可以的
#include <iostream> using namespace std; // 基类 class Shape { public: Shape(int w,int h) { width=w; height=h; } protected: int width; int height; }; // 派生类 class Rectangle: public Shape { public: Rectangle(int a,int b) { Shape(a,b); } };但我们可以把基类的构造函数放在子类构造函数的初始化列表上,以此实现调用基类的构造函数来为子类从基类继承的成员变量初始化。
#include <iostream> using namespace std; // 基类 class Shape { public: Shape(int w,int h) { width=w; height=h; } protected: int width; int height; }; // 派生类 class Rectangle: public Shape { public: Rectangle(int a,int b):Shape(a,b) { } };yyy
jas***an999@qq.com
参考地址
SunnyClear
238***9834@qq.com
关于构造函数初始化列表的执行顺序进行补充:
#include<iostream> using namespace std; class A { public: A() { cout << "call A()" << endl; } }; class B :A { public: B(int val) : A(), value(val) { val = 0; // 重新赋值 cout << "call B()" << endl; cout << val << endl; } private: int value; }; int main() { B b(10); return 0; } /* * 结果如下 * call A() * call B() * 0 * 说明放在初始化列表的部分在构造函数之前执行 */SunnyClear
238***9834@qq.com
小鹤
799***970@qq.com
派生类在继承基类的成员变量时,会单独开辟一块内存保存基类的成员变量,因此派生类自己的成员变量即使和基类的成员变量重名,但是也不会引起冲突。如下代码:
#include <iostream> using namespace std; //基类 class A { public: A(){n = 0;}; ~A(){}; int getA(){ return n;}; void setA(int t){ n = t;}; private: int n; }; //派生类 class B :public A { public: B(){ n = 0;}; ~B(){}; int getB(){ return n;}; void setB(int t){ n = t;}; private: int n; }; int main(int argc, char * argv[]) { B b; b.setA(10); //设置基类的成员变量n cout<<"A::n "<<b.getA()<<endl; cout<<"B::n "<<b.getB()<<endl; b.setB(9); //设置派生类的成员变量n cout<<"A::n "<<b.getA()<<endl; cout<<"B::n "<<b.getB()<<endl; return 0; }结果如下:
小鹤
799***970@qq.com
LiuJW0
308***9974@qq.com
构造函数调用顺序:基类 > 成员类 > 派生类;
多继承派生类: 基类构造顺序 依照 基类继承顺序调用
类成员:依照 类成员对象 定义顺序 调用成员类构造函数
#include <iostream> using namespace std; class Shape { // 基类 Shape public: Shape() { cout << "Shape" << endl; } ~Shape() { cout << "~Shape" << endl; } }; class PaintCost { // 基类 PaintCost public: PaintCost() { cout << "PaintCost" << endl; } ~PaintCost() { cout << "~PaintCost" << endl; } }; // 派生类 class Rectangle : public Shape, public PaintCost //基类构造顺序 依照 继承顺序 { public: Rectangle() :b(), a(), Shape(), PaintCost(){ cout << "Rectangle" << endl; } ~Rectangle() { cout << "~Rectangle" << endl; } PaintCost b; // 类成员变量构造顺序 依照 变量定义顺序 Shape a; }; int main(void) { Rectangle Rect; return 0; }结果:
LiuJW0
308***9974@qq.com
ShenyanWu
208***1347@qq.com
补充二楼说法,派生类是会继承父类的构造方法的,但在继承构造方法时有一些注意事项,这在 C++11 以及之后的标准中得到了支持。具体情况取决于派生类是否定义了自己的构造方法以及父类构造方法的可访问性。
#define _CRT_SECURE_NO_WARNINGS #include <iostream> using namespace std; class Base { public: Base() { num = 5; cout << "BaseConstructorCall:" << protectedMember << endl; } Base(int n) { num = n; cout << "BaseConstructorCallnum:" << protectedMember << endl; } ~Base() { cout << "BaseDestructorCall:" << protectedMember << endl; } protected: int protectedMember = 0; int num; }; class Derived1 : public Base { public: using Base::Base; // 这行声明继承父类的构造方法 void modifyMember(int newValue) { protectedMember = newValue; // 修改父类的成员 cout << "Derived1:" << num << endl; } }; class Derived2 : public Base { public: void modifyMember(int newValue) { protectedMember = newValue; // 修改同一个父类的成员 cout << "Derived2:" << num << endl; } }; int main() { Derived1 d1(88); Derived2 d2; d1.modifyMember(10); d2.modifyMember(20); return 0; }可以在输出中看到
主要是需要使用基类构造定义:
ShenyanWu
208***1347@qq.com
小凡
392***216@qq.com
多继承与初始化列表的结合使用:
#include <iostream> using namespace std; class Value{ public: Value(int x){ cout << "开始构造Value" << endl; value = x; } int getValue() { return value; } ~Value() { cout << "开始析构Value" << endl; } private: int value; }; class Num{ public: Num(int y) { cout << "开始构造Num" << endl; num = y; } int getNum() { return num; } ~Num() { cout << "开始析构Num" << endl; } private: int num; }; class Test : public Value,public Num{ public: Test(int x,int y):Value(x),Num(y) { cout << "开始构造Test" << endl; } getAllInfo() { cout << "value:" << getValue() << endl; cout << "num:" << getNum() << endl; } ~Test() { cout << "开始析构Test" << endl; } }; int main(){ Test t(10,20); t.getAllInfo(); return 0; }输出:
小凡
392***216@qq.com
不甘心
102***0588@qq.com
通过调用基类的公有成员函数来访问基类的私有成员变量:
#include <iostream> using namespace std; class Base { private: int privateVar; public: Base() : privateVar(10) {} void accessPrivateVar() { cout << "Base privateVar: " << privateVar << endl; } }; class Derived : public Base { public: void accessBasePrivateVar() { // 通过调用基类的公有成员函数来访问基类的私有成员变量 accessPrivateVar(); } }; int main() { Derived d; d.accessBasePrivateVar(); // 输出 Base privateVar: 10 return 0; }不甘心
102***0588@qq.com
kebing
222***5090@qq.com
以下是对ShenyanWu的笔记的补充,除了使用:
using Base::Base; // 这行声明继承父类的构造方法 void modifyMember(int newValue) { protectedMember = newValue; // 修改父类的成员 cout << "Derived1:" << num << endl; }还可以直接使用列表调用基类的构造函数,即改为:
class Derived1 : public Base { public: Derived1(int n):Base(n) {//通过列表调用父类的构造函数 } void modifyMember(int newValue) { protectedMember = newValue; // 修改父类的成员 cout << "Derived1:" << num << endl; } };kebing
222***5090@qq.com