// 第九章类的继承和虚函数 //.cpp: 主项目文件。 //1 继承如何与面向对像的编程思想适应 //2 根据现有类定义新类 //3 使用protected关键字为类成员指定新的访问特性 //4 如何使某个类成为另一个类的友元 //5 虚函数及其使用方法 //6 纯虚函数 //7 抽像类 //8 虚析构函数的概念,何时时使用虚析构函数 //9.2 类的继承 //派生类不继承的基类成员仅有析构函数,构造函数以及任何重载赋值运算符的成员函数 //9.2.1 基类的概念 #include "stdafx.h" #include <iostream> using namespace std; using namespace System; //9.2.2 基类的派生类 /*class CBox { public: double m_Length; double m_Width; double m_Height; CBox(double lv = 1.0,double wv =1.0, double hv = 1.0) :m_Length(lv), m_Width(wv), m_Height(hv) { } }; //定义CCandBox class CCandBox: public CBox { public: char* m_Contents; int count; CCandBox(char* str = "Candy") { m_Contents = new char[strlen(str) +1]; strcpy_s(m_Contents, strlen(str)+1, str); //strcpy_s三个参数,复制操作目标,目标缓冲区的长度和数据原 //如果两个数组都是静态的,即都不在堆上分配的,那么我们可以省略第二个实参,而只提供目标和数据扰的指针 //这样做是可以的,因为strcpy_s()函数还可以用作能够自动推断目标字符串长度的模板函数,因此 //当我们处理静态字符串时,调用该函数只提供目标少源字符串作为参数即可 } ~CCandBox() { delete []m_Contents; } }; int main(array<System::String ^> ^args) { CBox myBox(4.0, 3.0, 2.0); CCandBox myCandyBox; CCandBox myMintBox("Wafet Thin Mints"); cout<<endl; cout<<"myBox 的大小为"<<sizeof(myBox)<<"bytes"<<endl; cout<<"myCandyBox 的大小为"<<sizeof(myCandyBox)<<"bytes"<<endl; cout<<"myMintBox 的大小为"<<sizeof(myMintBox)<<"bytes"<<endl; //两个CCandBox对像大小相同,都占用32个字切,字符串的长度不影响对像的大小,因为容纳字符串内存是在自由存储器上分配的 //32个字节的构成情况如下:从基类CBox继承了3个double成员总共占用了24个字节 //指针成员m_Contents要占用4个字节,加一起来是28。 //编译器要在8字节的倍数地址上对齐成员,通过给CCandyBox类添加一个成员---比如int类型,应该能够证实这一点 //我们将发现,类对像的大小仍然是32个字节 system("pause"); return 0; }*/ //9.3 继承机制下的访问控制 /*class CBox { public: CBox(double lv = 1.0,double wv =1.0, double hv = 1.0) :m_Length(lv), m_Width(wv), m_Height(hv) { } double Volume() { return m_Length * m_Width * m_Height; } private: double m_Length; double m_Width; double m_Height; }; //定义CCandBox class CCandBox: public CBox { public: char* m_Contents; int count; CCandBox(char* str = "Candy") { m_Contents = new char[strlen(str) +1]; strcpy_s(m_Contents, strlen(str)+1, str); } //9.3.1 派生类中的构造函数的操作 CCandBox(double lv, double wv, double hv, char* str="Candy"):CBox(lv, wv, hv) { cout<<"执行派生类的构造函数"<<endl; m_Contents = new char[strlen(str)+1]; strcpy_s(m_Contents, strlen(str)+1, str); } //除了在派生类中添加了一个构造函数以外,我们还每个构造函数中添加了一条输出语句 //对CBox类构造函数的显式调用出现在派生类构造函数函数头包含的冒号之后, //注意:基类构造函数总是先于派生类构造函数被调用 //而基类的析构函数总是后于派生类的析构函数被调用 //double Volume()const //{ //return m_Length * m_Width * m_Height; //这里直接访问CBox的私有成员是非法的 //} ~CCandBox() { delete []m_Contents; } }; int main(array<System::String ^> ^args) { CBox myBox(4.0, 3.0, 2.0); CCandBox myCandyBox; CCandBox myMintBox(1.0, 2.0, 3.0, "Wafet Thin Mints"); cout<<endl; cout<<"myBox 的大小为"<<sizeof(myBox)<<"bytes"<<endl; cout<<"myCandyBox 的大小为"<<sizeof(myCandyBox)<<"bytes"<<endl; cout<<"myMintBox 的大小为"<<sizeof(myMintBox)<<"bytes"<<endl; cout<<"myCandyBox.Volume()=="<<myCandyBox.Volume()<<endl; cout<<"myMintBox.Volume()=="<<myMintBox.Volume()<<endl; system("pause"); return 0; }*/ //9.3.2 声明类的保护成员 //类的保护成员只能被类的成员函数和类的友元函数访问(还能被友元类的成员函数访问) /*class CBox { public: CBox(double lv = 1.0,double wv =1.0, double hv = 1.0) :m_Length(lv), m_Width(wv), m_Height(hv) { } double Volume() { return m_Length * m_Width * m_Height; } protected: //保护声明 //它们实际上还是私有成员,但可以被派生类的成员函数访问 double m_Length; double m_Width; double m_Height; }; //定义CCandBox class CCandBox: public CBox { public: char* m_Contents; int count; CCandBox(char* str = "Candy") { m_Contents = new char[strlen(str) +1]; strcpy_s(m_Contents, strlen(str)+1, str); } //9.3.1 派生类中的构造函数的操作 CCandBox(double lv, double wv, double hv, char* str="Candy"):CBox(lv, wv, hv) { cout<<"执行派生类的构造函数"<<endl; m_Contents = new char[strlen(str)+1]; strcpy_s(m_Contents, strlen(str)+1, str); } //可以直接计算体积 double Volume() { return m_Length * m_Width * m_Height; } ~CCandBox() { delete []m_Contents; } }; int main(array<System::String ^> ^args) { CBox myBox(4.0, 3.0, 2.0); CCandBox myCandyBox; CCandBox myMintBox(1.0, 2.0, 3.0, "Wafet Thin Mints"); cout<<endl; cout<<"myBox 的大小为"<<sizeof(myBox)<<"bytes"<<endl; cout<<"myCandyBox 的大小为"<<sizeof(myCandyBox)<<"bytes"<<endl; cout<<"myMintBox 的大小为"<<sizeof(myMintBox)<<"bytes"<<endl; cout<<"myCandyBox.Volume()=="<<myCandyBox.Volume()<<endl; cout<<"myMintBox.Volume()=="<<myMintBox.Volume()<<endl; system("pause"); return 0; }*/ //派生类对像析构函数调用顺序与构造函数相反,这是一条普通适用的规则 //创建对像时首先调用基类的构造函数,然后调用派生类的构造函数 //而销毁对像时首先调用派生类的析构函数,然后调用基类的析构函数 //9.3.3 继承类成员的访问级别 //1 公有继承一切不变 //2 保护继承,公有成员变保护成员,保护成员变私有成员 //3 私有继承,公有成员、保护成员变私有成员 //书上写的 //如果基类被声明为private, 其成员派生类中永远都不可访问 //如果基类被声明为public, 其成员在派生类中的访问级别保持不变 //如果基类被声明为protected,基public成员在派生类中将成为protected, protected不变 //9.4 派生类中的复制构造函数 //记信,复制构造函数是在声明用同类对像初始化的对像时被自动调用的 //CBox myBox(2.0, 3.0, 4.0); //CBox copyBOx(myBox); /*class CBox { public: CBox(double lv = 1.0,double wv =1.0, double hv = 1.0) :m_Length(lv), m_Width(wv), m_Height(hv) { } double Volume() { return m_Length * m_Width * m_Height; } //我们还要记得,为了避免无穷无尽的调用自身,复制构造函数的形参必须被指定为引用 //否则需要复制以传值方式传递的实参, CBox(const CBox& initBox) { cout<<"类的复制构造函数"<<endl; m_Length = initBox.m_Length; m_Width = initBox.m_Width; m_Height = initBox.m_Height; } protected: //保护声明 //它们实际上还是私有成员,但可以被派生类的成员函数访问 double m_Length; double m_Width; double m_Height; }; //定义CCandBox class CCandBox: public CBox { public: char* m_Contents; int count; CCandBox(char* str = "Candy") { m_Contents = new char[strlen(str) +1]; strcpy_s(m_Contents, strlen(str)+1, str); } //9.3.1 派生类中的构造函数的操作 CCandBox(double lv, double wv, double hv, char* str="Candy"):CBox(lv, wv, hv) { cout<<"执行派生类的构造函数"<<endl; m_Contents = new char[strlen(str)+1]; strcpy_s(m_Contents, strlen(str)+1, str); } //可以直接计算体积 double Volume() { return m_Length * m_Width * m_Height; } //调用一下CBox的复制构造函数 CCandBox(const CCandBox& initCB):CBox(initCB) { cout<<"执行CCandBox类的构造函数"<<endl; m_Contents = new char[strlen(initCB.m_Contents)+1]; strcpy_s(m_Contents, strlen(initCB.m_Contents)+1, initCB.m_Contents); } //在我们编写派生类的构造函数时,必须确保派生类对像的成员被正确初始化,基中当然包括继承的成员 //也就是基类的成员 //在为派生类编写构造函数时,需要初始化包括继承成员在内的派生类对像的所有成员 ~CCandBox() { delete []m_Contents; } }; int main(array<System::String ^> ^args) { CBox myBox(4.0, 3.0, 2.0); CCandBox myCandyBox; CCandBox myMintBox(1.0, 2.0, 3.0, "Wafet Thin Mints"); cout<<endl; cout<<"myBox 的大小为"<<sizeof(myBox)<<"bytes"<<endl; cout<<"myCandyBox 的大小为"<<sizeof(myCandyBox)<<"bytes"<<endl; cout<<"myMintBox 的大小为"<<sizeof(myMintBox)<<"bytes"<<endl; cout<<"myCandyBox.Volume()=="<<myCandyBox.Volume()<<endl; cout<<"myMintBox.Volume()=="<<myMintBox.Volume()<<endl; CBox copyBox(myBox); cout<<"copyBox.Volume()=="<<copyBox.Volume()<<endl; //刚刚不是说不能继承复制构造函数吗? 这个可能有问题 CCandBox mycopyBox(myMintBox); cout<<"mycopyBox.Volume()=="<<mycopyBox.Volume()<<endl; system("pause"); return 0; } */ //9.5 友元类成员 /* #include "stdafx.h" #include <iostream> using namespace std; using namespace System; //start 类的友元成员///////////////////////////////////////////////// class F1; class F2 { private: int var2; public: int copy(F1* f1); //在F2中声明一个函数,叁数为接收F1的地址 F2(F1* f1) //构造函数的参数为F1的地址 { var2 = copy(f1); //这里F2的构造函数不能直接访问f1的私有成员,只有通过copy这个友元函数来完成 } int GetVar() { return var2; } }; class F1 { private: int var; friend int F2::copy(F1* f1); public: F1(){ var = 250; } }; //最后来定义一个F2::copy()函数 int F2::copy(F1* f1) { return f1->var; } //end 类的友元成员///////////////////////////////////////////////// //下面用来定义一打瓶子的包装箱 class CBottle; class CCarton { public: CCarton(const CBottle& aBottle); double Volume() { cout<<"m_Height:"<<m_Height<<", m_Length:"<<m_Length<<", m_Width:"<<m_Width<<endl; return m_Height*m_Length*m_Width; } private: double m_Height; double m_Width; double m_Length; }; class CBottle { public: CBottle(double height, double diameter) { m_Height = height; m_Diameter = diameter; } friend CCarton::CCarton(const CBottle& aBottle); private: double m_Height; double m_Diameter; //这是定义友元函数的模式 }; CCarton::CCarton(const CBottle& aBottle) { m_Height = aBottle.m_Height; m_Length = 4.0 * aBottle.m_Diameter; m_Width = 3.0 * aBottle.m_Diameter; } int main(array<System::String ^> ^args) { //这里只是实现类的友元成员,并不是类的友元类 F1 f1; F2 f2(&f1); cout<<"var2:"<<f2.GetVar()<<endl; CBottle cbottle(30, 5); CCarton carton(cbottle); cout<<"carton:"<<carton.Volume()<<endl; system("pause"); return 0; }*/ //9.6 虚函数 /*#include "stdafx.h" #include <iostream> using namespace std; using namespace System; class CBox { public: void ShowVolume() { cout<<"CBox usable volume is "<<Volume()<<endl; } double Volume() { return m_Height * m_Length * m_Width; } CBox(double mh=1.0, double ml=1.0, double mw=1.0):m_Height(mh), m_Length(ml), m_Width(mw) { } protected://任何派生类的成员函数都可以访问它们 double m_Height; double m_Length; double m_Width; }; class CGlassBox: public CBox { public: double Volume() const { return 0.85 * m_Length * m_Width * m_Height; } CGlassBox(double mh, double ml, double mw):CBox(mh, ml, mw) { } }; int main(array<System::String ^> ^args) { CBox myBox(2.0, 3.0, 4.0); CGlassBox myCGlassBox(2.0, 3.0, 4.0); myBox.ShowVolume(); myCGlassBox.ShowVolume(); //我们希望在程序执行时再解决特定的对像实例中使用哪里个Volume()函数的问题,这种操作被称为动态连接或后期绑定 //我们希望ShowVolume()实际调用的Volume()函数版本由被处理的对像种类确定,而不是程序执行之前由编译器任意确定 //我们需要使用的是虚函数 system("pause"); return 0; }*/ //9.6.1 虚函数 //虚函数是以vritual关键字声明的基类函数,如果有基类中将某个函数指定为virtual,并且派生类中有另外一个该函数的定义 //则编译器将知道我们不想静态连接该函数,我们真正需要的是基于调用该函数的对像种类,在程序的特定位置选择调用哪一个函数 /*#include "stdafx.h" #include <iostream> using namespace std; using namespace System; class CBox { public: void ShowVolume() { cout<<"CBox usable volume is "<<Volume()<<endl; } //定义为虚函数 virtual double Volume() { return m_Height * m_Length * m_Width; } CBox(double mh=1.0, double ml=1.0, double mw=1.0):m_Height(mh), m_Length(ml), m_Width(mw) { } protected://任何派生类的成员函数都可以访问它们 double m_Height; double m_Length; double m_Width; }; class CGlassBox: public CBox { public: //定义为虚函数 virtual double Volume() { return 0.85 * m_Length * m_Width * m_Height; } //注意,虽然我们在派生类的Volume()定义中使用了virtual关键字,但这样做不是必需的,在基类中将该函数定义为virtual已经足够了 //建议在派生类中为虚函数指定virtual关键字,因为这样可以使阅读派生类定义的任何人都清楚地知道这些函数是动态选择的虚函数 CGlassBox(double mh, double ml, double mw):CBox(mh, ml, mw) { } }; int main(array<System::String ^> ^args) { CBox myBox(2.0, 3.0, 4.0); CGlassBox myCGlassBox(2.0, 3.0, 4.0); myBox.ShowVolume(); myCGlassBox.ShowVolume(); system("pause"); return 0; }*/ //要使用某个函数的表现出虚函数的行为,该函数在任何派生类中都必须有与基类函数相同的名称,形参数和返回类型,如果基类是const, //那么派生类中的函数也必须是const,如果我们企图使用不同形参或返回类型,或者将一个声明为const,而另一个不然,由虚函数机制将不能工作 //注意: //从派生类函数观点来看,CGlassBox派生类中的Volume()函数实际上隐藏了该函数的基类版本 //如果我们希望从某个派生类函数中调用基类的Volume()函数,由需要以CBox::Volume()这样的形式使用作用域解析运算符来引用基类函数 //9.6.2 使用指向类对像的指针 //使用指针处理基类和派生类的对像是一项重要的技术,指向基类对像的指针不仅可以包含基类对像的地址,还可以包含派生类对像的地址 //因此,我们可以根据被指向的对像种类,使用类型为"基类指针"的指针获得虚函数的不同行为 /*#include "stdafx.h" #include <iostream> using namespace std; using namespace System; class CBox { public: void ShowVolume() { cout<<"CBox usable volume is "<<Volume()<<endl; } //定义为虚函数 virtual double Volume() { return m_Height * m_Length * m_Width; } CBox(double mh=1.0, double ml=1.0, double mw=1.0):m_Height(mh), m_Length(ml), m_Width(mw) { } protected://任何派生类的成员函数都可以访问它们 double m_Height; double m_Length; double m_Width; }; class CGlassBox: public CBox { public: //定义为虚函数 virtual double Volume() { return 0.85 * m_Length * m_Width * m_Height; } //注意,虽然我们在派生类的Volume()定义中使用了virtual关键字,但这样做不是必需的,在基类中将该函数定义为virtual已经足够了 //建议在派生类中为虚函数指定virtual关键字,因为这样可以使阅读派生类定义的任何人都清楚地知道这些函数是动态选择的虚函数 CGlassBox(double mh, double ml, double mw):CBox(mh, ml, mw) { } }; int main(array<System::String ^> ^args) { CBox* pCbox; CBox myBox(2.0, 3.0, 4.0); pCbox = &myBox; cout<<"myBox.Volume:"; pCbox->ShowVolume(); CGlassBox myCGlassBox(2.0, 3.0, 4.0); pCbox = &myCGlassBox; cout<<"CGlassBox.Volume:"; pCbox->ShowVolume(); //结论:虚函数机制借助于指向基类的指针同样能够正常工作,实际调用的函数是基于被指向的对像类型而选择的 //这意味着即使我们不知道程序中某个基类指针所指对像的准确类型(比如某个指针作为实参传递给函数时) //虚函数机制也能确保调用正确的函数,这是一种特别强在的功能,因此务必充分理解 system("pause"); return 0; }*/ //9.6.3 使用引用处理虚函数 //如果我们定义一个形参为基类引用的函数,由可以给该函数传递派生类的对像作为实参,该函数在执行的时候,将自动为传递进来的对像选择造当的虚函数 /*#include "stdafx.h" #include <iostream> using namespace std; using namespace System; class CBox { public: void ShowVolume() { cout<<"CBox usable volume is "<<Volume()<<endl; } //定义为虚函数 virtual double Volume() { return m_Height * m_Length * m_Width; } CBox(double mh=1.0, double ml=1.0, double mw=1.0):m_Height(mh), m_Length(ml), m_Width(mw) { } protected://任何派生类的成员函数都可以访问它们 double m_Height; double m_Length; double m_Width; }; class CGlassBox: public CBox { public: //定义为虚函数 virtual double Volume() { return 0.85 * m_Length * m_Width * m_Height; } //注意,虽然我们在派生类的Volume()定义中使用了virtual关键字,但这样做不是必需的,在基类中将该函数定义为virtual已经足够了 //建议在派生类中为虚函数指定virtual关键字,因为这样可以使阅读派生类定义的任何人都清楚地知道这些函数是动态选择的虚函数 CGlassBox(double mh, double ml, double mw):CBox(mh, ml, mw) { } }; void Output(CBox& aBox); int main(array<System::String ^> ^args) { CBox myBox(2.0,3.0,4.0); CGlassBox myGlassBox(2.0,3.0,4.0); Output(myBox); Output(myGlassBox); system("pause"); return 0; } //class CBox; //该语句只是将名称CBox标识为此该还没有被定义的类,但已经足以使编译器知道CBox是个类名,同时使编译器能够了处理Output()函数的原型, //如果不指出CBox是一个类,那么Output()的原型声明将导致编译器生成一条出错消息 void Output(CBox& aBox) { aBox.ShowVolume(); } */ //9.6.4 纯虚函数 /*#include "stdafx.h" #include <iostream> using namespace std; using namespace System; const double PI = 3.14159265; //定义纯虚函数 class CContainer { public: virtual double Volume() const = 0; //定义虚函数Volume()的语句通过在函数头中添加等号和0,将该函数定义成没有任何内容,这样的函数被称为纯虚函数 virtual void ShowVolume() const { cout<<endl <<"Volume is"<<Volume()<<endl; } }; //9.6.5 抽像类 //包含纯虚函数的类称为抽像类,因为我们不能定义包含纯虚函数的类的对像,抽像类的存在唯一用途,就是定义派生类, //如果抽像类的派生类将基类的纯虚函数依然定义为纯虚函数,则该派生类也是抽像类 //纯虚函数是否存在是判断给定的类是否是抽像类的唯一条件 //同样,抽像类可以拥有多个纯虚函数,这种情况下,派生类必须给出基类中每个线纯虚函数的定义,否则将仍然是抽像类 class CBox : public CContainer { public: virtual void ShowVolume() const { cout<<endl <<"CBox usable volume is "<<Volume(); } virtual double Volume() const { return m_Width * m_Height * m_Length; } CBox(double lv=1.0, double wv=1.0, double hv=1.0):m_Length(lv), m_Width(wv), m_Height(hv) { } private: double m_Width; double m_Height; double m_Length; }; //CCan类-它表示啤酒或可乐罐 //ectern const double PI; class CCan: public CContainer { public: virtual double Volume() const { return 0.25*PI*m_Diameter*m_Height; } CCan(double hv=4.0, double dv=2.0):m_Height(hv), m_Diameter(dv) { } private: double m_Height; double m_Diameter; }; int main(array<System::String ^> ^args) { CContainer* pC1 = new CBox(2.0,3.0, 4.0); CContainer* pC2 = new CCan(6.5, 3.0); pC1->ShowVolume(); pC2->ShowVolume(); delete pC1; delete pC2; //由于派生类对像是动态创建的,因此我们不需要它们时必须使用delete运算符清理自由存储器 system("pause"); return 0; }*/ //9.6.6 间接基类 子类的基类可以是多另外一个基类派生出来的 /*#include "stdafx.h" #include <iostream> using namespace std; using namespace System; const double PI = 3.14159265; //定义纯虚函数 class CContainer { public: virtual double Volume() const = 0; //定义虚函数Volume()的语句通过在函数头中添加等号和0,将该函数定义成没有任何内容,这样的函数被称为纯虚函数 virtual void ShowVolume() const { cout<<endl <<"Volume is"<<Volume()<<endl; } }; class CBox : public CContainer { public: virtual void ShowVolume() const { cout<<endl <<"CBox usable volume is "<<Volume(); } virtual double Volume() const { return m_Width * m_Height * m_Length; } CBox(double lv=1.0, double wv=1.0, double hv=1.0):m_Length(lv), m_Width(wv), m_Height(hv) { } protected: double m_Width; double m_Height; double m_Length; }; class CGlassBox : public CBox { public: virtual double Volume() const { return 0.85 * m_Width * m_Height * m_Length; } CGlassBox(double lv, double wv, double hv):CBox(lv, wv, hv) { } private: }; //CCan类-它表示啤酒或可乐罐 //ectern const double PI; class CCan: public CContainer { public: virtual double Volume() const { return 0.25*PI*m_Diameter*m_Height; } CCan(double hv=4.0, double dv=2.0):m_Height(hv), m_Diameter(dv) { } private: double m_Height; double m_Diameter; }; int main(array<System::String ^> ^args) { CContainer* pC1 = new CBox(2.0,3.0, 4.0); CCan myCan(6.5, 3.0); CGlassBox myGlassBox(2.0, 3.0, 4.0); pC1->ShowVolume(); delete pC1; pC1 = &myCan; pC1->ShowVolume(); pC1 = &myGlassBox; pC1->ShowVolume(); //delete pC1; //这里为什么最后不delete pC1了 //是因为程序一运行结束,动态内存也就自动释放了吗??? cout<<endl; //由于派生类对像是动态创建的,因此我们不需要它们时必须使用delete运算符清理自由存储器 system("pause"); return 0; } //在赋邓指针另一个地址值之前,我们必须从自由存储器中删除CBox对像,如果不这样做,以后我们将无法清理自由存储器,因为已经失去了原来对像的地址 //当重新给指针赋值以及使用自由存储器时,这是个易犯的错误 */