[C++] 多态
1.多态性的概述
多态性指的是发出的同样的消息被不同类型的对象接受时有可能导致完全不同的行为。所谓消息是指对类的成员函数的调用,不同的行为指的不同的实现,也就是调用了不同的函数。
事实上,在程序设计中经常会使用到多态性。最简单的例子就是运算符了,例如我们使用运算符+,就可以实现整型数、浮点数、双精度类型之间的加法运算,这三种类型的加法操作其实是互不相同的,是由不同内容的函数实现的。这个例子就是使用了多态的特征。
1.1多态的类型
面向对象的多态性可以分为4种,重载多态,强制多态,包含多态,参数多态。前面两个统称为专用多态,后面两个统称为通用多态,普通函数及累的成员函数的重载都属于重载多态。接下来将着重介绍的运算符重载,上述加法运算分别适用于浮点数、整型数之间的就是重载的实例。强制多态是指将一个变元的类型加以变化,以符合一个函数或者操作的要求,前面所讲的加法运算符在进行浮点数与整数相加时,首先进行强制类型转换,把整型数转换为浮点数在相加的情况,就是强制多态的实例。
包含多态是类族中定义于不同类中的同名成员函数的多态性为,主要是通过虚函数来实现。参数多态与类模板相关联,在使用时必须赋予实际的类型才可以实例化。这样,由类的模板实例化的各个类都具有相同的操作,而操作对象的类型却各不相同。
1.2 多态的实现
在C++中,多态性的实现和联编(也称绑定)这一概念有关。绑定是指计算机程序自身彼此关联的过程,也就是把一个标识符名和一个存储地址在一起的过程;用面向对象的术语来讲,就是把一条消息和一个对象的方法相结合的过程(一个源程序经过编译、链接,成为可执行文件的过程是把可执行代码联编(或称装配)在一起的过程)。按照绑定的阶段不同分为静态绑定和动态绑定。其中在运行之前就完成的联编成为静态联编(前期联编);而在程序运行之时才完成的联编叫动态联编(后期联编)。
静态联编支持的多态性称为编译时多态性(静态多态性)。在C++中,编译时多态性是通过函数重载和模板实现的。利用函数重载机制,在调用同名函数时,编译系统会根据实参的具体情况确定索要调用的是哪个函数。
动态联编所支持的多态性称为运行时多态(动态多态)。在C++中,运行时多态性是通过虚函数来实现的。
再举一个通俗易懂的例子:比如买票这个行为,普通人买是全价;学生买是半价票等。
2 运算符重载
运算符重载是使同一个运算符作用于不同类型的数据时具有不同的行为。例如,我们声明了一个点类point和它的对象point p1(1,1),p2(3,3),并希望能使用“+”运算符实现表达式“p1+p2”,就需要重载“+”运算符。运算符重载是实质上将运算对象转化为运算函数的实参,并根据实参的类型来确定重载的运算函数。
2.1 运算符重载的规则
1. 只能重载C++中已有的运算符,不能臆造新的运算符;
2. 类属关系运算符“.”、作用域分辨符“::”、成员指针运算符“*”、sizeof运算符和三目运算符“?:”不能重载。
3. 重载之后运算符的优先级和结合性都不能改变,单目运算符只能重载为单目运算符,双目运算符只能重载为双目运算符。
4. 运算符重载后的功能应当与原有功能相类似。
5. 重载运算符含义必须清楚,不能有二义性。
运算符的重载形式有两种:
(1) 重载为类的成员函数(非静态成员函数);
(2) 重载为类的友元函数。
friend <函数类型> operator <运算符>(形参表)
{
函数体;
}
其中:
(1) 函数类型:重载运算符的返回值类型,即运算结果类型;
(2) operator:定义运算符重载函数的关键字。
(3) 运算符:要重载的运算符名称。
(4) 形参表:给出对象和类型。
(5) friend:运算符重载为友元函数时的关键字。
2.2 重载为成员函数
1.双目运算:oprd1 B oprd2把B重载为oprd1所属类的成员函数,只有一个形参,形参的类型是oprd2所属类。例如,经过重载之后,表达式oprd1 + oprd2就相当于函数调用oprd1.operator +(oprd2)。2.单目运算
1) 前置单目运算:U oprd 把U重载为oprd所属类的成员函数,没有形参。例如,“++”重载的语法形式为:<函数类型> operator ++( );++ oprd 就相当于函数调用oprd.operator ++( );
2) 后置单目运算 oprd V运算符V重载为oprd所属类的成员函数,带有一整型(int)形参。例如,后置单目运算符“--”重载的语法形式为:
<函数类型> operator -- ( int );oprd-- 就相当于函数调用oprd.operator -- ( 0 );

