c++_函数返回值,引用&作为函数返回值

本文探讨了C++中函数返回值的优化方法,包括如何避免不必要的临时对象创建,正确使用引用返回,以及理解返回局部对象与全局对象的区别。通过具体示例说明了不同返回值类型的影响。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

函数返回值

返回值是[const] T &

ST operator*( ST & a, ST & b){
	return ST( a.data * b.data);
}
ST a, b;

当你实际使用时: ST ret = a * b;
一共会有2次的 构造函数: 一次是ret 一次是函数返回值的temp临时对象

我们知道, 这个temp临时对象, 肯定是徒劳的, 是否可以优化 把它去掉呢???


错误做法1

ST & operator*( ST & a, ST & b){
	return ST( a.data * b.data);
}

返回一个: 指向临时对象的(引用/指针), 这自然是错误的; 因为临时对象,马上释放, 引用已经释放的内存 报错

错误做法2

ST & operator*( ST & a, ST & b){
	return *( new ST( a.data * b.data));
}

当遇到a * b * a时, 此时, a * b这个对象 用户就没有得到他, 他是匿名对象, 自然也就无法释放了. 造成: 内存泄漏

错误做法3

ST & operator*( ST & a, ST & b){
	static ST ret( a.data * b.data);
	return ret;
}

当遇到: if( a * b == c * d)时, 返回值永远是true.
因为, 所有operator函数的调用, 返回值都是同一个对象ret!!!
ret的值, 为, 最后一次 调用 operator
函数的 值.
所以, if( a * b == c * d), 其实他的本质是: if( c * d == c * d) 错误


所以, 请永远不要: 试图通过返回const T & 来 减少临时对象 优化程序

返回ST &, 只有1个场景:

class ST{
	Obj data;

	Obj & get_data(){ return data;}   ' 即, 返回的这个对象, 在该函数调用前 (已经)存在了!!! '
};

如果, 就是要优化 减少临时对象, 只有1种做法:

void func_multiply( ST & a, ST & b, ST & ret){
	ret.data = a.data * b.data;
}
ST a, b;

当你调用时: ST ret; func_multiply( a, b, ret);

返回值的类型是 T

返回值是T的意思是: 返回值是T, 不是T * T &

返回的对象是 局部对象

ST Get(){
	ST ret;
	return ret;
}

当你调用Get();时, 总共只会产生1个对象即ret
即: 函数返回给外部的对象, 就是这个ret对象本身

即, 你ST t = Get(); 时, 右侧的这个Get()所代表的对象 就是ret本身

这里好像很难理解, 因为此时的ST t = Get();, 右侧的ret对象 已经超出了 他的作用域!!!
你应该这样理解: return语句, 并不是一个函数的结束, 函数的结束 是取决于: 函数的右括号}

ST Get(){
	ST ret;
	return ret;
}

ST t = Get(), func1(), func2();

首先一点是肯定的: 接收到ret的析构函数, 就意味着: Get函数的结束

执行流程是:

  • 进入Get函数, 到return这行语句时, 将ret这个对象 本身, 返回给ST t这个对象 注意, 此时Get函数, 并没有结束!!!
    即, return就像是: 发送了一个信号, 这个信号 带着ret对象本身 返回到 原先切入点, 同时, 将ret这个对象 "标记"为: 临时对象
  • 然后, 紧接着 ,执行 func1, func2, 直到执行到;结束
  • 重点: 此时, 会返回到Get函数里, 然后遇到}, 这个ret才会 delete掉

即, return语句, 就像是Qt里的 [信号与槽], 他会跳到别的地方执行, 执行完后, 还会回来的!!!


Foo Get(){
	Foo a;
	return a;	' 或 return Foo(); 一样的 '
};

当你外界 Foo b = Get();, 全程只有1个对象a产生!!! (只有1个析构函数, 1个无参构造函数)

首先, 这确实是非常的 不可思议!!! (但是, 逻辑来讲, 这非常的合理!!)
因为, 你的目的, 就是让 a 和 b 两个对象 是相同的; 而, 如果 能用1个对象完成, 那肯定比用2个对象完成, 要好的多

