在c++ primer plus中是将继承和多态一起讲的,个人感觉继承的用处比多态要广
继承模型
继承是“is-a”关系,即派生类是基类的一种,如苹果是水果,就可以让苹果类继承水果类
发生继承时,派生类内部创建了一个基类副本,可以说派生类内有一个基类模型,并且添加了一些属于派生类自身的成员
继承方式及访问权限
class base
{
};
class son :public base
{
};
继承分为公有继承(public),保护继承(protected),私有继承(private)
关键字protected可以说是专为继承准备的
继承的语法及在派生类声明后加冒号,继承方式,继承基类
c++中派生类无论何种继承方式均不能直接访问基类私有成员,对私有成员的保护是c++程序设计的一个特点
公有继承(public)
基类的public在派生类中访问权限仍是public
基类的protected在派生类中访问权限仍是protected
基类的private在派生类中为无法访问
保护继承(protected)
关键字protected,即被保护成员可以在派生类中随意访问,但类外无法通过派生类直接访问
有点类似private关键字,只不过protected只对继承的派生类生效,在基类中访问权限和private 一致,相当于基类想让派生类随意访问,但不允许类外访问
基类的public在派生类中访问权限是protected
基类的protected在派生类中访问权限仍是protected
基类的private在派生类中为无法访问
私有继承(private)
基类的public在派生类中访问权限是private
基类的protected在派生类中访问权限是private
基类的private在派生类中为无法访问
可以说基类的成员在派生类中访问权限至少和继承方式一致,若基类的成员在基类中本身的访问权限高于继承方式,则这部分成员在派生类中访问权限与基类中保持一致
访问权限private>protected>public
无论何种继承方式都无法访问基类中的私有成员
继承中的重载关系
简单来说,派生类中如果有基类的童名成员会发生覆盖,而不是重载,通过“ 派生类 . "调用会优先调用派生类中的成员,如果想调用基类中的成员,需要在“ 派生类 . "加基类作用域
#include <iostream>
using namespace std;
class base
{
public:
void fun()
{
cout << "base function1" << endl;
}
void fun(int a)
{
cout << "base function2" << endl;
}
};
class son :public base
{
public:
int fun()
{
cout << "son function" << endl;
return 0;
}
};
int main()
{
son a;
a.fun();
a.base::fun();
return 0;
}
构造函数与析构函数
由于继承模型,派生类中有一个基类模型,所以会优先构建完基类在构建完派生类,所以先调用基类的构造函数,那么析构函数就应该是先析构派生类在析构基类
由于继承模型,派生类中一定有基类的模型,在构造派生类同时必须完成对基类的构造和对自身添加成员的初始化
使用初始化列表构造派生类
调用基类构造函数
派生类(参数1,参数2....) :基类(参数1...) ,派生类新增成员(参数3)....{}
#include <iostream>
using namespace std;
class base
{
public:
base(int a_1,int a_2):a1(a_1),a2(a_2){}
private:
int a1;
int a2;
};
class son :public base
{
public:
son(int a_1,int a_2,int a_3):base(a_1,a_2),a3(a_3){}
private:
int a3;
};
int main()
{
son a(1,2,3);
return 0;
}
调用基类赋值构造函数
这种通过括号赋值进行构造的构造函数由系统自动生成无需自己定义
派生类(基类引用,参数1....) :基类(基类引用) ,派生类新增成员(参数1)....{}
#include <iostream>
using namespace std;
class base
{
public:
base(int a_1,int a_2):a1(a_1),a2(a_2){}
private:
int a1;
int a2;
};
class son :public base
{
public:
son(base& b,int a_3):base(b),a3(a_3){}
private:
int a3;
};
int main()
{
base b(1, 2);
son a(b,3);
return 0;
}
特殊情况说明
貌似通过初始化列表构造的方法只有上面两种,lz在第一次学习的时候写过一个错误代码,就是直接初始化基类成员而不是初始化基类
这样是无法通过编译的,即使在基类中成员的访问权限时public,通过初始化列表无法直接初始化基类成员
当然如果基类有不需要参数的构造函数,可以在派生类构造函数中省略对基类的构造
class base
{
public:
base(int a_1=0,int a_2=0):a1(a_1),a2(a_2){}
private:
int a1;
int a2;
};
class son :public base
{
public:
son(int a_3):a3(a_3){}
private:
int a3;
};
int main()
{
son a(3);
return 0;
}
在构造函数内初始化基类
因为派生类新增的成员放在初始化列表里和构造函数内没什么区别,这里主要讨论对派生类中基类的初始化
无法在构造函数内调用基类构造函数实现对基类的构造,只能放到初始化列表中
在构造函数内初始化基类必须有基类成员的访问权限,同时这种方法不是调用基类构造函数,而是对基类成员初始化,系统会先调用默认构造函数
无效构造代码
创建了一个基类副本无实际意义
#include <iostream>
using namespace std;
class base
{
public:
base(int a_1=0,int a_2=0):a1(a_1),a2(a_2){}
protected:
int a1;
int a2;
};
class son :public base
{
public:
son(int a_3):a3(a_3)
{
base::base(1, 2);//无效调用,创造了一个基类副本
}
void fun()
{
cout << a1 << " " << a2 << endl;
}
private:
int a3;
};
int main()
{
son a(3);
a.fun();//派生类构造函数内调用基类构造函数无效
return 0;
}
有效赋值
只是对基类成员进行赋值,系统仍会默认调用基类默认构造函数
#include <iostream>
using namespace std;
class base
{
public:
base(int a_1=0,int a_2=0):a1(a_1),a2(a_2)
{
cout << "base create" << endl;
}
protected:
int a1;
int a2;
};
class son :public base
{
public:
son(int a_1,int a_2,int a_3):a3(a_3)
{
a1 = a_1;
a2 = a_2;
cout << "son create" << endl;
}
void fun()
{
cout << a1 << " " << a2 << endl;
}
private:
int a3;
};
int main()
{
son a(1,2,3);
a.fun();
return 0;
}
这种的缺点也很明显,只是名义上构造,系统还是走了一遍基类默认构造函数的流程,同时派生类必须有基类成员访问权限,一般情况下都是没有的
菱形继承多继承
继承的基类可以有很多,中间用逗号隔开即可,多继承时初始化基类要全部初始化
菱形继承即基类A,B,C继承A ,D继承A,这样D中就会有两个A的副本,继承模型是抽象的概念,D中的两个A副本可以相同也可以不同却决于赋值,抽象的概念即D中存在两个A副本
即this->B::A 和this->C::A, 有一种解决方法很简单直接D继承A,省去中间继承过程
还有就是虚继承 在继承方式前加入关键字virtual
//验证菱形继承中内存重复模型
class base
{
public:
int a;
};
class base1 :public base
{
public:
int a1;
};
class base2 :public base
{
public :
int a2;
};
class son :public base1, public base2
{
public:
int a3;
};
int main()
{
cout << sizeof(son) << endl;
return 0;
}
使用虚继承避免内存重复问题原理
第二级基类继承的不只一个完整的第一级基类还有一个指向第一级基类的虚基类指针,
派生类中有两个分别从第二级基类上继承过来的虚基类指针,分别记录了内存偏移量,经过偏移后共同指向一个第一级基类模型,因此最终派生类中就只存在一个第一级基类模型,避免了没必要的内存消耗和二义性问题
vbptr即虚基类指针,记录内存中的偏移量,效果就是无论从B的vbptr偏移去索引A,还是C的pbptr偏移去索引A,最终指向一个内存空间,这样第一基类在派生类中就只有一个,避免了二义性问题
#include <iostream>
using namespace std;
class base
{
public:
int a;
};
class base1 :virtual public base
{
public:
int a1;
};
class base2 :virtual public base
{
public :
int a2;
};
class son :public base1, public base2
{
public:
int a3;
};
class base3 : public base
{
public:
int a1;
};
class base4 : public base
{
public:
int a2;
};
class son1 :public base3, public base4
{
public:
int a3;
};
int main()
{
cout << "by virtual, sizeof son is ";
cout << sizeof(son) << endl;
son a;
a.base1::a = 0;
a.base1::a = 1;
cout << "in base1 a=" << a.base1::a << endl;
cout << "in base2 a=" << a.base2::a << endl;
cout << "in son a=" << a.a << endl;
cout << "without virtual, sizeof son1 is ";
cout << sizeof(son1) << endl;
son1 b;
b.base3::a = 0;
b.base4::a = 1;
cout << "in base3 a=" << b.base3::a << endl;
cout << "in base4 a=" << b.base4::a << endl;
//cout << "in son1 a=" << b.a << endl;二义性无法访问
return 0;
}
最后结果使用了虚继承反而更大了,因为少了一个第一级基类模型但是多了两个虚基类指针,只做演示,正常类内数据远比一个指针多