C++运算符重载函数基础及其值返回状态

  运算符重载是C++的重要组成部分,它可以让程序更加的简单易懂,简单的运算符使用可以使复杂函数的理解更直观。

  对于普通对象来说我们很自然的会频繁使用算数运算符让他们参与计算,但是对于自定义类的对象来说,我们是无论如何也不能阻止写出像下面的代码一样的程序来的。

  例子如下:

class Test 

    //过程省略 

 
int main() 

    Test a,c; 
    c=a+a; 
}

当然这样的代码是不能够通过编译的,c++对自定类的算术运算部分保留给了程序员,这也是符合c++灵活特性的。

  在c++中要想实现这样的运算就必须自定义运算符重载函数,让它来完整具体工作。

  在这里要提醒读者的是,自定义类的运算符重载函数也是函数,你重载的一切运算符不会因为是你自己定义的就改变其运算的优先级,自定义运算符的运算优先级同样遵循与内部运算符一样的顺序。

  除此之外,c++也规定了一些运算符不能够自定义重载,例如.、::、.*、.->、?:。

  下面我们来学习如何重载运算符,运算符重载函数的形式是:

返回类型 operator 运算符符号 (参数说明)
{

//函数体的内部实现
}

  运算符重载函数的使用主要分为两种形式,一种是作为类的友元函数进行使用另一种则是作为类的成员函数进行使用

  下面我们先看一下作为类的友元函数使用的例子:

//程序作者:管宁     
//站点:www.cndev-lab.com     
//所有稿件均有版权,如要转载,请务必著名出处和作者    
 
#include <iostream> 
using namespace std; 
 
class Test 


    public: 
        Test(int a = 0) 
        { 

            Test::a = a; 
        } 

        friend Test operator +(Test&,Test&); 
        friend Test& operator ++(Test&); 
    public: 
        int a; 
}; 

Test operator +(Test& temp1,Test& temp2)//+运算符重载函数 

    //cout<<temp1.a<<"|"<<temp2.a<<endl;//在这里可以观察传递过来的引用对象的成员分量 

    Test result(temp1.a+temp2.a); 
    return result; 

Test& operator ++(Test& temp)//++运算符重载函数 

    temp.a++; 
    return temp; 

int main() 

    Test a(100); 
    Test c=a+a; 

    cout<<c.a<<endl; 
    c++; 

    cout<<c.a<<endl; 

    system("pause"); 
}

  在例子中,我们对于自定义类Test来说,重载了加运算符与自动递增运算符,重载的运算符完成了同类型对象的加运算和递增运算过程。

  重载运算符函数返回类型和形式参数也是根据需要量进行调整的,下面我们来看一下修改后的加运算符重载函数。

  代码如下:

//程序作者:管宁     
//站点:www.cndev-lab.com     
//所有稿件均有版权,如要转载,请务必著名出处和作者    
 
#include <iostream> 
using namespace std; 
 
class Test 


    public: 
        Test(int a = 0) 
        { 

            Test::a = a; 
        } 

        friend Test operator +(Test&,const int&); 
    public: 

        int a; 
}; 
Test operator +(Test& temp1,const int& temp2)//+运算符重载函数 


    Test result(temp1.a * temp2); 
    return result; 

int main() 

    Test a(100); 
    Test c = a + 10; 
    cout<<c.a<<endl; 
    system("pause"); 

}

  上面修改后的例子中,我们让重载后的加运算符做的事情,事实上并不是同类型对象的加运算,而是自定义类对象与内置int常量对象的乘法运算。

  值得注意的是,对于运算符重载来说,我们并不一定要用它一定要做同类型对象的加法或者是其它运算,运算符重载函数本身就是函数,那么在函数体内部我们是可以做任何事情的,但是从不违背常规思维的角度来说,我们没有必要让重载加运算的函数来做与其重载的符号意义上完全不相符的工作,所以在使用重载运算符脱离原意之前,必须保证有足够的理由。

  下面我们讨论一下作为类成员函数的运算符重载函数的使用,及其函数的值返回与引用返回的差别。

  下面我们先看实例,而后逐步分析。

  代码如下(重要部分做了详细的注解):

//程序作者:管宁     
//站点:www.cndev-lab.com     
//所有稿件均有版权,如要转载,请务必著名出处和作者    
 
#include <iostream> 
using namespace std; 
 
