一、多态性

①概述:多态是指同样的消息被不同类型的对象接收时导致的不同行为。

②类型:

  可以分为四类:重载多态、强制多态、包含多态、参数多态。

       ------------------------ ---------------------------

                  专用多态      通用多态

③实现:

 从实现角度可以划分为两类:编译时多态和运行时多态。

绑定:就是把一条消息和一个对象的方法结合的过程。

   静态多态:在编译过程中确定了同名操作的具体对象。绑定工作在编译连接阶段完成的情况(实现手段:函数/运算符重载、模板)。

  动态多态:在程序运行过程中才动态的确定操作所针对的具体对象。绑定工作在程序运行阶段完成(实现手段:基类和派生类中建立同名函数,但功能不同)。

二、运算符重载

1、定义:运算符重载是对已有的运算符赋予多层含义,使同一个运算符作用于不同类型的数据是导致不同的行为。(实质:就是函数重载)

2、规则:①C++中的运算符除了少数几个之外,全部可以重载,而且只能重载C++中已经有的运算符。

        ②重载之后的运算符的优先级和结合性都不会改变。

        ③运算符重载是针对新数据类型的实际需要,对原有运算符进行适当的改造。

        ④C++标准规定,有些操作符是不能重载的,它们是类属关系运算符“.”、成员指针运算符“*”、作用域分辨符“::”、和三目运算符“?:”。

3、运算符重载的两种形式:重载为类的成员函数、重载为非成员函数。

①重载为类的成员函数:

返回类型  operator  运算符(形参表)
{
函数体
}

②重载为非成员函数:

返回类型  operator  运算符(形参表)
{
函数体
}

注意:返回类型指定了重载运算符的返回类型,也就是运算结果类型;

operator是定义运算符重载的关键字;

运算符即是要重载的运算符名称;

形参表中给出重载运算符所需要的参数和类型。

③程序实例:

1)重载为成员函数

例8-1

#include<iostream>
using namespace std;
class Complex {
public:
Complex(float a = , float b = ) :real(a), imag(b) {};//构造函数
Complex operator+ (const Complex& c2) const;//运算符+重载成员函数
Complex operator- (const Complex& c2) const;//运算符-重载成员函数
void display()const;//输出复数
private:
double real, imag;//复数实部,虚部
};
Complex Complex::operator+(const Complex& c2)const {//定义这个重载函数
return Complex(real + c2.real, imag + c2.imag);
}
Complex Complex::operator-(const Complex& c2)const {//定义这个重载函数
return Complex(real - c2.real, imag - c2.imag);
}
void Complex::display()const {
cout << "("<<real << "," << imag << ")" << endl;
}
int main() {
Complex c1(, ), c2(, ), c3;//定义三个对象
cout << "c1="; c1.display();
cout << "c2="; c2.display();
c3 = c1 - c2;//c3=c1.operator-(c2);相当于函数的调用
cout << "c3=c1-c2="; c3.display();
c3 = c1 + c2;//运用重载运算符+号
cout << "c3=c1+c2="; c3.display();//展示出来
return ;
}

注解:将复数的加减法这样的运算重载为类的成员函数,除了使用了关键字operator外,运算符重载成员函数与类的普通成员函数没有什么区别。

在使用时可以直接通过运算符、操作数的方式来完成函数调用(相当于函数的调用)。“+”“-”的原有功能不变,同时添加了新的针对复数运算的功能。

运行结果:

思考:若是复数加整数呢?如:c=c1+5

将主函数修改:

int main() {
Complex c1(, ), c2(, ), c3,c;//定义三个对象
cout << "c1="; c1.display();
//cout << "c2="; c2.display();
//c3 = c1 - c2;
//cout << "c3=c1-c2="; c3.display();
c = c1 + ;
cout << "c=c1+5="; c.display();
//c3 = c1 + c2;//运用重载运算符+号
//cout << "c3=c1+c2="; c3.display();//展示出来
return ;
}

可得运行结果:

可见:复数+整数不会形象重载运算符的使用。因为整数5在传递过程中已经转化为了复数形式。

思考:若改为整数+复数的形式,程序还可以运行吗?例c=5+c1;

c =  + c1;
cout << "c=5+c1="; c.display();

程序报错:

原因:数字在前无法转换为复数形式,常数在前无法调用函数5.operator+(c1);

可以使用友元函数来修改。即运算符重载为非成员函数。

2)重载为非成员函数(利用友元函数)

例8-3

