C++继承类这一部分虽然不是很难,但是也很重要,在这一块我自己做了一些总结,下面跟大家交流一下。
首先简单介绍一下继承的概念:继承(inheritance)是面向对象程序设计使代码可以复用的最重要的手段,它允许程序员在保持原有类特性的基础上进行扩展,增加功能。这样产生新的类,称派生类。
继承关系有三种:public(公有继承) ,protected(保护继承),private(私有继承),他们与成员访问限定符不同,不能将二者混为一谈,具体表示的含义与位置有关。不管是哪种继承方式,在派生类内部都可以访问基类的公有成员和保护成员,基类的私有成员存在但是在子类中不可见(不能访问)。
class继承中默认的继承权限为private而struct中默认的继承权限默认为public。
基类的private成员在派生类中是不能被访问的,如果基类成员不想在类外直接被访问,但需要在派生类中能访问,就定义为protected。可以看出保护成员限定符是因继承才出现的。下面给出一段代码:
#include<iostream>
using namespace std;
class Base
{
public:
int _pub;
protected:
int _pro;
private:
int _pri;
};
class Derived:<span style="color:#ff6600;">protected</span> Base//若改为private则在D类FunTest函数中所有成员都不能被访问,通过再建立一个类这可判断派生类到底是private还是protected
{
public:
int _d;
void Fun()
{
Derived d;
d._pub = 1;
d._pro = 2;
/*基类的private成员在派生类中不能被访问:
因为类的私有成员在类外不能被访问,而两个类的作用域不同所以会访问出错*/
//d._pri = 3;
}
};
class D:<span style="color:#ff6600;">public</span> Derived //这里继承关系必须为public
{
public:
int _d1;
void FunTest()
{
D d;
//d._pri = 1;
d._pro = 2;
d._pub = 3;
d._d1 = 4;
}
};
在继承中还有个概念也很重要就是同名隐藏。同名隐藏:基类和派生类中成员同名优先访问派生类,派生类成员将屏蔽基类对成员的直接访问。在子类成员函数中,可以使用 基类::基类成员 访问。
函数同成员一样也有同名隐藏,如果两个函数同名也调用派生类的函数而与函数有无参数无关。
Base中:void FunTest(int)
Derived中:void FunTest()
调用这两个函数时即使传参还是会调用Derived中的FunTest。
在继承关系中,派生类中若没有显示定义六个默认成员函数则编译系统会默认合成。
[cpp] view plain copy 在CODE上查看代码片派生到我的代码片
class Test1
{
public:
Test1()
{
cout<<"Test1()"<<endl;
}
~Test1()
{
cout<<"~Test1()"<<endl;
}
};
class Test2:public Test1
{
public:
Test2()
{
cout<<"Test2()"<<endl;
}
~Test2()
{
cout<<"~Test2()"<<endl;//call Test1::~Test1();
}
};
int main()
{
Test2 t2;
system("pause");
return 0;
}
运行结果显示:构造函数体执行次序为:先调基类构造函数--->再调派生类构造函数。
但调用顺序实则应为:先调派生类构造函数再在初始化列表中调用基类构造函数。
析构顺序与运行结果相同为:先调派生类再调基类。
几点说明:
1、基类没有缺省构造函数,派生类必须要在初始化列表中显式给出基类名和参数列表。
2、基类没有定义构造函数,则派生类也可以不用定义,全部使用缺省构造函数。
3、基类定义了带有形参表构造函数,派生类就一定定义构造函数。
4、基类构造函数给出,派生类没有,编译器会调用派生类默认构造函数。
5、必须在派生类初始化列表中调基类构造函数(因为只能创建一次,而在函数体中是赋值,赋值可以多次)
[cpp] view plain copy 在CODE上查看代码片派生到我的代码片
class Base
{
public:
Base(int b)//基类给出带参数的构造函数
{}
};
class Derived:public Base
{
public:
Derived()
//:Base(10)//初始化列表中若没有显式调用带参数的基类,则默认调用缺省的基类构造函数,则编译器会报错说Base中没有合适的默认构造函数.
//,_d(0)
{
_d = 2;
}
private:
int _d;
};
int main()
{
Derived d;
system("pause");
return 0;
}
继承兼容规则-----公有继承
继承兼容规则-----公有继承
继承与友元:友元函数不是类的成员函数所以不能继承。
继承与静态成员:基类定义了static成员,则整个继承体系里面只有一个这样的成员。无论派生出多少个子类,都只有一个static成员实例。
单继承&多继承&菱形继承
单继承&多继承&菱形继承
class B1
{
public:
int _b1;
};
class B2
{
public:
int _b2;
};
class D:public B1,public B2
{
public:
int _d;
};
int main()
{
D d;
d._b1 = 1;
d._b2 = 2;
d._d = 3;
system("pause");
return 0;
}
B1& b1 = d;//底层相当于指针b1 = &d;
B2& b2 = d;//和上面相比形式上无区别,但底层处理不同(B2*)((int)&d+sizeof(B1))
B1& b1 = d;//底层相当于指针b1 = &d;
B2& b2 = d;//和上面相比形式上无区别,但底层处理不同(B2*)((int)&d+sizeof(B1))
class B
{
public:
int _b;
};
class C1:public B
{
public:
int _c1;
};
class C2:public B
{
public:
int _c2;
};
class D:public C1,public C2
{
public:
int _d;
};
int main()
{
D d;
d._c1 = 1;
//d._b = 2;//编译器会报错“对"_b"的访问不明确”
d._c2 = 3;
d._d = 5;
cout<<"D_size:"<<sizeof(D)<<endl;
system("pause");
return 0;
}
因为上面这段代码有问题,所以引出虚继承,可以解决菱形继承的二义性和数据冗余的问题
所以应在C1,C2类的继承权限前加virtual 让其变成虚拟继承。
因为上面这段代码有问题,所以引出虚继承,可以解决菱形继承的二义性和数据冗余的问题
所以应在C1,C2类的继承权限前加virtual 让其变成虚拟继承。
class B
{
public:
int _b;
};
class C1:virtual public B//注意是在C1和C2类加virtual
//而不能写成class D:virtual public C1,virtual public C2
{
public:
int _c1;
};
class C2:virtual public B
{
public:
int _c2;
};
class D:public C1,public C2
{
public:
int _d;
};
int main()
{
D d;
d._b = 1;
d._c1 = 2;
d._c2 = 3;
d._d = 4;
cout<<sizeof(B)<<endl;
cout<<sizeof(C1)<<endl;
cout<<sizeof(C2)<<endl;
cout<<sizeof(D)<<endl;
system("pause");
return 0;
}
这段代码的解释和上面的图中一样。所以虚拟继承解决菱形继承的二义性就是使底层的基类成员只保存了一份,在访问各个对象时使用虚表指针加上偏移量即可。