

网页类
class IndexPage{
public://网页头部void Header(){cout << "网页头部!" << endl;}//网页左侧菜单void LeftNavigation(){cout << "左侧导航菜单!" << endl;}//网页主体部分void MainBody(){cout << "首页网页主题内容!" << endl;}//网页底部void Footer(){cout << "网页底部!" << endl;}
private:string mTitle; //网页标题
};#if 0
//如果不使用继承,那么定义新闻页类,需要重新写一遍已经有的代码
class NewsPage{
public://网页头部void Header(){cout << "网页头部!" << endl;}//网页左侧菜单void LeftNavigation(){cout << "左侧导航菜单!" << endl;}//网页主体部分void MainBody(){cout << "新闻网页主体内容!" << endl;}//网页底部void Footer(){cout << "网页底部!" << endl;}
private:string mTitle; //网页标题
};void test(){NewsPage* newspage = new NewsPage;newspage->Header();newspage->MainBody();newspage->LeftNavigation();newspage->Footer();
}
#else
//使用继承,可以复用已有的代码,新闻业除了主体部分不一样,其他都是一样的
class NewsPage : public IndexPage{
public://网页主体部分void MainBody(){cout << "新闻网页主主体内容!" << endl;}
};
void test(){NewsPage* newspage = new NewsPage;newspage->Header();newspage->MainBody();newspage->LeftNavigation();newspage->Footer();
}
#endif
int main(){test();return EXIT_SUCCESS;
}C++最重要的特征是代码重用,通过继承机制可以利用已有的数据类型来定义新的数据类型,新的类不仅拥有旧类的成员,还拥有新定义的成员。
一个B类继承于A类,或称从类A派生类B。这样的话,类A成为基类(父类), 类B成为派生类(子类)。
派生类中的成员,包含两大部分:
一类是从基类继承过来的,一类是自己增加的成员。
从基类继承过过来的表现其共性,而新增的成员体现了其个性。

派生类定义格式:
Class 派生类名 : 继承方式 基类名{//派生类新增的数据成员和成员函数
}三种继承方式:
public : 公有继承
private : 私有继承
protected : 保护继承
从继承源上分:
单继承:指每个派生类只直接继承了一个基类的特征
多继承:指多个基类派生出一个派生类的继承关系,多继承的派生类直接继承了不止一个基类的特征
派生类继承基类,派生类拥有基类中全部成员变量和成员方法(除了构造和析构之外的成员方法),但是在派生类中,继承的成员并不一定能直接访问,不同的继承方式会导致不同的访问权限。


