c++重载运算符时返回值为类的对象或者返回对象的引用问题

 更新时间:2022年11月25日 10:02:08   作者:Jegret  
这篇文章主要介绍了c++重载运算符时返回值为类的对象或者返回对象的引用问题,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教

重载运算符时返回值为类的对象或者返回对象的引用

最终的目的是为了进行连续的运算

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是没有拷贝构造函数的,因此引用也是必须的。

最后,类内友元函数的声明,并不等同于函数本身的声明。

以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。

相关文章

  • 全局静态存储区、堆区和栈区深入剖析

    全局静态存储区、堆区和栈区深入剖析

    在C++中,内存可分为系统数据区,自由存储区,文本区,const数据区,全局静态区,堆区和栈区
    2012-11-11
  • C++ 算法精讲之贪心算法

    C++ 算法精讲之贪心算法

    贪心算法(又称贪婪算法)是指,在对问题求解时,总是做出在当前看来是最好的选择。也就是说,不从整体最优上加以考虑,他所做出的仅是在某种意义上的局部最优解
    2022-03-03
  • 枚举类型的定义和应用总结

    枚举类型的定义和应用总结

    如果一种变量只有几种可能的值,可以定义为枚举类型。所谓“枚举类型”是将变量的值一一列举出来,变量的值只能在列举出来的值的范围内
    2013-10-10
  • C++拷贝构造函数中的陷阱

    C++拷贝构造函数中的陷阱

    这篇文章主要介绍了C++拷贝构造函数中的陷阱,拷贝构造函数大家都比较熟悉,通俗讲就是传入一个对象,拷贝一份副本。不过看似简单的东西,实际不注意的话就会产生问题,下面我们就来看看C++拷贝构造函数中都有哪些陷阱吧
    2022-01-01
  • C语言数据结构实现链表去重的实例

    C语言数据结构实现链表去重的实例

    这篇文章主要介绍了C语言数据结构实现链表去重的实例的相关资料,这里提供了题目及实例代码,需要的朋友可以参考下
    2017-07-07
  • windows 使用ffmpeg .a静态库读取Wav音频并保存PCM的方法

    windows 使用ffmpeg .a静态库读取Wav音频并保存PCM的方法

    这篇文章主要介绍了windows 使用ffmpeg .a静态库读取Wav音频并保存PCM,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2024-02-02
  • C语言中实现itoa函数的实例

    C语言中实现itoa函数的实例

    这篇文章主要介绍了C语言中实现itoa函数的实例的相关资料,希望通过本文能帮助到大家,让大家实现这样的功能,需要的朋友可以参考下
    2017-10-10
  • C++堆排序算法实例详解

    C++堆排序算法实例详解

    这篇文章主要介绍了C++堆排序算法,简单分析了堆排序算法的原理并结合实例形式分析了C++实现堆排序的具体操作技巧,需要的朋友可以参考下
    2017-08-08
  • QT实现简单TCP通信

    QT实现简单TCP通信

    这篇文章主要为大家详细介绍了QT实现简单的TCP通信,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2022-08-08

最新评论