一定要牢记这个知识点!!!

ST Get(){ ST o; return o;}
vector< ST> Get(){ vector< ST> o; return 0;}

' 一定要写成: '
ST ret = Get();   ' &ret == &o '
vector< ST> ret = Get(); ' &ret[0,1,2,..] == &o[0,1,2,..] '
	' 这里的这个=等号, 不会引发任何的 对象构造复制 '

-------

' 千万不要写成: '
ST ret; 	ret = Get();
vector< ST> ret;	ret = Get();
	' 天壤差别,   造成 (对象复制) '
实现原理

暂时还不知道…
我猜可能是: 你虽然是写成ST a = Get(); vector< ST> b = Get();
但是, 其实他是会变成: ST && a = Get(); vector< ST> && b = Get();


返回的对象是 全局对象

ST g;

ST Get(){
	return g;
}

这种写法, 和 把 ST g, 放到Get函数里, 是天壤之别的!!!

程序一开始, g的构造; 然后执行: Get();return g时, 返回的 并不是g!!!
而是会调用一次ST的构造, 即构造一个新的临时对象, 给返回回去.


反问, 如果说, 返回的就是g本身. 那么&Get()是可以的, 而且Get().set_xxx()会修改 全局变量g的值.
这个功能, 应该是: ST & Get(); 而此时是: ST Get(); 这并不符合ST Get()函数的 定义.

所以, 当ST Get(){ return obj;} 返回的obj, 是一个全局对象时, 他会构造一个临时对象 给返回


再比如说:

class Bar{
	Foo foo;
	Foo get_foo(){ return foo;}
};

当你调用 bar.get_foo();时, 返回的, 并不是bar.foo本身, 而是会构造一个(临时对象)拷贝构造, 返回回去

总之: Foo get(){ return ret;} 不管这个函数, 是全局/member函数
对于这个get(){} 函数作用域来说

  • 如果这个ret, 是这个get作用域里的, 即, ret离开get作用域 就消失, 那么: 返回值的 就是这个ret本身!!!
  • 否则, 这个ret, 离开get作用域后 依然存在, 那么, 在返回时: 不会返回ret本身, 而是会 构造一个(临时对象) 返回出去!!!

实例

ST Get(){ return ST();}

ST Gett(){ return Get();}

ST Gettt(){ return Gett();}

调用Gettt();时, 只会调用1次 ST的构造 (对应上面ST();)!!!
因为: 当 返回值是: 局部变量 时, 返回的就是这个对象本身.


