关键字:继承
继承的概念
继承是类与类之间的关系,是一个很简单很直观的概念,与现实世界中的继承类似,例如儿子继承父亲的财产。
继承(Inheritance)可以理解为一个类从另一个类获取成员变量和成员函数的过程。例如类 B 继承于类 A,那么 B 就拥有 A 的成员变量和成员函数。被继承的类称为父类或基类,继承的类称为子类或派生类。
派生类除了拥有基类的成员,还可以定义自己的新成员,以增强类的功能。
以下是两种典型的使用继承的场景:
1) 当你创建的新类与现有的类相似,只是多出若干成员变量或成员函数时,可以使用继承,这样不但会减少代码量,而且新类会拥有基类的所有功能。
2) 当你需要创建多个类,它们拥有很多相似的成员变量或成员函数时,也可以使用继承。可以将这些类的共同成员提取出来,定义为基类,然后从基类继承,既可以节省代码,也方便后续修改成员。
继承的语法和权限
class People
{
private:
int m_age;
string m_name;
public:
People(int age, string name);
~People();
//void set1(int age, string name);
void show();
};
//*******************以上是父类/基类People**************************
//*******************以下是子类/派生类Student**************************
class Student : public People //Student类公有继承People类
{
private:
int m_id;
public:
Student(int id);
~Student();
//void set2(int id);
void print();
};
继承的权限:基类成员在派生类中的访问权限不得高于继承方式中指定的权限。
1) public继承方式
基类中所有 public 成员在派生类中为 public 属性;
基类中所有 protected 成员在派生类中为 protected 属性;
基类中所有 private 成员在派生类中不能使用。
2) protected继承方式
基类中的所有 public 成员在派生类中为 protected 属性;
基类中的所有 protected 成员在派生类中为 protected 属性;
基类中的所有 private 成员在派生类中不能使用。
3) private继承方式
基类中的所有 public 成员在派生类中均为 private 属性;
基类中的所有 protected 成员在派生类中均为 private 属性;
基类中的所有 private 成员在派生类中不能使用。
继承中的构造与析构
•接上面给出的父类与子类,当父类不提供无参构造函数时,使用初始化列表来给父类构造函数传递参数
•子类的构造函数只能构造自己的成员变量,而从父类继承来的成员变量需要用调用父类的构造函数
•构造函数的调用顺序一定是父类构造函数在前,析构函数相反
Student::Student(int id) :People(20,"sjw")
//父类不提供无参构造函数时,使用初始化列表来给父类构造函数传递参数
{
m_id = id;
cout << "Student constructor" << endl;
}
继承与组合混搭情况下,构造和析构调用原则: 先构造父类,再构造成员变量、最后构造自己
先析构自己,在析构成员变量、最后析构父类
//先构造的对象,后释放
继承中的同名成员变量处理方法 :
1、当子类成员变量与父类成员变量同名时
2、子类依然从父类继承同名成员
3、在子类中通过作用域分辨符::进行同名成员区分(在派生类中使用基类的同名成员,显式地使用类名限定符)
class B :public A
{
public:
int m_b; //子类中存在与父类中同名的成员变量与函数时
int m_c; //同样继承父类,不覆盖
void print()
{
cout << "BBBBBBBBBBBBBBBBBBBBBBB" << endl;
}
};
/*********************以下是主函数中调用的方法***********************************/
//需调用父类的同名成员变量或函数时,加作用域限定符即可
cout << &b.A::m_b << endl;
cout << &b.m_b << endl; //不加作用域调用时,默认调用子类自己的成员变量
继承中的同名成员变量处理方法 :
1、当子类成员变量与父类成员变量同名时
2、子类依然从父类继承同名成员
3、在子类中通过作用域分辨符::进行同名成员区分(在派生类中使用基类的同名成员,显式地使用类名限定符)
class B :public A
{
public:
int m_b; //子类中存在与父类中同名的成员变量与函数时
int m_c; //同样继承父类,不覆盖
void print()
{
cout << "BBBBBBBBBBBBBBBBBBBBBBB" << endl;
}
};
/*********************以下是主函数中调用的方法***********************************/
//需调用父类的同名成员变量或函数时,加作用域限定符即可
cout << &b.A::m_b << endl;
cout << &b.m_b << endl; //不加作用域调用时,默认调用子类自己的成员变量
多继承
以上提到的都是用单继承做举例,下面介绍多继承,用一段简单的代码解释:
class A
{
public:
int m_a;
int m_b;
static int count; //类内定义
void print()
{
cout << "AAAAAAAAAAAAAAAAAAAAA" << endl;
}
};
int A::count = 666; //静态成员变量一定要在类外初始化
/****************************************************************************/
cout << b.count << endl; //子类同样继承了父类的静态成员变量
A a;
cout << a.countb << endl; //父类无法访问子类的静态成员变量
•多个基类的派生类构造函数可以用初始式调用基类构造函数初始化数据成员
•执行顺序与单继承构造函数情况类似。多个直接基类构造函数执行顺序取决于定义派生类时指定的各个继承基类的顺序。与初始化列表无关。
• 一个派生类对象拥有多个直接或间接基类的成员。不同名成员访问不会出现二义性。如果不同的基类有同名成员,派生类对象访问时应该加以识别。
如果一个派生类从多个基类派生,而这些基类又有一个共同的基类,则在对该基类中声明的名字进行访问时,可能产生二义性
解决二义性的方法:虚继承
•要使这个公共基类在派生类中只产生一个子对象,必须对这个基类声明为虚继承,使这个基类成为虚基类。
• 虚继承声明使用关键字 virtual
class A1 :virtual public A
{
public:
int m_a1;
};
class A2 : virtual public A
{
public:
int m_a2;
};
可以通过打印地址的方式查看虚继承带来的变化,定义对象后原来连续的地址空间因为加入虚指针而发生改变,当成员变量增多时能极大地节省空间。
C++的向上转型和兼容性原则
向上转型:
类其实也是一种数据类型,也可以发生数据类型转换,不过这种转换只有在基类和派生类之间才有意义,并且只能将派生类赋值给基类,包括将派生类对象赋值给基类对象、将派生类指针赋值给基类指针、将派生类引用赋值给基类引用,这在 C++ 中称为向上转型(Upcasting)。相应地,将基类赋值给派生类称为向下转型(Downcasting)。
向上转型非常安全,可以由编译器自动完成;向下转型有风险,需要程序员手动干预。
兼容性原则:
•子类对象可以当作父类对象使用
•子类对象可以直接赋值给父类对象
•子类对象可以直接初始化父类对象
•父类指针可以直接指向子类对象
•父类引用可以直接引用子类对象
在替代之后,派生类对象就可以作为基类的对象使用,但是只能使用从基类继承的成员。
类型兼容规则是多态性的重要基础之一
小结
• 单继承的派生类只有一个基类。多继承的派生类有多个基类。
• 派生类对基类成员的访问由继承方式和成员性质决定。
• 创建派生类对象时,先调用基类构造函数初始化派生类中的基类成员,调用析构函数的次序和调用构造函数的次序相反。
• C++提供虚继承机制,防止类继承关系中成员访问的二义性