导图社区 多态性
c++中关于多态性的总结,详细介绍了多态性的总体架构、运算符重载、类型转换以及虚函数动态重载等内容。
编辑于2025-05-22 15:33:35多态性
总的架构
什么是多态性?
相同消息->不同的对象->不同的行为
多态性的实现
联编(绑定)
运行之前
静态联编(前期):编译时(静态)多态性
使用:函数重载(运算符重载)+模板
运行时
动态联编(后期):运行时(动态)多态性
使用:虚函数
运算符重载
创建运算符重载函数(3种)
1、类外定义
问题引导:对象不能相加
Complex com1(1.1, 2.2), com2(3.3, 4.4), total; total = com1 + com2; //错误
问题解决:运算符重载函数
operator+ 加法 operator- 减法 operator* 乘法 operator< 小于
定义函数
Complex operator+(Complex om1, Complex om2) { Complex t; t.real = om1.real + om2.real; t.imag = om1.imag+om2.imag; return t; }
定义完成后可使用语句:total=com1+com2或total=operator+(com1,com2)
特例:C++中不能使用重载的运算符
. 成员访问运算符 .* 成员指针访问运算符 :: 作用域运算符 sizeof 长度运算符 ?: 条件运算符
特例:赋值运算符=
一般,用于类对象(至少1个)的运算符必须重载,但赋值运算符“=”例外。 但,当数据成员中包含指向动态分配内存的指针成员时,使用系统提供的对象复制运算符函数就不能满足程序要求,就需要用户自己编写赋值运算符重载函数
解决1中不能访问类的私有/保护成员的问题
定义为类的友元函数2
定义为类的成员函数3
2、友元运算符重载函数
形式
类内部
friend 函数类型 operator 运算符(形参表) { 函数体 }
类中声明,类外定义
声明 class X{ ... friend 函数类型 operator 运算符(形参表); ... }; 定义 函数类型 operator 运算符(形参表) { 函数体 }
双目运算符重载
2个操作数:在运算符的左右两侧
@:表示所要重载的运算符
在类中采用@且aa,bb是类X中的两个对象 下面两种调用方式是等价的 aa@bb; //隐式调用 operator@(aa,bb); //显式调用 例子: A3=A1+A2; A4=A1-A2; A5=A1*A3; A6=A1/A4; 在C++中解释为: A3=operator+(A1,A2); A4=operator-(A1,A2); A5=operator*(A1,A2); A6=operator/(A1,A2);
临时对象
Complex operator+(Complex &a, Complex &b) { Complex temp; temp.real = a.real +b.real; temp.imag = a.imag+b.imag; return temp; } 直接用类的构造函数来生成一个临时对象(如下,上下两种写法是等价的) Complex operator+(Complex &a, Complex &b) { return Complex(a.real+b.real,a.imag+b.imag); }
单目运算符重载
需要1个显式的操作数
@
单目运算符@,aa是类的对象 下面2式是等价的 @aa; //隐式调用 operator@(aa); //显示调用
有的运算符不能定义为友元运算符重载函数,如赋值运算符“=”、下标运算符“[]”、函数调用运算符“( )”等
3、成员运算符重载函数
语法形式
类内部
函数类型 operator 运算符(形参表) { 函数体 }
类中声明,类外定义
类内部声明: class X{ //... ... 函数类型 operator 运算符(形参表); //.... ... }; 类外定义: 函数类型 X::operator 运算符(形参表) { 函数体 }
在成员运算符重载函数的形参表中,运算符
单目->参数表为空
双目->参数表中有一个操作数
双目运算符重载:形参表中只有一个参数,作为运算符的右操作数,另一个操作数(左操作数)是隐含的,是该类的当前对象,它是通过this指针隐含地传递给函数
例如: class X{ ... int operator+(X a); ... }; 2个操作数,一个是当前对象,另一个是类X的对象a
@
aa@bb; //隐式调用 aa.operator@(bb); //显式调用 例子: A3=A1+A2; A4=A1-A2; A5=A1*A3; A6=A1/A4; 在C++中解释为: A3=A1.operator+(A2); A4=A1.operator-(A2); A5=A1.operator*(A2); A6=A1.operator/(A2);
单目运算符重载:参数表中没有参数,此时当前的对象作为运算符的一个操作数
Coord Coord ::operator++() //定义运算符++重载函数operator++ { ++x; ++y; return *this; //返回当前对象的值 }
@
等价: @aa; //隐式调用 aa.operator@(); //显式调用 例子: ++ob; 或 ob.operator++(); 当成员函数重载单目运算符时,没有参数显式地传递给成员运算符函数。参数是通过this指针隐含地传递给函数
2和3的比较
选择经验: 1、对于单目运算符,建议选择成员函数 2、对于运算符“=、()、[]、->“只能作为成员函数 3、对于运算符”+=、-=、/=、*=、&=、!=、~=、%=、<<=、>>=“,建议重载为成员函数 4、对于其他运算符,建议重载为友元函数
双目运算符:重载为友元更好
成员:参数表含有1个参数
友元:参数表中含有2个参数
单目运算符:重载为成员更好
成员:没有参数
友元:只有1个参数
如果运算符所需的操作数(尤其是第一个操作数)希望有隐式类型转换,则运算符重载必须用友元函数,而不能用成员函数
”++“和”--“的重载
ob++和++ob的区别:参数表是否插入关键字int,调用是参数int一般被传递值为0
前缀:++ob ob.operator++( ); //成员函数重载 operator++(X &ob); //友元函数重载,其中o为X类对象的引用 后缀:ob++ ob.operator++(int); //成员函数重载 operator++(X &ob,int); //友元函数重载,其中o为X类对象的引用
赋值运算符“=”的重载
系统默认的赋值运算符函数
例如: X &X::operator=(const X &source) { //成员间赋值 }
指针悬挂问题:类中有指针类型
用深层复制解决指针悬挂问题:分配新区域->例子:书本p222
类的赋值运算符“=”只能重载为成员函数,不能重载为友元函数(书上的描述不是很理解)
下标运算符“[ ]”的重载:双目运算符(也不是特别理解)p224
X[Y]; -> X.operator[](Y); 下标运算符重载函数只能定义成员函数,形式如下: 返回类型 类名 ::operator[](形参) { //函数体 }
访问类中私有数据
类型转换
系统预定义类型间的转换(标准数据类型间的转换)
1、隐式转换
规则: ①赋值表达式A=B,B转换成A类型后进行赋值 ②char或short类型变量与int进行运算时,将char或short转换成int类型 ③当两个操作对象类型不一致,在算术运算前,级别低的类型自动转换为级别高的类型 例子: int x=5,y; y=3.5+x; 过程:x值的5转换成double类型,然后与3.5相加得到8.5,再像整数y赋值时,将8.5转换成整数8,然后赋值给y。
2、显示转换
①C语言: (类型名)表达式 例如: double i=2.2,j=3.2; cout<<(int)(i+j); ②C++: 类型名(表达式) 例如: double i=2.2,j=3.2; cout<<int(i+j);
类类型与系统预定义类型间的转换(方式)
转换构造函数:将一个其他类型的数据转换成它所在类的对象——只有一个形参
一、 通常,使用转换构造函数将一个指定的数据转换成类对象的方法如下: ①声明一个类(例如Complex) ②在类中定义一个只有一个参数的构造函数,参数是待转换类型的数据,在函数体中指定转换方法。 例如: Complex(double r) {real=r;imag=0;} //函数体中指定转换的方法 ③形式: 类名(待转换类型的数据) 例如: Complex(7.7) 二、 下面两条句子等价: 1、 Complex com2(7.7); //调用转换构造函数,将7.7转换成对象com2 total=com1+com2; //两个Complex类对象相加 2、 total=com1+Complex(7.7);
类型转换函数:将一个类的对象转换成另一类型的数据
类中定义格式: operator 目标类型() { 函数体 } 例如: 声明了一个Complex类, 在Complex类中定义一个类型转换函数: operator double() { return real;}
注意点: 1、此函数只能定义为一个类的成员函数而不能为类的友元函数 2、既没有参数,也不能在函数名前面指定函数类型 3、函数中必须有return语句
隐式转换的例子要的话可见书本p234
虚函数:动态的重载 ——允许函数调用与函数体之间的联系在运行时才建立
引入:C++中规定:基类的对象指针可以指向它的公有派生的对象,但是当其指向公有派生类对象时,它只能访问派生类中从基类继承来的成员,而不能访问公有派生类中定义的成员。
定义:在基类中用virtual说明,在派生类中重新定义的函数
虚函数的定义在基类中进行
定义: virtual 函数类型 函数名(形参表) { 函数体 }
在基类中,只声明虚函数原型(需加上virtual),而在类外定义虚函数时,则不必加virtual
在派生类中重新定义虚函数时,virtual可写可不写
虚函数必须是其所在类的成员函数
不能是友元函数,也不能是静态成员函数。 因为虚函数调用要靠特定的对象来决定该激活哪个函数。
作用:允许在派生类中重新定义与基类同名的函数,并且可以通过基类指针或引用来访问基类和派生类中的同名函数
虚析构函数:virtual ~类名();
解决由new建立的派生类无名对象和定义了一个基类的指针,并将无名对象的地址赋给这个对象指针,利用delete撤销无名对象时,只执行基类的虚构函数,而不执行派生类的虚构函数的问题
虚函数与重载函数的关系
多重继承与虚函数
纯虚函数和抽象类:基类是一个抽象的概念,没有具体点意义
纯虚函数一般形式:virtual 函数类型 函数名(参数表)=0;
抽象类:类中至少有一个纯虚函数