C++ 多态性 运算符重载

注意:本文中代码均使用 Qt 开发编译环境

面向对象的多态性可以分为四类:重载多态、强制多态、包含多态和参数多态,前两种统称为专用多态,而后两种也称为通用多态。

多态的实现###

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

确定操作的具体对象的过程就是绑定(binding,也叫联编)。绑定是指计算机程序自身彼此关联的过程,也就是把一个标识符名和一个存储地址联系在一起的过程;就是把一条消息和一个对象的方法相结合的过程。按照绑定进行的阶段不同,可分为:静态绑定和动态绑定,这两种绑定过程中分别对应着多态的两种实现方式:
1.绑定工作在编译连接阶段完成的情况称为静态绑定。也叫早期绑定或前绑定。
2.绑定工作在程序运行阶段完成的情况称为动态绑定。也叫晚期绑定或后绑定。

运算符重载###

C++ 中预定义的运算符的操作对象只能是基本数据类型。但实际上,对于许多用户自定义类型(例如类),也需要类似的运算操作。这时就必须在C++中重新定义这些运算符,赋予已有运算符新的功能,使它能够用于特定类型执行特定的操作。运算符重载的实质是函数重载,它提供了 C++ 的可扩展性,也是 C++ 最吸引人的特性之一。

运算符重载是通过创建运算符函数实现的,运算符函数定义了重载的运算符将要进行的操作。运算符函数的定义与其他函数的定义类似,惟一的区别是运算符函数的函数名是由关键字 operator 和其后要重载的运算符符号构成的。

运算符函数定义的一般格式如下:

<返回类型说明符> operator  <运算符符号> (<参数表>)
{
     <函数体>
}

运算符重载时要遵循以下规则:

(1) 除了类属关系运算符 "." 、成员指针运算符 ".*" 、作用域运算符 "::" 、sizeof 运算符和三目运算符 "?:" 以外,C++ 中的所有运算符都可以重载。

(2) 重载运算符限制在 C++ 语言中已有的运算符范围内的允许重载的运算符之中,不能创建新的运算符。

(3) 运算符重载实质上是函数重载,因此编译程序对运算符重载的选择,遵循函数重载的选择原则。

(4) 重载之后的运算符不能改变运算符的优先级和结合性,也不能改变运算符操作数的个数及语法结构。

(5) 运算符重载不能改变该运算符用于内部类型对象的含义。它只能和用户自定义类型的对象一起使用,或者用于用户自定义类型的对象和内部类型的对象混合使用时。

(6) 运算符重载是针对新类型数据的实际需要对原有运算符进行的适当的改造,重载的功能应当与原有功能相类似,避免没有目的地使用重载运算符。

示例1:

#include <QCoreApplication>
#include <QDebug>

class complex{
public:
    complex(double r=0.0, double i=0.0) : real(r), imag(i) {}
    complex operator +(complex c2);
    complex operator -(complex c2);
    QString display() const;

private:
    double real;
    double imag;
};

complex complex::operator + (complex c2){
    return complex(real + c2.real, imag + c2.imag);
}

complex complex::operator - (complex c2){
    return complex(real - c2.real, imag - c2.imag);
}

QString complex::display() const {
    return  "(" + QString::number(real) + ", " + QString::number(imag) + ")";
}

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);

    complex c1(5, 4), c2(2, 10), c3;

    qDebug() << "c1 = " << c1.display();
    qDebug() << "c2 = " << c2.display();

    c3 = c1 - c2;
    qDebug() << "c3 = c1 - c2 = " << c3.display();

    c3 = c1 + c2;
    qDebug() << "c3 = c1 + c2 = " << c3.display();

    return a.exec();
}

运行结果:


运行结果

运算符函数重载一般有两种形式:重载为类的成员函数和重载为类的非成员函数。非成员函数通常是友元。(可以把一个运算符作为一个非成员、非友元函数重载。但是,这样的运算符函数访问类的私有和保护成员时,必须使用类的公有接口中提供的设置数据和读取数据的函数,调用这些函数时会降低性能。可以内联这些函数以提高性能。)

成员函数运算符###

运算符重载为类的成员函数的一般格式为:

<函数类型> operator <运算符> ( <参数表> ) {
     <函数体>
}    

当运算符重载为类的成员函数时,函数的参数个数比原来的操作数要少一个(后置单目运算符除外),这是因为成员函数用this指针隐式地访问了类的一个对象,它充当了运算符函数最左边的操作数。因此:

(1) 双目运算符重载为类的成员函数时,函数只显式说明一个参数,该形参是运算符的右操作数。

(2) 前置单目运算符重载为类的成员函数时,不需要显式说明参数,即函数没有形参。

(3) 后置单目运算符重载为类的成员函数时,函数要带有一个整型形参。

调用成员函数运算符的格式如下:

<对象名>.operator <运算符>(<参数>)

它等价于

<对象名><运算符><参数>

例如:a+b等价于a.operator+(b)。一般情况下,我们采用运算符的习惯表达方式。

示例2:

#include <QCoreApplication>
#include <QDebug>

class Clock {
public:
    explicit Clock();
    Clock(int NewH=0,int NewM=0,int NewS=0);
    QString ShowTime() const;
    Clock& operator ++();   //前置单目运算符重载
    Clock operator ++(int);  //后置单目运算符重载

private:
    int Hour, Minute, Second;
};