#include<iostream>
using namespace std;
class Complex {
public:
Complex(float a = , float b = ) :real(a), imag(b) {};
void display() {
cout <<"("<< real << "," << imag << ")" << endl;
}
friend Complex operator+(const Complex& c1, const Complex& c2);//用友元函数在类外重载-号
friend Complex operator-(const Complex& c1, const Complex& c2);//用友元函数在类外重载-号
double real, imag;
};
Complex operator+(const Complex& c1, const Complex& c2) {//重载函数
return Complex(c1.real + c2.real, c1.imag + c2.imag);
}
Complex operator-(const Complex& c1, const Complex& c2) {//重载函数
return Complex(c1.real - c2.real, c1.imag - c2.imag);
}
int main() {
Complex c1(, ), c2(, ), c3;
cout << "c1="; c1.display();
cout << "c2="; c2.display();
c3 = c1 - c2;
cout << "c3=c1-c2="; c3.display();
c3 = c1 + c2;//运用重载运算符+号
cout << "c3=c1+c2="; c3.display();//展示出来
return ;
}

分析:将运算符重载为类的非成员函数,就必须把操作数全部通过形参的方式传递给运算符重载函数。

运行结果:

思考:由此方法可以解决我们上例问题:整数+复数

例c=5+c1;

class Complex {
public:
Complex(float a = , float b = ) :real(a), imag(b) {};
void display() {
cout <<"("<< real << "," << imag << ")" << endl;
}
friend Complex operator+(const Complex& c1, const Complex& c2);//用友元函数在类外重载-号
friend Complex operator-(const Complex& c1, const Complex& c2);//用友元函数在类外重载-号
friend Complex operator+(int, Complex);
double real, imag;
};
Complex operator+(int c, Complex d)
{
Complex sum;
sum.real = c + d.real;
sum.imag = d.imag;
return sum;
}
main()
{
c = + c1;
cout << "c=5+c1="; c.display();
}

运行结果:

注意:

    一般在C++中只要定义非成员函数,就使用友元函数。(可以访问私有成员)

3)单目运算符的重载

例8-2  “++”的重载

注意:

单目运算符分为前置++和后置++。所以规定:

         对于前置单目运算符,重载函数没有形参;

         对于后置单目运算符,重载函数有一个int型形参。

#include<iostream>
using namespace std;
class Complex {
public:
Complex(float a = , float b = ) :real(a), imag(b) {};
void display() {
cout <<"("<< real << "," << imag << ")" << endl;
}
Complex& operator ++();//++的前置运算符重载
Complex operator ++(int);//++的后置重载,需要用int参数来区分
private:
float real, imag;
};
Complex& Complex::operator ++()//前置
{
real++;
imag++;
return (*this);
}
Complex Complex::operator ++(int) {//后置,返回一个对象
return Complex(real++, imag++);
}
int main() {
Complex c1(, ), c2(, ), c3, c4;
c3 = c1++;//前置运算
c4 = ++c2;//后置运算
c1.display();
c2.display();
c3.display();
c4.display();
return ;
}

运行结果:

 分析:

前置单目运算符与后置单目运算符最主要的区别就在于重载函数的形参。

本例中后置++运算符int型参数在函数体中并未使用,纯粹用来区别前置与后置,因此参数表中可以只给出类型名,没有参数名。

三、虚函数

虚函数是动态绑定的基础。虚函数必须是非静态的成员函数。虚函数经过派生之后,在类族中就可以实现运行过程中的多态。

1、一般数函数成员

virtual 函数类型 函数名(形参表);

实际上就使用关键字virtual来限定成员函数,虚函数声明只能出现在类定义中的函数原型声明中,而不能在成员函数实现的时候。

注意:

运行过程中的多态需要满足三个条件:①满足赋值兼容规则;②要声明虚函数;③由成员函数来调用或者通过指针引用来访问虚函数。

程序实例:例8-4

#include<iostream>
using namespace std;
class Base1{
public:
virtual void display()const;//虚函数
};
void Base1::display()const{
cout<<"Base1::display()"<<endl;
}
class Base2:public Base1{
public:
virtual void display()const;
};
void Base2::display()const{
cout<<"Base2::display()"<<endl;
}
class Derived:public Base2{
public:
virtual void display()const;
};
void Derived::display()const{
cout<<"Derived::display()"<<endl;
}
void fun(Base1 *p){
p->display();
}
int main(){
Base1 base1;
Base2 base2;
Derived derived;
fun(&base1);
fun(&base2);
fun(&derived);
return ;
}

分析:

