一、父类和子类的同名成员函数处理
1子类继承父类所有成员函数和成员变量
class Base
{
public:
void func(void)
{
cout<<"父类中的void func"<<endl;
}
void func(int a)
{
cout<<"父类中的int func a = "<<a<<endl;
}
};
class Son:public Base
{
public:
};
void test01()
{
//为啥构造和析构除外?父类的构造和析构 只有父类自己知道该怎么做(构造和析构 系
统自动调用)
//子类会继承父类所有成员函数(构造和析构函数除外) 和成员变量
Son ob1;
ob1.func();//访问的是父类的void func(void)
ob1.func(10);//访问的是父类的func(int a)
}
2.子类和父类同名成员函数
class Base
{
public:
void func(void)
{
cout<<"父类中的void func"<<endl;
}
void func(int a)
{
cout<<"父类中的int func a = "<<a<<endl;
}
};
class Son:public Base
{
public:
//一旦子类 实现了 父类的同名成员函数 将屏蔽所有父类同名成员函数
void func(void)
{
cout<<"子类中voidfunc"<<endl;
}
};
void test01()
{
//为啥构造和析构除外?父类的构造和析构 只有父类自己知道该怎么做(构造和析构 系
统自动调用)
//子类会继承父类所有成员函数(构造和析构函数除外) 和成员变量
Son ob1;
ob1.func();
//ob1.func(10);//err //一旦子类 实现了 父类的同名成员函数 将屏蔽所有父类同名
成员函数
//如果用户 必须要调用父类 的同名成员函数 必须加作用域
ob1.Base::func();//调用父类的void func
ob1.Base::func(10);//调用父类的int func
}
int main(int argc, char *argv[])
{
test01();
return 0;
}
二、非自动继承的函数
不是所有的函数都能自动从基类继承到派生类中。构造函数和析构函数用来处理对 象的创建和析构操作,构造和析构函数只知道对它们的特定层次的对象做什么,也 就是说构造函数和析构函数不能被继承,必须为每一个特定的派生类分别创建。 另外 operator=也不能被继承,因为它完成类似构造函数的行为。也就是说尽管我 们知道如何由=右边的对象如何初始化=左边的对象的所有成员,但是这个并不意 味着对其派生类依然有效。 在继承的过程中,如果没有创建这些函数,编译器会 自动生成它们。
三、继承中的静态成员特性
静态成员函数和非静态成员函数的共同点:
1. 他们都可以被继承到派生类中。
2. 如果重新定义一个静态成员函数,所有在基类中的其他重载函数会被隐藏。
3. 如果我们改变基类中一个函数的特征,所有使用该函数名的基类版本都会被隐 藏
class Base{
public:
static int getNum(){ return sNum; }
static int getNum(int param){
return sNum + param;
}
public:
static int sNum;
};
int Base::sNum = 10;
class Derived : public Base{
public:
static int sNum; //基类静态成员属性将被隐藏
#if 0
//重定义一个函数,基类中重载的函数被隐藏
static int getNum(int param1, int param2){
return sNum + param1 + param2;
}
#else
//改变基类函数的某个特征,返回值或者参数个数,将会隐藏基类重载的函数
static void getNum(int param1, int param2){
cout << sNum + param1 + param2 << endl;
}
#endif
};
int Derived::sNum = 20;
四、多继承
我们可以从一个类继承,我们也可以能同时从多个类继承,这就是多继承。但是由 于多继承是非常受争议的,从多个类继承可能会导致函数、变量等同名导致较多的 歧义。
class Base1{
public:
void func1(){ cout << "Base1::func1" << endl; }
};
class Base2{
public:
void func1(){ cout << "Base2::func1" << endl; }
void func2(){ cout << "Base2::func2" << endl; }
};
//派生类继承 Base1、Base2
class Derived : public Base1, public Base2{};
int main(){
Derived derived;
//func1 是从 Base1 继承来的还是从 Base2 继承来的?
//derived.func1();
derived.func2();
//解决歧义:显示指定调用那个基类的 func1
derived.Base1::func1();
derived.Base2::func1();
return EXIT_SUCCESS;
}
多继承会带来一些二义性的问题, 如果两个基类中有同名的函数或者变量,那么 通过派生类对象去访问这个函数或变量时就不能明确到底调用从基类 1 继承的版 本还是从基类 2 继承的版本? 解决方法就是显示指定调用那个基类的版本。
菱形继承
两个派生类继承同一个基类而又有某个类同时继承者两个派生类,这种继承被称为 菱形继承,或者钻石型继承。
这种继承所带来的问题:
1. 羊继承了动物的数据和函数,鸵同样继承了动物的数据和函数,当草泥马调用 函数或者数据时,就会产生二义性。
2. 草泥马继承自动物的函数和数据继承了两份,其实我们应该清楚,这份数据我 们只需要一份就可以。
class BigBase{
public:
BigBase(){ mParam = 0; }
void func(){ cout << "BigBase::func" << endl; }
public:
int mParam;
};
class Base1 : public BigBase{};
class Base2 : public BigBase{};
class Derived : public Base1, public Base2{};
int main(){
Derived derived;
//1. 对“func”的访问不明确
//derived.func();
//cout << derived.mParam << endl;
cout << "derived.Base1::mParam:" << derived.Base1::mParam << endl;
cout << "derived.Base2::mParam:" << derived.Base2::mParam << endl;
//2. 重复继承
cout << "Derived size:" << sizeof(Derived) << endl; //8
return EXIT_SUCCESS;
}
上述问题如何解决?对于调用二义性,那么可通过指定调用那个基类的方式来解 决,那么重复继承怎么解决? 对于这种菱形继承所带来的两个问题,c++为我们提 供了一种方式,采用虚基类。那么我们采用虚基类方式将代码修改如下:
class BigBase{
public:
BigBase(){ mParam = 0; }
void func(){ cout << "BigBase::func" << endl; }
public:
int mParam;
};
class Base1 : virtual public BigBase{};
class Base2 : virtual public BigBase{};
class Derived : public Base1, public Base2{};
int main(){
Derived derived;
//二义性问题解决
derived.func();
cout << derived.mParam << endl;
//输出结果:12
cout << "Derived size:" << sizeof(Derived) << endl;
return EXIT_SUCCESS;
}
以上程序 Base1 ,Base2 采用虚继承方式继承 BigBase,那么 BigBase 被称为虚基 类。 通过虚继承解决了菱形继承所带来的二义性问题。 但是虚基类是如何解决二 义性的呢?并且 derived 大小为 12 字节,这是怎么回事?
虚继承实现原理
class BigBase{
public:
BigBase(){ mParam = 0; }
void func(){ cout << "BigBase::func" << endl; }
public: int mParam;
};
#if 0 //虚继承
class Base1 : virtual public BigBase{};
class Base2 : virtual public BigBase{};
#else //普通继承
class Base1 : public BigBase{};
class Base2 : public BigBase{};
#endif
class Derived : public Base1, public Base2{};
通过内存图,我们发现普通继承和虚继承的对象内存图是不一样的。我们也可以猜 测到编译器肯定对我们编写的程序做了一些手脚。
BigBase 菱形最顶层的类,内存布局图没有发生改变。 Base1 和 Base2 通过虚继 承的方式派生自 BigBase,这两个对象的布局图中可以看出编译器为我们的对象中增 加了一个 vbptr (virtual base pointer),vbptr 指向了一张表,这张表保存了当前的虚 指针相对于虚基类的首地址的偏移量。
Derived 派生于 Base1 和 Base2,继承了两 个基类的 vbptr 指针,并调整了 vbptr 与虚基类的首地址的偏移量。 由此可知编译器帮我们做了一些幕后工作,使得这种菱形问题在继承时候能只继承 一份数据,并且也解决了二义性的问题。现在模型就变成了 Base1 和 Base2 Derived 三个类对象共享了一份 BigBase 数据。
当使用虚继承时,虚基类是被共享的,也就是在继承体系中无论被继承多少次,对 象内存模型中均只会出现一个虚基类的子对象(这和多继承是完全不同的)。即使 共享虚基类,但是必须要有一个类来完成基类的初始化(因为所有的对象都必须被 初始化,哪怕是默认的),同时还不能够重复进行初始化,那到底谁应该负责完成 初始化呢?C++标准中选择在每一次继承子类中都必须书写初始化语句(因为每一 次继承子类可能都会用来定义对象),但是虚基类的初始化是由最后的子类完成, 其他的初始化语句都不会调用
class BigBase{
public:
BigBase(int x){mParam = x;}
void func(){cout << "BigBase::func" << endl;}
public:
int mParam;
};
class Base1 : virtual public BigBase{
public:
Base1() :BigBase(10){} //不调用 BigBase 构造
};
class Base2 : virtual public BigBase{
public:
Base2() :BigBase(10){} //不调用 BigBase 构造
};
class Derived : public Base1, public Base2{
public:
Derived() :BigBase(10){} //调用 BigBase 构造
};
//每一次继承子类中都必须书写初始化语句
int main(){
Derived derived;
return EXIT_SUCCESS;
}
注意: 虚继承只能解决具备公共祖先的多继承所带来的二义性问题,不能解决没 有公共祖先的多继承的. 工程开发中真正意义上的多继承是几乎不被使用,因为多重继承带来的代码复杂性 远多于其带来的便利,多重继承对代码维护性上的影响是灾难性的,在设计方法 上,任何多继承都可以用单继承代替。