Clock::Clock() : Hour(0),  Minute(0), Second(0) {
}

Clock::Clock(int NewH, int NewM, int NewS) {
    Hour = qMax(0, qMin(NewH, 23));
    Minute = qMax(0, qMin(NewM, 59));
    Second = qMax(0, qMin(NewS, 59));
}

QString Clock::ShowTime() const {
    return QString::number(Hour) + ":"
            + QString::number(Minute) + ":"
            + QString::number(Second);
}

Clock& Clock::operator ++() {
    if (++Second>=60) {
        Second = Second - 60;
        if (++Minute >= 60) {
            Minute = Minute - 60;
            Hour = (++Hour) % 24;
        }
    }
    return *this;
}

Clock Clock::operator ++(int) {
    Clock old = *this;
    ++(*this);
    return old;
}

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);

    Clock myClock(23,59,59);
    qDebug() << "myClock is: "  << myClock.ShowTime();
    qDebug() << "myClock++:"    << (myClock++).ShowTime();
    qDebug() << "myClock is: "  << myClock.ShowTime();
    qDebug() << "++myClock:"    << (++myClock).ShowTime();
    qDebug() << "++myClock:"    << (++myClock).ShowTime();

    return a.exec();
}

运行结果:


运行结果

友元函数运算符###

运算符重载为类的友元函数的一般格式为:

friend <函数类型> operator <运算符> ( <参数表> ) {
     <函数体>
}

当运算符重载为类的友元函数时,由于没有隐含的this指针,因此操作数的个数没有变化,所有的操作数都必须通过函数的形参进行传递,函数的参数与操作数自左至右一一对应。

调用友元函数运算符的格式如下:

operator <运算符>(<参数1>,<参数2>)

它等价于

<参数1><运算符><参数2>

例如:a+b等价于operator+(a,b)。

友元示例:

#include <QCoreApplication>
#include <QDebug>

class complex {
public:
    complex(double r=0.0,double i=0.0) : real(r), imag(i) {
    }
    friend complex operator + (complex c1, complex c2);
    friend complex operator - (complex c1, complex c2);
    QString display() const;
private:
    double real;
    double imag;
};

complex operator + (complex c1,complex c2) {
    return complex(c1.real + c2.real, c1.imag + c2.imag);
}

complex operator - (complex c1,complex c2) {
    return complex(c1.real - c2.real, c1.imag - c2.imag);
}

QString complex::display() const {
    return "(" + QString::number(real) + ","
            + QString::number(imag) +")";
}

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);

    complex c1(5,4), c2(2,10), c3;

    qDebug() << "c1 =" << c1.display();
    qDebug() << "c2 =" << c2.display();

    c3 = c1 - c2;
    qDebug() << "c3 = c1-c2 = " << c3.display();

    c3 = c1 + c2;
    qDebug() << "c3 = c1+c2 = " << c3.display();

    return a.exec();
}

运行结果:


运行结果

两种重载形式的比较

在多数情况下,将运算符重载为类的成员函数和类的友元函数都是可以的。但成员函数运算符与友元函数运算符也具有各自的一些特点:

(1) 一般情况下,单目运算符最好重载为类的成员函数;双目运算符则最好重载为类的友元函数。

(2) 以下一些双目运算符不能重载为类的友元函数:=、()、[]、->。

(3) 类型转换函数只能定义为一个类的成员函数而不能定义为类的友元函数。

(4) 若一个运算符的操作需要修改对象的状态,选择重载为成员函数较好。

(5) 若运算符所需的操作数(尤其是第一个操作数)希望有隐式类型转换,则只能选用友元函数。

(6) 当运算符函数是一个成员函数时,最左边的操作数(或者只有最左边的操作数)必须是运算符类的一 个类对象(或者是对该类对象的引用)。如果左边的操作数必须是一个不同类的对象,或者是一个内部 类型的对象,该运算符函数必须作为一个友元函数来实现。

(7) 当需要重载运算符具有可交换性时,选择重载为友元函数。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,456评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,370评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,337评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,583评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,596评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,572评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,936评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,595评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,850评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,601评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,685评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,371评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,951评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,934评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,167评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,636评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,411评论 2 342

推荐阅读更多精彩内容

  • C++运算符重载-上篇 本章内容:1. 运算符重载的概述2. 重载算术运算符3. 重载按位运算符和二元逻辑运算符4...
    Haley_2013阅读 2,280评论 0 51
  • 前言 把《C++ Primer》[https://book.douban.com/subject/25708312...
    尤汐Yogy阅读 9,504评论 1 51
  • C++运算符重载-下篇 本章内容:1. 运算符重载的概述2. 重载算术运算符3. 重载按位运算符和二元逻辑运算符4...
    Haley_2013阅读 1,430评论 0 49
  • 重新系统学习下C++;但是还是少了好多知识点;socket;unix;stl;boost等; C++ 教程 | 菜...
    kakukeme阅读 19,784评论 0 50
  • 童年,似乎是很久远的时光了。但每每想起,依旧记忆鲜活,反复就像昨天,欢声笑语还回荡在耳际。 1、念念不忘故乡水 ...
    苏小矣阅读 548评论 0 1