//演示重载二维点point的“+”、“-”运算符 #include <iostream> using namespace std; class point { private: float x, y; public: point(float xx = 0, float yy = 0) { x = xx; y = yy; } float get_x() { return x; } float get_y() { return y; } point operator+(point p1); //重载运算符“+” point operator-(point p1); //和“-”为成员函数 }; point point::operator+(point q) { return point(x + q.x, y + q.y); } point point::operator-(point q) { return point(x - q.x, y - q.y); } int main() { point p1(3, 3), p2(2, 2), p3, p4; //声明point类的对象 p3 = p1 + p2; //两点相加 p4 = p1 - p2; //两点相减 cout << "p1+p2: x=" << p3.get_x() << ", y=" << p3.get_y() << endl; cout << "p1- p2: x=" << p4.get_x() << ", y=" << p4.get_y() << endl; return 0; } // p1+p2: x=5, y=5 // p1- p2: x=1, y=1
演示单目运算符:

// 运算符前置++和后置++重载为时钟类的成员函数。 // 前置单目运算符,重载函数没有形参,对于后置单目运算符,重载函数需要有一个整型形参。 // 操作数是时钟类的对象。 // 实现时间增加1秒钟。 #include <iostream> using namespace std; class Clock //时钟类声明 { public: //外部接口 Clock(int NewH = 0, int NewM = 0, int NewS = 0); void ShowTime(); Clock &operator++(); //前置单目运算符重载 Clock operator++(int); //后置单目运算符重载 private: //私有数据成员 int Hour, Minute, Second; }; Clock &Clock::operator++() //前置单目运算符重载函数 { Second++; if (Second >= 60) { Second = Second - 60; Minute++; if (Minute >= 60) { Minute = Minute - 60; Hour++; Hour = Hour % 24; } } return *this; } Clock::Clock(int NewH, int NewM, int NewS) { if (0 <= NewH && NewH < 24 && 0 <= NewM && NewM < 60 && 0 <= NewS && NewS < 60) { Hour = NewH; Minute = NewM; Second = NewS; } else cout << "Time error!" << endl; } void Clock::ShowTime() { cout << Hour << ":" << Minute << ":" << Second << endl; } Clock Clock::operator++(int) //后置单目运算符重载 { //注意形参表中的整型参数 Clock old = *this; ++(*this); return old; } int main() { Clock myClock(23, 59, 59); cout << "First time output:"; myClock.ShowTime(); cout << "Show myClock++:"; (myClock++).ShowTime(); cout << "Show ++myClock:"; (++myClock).ShowTime(); return 0; } // First time output:23:59:59 // Show myClock++:23:59:59 // Show ++myClock:0:0:1
2.3 重载为友元函数
1. 双目运算:oprd1 B oprd2双目运算符B重载为oprd1所属类的友元函数,该函数有两个形参,表达式oprd1 B oprd2就相当于函数调用operator B (oprd1,oprd2)。
2.单目运算
1) 前置单目运算U oprd前置单目运算符U重载为oprd所属类的友元函数,表达式U oprd相当于函数调用operator U(oprd)。
2) 后置单目运算oprd V后置单目运算符V重载为oprd所属类的友元函数,表达式oprd V相当于函数调用operator V(oprd,int)。
演示使用运算符重载为友元函数的方法重做两点加减法运算

#include <iostream> using namespace std; class point { private: float x, y; public: point(float xx = 0, float yy = 0) { x = xx; y = yy; } float get_x() { return x; } float get_y() { return y; } friend point operator+(point p1, point p2); //重载“+”和 friend point operator-(point p1, point p2); //“-”为友元函数 }; point operator+(point p1, point p2) { return point(p1.x + p2.x, p1.y + p2.y); } point operator-(point p1, point p2) { return point(p1.x - p2.x, p1.y - p2.y); } int main() { point p1(3, 3), p2(2, 2), p3, p4; //声明point类的对象 p3 = p1 + p2; //两点相加 p4 = p1 - p2; //两点相减 cout << "p1+p2: x=" << p3.get_x() << ", =" << p3.get_y() << endl; cout << "p1- p2: x=" << p4.get_x() << ", =" << p4.get_y() << endl; } //程序运行结果:p1 + p2 : x = 5, y = 5 p1 - p2 : x = 1, y = 1
2.4其他运算符重载
1.比较运算符重载
例如,<、>、<=、>=、==和!=。
2.赋值运算符重载
例如,=、+=、-=、*=和/=。
这些运算符的重载比较简单。

