一、继承的概念和定义
1.什么是继承?
继承,顾名思义:就和现实生活中,孩子继承父母的东西有点类似。比如,你父亲的财产,你可以继承下来,你就可以使用父亲的钱。
官方一点的介绍:
继承 (inheritance) 机制是面向对象程序设计 使代码可以复用 的最重要的手段,它允许程序员在 保 持原有类特性的基础上进行扩展 ,增加功能,这样产生新的类,称派生类。继承 呈现了面向对象 程序设计的层次结构 ,体现了由简单到复杂的认知过程。以前我们接触的复用都是函数复用, 继 承是类设计层次的复用
class 派生类的名字 :(冒号) 继承方式 基类的名字{//主体};
下面是一个继承:apple类没有P()函数,但是它继承了Fruit,所以apple也相当于有了P()类的P()函数。所以使用apple类的对象,调用P()函数,打印出了Fruit;
2.继承类的继承方式和访问限定符
2.1有三种继承方式:
public继承,protected继承和private继承
他们的访问权限:
public>protected>private
2.2继承类访问基类成员的权限
巧记这个权限就是:小小取小,私有特例
基类和派生类的权限,谁的权限小,继承后就是那个权限。对于基类是private的,子类继承不了。
总结:
1.基类private成员无论以什么方式继承到派生类中都是不可见的。这里的不可见是指基类的私有成员还是被继承到了派生类对象中,但是语法上限制派生类对象不管在类里面还是类外面都不能去访问它。
2.基类private成员在派生类中不能被访问,如果基类成员不想在派生类外直接被访问,但需要在派生类中访问,就定义为protected。可以看出保护成员限定符是因继承才出现的。
3.基类的私有成员在子类都是不可见;基类的其他成员在子类的访问方式就是访问限定符和继承方式中权限更小的那个(权限排序:public>protected>private)。
4.使用关键字class时默认的继承方式是private,使用struct时默认的继承方式是public,但最好显式地写出继承方式。
二、基类和派生类
1.基类和派生类对象赋值转换
class Fruit
{
public:
void P()
{
cout << "Fruit" << endl;
}
protected:
string color;
string name;
};
class apple :public Fruit
{
public:
protected:
int size;
string shape;
};
由上面报错行,我们可以看出。基类不能给派生类赋值。(父类不能给子类赋值)。但是子类可以给父类赋值。其实也有道理的。因为子类对父类进行扩展,拥有父类没有的成员。
而子类可以给父类赋值,也是同样的道理。父类有的子类继承了,所以子类给父类赋值时,是可以的。但是并不是将子类的全部成员都赋值过去,只将父类有的部分赋值过去。这种方法叫切割。
2.继承中的作用域
2.1基类和派生类都有自己的作用域
2.2如果父类和子类有相同的成员,那么子类使用是,只会调用自己的。会对继承父类的同名成员进行隐藏,也叫重定义。(也可以显式调用,使用::符号Fruit::_name)
2.3对于成员函数,只要名字一样就构成重定义
上面的apple类,他和Fruit类都有P()函数,当apple的对象调用时,他会调用自己的。对于Fruit类和apple类都有相同成员_name,子类中调用,可以通过Fruit::_name来调用父类的成员
3.派生类的默认成员函数
默认成员函数,我们不写,编译器也会默认生成。
1. 子 类的构造函数必须调用父类的构造函数初始化父类的那一部分成员。如果父类没有默认 的构造函数,则必须在子类构造函数的初始化列表阶段显示调用。(下左图)2.派生类对象初始化先调用基类构造再调派生类构造。3. 派生类对象析构清理先调用派生类析构再调基类的析构。(下右图)4.派生类的析构函数会在被调用完成后自动调用基类的析构函数清理基类成员。因为这样才能保证派生类对象先清理派生类成员再清理基类成员的顺序。下图
5.派生类的拷贝构造函数必须调用基类的拷贝构造完成基类的拷贝初始化。见下图
class Fruit
{
public:
Fruit(const string name,const string color)
:_name(name)
,_color(color)
{ cout << "Fruit()" << endl; }
Fruit(const Fruit& F){
cout << "const Fruit& F" << endl;
}
~Fruit(){cout << "~Fruit()" << endl;}
protected:
string _color;
string _name ;
};
class apple :public Fruit
{
public:
apple(const string name, const string size,const string color)
:Fruit(name,color)
, _size(size)
{
cout << "apple()" << endl;
}
apple (const apple& a)
:Fruit(a)
,_size(a._size)
{
cout << "const apple& a" << endl;
}
~apple() { cout << "~apple()" << endl; }
protected:
string _size ;
};
void test()
{
apple a("apple", "red", "big");
apple a2("apple","yellow", "big");
apple p(a);
}
6.赋值运算符重载,必须用父类的运算符重载来完成
4.继承的其他一些关系
1.友元关系不能继承 ,也就是说基类友元不能访问子类私有和保护成员2. 基类定义了 static 静态成员,则整个继承体系里面只有一个这样的成员 。无论派生出多少个子 类,都只有一个 static 成员实例 。
三、单继承和多继承
1.单继承
2.多继承
3.菱形继承
下面这个情况也构成菱形继承,并不是严格的菱形才算。
原因:因为B和C都继承了A的_a ,当D继承B和C后,里面的_a不知道用B继承的_a还是用C继承的_a.,尽量不要使用菱形继承,很麻烦
4.解决方法
4.1加前置声明
4.2使用虚继承
关于虚继承是什么,当你学了多态就知道了。可以在我后续的文章看到。
virtual是定义C++中虚函数的关键字 。在面向对象程序设计领域,C++、Object Pascal 等语言中有虚函数(英语:virtual function)或虚方法(英语:virtual method)的概念。这种函数或方法可以被子类继承和覆盖,通常使用动态调度实现。这一概念是面向对象程序设计中(运行时)多态的重要组成部分。简言之,虚函数可以给出目标函数的定义,但该目标的具体指向在编译期可能无法确定。