c++中的继承是类与类之间的关系,是一个很简单很直观的概念,与现实中的继承关系类似,例如儿子可以继承父亲的财产。
继承(Inheritance)可以理解成为一个类从另一个类中获取成员变量和成员函数的过程,例如类B继承于类A,那么B就拥有A的成员变量和成员函数。
在c++中被继承的类称为父类或基类,继承的类称为子类或派生类。"子类"和"父类"通常放在一起称呼,"基类"和"派生类"通常放在一起称呼。派生类除了拥有基类的成员,还可以定义自己的新成员,以增强类的功能。
使用继承的两个典型场景:
- 当你创建的新类与现有类相似,只是多出若干成员变量和成员函数,可使用继承,这样不但会减少代码量,而且新类会拥有基类的所有功能。当你需要创建多个类,它们有很多相似的成员变量或成员函数时,可使用继承,可以将这些类的共同成员提取出来,定义为基类,然后从基类继承,既可以节省代码,也方便后续修改成员。
示例:
class Apple:public Fruit
继承的一般语法为:
class 派生类名: [继承方式] 基类名{
派生类新增加的成员
};
2.继承方式
继承方式限定了基类成员在派生类中的访问权限,包括public(公有的)、private私有的和protected(受保护的)。此项是可选项默认是private。
public、protected、private 这三个关键字既可以修饰类的成员,还可以指定继承方式。
2.1 public、protected、private 修饰类的成员类成员的访问权限由高到低依次为 public --> protected --> private,public 成员可以通过对象来访问,private 成员不能通过对象访问。protected 成员和 private 成员类似,也不能通过对象访问。但是当存在继承关系时,protected 和 private 就不一样了,基类中的 protected 成员可以在派生类中使用,而基类中的 private 成员不能在派生类中使用。
2.2 public、protected、private 指定继承方式不同的继承方式会影响基类成员在派生类中的访问权限
- public继承方式
基类中所有 public 成员在派生类中为 public 属性;基类中所有 protected 成员在派生类中为 protected 属性;基类中所有 private 成员在派生类中不能使用。
- protected继承方式
基类中的所有 public 成员在派生类中为 protected 属性;基类中的所有 protected 成员在派生类中为 protected 属性;基类中的所有 private 成员在派生类中不能使用。
- private继承方式
基类中的所有 public 成员在派生类中均为 private 属性;基类中的所有 protected 成员在派生类中均为 private 属性;基类中的所有 private 成员在派生类中不能使用。 3.继承中的构造和析构
在c++的类继承中,不管以什么样的方式继承,建立对象时,首先调用基类的构造函数,然后在调用下一个派生类的构造函数,以此类推;而析构对象时,其顺序正好与构造相反。
例子:
#include#include using namespace std; using namespace cv; class Fruit { public: Fruit() { cout << "Fruit默认构造函数调用" << endl; } ~Fruit() { cout << "Fruit析构" << endl; } }; class Apple : public Fruit { public: Apple() { cout << "Apple默认构造函数调用" << endl; } ~Apple() { cout << "Apple析构" << endl; } }; void test(){ Apple apple; } int main() { test();//Fruit构造->Apple构造->Apple析构->Fruit析构 waitKey(1000); return 0; }
结果
子类是不会继承父类的构造函数和析构函数,父类没有默认构造函数,子类也不能有。
例子:
当父类构造函数有参数时,需要在子类初始化列表(参数列表)中显示调用父类构造函数。
例子
#include#include using namespace std; using namespace cv; class Fruit { public: Fruit(int a) { this->m_A = a; cout << "我是Fruit的有参构造" << endl; } int m_A; }; class Apple : public Fruit { public: //Apple() {};//因为基类中没有默认的构造函数 所以会报错 Apple(int a) :Fruit(a)//利用初始化列表的方式 显示调用 传值给基类有参构造 { } }; void test(){ Apple apple(5); cout << apple.m_A << endl; } int main() { test(); waitKey(1000); return 0; }
结果
如果派生类中的成员(包括成员变量和成员函数)和基类中的成员重名,那么就会遮蔽从基类继承过来的成员。所谓遮蔽就是在派生类中使用该成员(包括在定义派生类时使用,也包括通过派生类对象访问该成员)时,实际上使用的是派生类新增的成员,而不是从基类继承来的。
例子:
#includeusing namespace std; //基类 class Fruit { public: Fruit(int fruitid); void getFruitId(); protected: int fruit; private: }; void Fruit::getFruitId() { cout << "我是水果第" << fruit << "号" << endl; }; Fruit::Fruit(int fruitid) { this->fruit = fruitid; } //派生类 class Apple : public Fruit { public: void getFruitId();//遮蔽基类的getFruitId Apple(int a) :Fruit(a) {}; }; void Apple::getFruitId() { cout << "我是苹果第" << fruit << "号" << endl; } int main() { Apple apple(2); //使用的是派生类新增的成员函数,而不是从基类继承的 apple.getFruitId(); //使用的是从基类继承来的成员函数 apple.Fruit::getFruitId(); }
结果:
基类的成员函数和派生类成员函数不构成重载
基类的成员和派生类成员的名字一样时会造成遮蔽,这句话对于成员变量很好理解,对于成员函数时要引起注意,不管函数的参数如何,只要名字一样造成就会造成遮蔽。换句话来讲,基类成员函数和派生类成员函数不会构成重载,如果派生类有同名函数,那么就会遮蔽基类中的所有的同名函数,不管它们的参数是否一样。
图1 菱形继承示意图
注意:
虚继承解决了在菱形继承体系里面子类对象包含多份父类对象的数据冗余并且浪费空间的问题。虚继承看起来结构较为复杂,一般不得已不要定义菱形结构的虚继承体系结构,因为使用虚继承虽然解决数据冗余问题但同时也带来了性能上的损耗,得不偿失。