#include <iostream> using namespace std; class point { private: float x, y; public: point(float xx = 0, float yy = 0) { x = xx; y = yy; } point(point &); ~point() {} bool operator==(point); bool operator!=(point); point operator+=(point); point operator-=(point); float get_x() { return x; } float get_y() { return y; } }; point::point(point &p) { x = p.x; y = p.y; } bool point::operator==(point p) { if (x == p.get_x() && y == p.get_y()) return 1; else return 0; } bool point::operator!=(point p) { if (x != p.get_x() && y != p.get_y()) return 1; else return 0; } point point::operator+=(point p) { this->x += p.get_x(); this->y += p.get_y(); return *this; } point point::operator-=(point p) { this->x -= p.get_x(); this->y -= p.get_y(); return *this; } int main() { point p1(1, 2), p2(3, 4), p3(5, 6); cout << "p1 == p2? " << (p1 == p2) << endl; cout << "p1 != p2? " << (p1 != p2) << endl; p3 += p1; cout << "p3+=p1, p3: " << p3.get_x() << "," << p3.get_y() << endl; p3 -= p1; cout << "p -= p1, p3: " << p3.get_x() << "," << p3.get_y() << endl; } // 程序运行结果为: // p1==p2? 0 // p1!= p2? 1 // p3 += p1, p3: 6, 8 // p3 -= p1, p3: 5, 6

#include <iostream> #include <string.h> using namespace std; class word { private: char *str; public: word(char *s) { str = new char[strlen(s) + 1]; strcpy(str, s); } char &operator[](int k) { return *(str + k); } void disp() { cout << str << endl; } }; int main() { char *s = "china"; word w(s); w.disp(); int n = strlen(s); while (n >= 0) { w[n - 1] = w[n - 1] - 32; n--; } w.disp(); } // 程序运行结果为: china // CHINA

#include <iostream> #include <malloc.h> using namespace std; class rect { private: int length, width; public: rect(int l, int w) { length = l; width = w; } void *operator new(size_t size) // size_t unsigned integer { return malloc(size); } void operator delete(void *p) { free(p); } void disp() { cout << "area: " << length * width << endl; } }; int main() { rect *p; p = new rect(5, 9); //调用构造函数rect( 5, 9 ) p->disp(); delete p; } //程序运行结果为:area: 45
6逗号运算符重载

// 演示重载逗号“,”和加号“+”运算符的程序 #include <iostream> #include <malloc.h> using namespace std; class point { private: int x, y; public: point(){}; point(int xx, int yy) { x = xx; y = yy; } point operator,(point r) { return r; } point operator+(point r) { point t; t.x = x + r.x; t.y = y + r.y; return t; } void disp() { cout << "area: " << x * y << endl; } }; int main() { point p1(1, 2), p2(3, 4), p3(5, 6); p1.disp(); p2.disp(); p3.disp(); p1 = (p1, p2 + p3, p3); //返回右操作数p3的坐标 p1.disp(); } // 程序运行结果为: area: 2 // area: 12 // area: 30 // area: 30
3 虚函数
虚函数是动态绑定的基础。虚函数必须是非静态的成员函数。虚函数经过派生之后,在类族中就可以实现运行过程中的多态。根据赋值兼容规则,可以使用派生类的对象代替基类对象。 如果用基类类型的指针指向派生类对象,就可以通过这个指针访问该对象,问题是访问到的只是从基类继承来的同名成员。解决这一问题的方法是:如果需要通过基类的指针指向派生类的对象,并访问某个与基类同名的成员,那么首先在基类中将这个同名函数说明为虚函数。这样,通过基类类型的指针,就可以使属于不同派生类的不同对象产生不同的行为,从而实现运行时多态。
3.1 虚函数的定义
虚函数的定义语法是:
virtual <函数类型> <函数名>(形参表)
{
函数体
}
虚函数只能出现在类定义中的函数原型声明中,而不能在成员函数实现的时候。
运行时多态有三个条件:首先类之间满足赋值兼容规则,其二是要声明虚函数,第三要由成员函数来调用或者是通过指针、引用来访问虚函数。如果是使用对象名来访问虚函数,则绑定在编译过程中就可以进行(静态绑定),而无须再运行过程中进行。
演示定义和访问虚函数

