第7章 继承
7.1 类之间的关系
7.2 基类和派生类
7.3 基类的初始化
7.4 继承的应用实例
第8章 虚函数与多态性
8.2 类指针的关系
7.1 类之间的关系
has-A,uses-A和is-A
has-A 包含关系,用以描述一个类由多个“部件类”构成。实现has-A关系用类成员表示,即一个类中的数据成员是另一种已经定义的类。
uses-A 一个类部分地使用另一个类。通过类之间成员函数的相互联系,定义友元或对象参数传递实现。
is-A 机制称为“继承”。关系具有传递性,不具有对称性。
继承是类之间定义的一种重要关系。
一个B类继承A类,或称从类A派生类B。
类A称为基类(父类),类B称为派生类(子类)
7.2 基类和派生类
类继承关系的语法形式
class 派生类名:基类名表
{
数据成员和成员函数声明
};
基类名表 构成
访问控制 基类名1,访问控制 基类名2,...,访问控制 基类名n
7.2.1访问控制
派生类对基类成员的使用,与继承访问控制和基类中成员性质有关。
公有继承
基类的公有成员->派生类的公有成员
基类的保护成员->派生类的保护成员
私有继承
基类的公有成员和保护成员->派生类的私有成员
保护继承
基类的公有成员和保护成员->派生类的保护成员
不论哪种方式继承,派生类都不能直接使用基类的私有成员
派生类支配基类的同名函数
对象一定先调用自己的同名成员函数,如果自己没有同名函数,则调用直接基类的同名函数,依此类推。当然,使用作用域分辨运算符可以指定要调用的函数。
二义性
可以使用作用域分辨运算符“::”和成员名限定解决二义性。
//子类函数与父类函数同名,覆盖父类函数
//每一个子类都会生成一个默认的父类对象,调用同名父类函数的方法
1 #include "coder.h" 2 #include "cppcoder.h" 3 4 void main() 5 { 6 cppcoder *ptansheng = new cppcoder; 7 8 ptansheng->girlfriend(); 9 ptansheng->ui(); 10 ptansheng->coding();//子类函数与父类函数同名,覆盖父类函数 11 ptansheng->coder::coding();//每一个子类都会生成一个默认的父类对象,调用同名父类函数的方法 12 13 delete ptansheng; 14 15 system("pause"); 16 }
赋值兼容规则是指在公有派生情况下,一个派生类的对象可以作为基类的对象来使用的情况。
(1)派生的对象可以赋给基类的对象。
(2)派生类的对象可以初始化基类的引用。
(3)派生类的对象的地址可以赋给指向基类的指针。
但是,在后两种情况下,这样的引用或者指针只能访问派生类对象所继承的基类成员。
//用父类的指针可以接受子类的地址
reinterpret_cast专业转换指针
1 void main() 2 { 3 coder *pcoder = new cppcoder;//用父类的指针可以接受子类的地址 4 pcoder->coding(); 5 6 cppcoder *pcppcoder = reinterpret_cast<cppcoder *>(pcoder);//专业转换指针 7 pcppcoder->coding(); 8 9 std::cout << typeid(pcoder).name() << std::endl; 10 std::cout << typeid(pcppcoder).name() << std::endl; 11 12 system("pause"); 13 }
7.2.2重名成员
派生类定义了与基类同名的成员,在派生类中访问同名成员时屏蔽了基类的同名成员。
在派生类中使用基类的同名成员,显式地使用类名限定符:
类名::成员
//访问基类的同名函数
//reinterpret_cast专业转换指针
1 #include <iostream> 2 3 class father 4 { 5 public: 6 father() 7 { 8 num = 99; 9 } 10 int num; 11 void print() 12 { 13 std::cout << num << std::endl; 14 } 15 }; 16 17 class son :public father 18 { 19 public: 20 son() 21 { 22 num = 89; 23 } 24 int num; 25 void print() 26 { 27 std::cout << num << std::endl; 28 } 29 }; 30 31 void main() 32 { 33 son *pson = new son; 34 35 pson->print();//89 36 pson->father::print();//访问基类的同名函数,98 37 38 father *p = reinterpret_cast<father *>(pson);//reinterpret_cast专业转换指针 39 p->print();//98 40 41 system("pause"); 42 }
7.2.3派生类中的静态成员
基类定义的静态成员,将被所有派生类共享。
根据派生成员自身的访问特性和派生类的继承方式,在类层次体系中具有不同的访问特性。
派生类中访问静态成员,用以下形式显式说明:
类名::成员
或通过对象访问:
对象名.成员
注意:静态成员可以被继承,这时基类对象和派生类的对象共享该静态成员。
静态数据成员,统计某个类以及派生类的所有对象的个数
静态成员函数,可以脱离对象调用的函数
1 #include <iostream> 2 3 class myclass//基类 4 { 5 public: 6 int data; 7 static int num;//声明静态变量 8 myclass() 9 { 10 num++;//静态数据成员,统计某个类以及派生类的所有对象的个数 11 } 12 static void print() 13 { 14 //this->data;//静态成员函数没有this指针 15 std::cout << num << std::endl; 16 } 17 }; 18 19 int myclass::num = 0;//定义静态变量 20 21 class ziclass :public myclass//子类 22 { 23 }; 24 25 class sunclass :public ziclass//孙类 26 { 27 }; 28 29 void main() 30 { 31 ziclass *p = new ziclass; 32 ziclass z1; 33 sunclass *ps = new sunclass; 34 35 p->print();//3 36 p->myclass::print();//3 37 38 ps->print();//3 39 ps->ziclass::myclass::print();//3 40 41 system("pause"); 42 }
7.3 基类的初始化
建立一个类层次后,通常创建某个派生类的对象,包括使用基类的数据和函数。
C++提供一种机制,在创建派生类对象时用指定参数调用基类的构造函数来初始化派生类继承基类的数据。
定义派生类的构造函数的一般形式如下:
派生类名::派生类名(参数表0):基类名(参数表)
{
...//函数体
}
1 #include <iostream> 2 3 class myclass 4 { 5 private: 6 int x; 7 public: 8 myclass() :x(0)//构造函数 9 { 10 std::cout << "myclass init without num" << std::endl; 11 } 12 myclass(int num) :x(num)//构造函数 13 { 14 std::cout << "myclass init with num" << std::endl; 15 } 16 }; 17 18 class myziclass :public myclass 19 { 20 private: 21 int x; 22 int y; 23 public: 24 myziclass()//构造函数 25 { 26 std::cout << "myziclass init without num" << std::endl; 27 } 28 myziclass(int num) :myclass(num), x(num + 1), y(num + 2)//派生类名::派生类名(参数表0):基类名(参数表) 29 { 30 std::cout << "myziclass init with num" << std::endl; 31 } 32 }; 33 34 void main() 35 { 36 myziclass *p = new myziclass(1); 37 38 system("pause"); 39 }
7.4 继承的应用实例
点,线,圆,球
1 #include <iostream> 2 3 class dian 4 { 5 private: 6 int x; 7 int y; 8 int z; 9 public: 10 friend class xian; 11 dian(int a, int b, int c) :x(a), y(b), z(c) 12 { 13 14 } 15 void print() 16 { 17 std::cout << "x=" << x << " y=" << y << " z=" << z << std::endl; 18 } 19 }; 20 21 class xian//包含设计 22 { 23 private: 24 dian dian1; 25 dian dian2; 26 public: 27 xian(dian dianx, dian diany) :dian1(dianx), dian2(diany) 28 { 29 30 } 31 double getlength()//线的长度 32 { 33 double length = 0; 34 length = sqrt((dian1.x - dian2.x)*(dian1.x - dian2.x) + (dian1.y - dian2.y)*(dian1.y - dian2.y) + (dian1.z - dian2.z)*(dian1.z - dian2.z)); 35 return length; 36 } 37 }; 38 39 class yuan :public xian//继承设计 40 { 41 public: 42 yuan(dian dianx, dian diany) :xian(dianx, diany) 43 { 44 45 } 46 double getmianji()//圆的面积 47 { 48 return 3.1415926*(this->getlength())*(this->getlength()); 49 } 50 double getzhouchang()//圆的周长 51 { 52 return 3.1415926 * 2 * (this->getlength()); 53 } 54 }; 55 56 class qiu :public yuan//继承设计 57 { 58 public: 59 qiu(dian dian1, dian dian2) :yuan(dian1, dian2) 60 { 61 62 } 63 double getmianji()//球的面积 64 { 65 return 3.1415926 * (this->getlength()) * (this->getlength()) * 4; 66 } 67 double getzhouchang()//球的周长 68 { 69 return 4 / 3.0*.31415626*(this->getlength()) *(this->getlength()) *(this->getlength()); 70 } 71 72 }; 73 74 void main() 75 { 76 dian dian1(0, 0, 1);//创建一个点 77 dian dian2(0, 0, 6);//创建一个点 78 79 dian1.print();//打印点的坐标 80 dian2.print();//打印点的坐标 81 82 xian xian1(dian1, dian2);//创建一个线 83 84 std::cout << xian1.getlength() << std::endl;//打印线的长度 85 86 yuan yuan1(dian1, dian2);//创建一个圆 87 88 std::cout << yuan1.getmianji() << std::endl;//打印圆的面积 89 90 std::cout << yuan1.getzhouchang() << std::endl;//打印圆的周长 91 92 qiu qiu1(dian1, dian2); 93 94 std::cout << qiu1.getmianji() << std::endl;//打印球的面积 95 96 std::cout << qiu1.getzhouchang() << std::endl;//打印球的周长 97 98 system("pause"); 99 }
第8章 虚函数与多态性
多态性是指一个名字,多种语义;或界面相同,多种实现。
重载函数是多态性的一种简单形式。
虚函数允许函数调用与函数体的联系在运行时才进行,称为动态联编。
8.2 类指针的关系
基类指针和派生类指针与基类对象和派生类对象4种可能匹配:
1 直接用基类指针引用基类对象;
2 直接用派生类指针引用派生类对象;
3 用基类指针引用一个派生类对象;
4 用派生类指针引用一个基类对象;
//类而言,数据是私有的,代码是公有的
//指针为空,指向一个类,可以直接调用方法,但是涉及内部成员会崩溃,不涉及内部成员可以执行
//父类指针引用父类对象,完全正常引用
//子类指针引用子类对象,覆盖父类的同名函数
//父类指针引用子类对象,只能引用父类中的函数
//子类指针引用父类对象
//不涉及内部数据的函数会调用成功//涉及到内部数据的会调用成功,但是执行失败
//子类指针需要使用作用域运算符pnewnewzi->fu::print();才能引用父类函数
//static_cast用于一般的数据类型转换
zi *pnewnewzi = static_cast<zi *>(new fu);
1 #include <iostream> 2 #include "fu.h" 3 #include "zi.h" 4 5 //类而言,数据是私有的,代码是公有的 6 //指针为空,指向一个类,可以直接调用方法,但是涉及内部成员会崩溃,不涉及内部成员可以执行 7 8 //父类指针引用父类对象,完全正常引用 9 //子类指针引用子类对象,覆盖父类的同名函数 10 //父类指针引用子类对象,只能引用父类中的函数 11 //子类指针引用父类对象 12 //不涉及内部数据的函数会调用成功 13 //涉及到内部数据的会调用成功,但是执行失败 14 //子类指针需要使用作用域运算符pnewnewzi->fu::print();才能引用父类函数 15 16 void main() 17 { 18 //4用派生类指针引用一个基类对象 19 //zi *pnewzi = new fu;//error C2440: “初始化”: 无法从“fu *”转换为“zi *” 20 21 //static_cast用于一般的数据类型转换 22 fu *pnewnewfu = new fu; 23 zi *pnewnewzi = static_cast<zi *>(pnewnewfu); 24 25 pnewnewzi->fufu();//我是你爹 26 pnewnewzi->zizi();//我是你儿子 27 pnewnewzi->fu::print();//父亲 28 //pnewnewzi->zi::print();//报错 29 //std::cout << pnewnewzi->strzi << std::endl;//报错 30 31 //3用基类指针引用一个派生类对象 32 //fu *pnewfu = new zi; 33 //pnewfu->print();//父亲 34 //pnewfu->fufu();//我是你爹 35 36 //2直接用派生类指针引用派生类对象 37 //zi *pzi = new zi; 38 //pzi->print();//儿子,子类函数覆盖父类函数 39 //pzi->zizi();//我是你儿子 40 //pzi->fufu();//我是你爹 41 42 //1直接用基类指针引用基类对象 43 //fu *pfu = new fu; 44 //pfu->print();//父亲 45 //pfu->fufu();//我是你爹 46 47 //类的对象形式 48 //fu fu1; 49 //fu1.print();//父亲 50 //fu1.fufu();//我是你爹 51 52 system("pause"); 53 }
//1直接用基类指针引用基类对象,正常
//2直接用派生类指针引用派生类对象,正常
//3用基类指针引用一个派生类对象,子类没有被删除,会内存泄漏
解决办法:使用虚析构函数。
//4用派生类指针引用一个基类对象,内存越界,超过界限释放内存,有时候会出错
1 #include <iostream> 2 #include "fu.h" 3 #include "zi.h" 4 5 void main() 6 { 7 {//1直接用基类指针引用基类对象,正常 8 //fu *pfu = new fu; 9 //delete pfu; 10 } 11 12 {//2直接用派生类指针引用派生类对象,正常 13 //zi *pzi = new zi; 14 //delete pzi; 15 } 16 17 {//3用基类指针引用一个派生类对象,子类没有被删除,会内存泄漏 18 //fu *pnewfu = new zi; 19 //delete pnewfu; 20 } 21 22 {//4用派生类指针引用一个基类对象,内存越界,超过界限释放内存,有时候会出错 23 zi *pnewzi = static_cast<zi *>(new fu); 24 delete pnewzi; 25 } 26 27 system("pause"); 28 }
//成员变量的覆盖
//类A是基类,类B是类A的派生类,如果类B重新说明了新的数据成员,而这些新的数据成员与继承过来的数据成员同名,那么这些新的数据成员和继承过来的数据成员是相互独立的,不论是否静态数据成员
1 #include <iostream> 2 3 //成员变量的覆盖 4 //类A是基类,类B是类A的派生类,如果类B重新定义了新的重名数据成员,这些新的数据成员和继承的数据成员是相互独立的 5 6 class A 7 { 8 public: 9 int num; 10 static int data;//声明静态数据成员 11 }; 12 13 class B :public A 14 { 15 public: 16 int num; 17 static int data;//声明静态数据成员 18 }; 19 20 int A::data = 1;//定义静态数据成员 21 int B::data = 2;//定义静态数据成员 22 23 void main() 24 { 25 B b1; 26 b1.num = 10; 27 b1.A::num = 20; 28 29 std::cout << b1.num << " " << &b1.num << std::endl;//10 30 std::cout << b1.A::num << " " << &b1.A::num << std::endl;//20 31 32 std::cout << b1.data << " " << &b1.data << std::endl;//2 33 std::cout << b1.A::data << " " << &b1.A::data << std::endl;//1 34 35 system("pause"); 36 }