概念定义
继承是使用已经编写完毕的类创建新类,新的类具有原有的类的属性和操作,新类可以在原有类上进行修改和增补。
新类也成为派生类/子类
原有类称为基类/父类
定义格式
// 基类
class Animal {
// eat() 函数
// sleep() 函数
};
//派生类
class Dog : public Animal {
// bark() 函数
};
继承方式
当一个类派生自基类,该基类可以被继承为 public、protected 或 private 几种类型。
我们几乎不使用 protected 或 private 继承,通常使用 public 继承。
总结:
- 基类的private成员无论以何种方式继承,在派生类中都是不可见的。如需访问,需要在基类中设定接口访问函数
- 基类成员不想在类外直接被访问,但需要在派生类中能访问,就定义为protected。可以看出保护成员限定符是因继承才出现的。
- 基类成员在派生类中的访问方式==Min(成员在基类中的访问限定符,继承方式)
- 使用关键字class时默认的继承方式是private,使用struct时默认的继承方式是public,不过最好显示的写出继承方式。
继承与重定义
对基类数据成员的重定义
派生类和基类具有相同的数据成员时,派生类将自动屏蔽对基类同名成员的直接访问,也叫隐藏,只能通过(基类::基类成员 )显示访问
class Base{
public:
int x;
};
class Derived:public Base{
public:
int x; //非Base中的x
}
对基类成员函数的重定义
重定义(overwrite):只要子类和父类函数名相同就构成隐藏
class Base{
public:
void show(){
cout<<"Base show"<<endl;
}
};
class Derived:public Base{
public:
void show(){
cout<<"Derived show"<<endl;
}
void show(int n)
{
cout<<"Derived show"<<endl;
}
};
覆盖(override):虚函数
重载(overload):作用域相同,函数名相同,但是形参列表不同,如Derived类中的show和show(int n)
不能被自动继承的成员函数
- 构造函数
- 析构函数
- 运算符重载函数
构造函数
1.派生类不能继承基类的构造函数,需自己定义
2.声明构造函数时,只需对新增成员初始化,对继承来的基类成员调用基类构造函数初始化
3.派生类的构造函数需给基类构造函数传参
4.当基类没有默认构造函数时,需要在派生类初始化列表中调用基类构造函数
class A
{
public:
A(int a):_a(a){}
int _a;
};
class B :public A
{
public:
B(int b,int a):_b(b),A(a) //调用基类构造初始化
{}
int _b;
};
void Test2()
{
B b(10, 100);
cout << b._b <<" "<< b._a << endl; //10 100
}
5.构造函数顺序:基类对象成员–>基类自身–>派生类对象–>派生类自身
class C {
public:
C()
{
cout << "C" << endl;
}
~C()
{
cout << "~C" << endl;
}
};
class A
{
public:
A(int n):_num(n){
cout << "A" << endl;
}
~A()
{
cout << "~A" << endl;
}
int _num;
C c;
};
class D {
public:
D()
{
cout << "D" << endl;
}
~D()
{
cout << "~D" << endl;
}
};
class B :public A
{
public:
B(int n1,int n2):num(n1),A(n2)
{
cout << "B" << endl;
}
~B()
{
cout << "~B" << endl;
}
int num;
D d;
};
继承与友元
友元关系不能被继承
基类友元不能访问子类私有和保护成员
A是B的友元类,C是A的派生类,则C不是B的友元类
继承与静态成员
静态成员,则整个继承体系里面只有一个这样的成员。无论派生出多少个子类,都只有一个static成员实例。
继承与组合的区别
共同点 |
---|
无论是继承还是组合,本质上都是把子对象放在新类型中,二者都使用构造函数的初始化列表去构造这些子对象 |
继承 | 组合 |
---|---|
新类与已存在的类有相同的接口 | 新类内部具有已存在类的功能 |
public继承是一种is-a的关系。也就是说每个派生类对象都是一个基类对象。 | 组合是一种has-a的关系。假设B组合了A,每个B对象中都有一个A对象。 |
派生类和基类间的依赖关系很强,耦合度高 | 对象的内部细节是不可见的组合类之间没有很强的依赖关系,耦合度低。 |
结论:
实际尽量多去用组合。组合的耦合度低,代码维护性好。不过继承也有用武之地的,有
些关系就适合继承那就用继承,另外要实现多态,也必须要继承。类之间的关系可以用
继承,可以用组合,就用组合。
多继承
单继承:一个派生类最多只继承一个基类
多重继承:一个派生类可以继承多个基类
class <派生类名>:<继承方式1><基类名1>,<继承方式2><基类名2>,…
{
<派生类类体>
};
菱形继承:菱形继承是多继承的一种特殊情况。
数据冗余和二义性:
//人类
class Person
{
public :
string _name ; // 姓名
};
//学生类
class Student : public Person
{
protected :
int _num ; //学号
};
//教师类
class Teacher : public Person
{
protected :
int _id ; // 职工编号
};
//助教类
class Assistant : public Student, public Teacher
{
protected :
string _majorCourse ; // 主修课程
};
void Test()
{
Assistant a;
a._name="小王"; //会有二义性无法明确知道访问的是哪一个
// 需要显示指定访问哪个父类的成员可以解决二义性问题,但是数据冗余问题无法解决
a.Student::_name = "xxx";
a.Teacher::_name = "yyy";
}
菱形继承的解决:采用虚基类
虚基类
引入:用于有共同基类的场合
用法:以virtual修饰说明基类 class 子类名: virtual public 父类名
作用:
解决多继承时可能发生的对同一基类继承多次所造成的二义性问题,
注:
虚基类的成员是由最远派生类的构造函数通过调用虚基类的构造函数进行初始化的。
在整个继承结构中,直接或间接继承虚基类的所有派生类,都必须在构造函数的成员初始化表中给出对虚基类的构造函数的调用。如果未列出,则表示调用该虚基类的默认构造函数。
class Person
{
public:
Person(string name):_name(name)
{
cout << "Person" << endl;
}
string _name; // 姓名
};
class Student : virtual public Person
{
public:
Student(int num, string name) :_num(num), Person(name)
{
cout << "Student" << endl;
}
protected:
int _num; //学号
};
//教师类
class Teacher : virtual public Person
{
public:
Teacher(int id, string name) :_id(id), Person(name)
{
cout << "Teacher" << endl;
}
protected:
int _id; // 职工编号
};
//助教类
class Assistant : public Student, public Teacher
{
public:
Assistant(string majorCourse, int id, int num,string name): _majorCourse(majorCourse),Student(id,name),Teacher(num,name),Person(name)
{
cout << "Assistant" << endl;
}
protected:
string _majorCourse; // 主修课程
};
以上代码,调用时先创建Assistant对象,构造函数的执行顺序是先执行虚基类Person的构造,接着按照时声明的继承次序分别调用Student、Teacher构造函数,最后再调用最派生类构造