&lt;think&gt;嗯,用户问的是函数返回值不是引用(也就是没有用&amp;)的情况下,什么时候可以作为左值。这个问题需要仔细分析C++中的返回值类型和左值的条件。 首先,我得回忆一下左值的定义。左值是指那些有明确内存地址的表达式,可以出现在赋值语句的左边。比如变量、数组元素、结构成员等都是左值。而右值则是临时的,没有持久的内存地址,比如字面量或临时对象。 然后,函数返回值通常是一个右值,特别是当返回类型不是引用的时候。比如,一个函数返回int,那么这个返回值是右值,不能放在赋值左边。例如,func() = 5 会报错,因为func返回的是右值。 但这里用户问的是什么时候函数返回值不是引用却可以作为左值。这说明可能存在例外情况。这时候可能需要想到类的成员函数返回引用的情况。例如,重载赋值操作符时,通常返回的是对象的引用,这样允许链式赋值。比如,a = b = c,这里的b = c返回的是b的引用,所以a可以再赋值。 不过用户明确指出函数返回值不是引用,所以这种情况可能不适用。另一个可能性是,如果函数返回的是一个对象,而该对象重载了赋值操作符,但这可能涉及到右值引用的使用,比如移动语义。不过,即使这样,直接对右值进行赋值通常是不允许的,除非有特殊处理。 或者,用户可能是指某些特定的情况,比如函数返回的是指针,然后通过指针间接修改。例如,返回指针的函数可以解引用后作为左值,如*func() = 5,但这需要函数返回的是一个有效的指针。不过这里函数返回的是指针本身,而指针的解引用操作是左值,但函数返回值类型是指针,不是引用,所以这可能是一个情况。 不过严格来说,函数返回的是指针类型,而不是对象的引用。用户的问题可能更关注返回类型是对象而非引用的情况。这时候,如果函数返回的是对象,但该对象有成员函数返回引用,或者通过操作符重载,比如重载了operator[],那么可能可以。例如,某个类的成员函数返回一个临时对象,而该对象重载了operator[],返回的是元素的引用,从而允许作为左值。例如: struct Wrapper { int data[10]; int&amp; operator[](int idx) { return data[idx]; } }; Wrapper createWrapper() { return Wrapper(); } 然后,createWrapper()[0] = 5; 这里createWrapper()返回的是一个临时对象(右值),但它的operator[]返回的是int&amp;,所以可以赋值。这种情况下,函数返回值是对象(非引用),但通过成员函数返回引用,使得整个表达式可以作为左值。 因此,结论可能是:当函数返回的对象类型具有成员函数或重载操作符,这些函数返回左值引用时,即使函数自身返回的是非引用类型,其返回值仍可以作为左值使用的一部分。例如通过成员访问操作符或下标操作符间接获得左值引用的情况。&lt;/think&gt;在C++中,当函数返回值类型不是引用(即没有使用`&amp;`)时,返回值通常是右值(rvalue)。但有以下两种特殊场景,其返回值仍可作为表达式左值: --- ### 1. **返回对象包含左值引用的成员函数** 若函数返回一个对象,且该对象类型重载了**返回左值引用的成员函数**(如`operator[]`或自定义函数),则可通过成员访问间接作为左值。 #### 示例 ```cpp class ArrayWrapper { public: int data[5]; int&amp; operator[](int index) { return data[index]; } }; ArrayWrapper createArray() { return ArrayWrapper(); // 返回非引用对象(右值) } int main() { createArray()[2] = 42; // 通过operator[]返回左值引用,可作为左值 // 等价于:临时对象的data[2] = 42 } ``` - **关键点**: - `createArray()`返回临时对象(右值)。 - 临时对象调用`operator[]`,返回`int&amp;`(左值引用)。 - 因此`createArray()[2]`整体表现为左值。 --- ### 2. **类类型重载赋值操作符** 若函数返回的类类型重载了赋值操作符(如`operator=`),虽然返回值本身是右值,但赋值操作符的参数可以是左值。 #### 示例 ```cpp class CustomString { public: CustomString&amp; operator=(const char* str) { // 实现赋值逻辑 return *this; } }; CustomString createString() { return CustomString(); // 返回非引用对象(右值) } int main() { createString() = &quot;Hello&quot;; // 调用右值的operator=,参数&quot;Hello&quot;是右值 } ``` - **关键点**: - `createString()`返回临时对象(右值)。 - 右值调用`operator=`,语法合法。 - 赋值操作符的参数`&quot;Hello&quot;`是右值,但操作符本身修改的是临时对象的状态。 --- ### 总结表 | 场景 | 原理 | 示例 | |-----------------------|----------------------------------------------------------------------|-------------------------------| | 对象成员返回左值引用 | 临时对象调用返回引用的成员函数(如`operator[]`),结果作为左值 | `createArray()[2] = 42` | | 类重载赋值操作符 | 右值调用`operator=`,直接修改临时对象状态(参数可为右值) | `createString() = &quot;Hello&quot;` | --- ### 注意事项 - **右值属性不变**:即使上述操作合法,函数返回值本身仍是右值。能作为左值的本质是通过成员函数间接获得左值引用。 - **生命周期风险**:临时对象的成员引用需确保在有效生命周期内使用,否则导致未定义行为。 通过理解对象成员函数的行为,可以灵活利用C++的特性实现类似左值的操作。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值