/基类
class A{
public:int mA;
protected:int mB;
private:int mC;
};//1. 公有(public)继承
class B : public A{
public:void PrintB(){cout << mA << endl; //可访问基类public属性cout << mB << endl; //可访问基类protected属性//cout << mC << endl; //不可访问基类private属性}
};
class SubB : public B{void PrintSubB(){cout << mA << endl; //可访问基类public属性cout << mB << endl; //可访问基类protected属性//cout << mC << endl; //不可访问基类private属性}
};
void test01(){B b;cout << b.mA << endl; //可访问基类public属性//cout << b.mB << endl; //不可访问基类protected属性//cout << b.mC << endl; //不可访问基类private属性
}//2. 私有(private)继承
class C : private A{
public:void PrintC(){cout << mA << endl; //可访问基类public属性cout << mB << endl; //可访问基类protected属性//cout << mC << endl; //不可访问基类private属性}
};
class SubC : public C{void PrintSubC(){//cout << mA << endl; //不可访问基类public属性//cout << mB << endl; //不可访问基类protected属性//cout << mC << endl; //不可访问基类private属性}
};
void test02(){C c;//cout << c.mA << endl; //不可访问基类public属性//cout << c.mB << endl; //不可访问基类protected属性//cout << c.mC << endl; //不可访问基类private属性
}
//3. 保护(protected)继承
class D : protected A{
public:void PrintD(){cout << mA << endl; //可访问基类public属性cout << mB << endl; //可访问基类protected属性//cout << mC << endl; //不可访问基类private属性}
};
class SubD : public D{void PrintD(){cout << mA << endl; //可访问基类public属性cout << mB << endl; //可访问基类protected属性//cout << mC << endl; //不可访问基类private属性}
};
void test03(){D d;//cout << d.mA << endl; //不可访问基类public属性//cout << d.mB << endl; //不可访问基类protected属性//cout << d.mC << endl; //不可访问基类private属性
}在C++编译器的内部可以理解为结构体,子类是由父类成员叠加子类新成员而成:
class Aclass{
public:int mA;int mB;
};
class Bclass : public Aclass{
public:int mC;
};
class Cclass : public Bclass{
public:int mD;
};
void test(){cout << "A size:" << sizeof(Aclass) << endl;cout << "B size:" << sizeof(Bclass) << endl;cout << "C size:" << sizeof(Cclass) << endl;
}查看类继承的内部模型:
找到VS2013开发人员命令提示程序(一般在:C:\Program Files(x86)\Microsoft Visual Studio 12.0\Common7\Tools\Shortcuts),打开,然后复制你工程路径,命令:cd 路径,进入你工程文件夹中(如果工程不在C盘在E盘的话,要再E:下),然后命令:
cl /d1 reportSingleClassLayout类名文件名全称
如:cl /d1reportSingleClassLayoutSon test.cpp
子类对象在创建时会首先调用父类的构造函数
父类构造函数执行完毕后,才会调用子类的构造函数
当父类构造函数有参数时,需要在子类初始化列表(参数列表)中显示调用父类构造函数
析构函数调用顺序和构造函数相反

class A{
public:A(){cout << "A类构造函数!" << endl;}~A(){cout << "A类析构函数!" << endl;}
};class B : public A{
public:B(){cout << "B类构造函数!" << endl;}~B(){cout << "B类析构函数!" << endl;}
};class C : public B{
public:C(){cout << "C类构造函数!" << endl;}~C(){cout << "C类析构函数!" << endl;}
};void test(){C c;
}
class D{
public:D(){cout << "D类构造函数!" << endl;}~D(){cout << "D类析构函数!" << endl;}
};
class A{
public:A(){cout << "A类构造函数!" << endl;}~A(){cout << "A类析构函数!" << endl;}
};
class B : public A{
public:B(){cout << "B类构造函数!" << endl;}~B(){cout << "B类析构函数!" << endl;}
};
class C : public B{
public:C(){cout << "C类构造函数!" << endl;}~C(){cout << "C类析构函数!" << endl;}
public:D c;
};
void test(){C c;
}当子类成员和父类成员同名时,子类依然从父类继承同名成员
如果子类有成员和父类同名,子类访问其成员默认访问子类的成员(本作用域,就近原则)
在子类通过作用域::进行同名成员区分(在派生类中使用基类的同名成员,显示使用类名限定符)
class Base{
public:Base():mParam(0){}void Print(){ cout << mParam << endl; }
public:int mParam;
};class Derived : public Base{
public:Derived():mParam(10){}void Print(){//在派生类中使用和基类的同名成员,显示使用类名限定符cout << Base::mParam << endl;cout << mParam << endl;}//返回基类重名成员int& getBaseParam(){ return Base::mParam; }
public:int mParam;
};int main(){Derived derived;//派生类和基类成员属性重名,子类访问成员默认是子类成员cout << derived.mParam << endl; //10derived.Print();//类外如何获得基类重名成员属性derived.getBaseParam() = 100;cout << "Base:mParam:" << derived.getBaseParam() << endl;return EXIT_SUCCESS;
}注意: 如果重新定义了基类中的重载函数,将会发生什么?
class Base{
public://重载函数void func1(){cout << "Base::void func1()" << endl;};void func1(int param){cout << "Base::void func1(int param)" << endl;}//非重载函数void myfunc(){cout << "Base::void myfunc()" << endl;}
};class Derived1 : public Base{};
class Derived2 : public Base{
public:void myfunc(){//基类myfunc被隐藏,可通过类作用域运算符指定调用基类myfunc函数//Base::myfunc();cout << "Derived2::void myfunc()" << endl;}
};
class Derived3 : public Base{
public://改变成员函数的参数列表void func1(int param1, int param2){//Base::func1(10); //类的内部可通过类作用域运算符访问基类重载版本的函数cout << "Derived3::void func1(int param1,int param2)" << endl;};
};
class Derived4 : public Base{
public://改变成员函数的返回值int func1(int param){Base::func1(10);cout << "Derived4::int func1(int param)" << endl;return 0;}
};//和基类非重载函数重名
void test01(){Derived1 derived1;derived1.myfunc();//和基类函数重名Derived2 derived2;derived2.myfunc();
}//和基类重载函数重名
void test02(){Derived3 derived3;//derived3.func1(); //基类重载版本的函数fun1被全部隐藏,子类外部不可访问//derived3.func1(10);derived3.func1(10,20);Derived4 derived4;//derived4.func1(); //基类重载版本的函数fun1被全部隐藏,子类外部不可访问derived4.func1(10);
}//结论:任何时候重新定义基类中的任何一个函数,子类中这种函数的任何版本都
//会被隐藏(非覆盖,可通过类作用域运算符调用)任何时候重新定义基类中的一个重载函数,在新类中所有的其他版本将被自动隐藏.
不是所有的函数都能自动从基类继承到派生类中。构造函数和析构函数用来处理对象的创建和析构操作,构造和析构函数只知道对它们的特定层次的对象做什么,也就是说构造函数和析构函数不能被继承,必须为每一个特定的派生类分别创建。
另外operator=也不能被继承,因为它完成类似构造函数的行为。也就是说尽管我们知道如何由=右边的对象如何初始化=左边的对象的所有成员,但是这个并不意味着对其派生类依然有效。
在继承的过程中,如果没有创建这些函数,编译器会自动生成它们。
静态成员函数和非静态成员函数的共同点:
他们都可以被继承到派生类中。
如果重新定义一个静态成员函数,所有在基类中的其他重载函数会被隐藏。
如果我们改变基类中一个函数的特征,所有使用该函数名的基类版本都会被隐藏。
静态成员函数不能是虚函数(virtual function)。
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();//解决歧义:显示指定调用那个基类的func1derived.Base1::func1(); derived.Base2::func1();return EXIT_SUCCESS;
}多继承会带来一些二义性的问题, 如果两个基类中有同名的函数或者变量,那么通过派生类对象去访问这个函数或变量时就不能明确到底调用从基类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; //8return 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;//输出结果:12cout << "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: | ![]() | ![]() |
Derived: | ![]() | ![]() |
通过对象布局图,我们发现普通继承和虚继承的对象内存图是不一样的。我们也可以猜测到编译器肯定对我们编写的程序做了一些手脚。
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;
}注意:虚继承只能解决具备公共祖先的多继承所带来的二义性问题,不能解决没有公共祖先的多继承的。
工程开发中真正意义上的多继承是几乎不被使用,因为多重继承带来的代码复杂性远多于其带来的便利,多重继承对代码维护性上的影响是灾难性的,在设计方法上,任何多继承都可以用单继承代替。
Jerry Schwarz,输入输出流(iostream)的作者,曾在个别场合表示如何他重新设计iostream的话,很可能从iostream中去除多重继承。