// 演示定义和访问虚函数 #include <iostream> using namespace std; class base //定义基类base { public: virtual void who() //虚函数声明 { cout << "this is the class of base !" << endl; } }; class derive1 : public base //定义派生类derive1 { public: void who() //重新定义虚函数 { cout << "this is the class of derive1 !" << endl; } }; class derive2 : public base //定义派生类derive2 { public: void who() //重新定义虚函数 { cout << "this is the class of derive2 !" << endl; } }; int main() { base obj, *ptr; //声明基类对象obj、指针ptr derive1 obj1; //声明派生类1的对象obj1 derive2 obj2; //声明派生类2的对象obj2 ptr = &obj; //基类指针指向基类对象 ptr->who(); //调用基类成员函数 ptr = &obj1; //基类指针指向派生类1对象 ptr->who(); //调用派生类1成员函数 ptr = &obj2; //基类指针指向派生类2对象 ptr->who(); //调用派生类2成员函数 return 1; } // 程序运行结果为: // this is the class of base! // this is the class of derive1! // this is the class of derive2! // 如果在基类中去掉关键词virtual,则 // 程序运行结果为: // this is the class of base! // this is the class of base! // this is the class of base!
3.2 虚函数与重载的关系
在派生类中重载一个虚函数,要求函数名、返回类型、参数个数、参数类型以及参数的顺序都与基类中原型完全相同,不能有任何的不同。
而一般的函数重载,只要函数名相同即可,函数的返回类
型及所带的参数可以不同。
3.3 虚函数的限制
设置虚函数须注意:
(1) 只有成员函数才能声明为虚函数。因为虚函数仅适用于有继承关系的类对象,所以普通函数不能声明为虚函数。
(2) 虚函数必须是非静态成员函数。这是因为静态成员函数不受限于某个对象。
(3) 内联函数不能声明为虚函数。因为内联函数不能在运行中动态确定其位置。
(4) 构造函数不能声明为虚函数。多态是指不同的对象对同一消息有不同的行为特性。虚函数作为运行过程中多态的基础,主要是针对对象的,而构造函数是在对象产生之前运行的,因此,虚构造函数是没有意义的。
(5) 析构函数可以声明为虚函数。析构函数的功能是在该类对象消亡之前进行一些必要的清理工作,析构函数没有类型,也没有参数,和普通成员函数相比,虚析构函数情况略为简单些。
虚析构函数的声明语法为:
virtual ~类名
例如:
class B
{
public:
……
virtual ~B( );
};
3.3 纯虚函数
纯虚函数是一个在基类中没有定义具体操作内容的虚函数,要求各派生类根据实际需要定义自己的实现内容。
纯虚函数的声明形式为:virtual <函数类型> <函数名> ( 参数表 ) = 0
纯虚函数与一般虚函数成员的原型在书写形式上的不同就在于后面加了“=0”,表明在基类中不用定义该函数,它的实现部分(函数体)留给派生类去做。
3.4 抽象类
抽象类的主要作用是通过它为一个类族建立一个公共的接口,使它们能够更有效地发挥多态特性。一个抽象类至少带有一个纯虚函数。
使用抽象类时需注意以下几点:
(1) 抽象类只能用作其他类的基类,不能建立抽象类对象。抽象类处于继承层次结构的较上层,一个抽象类自身无法实例化,只能通过继承机制,生成抽象类的非抽象派生类,然后再实例化。
(2) 抽象类不能用作参数类型、函数返回值或显式转换的类型。
(3) 可以声明一个抽象类的指针和引用。
抽象类和纯虚函数例子:

//演示抽象类和纯虚函数 #include <iostream> using namespace std; const double PI = 3.14159; class Shapes //抽象基类Shapes声明 { protected: int x, y; public: void setvalue(int xx, int yy = 0) { x = xx; y = yy; } virtual void display() = 0; //纯虚函数成员 }; class Rectangle : public Shapes //派生类Rectangle声明 { public: void display() //虚成员函数 { cout << "The area of rectangle is : " << x * y << endl; } }; class Circle : public Shapes //派生类Circle声明 { public: void display() //虚成员函数 { cout << "The area of circle is : " << PI * x * x << endl; } }; int main() { Shapes *ptr[2]; //声明抽象基类指针 Rectangle rect; //声明派生类对象rect Circle cir; //声明派生类对象cir ptr[0] = ▭ //抽象基类指针指向Rectangle对象 ptr[0]->setvalue(5, 8); //设置矩形边长 ptr[0]->display(); //调用rect虚成员函数显示矩形面积 ptr[1] = ○ //抽象基类指针指向Circle类对象 ptr[1]->setvalue(10); //设置圆形半径 ptr[1]->display(); //调用cir虚成员函数显示圆形面积 } // 程序中类Shapes、Rectangle和Circle属于同一 // 个类族,抽象类Shapes通过纯虚函数为整个类族提供 // 了通用的外部接口。通过公有派生而来的子类,给出 // 了纯虚函数的具体函数体实现,因此是非抽象类。我 // 们可以定义非抽象类的对象,同时根据赋值兼容规则, // 抽象类Shapes类型的指针也可以指向任何一个派生类 // 的对象,通过基类Shapes的指针可以访问到正在指向 // 的派生类Rectangle和Circle类对象的成员,这样就实 // 现了对同一类族中的对象进行统一的多态处理。程序 // 运行结果为: // The area of rectangle is: 40 // The area of circle is : 314.159
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 【.NET】调用本地 Deepseek 模型
· CSnakes vs Python.NET:高效嵌入与灵活互通的跨语言方案对比
· DeepSeek “源神”启动!「GitHub 热点速览」
· 我与微信审核的“相爱相杀”看个人小程序副业
· Plotly.NET 一个为 .NET 打造的强大开源交互式图表库