c++重载运算符时返回值为类的对象或者返回对象的引用问题
作者:Jegret
重载运算符时返回值为类的对象或者返回对象的引用
最终的目的是为了进行连续的运算
a = b + c + d; //不只是两个对象相加,是为了两个以上的对象的相加
以上面的代码为例,假设a,b,c,d都是同一个类(classA)的不同对象,假如我重载这个类的加号时,返回值类型不是此类或者他的引用,如下
void operator+(classA &a, classA &b) { //加法运算 }
那么在我最上面的代码中,b + c 的值就为空(或者其他类型),那么这个得出来的值就没有办法继续和d来进行加法的运算,也没有办法赋值给a了,这也就无法实现我所期望的连续运算的目的。
所以,c++重载运算符时返回值为类的对象是为了实现连续的运算(大多数这种运算其实是赋值运算或者<< >>)
如果你以后要重载运算符,有什么连续运算的需要,就可以使用这种方法,不过最好也要遵守c++中一些语法的规定,不要重载出来的东西让人摸不着头脑
那么,现在讲为什么有一些函数要返回对象的引用,这其实是为了提高程序的运行效率,众所周知,当一个函数返回一个值时,他并不是将你在此函数里面原有的你想要的那个值返回,而是将你想要的那个值,复制一下,然后把这个复制出来的值给返回,如下
classA operator+(classA &a, classA &b) { classA temp; //加法运算 return temp; }
代码最终返回的并不是你在函数里定义的那个temp,而是将temp复制了一份,将复制的那份返回了,函数里定义的那个temp在函数运行结束后就释放了。
同理,就算你没有在函数里面新建东西,而是直接返回通过a和b运算出来的一些东西(或者你直接返回*this),他也要复制一下。
所以,只要你不采用返回类的对象,而是返回类的对象的引用,就不会复制,也即不会调用类的复制构造函数,也即提升了程序的效率
classA & operator+(classA &a, classA &b) { //加法运算 //a.time = a.time + b.time; //假设time是此类的一个属性 //return a; //加法只有这样可以实现连续的运算,有些鸡肋,但是=,<<,>>,就很好用了 }
所以,当你重载的运算符需要连续的运算时,你可以返回他的类的对象。如果你想要减少不必要的开销(复制构造函数),提高程序效率,并且你返回的对象是需要作为左值的话,那么你可以返回他的引用
不过绝大部分 返回对象 或者 他的引用 实现连续运算,都是用在重载 赋值运算符= 或者 插入运算符<< 或者 提取运算符>> 中的。
那里面比较好用,其他的如加法那些,连续运算就很鸡肋,也比较难实现,但是返回引用可以提升效率却是真真的好啊
关于运算符重载中返回值的坑及解决
相信不少朋友在学习运算符重载的时候,都会被参数与返回值应该是左值引用,还是右值引用,还是const常量所困扰。当然我无法一一枚举,这次先讲一下返回值的坑 (没错就是我亲手写的bug)
E0334 “Myclass” 没有适当的复制构造函数
其实这个问题的根源是,没有定义常量参数类型的拷贝构造函数所致
先来看看代码
//头文件head.h class Myclass { private: int a; public: Myclass(int b=0):a(b) {} //构造函数 Myclass(Myclass& c); //复制构造函数 ~Myclass(){} //析构函数 Myclass operator+(Myclass& d); //重载+运算符 friend ostream& operator<<(ostream& os ,const Myclass& d); //重载<<运算符 }; //以下是定义 Myclass::Myclass(Myclass& c) { a = c.a; } Myclass Myclass::operator+(Myclass& d) { return Myclass(d.a+a); //!!此处报错 } ostream& operator<<(ostream& os,const Myclass& d) { os << d.a << std::endl; return os; } //main.cpp #include"head.h" int main() { Myclass a1(5); Myclass a2(12); Myclass sum = a1 + a2; //!!此处报错 std::cout << sum; }
代码在VS中,又出现了令人讨厌的小红线,没有适当的复制构造函数,这就有疑问了, 不是明明有个构造函数Myclass(int b=0):a(b) {}吗,参数是int很合适啊?
于是,我们定义一个临时变量temp,再将它返回,此时会隐式调用拷贝构造函数而后返回一个副本后原来的temp就die了,因此返回值不可以是引用。
下面是代码
Myclass Myclass::operator+(Myclass& d) { Myclass temp(d.a + a); return temp; }
此时第一处报错消失了,但是第二处报错依然存在,而且仍为 “没有适当的复制构造函数”,这就说明了,我的入手方向应该是拷贝构造函数
经过博主的调试,得知是因为函数的返回值是一个纯右值,为了验证这个想法,使用了右值引用,来接收这个纯右值(当然,右值引用更多的是用在移动构造函数上,将 将亡值“偷”出来)
#include"head.h" int main() { Myclass a1(5); Myclass a2(12); Myclass&& sum = a1 + a2; }
果然,它不报错了
但是考虑到实用性,总不能让用户今后做个加法都要用右值引用接收吧,因此,我们要从源头解决,即重载拷贝构造函数。
值得思考的是,右值不就是被赋值的那个吗,为什么用Myclass&& sum = a1 + a2;无法赋值呢?众所周知,Myclass&& sum = a1 + a2;调用的是拷贝构造函数,类不同于基本数据类型,它要通过程序员来
设置一系列的功能,我们没有设置接受,Myclass类型的右值的功能,只定义了接受int类型的右值的功能,这自然是不行的了。
因此,重载拷贝构造函数
Myclass::Myclass(const Myclass& c) { a = c.a; }
此时就能运行了
E0349 没有与这些操作数匹配的 “<<” 运算符
关于流运算符为什么要写成$ostream& operator<<(ostream& os,const Myclass& d); 而非ostream& operator<<(ostream& os,Myclass& d);这个问题,网上绝大部分的回答都是输出没必要修改值。
那么我们先定义后者
#head.h #pragma once #include<iostream> using std::ostream; class Myclass { private: int a; public: Myclass(int b=0):a(b) {} Myclass(Myclass& c); Myclass(const Myclass& c); ~Myclass(){} Myclass operator+(Myclass& d); friend ostream& operator<<(ostream& os ,Myclass& d); }; Myclass::Myclass(const Myclass& c) { a = c.a; } Myclass::Myclass(Myclass& c) { a = c.a; } Myclass Myclass::operator+(Myclass& d) { Myclass temp(d.a + a); return temp; } ostream& operator<<(ostream& os,Myclass& d) { os << d.a << std::endl; return os; } #main.cpp #include"head.h" int main() { Myclass a1(5); Myclass a2(12); Myclass&& sum = a1 + a2; std::cout << a1 + a2; //此处有讨厌小红线 }
不难发现,讨厌的小红线又出来了。
我们可以想象一下这个过程,a1.operator+(a2),返回了个临时变量,暂且假设它叫newguy,那么newguy为一个右值,又调用了函数os.<<(&d),传参为&d=newguy,现在问题来了,左值引用怎么能够接受一个纯右值呢? 而我们定义的重载的流运算符接受的参数类型为左值,我们并没有给出从左值到右值强制类型转换的函数,但是在上一部分,我们给出了从右值到左值的拷贝构造函数,因此,将流运算符声明为前者更好。
C3861 “function”: 找不到标识符
这个问题应该是非常常见的,不习惯将函数(或是类)先声明后定义而又喜欢让函数(或是类)相互调用,但是在类模板它比以上两种更为隐蔽。
#include<iostream> class A { friend void show(); //“声明”函数 friend void show1(); //“声明”函数 }; void show() //定义 { show1(); } void show1(){} //定义 int main() { A a; show(); //调用 show1(); //调用 }
以上流程看似声明->定义->调用非常完美,实则还是会报错的,不过跟以上两种不一样的是,它是在linking的时候出错,这是为什么呢?
原来友元函数并不属于这个类的一部分,在类内定义仅仅是为了告诉编译器“这个函数是这个类的友元函数”,并没有对这个函数本身进行声明,因此,正确的做法应该是这样的:
#include<iostream> void show(); void show1(); class A { friend void show(); friend void show1(); }; void show() { show1(); } void show1(){} int main() { A a; show(); show1(); }
Summary
本文主要讲了三点。
首先,要注意将拷贝构造函数重载。
其次,要将流运算符<<的参数类型确定为(ostream&,const myclass&),当然,istream则万万不可const,ostream是没有拷贝构造函数的,因此引用也是必须的。
最后,类内友元函数的声明,并不等同于函数本身的声明。
以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。