class Test 


    public: 
        Test(int a = 0) 
        { 

            Test::a = a; 
        } 

        Test(Test &temp) 
        //运算符重载函数为值返回的时候会产生临时变量,临时变量与局部变量result的复制会调用拷贝构造函数,临时变量的生命周期是在拷贝构造函数运行完成后才结束,但如果运算符重载函数返回的是引用,那么不会产生临时变量,而局部变量result的生命周期在运算符重载函数退出后立即消失,它的生命周期要比临时变量短,所以当外部对象获取返回值的内存地址所存储的值的时候,获得是一个已经失去效果的内存地址中的值,在这里的值返回与引用返回的对比,证明了临时变量的生命周期比局部变量的生命周期稍长。 

        { 
            cout<<"载入拷贝构造函数"<<"|"<<temp.a<<endl;//注意这里,如果修改运算符重载函数为返回引用,这里就会出现异常,temp.a将获得一个随机值。 

            Test::a = temp.a; 
        } 

        ~Test()//在mian()内析构的过程是result局部变量产生的 

        { 
            cout<<"载入析构函数!"<<endl; 
            cin.get(); 
        } 
        Test operator +(Test& temp2)//+运算符重载函数 
        { 
            //cout<<this->a<<endl; 

            Test result(this->a+temp2.a); 

            return result; 
        } 

        Test& operator ++()//++运算符重载函数 
 
        //递增运算符是单目运算符,使用返回引用的运算符重载函数道理就在于它需要改变自身。 
        //在前面我们学习引用的单元中我们知道,返回引用的函数是可以作为左值参与运算的,这一点也符合单目运算符的特点。 

        //如果把该函数改成返回值,而不是返回引用的话就破坏了单目预算改变自身的特点,程序中的++(++c)运算结束后输出c.a,会发现对象c只做了一次递增运算,原因在于,当函数是值返回状态的时候括号内的++c返回的不是c本身而是临时变量,用临时变量参与括号外的++运算,当然c的值也就只改变了一次。 

        { 
            this->a++; 

            return *this; 

        } 
    public: 
        int a; 
}; 
 
int main() 

    Test a(100); 
    Test c=a+a; 

    cout<<c.a<<endl; 
    c++; 

    cout<<c.a<<endl; 
    ++c; 

    cout<<c.a<<endl; 

    ++(++c); 
    cout<<c.a<<endl; 
    system("pause"); 

}

  上例中运算符重载函数以类的成员函数方式出现,细心的读者会发现加运算和递增运算重载函数少了一个参数,这是为什么呢?
  operator +(Test& temp2)//+运算符重载函数   
        { 

            Test result(this->a+temp2.a);   

            return result;   
        }

  执行运算符重载函数返回引用将不产生临时变量,外部的Test c=a+a;
将获得一个局部的,栈空间内存地址位置上的值,而栈空间的特性告诉我们,当函数退出的时候函数体中局部对象的生命周期随之结束,所以保存在该地址中的数据也将消失,当c对象去获取存储在这个地址中的值的时候,里面的数据已经不存在,导致c获得的是一个随机值,所以作为双目运算的加运算符重载函数是不益采用返回引用方式编写的,当然如果一定要返回引用,我们可以在堆内存中动态开辟空间存储数据,但是这么做会导致额外的系统开销,同时也会让程序更难读懂。

  对于递增运算符来说,它的意义在于能够改变自身,返回引用的函数是可以作为左值参与运算的,所以作为单目运算符,重载它的函数采用返回引用的方式编写是最合适的。

  如果我们修改递增运算符重载函数为值返回状态的时候,又会出现什么奇怪的现象呢?

  代码如下:

Test operator ++() 
        { 

            return this->a++; 
        }

  表面上是发现不出什么特别明显的问题的,但是在main()函数中++(++c);的执行结果却出乎意料,理论上应该是204的值,却只是203,这是为什么呢?

  因为当函数是值返回状态的时候括号内的++c返回的不是c本身而是临时变量,用临时变量参与括号外的++运算,当然c的值也就只改变了一次。结果为203而不是204。

  对于运算符重载函数来说,最后我们还要注意一个问题,当运算符重载函数的形式参数类型全部为内部类型的时候,将不能重载

 
 