程序中Base1,Base2,和Derived属于同一个类族,而且是通过公有派生而来的,因此满足赋值兼容规则。同时,基类Base1的成员函数display()声明为虚函数,程序中使用对象指针来访问成员函数。这样的绑定过程就是在运行中完成,实现了运行中的多态。如果没有虚函数的话,那么程序将会进行静态绑定,fun函数中用的是Base1的指针,那么不管传进去的是什么指针,都会调用Base1的display函数。

2、虚析构函数

①声明语法:

virtual ~类名();

  如果一个类的析构函数是虚析构函数,那么由他派生而来的所有子类的析构函数也是虚函数。

②作用:

  析构函数设置为虚函数之后,在使用指针引用时可以动态绑定,实现运行时多态,保证使用基类类型指针就能调用适当的析构函数针对不同的对象进行清理工作。

③举例:

练习8-9

#include <iostream>
using namespace std;
class BaseClass
{
public:
virtual ~BaseClass() { cout << "BaseClass killed" << endl; }
};
class DerivedClass :BaseClass
{
public:
~DerivedClass() { cout << "DerivedClass killed" << endl; }
};
int main()
{
BaseClass* pBase = (BaseClass*)new DerivedClass();
delete pBase;
return ;
}

运行结果:

注意:

    如果有可能通过基类指针来调用对象的析构函数(通过delete),就需要让基类的析构函数称为虚函数,否则会产生不良的后果。

3、纯虚函数与抽象类

①纯虚函数

纯虚函数是一个在基类中声明的虚函数,他在该基类中没有定义具体的操作内容,要求各派生类根据实际需要给出各自的定义。

声明格式:

virtual 函数类型 函数名(参数表)=;

注意:

  声明为纯虚函数之后,基类中就可以不再给出函数的实现部分。纯虚函数的函数体由派生类给出。

区别于函数体为空的虚函数:纯虚函数根本没有函数体,而空的虚函数的函数体为空。

②抽象类

带有纯虚函数的类是抽象类。(抽象类不能实例化)

例:习题8-6

#include<iostream>
using namespace std;
class Shape {
public:
virtual double GetArea() = ; //纯虚函数
virtual double GetPerim() = ; //纯虚函数
};
class Rectangle :virtual public Shape
{
public:
Rectangle(double a, double b) {
len = a;
kuan = b;
}
double GetArea() {
return len * kuan;
}
double GetPerim() {
return * (len + kuan);
}
private:
double len;
double kuan;
};
class Circle :virtual public Shape {
public:
Circle(double a) {
r = a;
}
double GetArea() {
return 3.14 * r * r;
}
double GetPerim() {
return * 3.14 * r;
}
private:
double r;
};
int main() {
double len, kuan, r;
cout << "请输入长,宽:" << endl;
cin >> len >> kuan;
Rectangle rect(len, kuan);
cout << "面积是:" << rect.GetArea() << endl << "周长是:" << rect.GetPerim() << endl;
cout << "请输入半径:" << endl;
cin >> r;
Circle c(r);
cout << "面积是:" << c.GetArea() << endl << "周长是:" << c.GetPerim() << endl;
return ;
}

分析:因为矩形的面积周长与圆的面积周长计算方法不同,所以声明为纯虚函数。具体计算方式由各派生类根据实际需要给出各自的定义。

四、总结

①在面向对象的程序设计中,使用多态能够增强程序的可扩充性,即程序需要修改或增加功能时,只需改动或增加较少的代码。

②使用多态能起到精简代码的作用。

