一、多态概念
分类:
1.按照表现形式:①函数重载 :调用同一对象相同函数名产生不同行为;
②虚函数:不同对象接受同一消息时产生不同行为。
2.按照系统角度:①静态多态性:静态绑定,早期联编,通过函数重载实现;
②动态多态性:动态绑定,滞后联编,通过虚函数和继承实现。
二、虚函数
1.介绍虚函数:
基类成员函数用virtual修饰(普通函数不能声明为虚函数)。
出现原因:早期绑定机制(静态)使 指向派生类对象 的 基类指针or引用 只能调用 基类的函数,无法使用派生类的同名函数。
作用:实现运行时绑定,虚函数在基类和派生类之间提供了同名接口界面,相同名字的虚函数可以在派生类中重新定义,以实现用指针or引用调用不同的函数功能。
定义虚函数的语法格式如下:
class 类名{ virtual 返回类型 函数名 形参表;} 注意:①只有成员函数才能够声明为虚函数,且不能是静态成员函数 or 内联函数(这两种是早期联编的) or 构造函数。可以是析构函数 。
②若基类中定义了虚函数,其派生类中的同名函数也必定是虚函数,并且派生类需要重新定义这个函数。要求两者的函数原型完全相同 ,如果不完全相同,不具有虚特性。
③必须是通过基类的指针or引用 才能实现动态联编,通过基类的对象调用虚函数只能实现静态联编。
④虚函数具有传递性,基类派生的继承链上所有的派生类中原型完全相同的函数都是虚函数,必须使用public继承方式,否则无法实现虚特性。
举个栗子
#include<iostream>using namespace std;class First{ public: First(int num){ number = num ; } virtual void show()const{ cout<<"First number : " << number <<endl; } int getNumber()const{return number;} private: int number;};class Second : public First{ public: Second(int num):First(num){} void show()const{ cout<<"Second number : "<<getNumber()<<endl; }};class Third : public First{ public: Third(int num):First(num){} void show()const{ cout<<"Third number : "<<getNumber()<<endl; }};void function(const First &obj){ obj.show();}int main(){ First f(1); Second s(2); Third t(3); /* 指针 */ cout<<"基类指针情况:"<<endl; First *point; point = &f; point->show(); point = &s; point->show(); point = &t; point->show(); /* 引用 */ cout<<"基类引用情况:"<<endl; function(f); function(s); function(t);}输出:基类指针情况:First number : 1Second number : 2Third number : 3基类引用情况:First number : 1Second number : 2Third number : 32.覆盖与隐藏
这两个概念是出现在基类和派生类的类内同名函数之间的,不是互斥关系,我们需要好好区分:
覆盖:派生类重新定义在派生类中定义的同名函数,覆盖掉基类的函数功能。
条件:派生类和基类的同名函数原型完全相同 && 基类的函数为虚函数。
隐藏:是与作用域有关的概念, 在派生类和基类关系中指派生类内函数or对象调用同名函数调用的是派生类自己的,父类的同名函数被隐藏了。
条件:派生类和基类的同名函数原型不同 or 基类的函数不是虚函数。
突破隐藏:通过基类指针可以调用基类的非虚函数。
可以理解为:覆盖的目标是重新实现基类的方法,隐藏只是继承关系中的一种特性。二者的显性区别发生在:用基类指针访问派生类的同名函数。
3.虚析构函数
目的:确保多重继承情况下将对象完全销毁,防止使用基类指针销毁派生类对象时,只调用了基类析构函数,只析构了基类对象。
在有继承关系时,析构函数就应该设计为虚析构函数。
派生类的析构函数最好也加上virtual。
4.纯虚函数
在基类中没有函数实现,子类中实现基类声明的纯虚函数。
语法格式如下:
class 类名{ public: virtual 返回类型 函数名(参数表) = 0;}; 注意:①若派生类中没有对基类的声明的纯虚函数进行定义,则该函数在派生类中依然为纯虚函数。
②一旦在类中有没实现的纯虚函数,该类就不能创建实例对象。
5.抽象类(抽象基类)
定义:类中有未实现的纯虚函数。
举个栗子:
#include<iostream>using namespace std;class animal{ public: virtual void eat() = 0;};class dog:public animal{ public: void eat(){ cout<<"eat bones"<<endl; }};void function(animal *a){ a->eat();}int main(){ dog d; animal *p = &d; function(p);} 注意:①不能用抽象类创建实例对象,因此也不可能用抽象类对象作为函数传人的参数、函数返回值类型或显示转换类型。
②可以使用抽象类的指针or引用,支持运行时多态。
三、重载
1.函数重载
函数重载概念:在同一作用域内让多个不同的函数体共用同一个函数名的方式称为函数重载。
分类:①同一个类的同名函数重载; ②类外的全局函数重载。
重载函数之间的区别:差别出现在参数类型、参数数量和参数顺序上。(注意:char、const char、char&、const char&不能相互区分,char 和 char*之间可以)
函数重载的二义性问题:①全默认参情况下:
int Calculate();int Calculate(int a = 4);int Calculate(int a = 4,int b = 5);int Calculate(int a = 4,int b = 5,int c = 6);调用Calculate();时编译器无法确定执行上述哪个。 ②重载函数中含有隐式类型转换时:
int Calculate(int a);int Calculate(float a);书上说调用Calculate(0.5)时可能会报错,然而并没有。构造拷贝函数重载:需要重载的三种情况:①以对象作为函数参数,以值传递的方式传入函数时;
②以对象作为函数返回值,以值传递的方式从函数返回对象时;
③用对象初始化另一对象时。
举个栗子:
#include<iostream>#include<cstring>using namespace std;class animal{ private: string name; public: animal(){} animal(string name){this->name = name;} string getName(){ return name; } void setName(string name){ this->name = name; }};class person{ private: int number; animal *pet; public: person(){} person(int num,animal *p){ number = num; pet = p; } person(const person &a){ number = a.number; animal *temp = new animal(a.pet->getName()); this->pet = temp; } void setNum(int num){this->number = num;} int getNum(){return number;} void setPet(string newname){ pet->setName(newname); } animal getPet(){ return *pet; } void showMessage(){ cout<<"number : "<<number<<" , pet name : "<<pet->getName()<<endl; } person &operator=(person &a){ setNum(a.number); animal *temp = new animal(a.pet->getName()); this->pet = temp; return *this; }};int main(){ animal cat("cat"); person a(1,&cat); person c(a); c.showMessage(); c.setPet("rabbit"); c.showMessage(); a.showMessage(); }输出:number : 1 , pet name : catnumber : 1 , pet name : rabbitnumber : 1 , pet name : cat2.运算符重载
实际上是函数重载的一种,属于静态多态性,区分运算符重载方式和函数重载一致。
语法格式如下:
返回类型 operator 运算符 形参表{ 函数体;} 注意:①运算对象中至少一个自定义的数据类型。
②不能违背运算符原来的语法规则,不能改变原来的优先级和结合性。
③以下运算符不能重载(注意三目运算符不能重载):
sizeof . .* :: ?: ④以下运算符只能重载为类的非成员运算符(看后面解释)。
<< >> ⑤以下运算符只能重载为成员运算符:
= () [] -> ⑥不能重载C++内本来没有的运算符。
- 类的成员运算符函数重载:在类内进行运算符重载。 基本语法格式如下:
返回类型 operator 运算符 形参表{函数体;}
注意:该运算符由类的对象调用(第一操作数为特定类型),因此函数参数比运算符要求的个数少1。
- 非成员运算符函数重载:类外作为一般函数,也可以加上friend关键字重载为类的友元函数。基本语法格式如下:
返回类型 operator 运算符 形参表{函数体;}
②单目运算符最好重载为成员函数,双目运算符最好重载为类的非成员函数。
下面分别是单目、双目、赋值、拷贝举栗:
①单目运算符:
注意++、-- 前缀和后缀的区别
#include<iostream>using namespace std;class person{ private: int number; public: person(int num){number = num;} int operator++(){ //前缀自增 number = number + 1; return number; } int operator++(int){ //后缀自增,通过加一个形参int和前缀自增区别 int temp = number; number = number + 1; return temp; } int getNum(){ return number; }};int main(){ person p(0); cout<<"++p:"<<++p<<endl; cout<<"p.getNum():"<<p.getNum()<<endl; cout<<"p++:"<<p++<<endl; cout<<"p.getNum():"<<p.getNum()<<endl;}输出:++p:1p.getNum():1p++:1p.getNum():2②双目运算符:如+号,如果是类的成员运算符重载,第一操作对象由this指针给出。
#include<iostream>using namespace std;class person{ private: int number; public: person(int num){number = num;} int getNum(){ return number; }};int operator+(person a,person b){ return a.getNum()+b.getNum();}int main(){ person a(1); person b(1); cout<<"a+b="<<a+b<<endl; }输出:a+b=2③赋值运算符:系统会为每个类自动生成默认的赋值运算符完成浅拷贝,但最好自己重载一个实现深拷贝。
要求:赋值运算符重载函数不能是静态函数 and 友元函数。
需要重写的情况:类中有指针数据成员时。
浅拷贝示例:
#include<iostream>#include<cstring>using namespace std;class animal{ private: string name; public: animal(string name){this->name = name;} string getName(){ return name; } void setName(string name){ this->name = name; }};class person{ private: int number; animal *pet; public: person(int num,animal *p){ number = num; pet = p; } void setPet(string newname){ pet->setName(newname); } void showMessage(){ cout<<"number : "<<number<<" , pet name : "<<pet->getName()<<endl; }};int main(){ animal cat("cat"); person a(1,&cat); person b =a; b.setPet("dog"); a.showMessage(); b.showMessage();}输出:number : 1 , pet name : dog //改了b指针指向的内容,相当于改了a指针指向的内容。number : 1 , pet name : dog 深拷贝示例:
#include<iostream>#include<cstring>using namespace std;class animal{ private: string name; public: animal(){} animal(string name){this->name = name;} string getName(){ return name; } void setName(string name){ this->name = name; }};class person{ private: int number; animal *pet; public: person(){} person(int num,animal *p){ number = num; pet = p; } void setNum(int num){this->number = num;} int getNum(){return number;} void setPet(string newname){ pet->setName(newname); } animal getPet(){ return *pet; } void showMessage(){ cout<<"number : "<<number<<" , pet name : "<<pet->getName()<<endl; } person &operator=(person &a){ setNum(a.number); animal *temp = new animal(a.pet->getName()); this->pet = temp; return *this; }};int main(){ animal cat("cat"); person a(1,&cat); person b; b = a; b.showMessage(); b.setNum(2); b.setPet("dog"); a.showMessage(); b.showMessage(); //注意 : 在声明时赋值只会调用默认赋值函数,实现浅拷贝。}输出:number : 1 , pet name : catnumber : 1 , pet name : catnumber : 2 , pet name :