《挑战30天C++入门极限》C++运算符重载函数基础及其值返回状态的更多相关文章

  1. 《挑战30天C++入门极限》对C++递增(增量)运算符重载的思考

        对C++递增(增量)运算符重载的思考 在前面的章节中我们已经接触过递增运算符的重载,那时候我们并没有区分前递增与后递增的差别,在通常情况下我们是分别不出++a与a++的差别的,但的确他们直接是 ...

  2. 《挑战30天C++入门极限》C++运算符重载赋值运算符

        C++运算符重载赋值运算符 自定义类的赋值运算符重载函数的作用与内置赋值运算符的作用类似,但是要要注意的是,它与拷贝构造函数与析构函数一样,要注意深拷贝浅拷贝的问题,在没有深拷贝浅拷贝的情况下 ...

  3. 《挑战30天C++入门极限》C++运算符重载转换运算符

        C++运算符重载转换运算符 为什么需要转换运算符? 大家知道对于内置类型的数据我们可以通过强制转换符的使用来转换数据,例如(int)2.1f;自定义类也是类型,那么自定义类的对象在很多情况下也 ...

  4. 《挑战30天C++入门极限》C++中利用构造函数与无名对象简化运算符重载函数

        C++中利用构造函数与无名对象简化运算符重载函数 在完整描述思想之前,我们先看一下如下的例子,这个例子中的加运算符重载是以非成员函数的方式出现的: //程序作者:管宁  //站点:www.cn ...

  5. 《挑战30天C++入门极限》C++的iostream标准库介绍(3)

        C++的iostream标准库介绍(3) C语言提供了格式化输入输出的方法,C++也同样,但是C++的控制符使用起来更为简单方便,在c++下有两中方法控制格式化输入输出. 1.有流对象的成员函 ...

  6. 《挑战30天C++入门极限》C++的iostream标准库介绍(1)

        C++的iostream标准库介绍(1) 我们从一开始就一直在利用C++的输入输出在做着各种练习,输入输出是由iostream库提供的,所以讨论此标准库是有必要的,它与C语言的stdio库不同 ...

  7. 《挑战30天C++入门极限》C++面向对象编程入门:构造函数与析构函数

        C++面向对象编程入门:构造函数与析构函数 请注意,这一节内容是c++的重点,要特别注意! 我们先说一下什么是构造函数. 上一个教程我们简单说了关于类的一些基本内容,对于类对象成员的初始化我们 ...

  8. 《挑战30天C++入门极限》C++面向对象编程入门:类(class)

        C++面向对象编程入门:类(class) 上两篇内容我们着重说了结构体相关知识的操作. 以后的内容我们将逐步完全以c++作为主体了,这也意味着我们的教程正式进入面向对象的编程了. 前面的教程我 ...

  9. 《挑战30天C++入门极限》新手入门:C/C++中的结构体

        新手入门:C/C++中的结构体 什么是结构体? 简单的来说,结构体就是一个可以包含不同数据类型的一个结构,它是一种可以自己定义的数据类型,它的特点和数组主要有两点不同,首先结构体可以在一个结构 ...

随机推荐

  1. 使用 SetParent 制作父子窗口的时候,如何设置子窗口的窗口样式以避免抢走父窗口的焦点

    原文:使用 SetParent 制作父子窗口的时候,如何设置子窗口的窗口样式以避免抢走父窗口的焦点 制作传统 Win32 程序以及 Windows Forms 程序的时候,一个用户看起来独立的窗口本就 ...

  2. Windows 系统上用 .NET/C# 查找所有窗口,并获得窗口的标题、位置、尺寸、最小化、可见性等各种状态

    原文:Windows 系统上用 .NET/C# 查找所有窗口,并获得窗口的标题.位置.尺寸.最小化.可见性等各种状态 在 Windows 应用开发中,如果需要操作其他的窗口,那么可以使用 EnumWi ...

  3. NetCore实例提供的依赖注入的生命周期

    Transient: 每一次GetService都会创建一个新的实例,每次从容器 (IServiceProvider)中获取的时候都是一个新的实例Scoped: 在同一个Scope内只初始化一个实例 ...

  4. 15天入门RT-Thread之第一天

    今天开始学习jiezhi15天的RT-Thread入门系列课程 感谢RT-Thread提供的免费课程,终于可以系统入门RT-Thread ,感兴趣的同学可以关注RT-Thread官方公众号,获取最新的 ...

  5. C# vb .net实现消除红眼效果

    在.net中,如何简单快捷地实现Photoshop滤镜组中的消除红眼效果呢?答案是调用SharpImage!专业图像特效滤镜和合成类库.下面开始演示关键代码,您也可以在文末下载全部源码: 设置授权 第 ...

  6. Math对象的一些方法

    ceil(n) 返回n向上取整的最近的整数floor(n) 返回n向下取整到最近的整数max(a,b,c...) 返回最大值min(a,b,c...) 返回最小值round(n) 返回n四舍五入的最近 ...

  7. springboot学习入门简易版五---springboot2.0整合jsp(11)

    springboot对jsp支持不友好,内部tomcat对jsp不支持,需要使用外部tomcat,且必须打包为war包. 1 创建maven项目 注意:必须为war类型,否则找不到页面. 且不要把js ...

  8. Linux命令——tree

    参考:Linux tree Command Tutorial for Beginners (6 Examples) 简介 Linux tree命令用于以树状图列出目录的内容. 执行tree指令,它会列 ...

  9. unity 之 场景切换进度条显示

    一.UI.建立slider适当更改即可: 二.新增loadScene脚本,用来进行场景切换,将其绑定任意物体上面.博主以放置主相机为例.参数分别为进度条(用来设置value值),显示进度文本text: ...

  10. 小白怎么用最短时间高效的学习Python?

    之所以写这篇文章,在标题里已经表达得很清楚了.做技术的人都知道,时间就是金钱不是一句空话,同一个技术,你比别人早学会半年,那你就能比别人多拿半年的钱.所以有时候别人去培训我也不怎么拦着,为什么?因为培 ...