C++多态性----运算符重载与虚函数的更多相关文章

  1. c++运算符重载和虚函数

    运算符重载与虚函数 单目运算符 接下来都以AClass作为一个类例子介绍 AClass{ int var } 区分后置++与前置++ AClass operator ++ () ++前置 一般设计为返 ...

  2. c++入门之—运算符重载和友元函数

    运算符重载的意义是:将常见的运算符重载出其他的含义:比如将*重载出指针的含义,将<<与cout联合使用重载出输出的含义,但需要认识到的问题是:运算符的重载:本质仍然是成员函数,即你可以认为 ...

  3. C++运算符重载(友元函数方式)

    我们知道,C++中的运算符重载有两种形式:①重载为类的成员函数(见C++运算符重载(成员函数方式)),②重载为类的友元函数. 当重载友元函数时,将没有隐含的参数this指针.这样,对双目运算符,友元函 ...

  4. 【C++】多态性(函数重载与虚函数)

    多态性就是同一符号或名字在不同情况下具有不同解释的现象.多态性有两种表现形式: 编译时多态性:同一对象收到相同的消息却产生不同的函数调用,一般通过函数重载来实现,在编译时就实现了绑定,属于静态绑定. ...

  5. C++继承-重载-多态-虚函数

    C++ 继承 基类 & 派生类 一个类可以派生自多个类,这意味着,它可以从多个基类继承数据和函数.定义一个派生类,我们使用一个类派生列表来指定基类.类派生列表以一个或多个基类命名,形式如下: ...

  6. C++ 派生类函数重载与虚函数继承详解

    目录 一.作用域与名字查找 1.作用域的嵌套 2.在编译时进行名字查找 3.名字冲突与继承 4.通过作用域运算符来使用隐藏的成员 二.同名函数隐藏与虚函数覆盖 1.几种必须区分的情况 2.一个更复杂的 ...

  7. C++抽象编程·运算符重载与友元函数

    运算符重载(Operator overloading) 从我们在几个前篇的类的层次介绍中可以知道,C++可以扩展标准运算符,使其适用于新类型.这种技术称为运算符重载. 例如,字符串类重载+运算符,使其 ...

  8. C++运算符重载形式——成员函数or友元函数

    运算符重载是C++多态的重要实现手段之一.通过运算符重载对运算符功能进行特殊定制,使其支持特定类型对象的运算,执行特定的功能,增强C++的扩展功能. 运算符重载的我们需要坚持四项基本原则: (1)不可 ...

  9. C++运算符重载(成员函数方式)

    一.运算符重载 C++中预定义的运算符的操作对象只能是基本数据类型,实际上,对于很多用户自定义类型,也需要有类似的运算操作.如果将C++中这些现存的运算符直接作用于用户自定义的类型数据上,会得到什么样 ...

随机推荐

  1. cube.js 学习 cube 连接mongodb 试用

    cube.js 对于mongodb 的连接是通过mongodb bi connector(mysql 协议)处理的,以下为简单的试用 安装mongo bi connector 这个玩意用docker ...

  2. Theano安装笔记

    由于实验需要,近三个月来,安装过十几次Theano,基本上每次都是从最基本的nvidia driver装起.总结一些粗浅的安装心得. GPU:Nvidia K40, M40, M60 软件环境:Unb ...

  3. 手把手教你用Python代码实现微信聊天机器人 -- Python wxpy

    关注我,每天都有优质技术文章推送,工作,学习累了的时候放松一下自己. 本篇文章同步微信公众号 欢迎大家关注我的微信公众号:「醉翁猫咪」 来学习了,微信聊天机器人. 环境要求: Windows / Li ...

  4. Linux环境下Nexus3.6安装

    1.  安装JDK   2. 下载nexus开源版本即可,Nexus OSS下载 流程 3.  解压文件,会的得到两个文件夹[nexus-3.6.0]和[sonatype-work] tar -zxv ...

  5. Linux最大线程数限制

    开始以为是内存不足导致无法创建线程,把jvm的-Xms,-Xmx的2个参数都加大一倍:-Xms2048m -Xmx2048m.把-Xss参数调小,还是启动失败.应该是系统方面的限制了,这台机器上搞了1 ...

  6. ckplayer去掉/修改右上角logo(位置)

    ckplayer.js中搜索:logo(ckplayer.xml中搜索<logo>) 1:去掉的方法是修改成logo:'null'(ckplayer.xml中修改成<logo> ...

  7. Hive(一)—— 启动与基本使用

    一.基本概念 Hive用于解决海量结构化日志的数据统计问题. Hive是基于Hadoop的一个数据仓库工具.本质是将HQL(Hive的查询语言)转化成MapReduce程序. HIve处理的数据存储在 ...

  8. SpringCloud Feign 常用代码

    服务提供者 服务提供者,是位于其他项目里面的. 服务提供者提供的方法,在Controller层里面,有可访问的Url. @Controller @RequestMapping("/order ...

  9. java 中利用反射机制获取和设置实体类的属性值

    摘要: 在java编程中,我们经常不知道传入自己方法中的实体类中到底有哪些方法,或者,我们需要根据用户传入的不同的属性来给对象设置不同的属性值,那么,java自带的反射机制可以很方便的达到这种目的,同 ...

  10. 迁移Git项目到Gitlab

    假定Gitlab已经安装, 假定要迁移的Git项目目录为 demo 首先在Gitlab里创建一个新的project, 名称为demo (或者其他名称都可以) 然后在现有的Git项目目录下, 进行以下操 ...