c++中有些重載運算符為什麼要返回引用?

1. c++中有些重載運算符為什麼要返回引用,單單為了避免析構再構造嗎?

2. 在此情況下,返回值為NULL或類似情況怎麼辦?

3. 返回的引用賦給一個變數後,那個變數是不是引用?怎麼理解?

4. 據說返回局部對象的引用非常危險,請問返回引用到底有什麼作用?,難道輸入輸出流返回的不是局部對象的引用?


謝邀,我嘗試一個個地解答。

1. c++中有些重載運算符為什麼要返回引用,單單為了避免析構再構造嗎?

不是。「有些」重載運算符要返回的引用,是為了返回它本身。如

class TestClass {
private:
int number;
public:
TestClass operator+=(const TestClass rhs) {
number += rhs.number;
return *this;
}
};

上例是一個C++的不完整的簡單類,其中+=運算符,它本身的意義是「自增,並返回自增後的值」,所以就要返回自己,而不是返回一個自己的拷貝。

2. 在此情況下,返回值為NULL或類似情況怎麼辦?

引用不可為空,樓主所說的情況不存在。

3. 返回的引用賦給一個變數後,那個變數是不是引用?怎麼理解?

#include &
using namespace std;

class StupidClass {
int flag;
public:
StupidClass(int flag): flag(flag) {
cout &<&< "Constructor " &<&< flag &<&< endl; } ~StupidClass() { cout &<&< "Destructor " &<&< flag &<&< endl; } StupidClass(const StupidClass rhs) { cout &<&< "Copy Constructor *this=" &<&< flag &<&< " rhs=" &<&< rhs.flag &<&< endl; } StupidClass operator=(const StupidClass rhs) { cout &<&< "Operator = *this=" &<&< flag &<&< " rhs=" &<&< rhs.flag &<&< endl; return *this; } StupidClass operator+=(const StupidClass rhs) { cout &<&< "Operator += *this=" &<&< flag &<&< " rhs=" &<&< rhs.flag &<&< endl; flag += rhs.flag; return *this; } }; int main() { StupidClass var1(1), var2(2); StupidClass var3 = var1 += var2; return 0; }

輸出是

Constructor 1
Constructor 2
Operator += *this=1 rhs=2
Copy Constructor *this=3 rhs=3
Destructor 3
Destructor 2
Destructor 3

所以結果就是先執行var1 += var2,然後使用得來的var1對var3拷貝構造。其中var1中的flag變成3了(看後面兩個Destructor 3,和Copy Constructor *this=3 rhs=3)。

如果把var3的類型改成StupidClass,那麼輸出就變成

Constructor 1
Constructor 2
Operator += *this=1 rhs=2
Destructor 2
Destructor 1

那麼var3就是var1的引用了。

4. 據說返回局部對象的引用非常危險,請問返回引用到底有什麼作用?,難道輸入輸出流返回的不是局部對象的引用?

輸入輸出流重載的是 &<&< 和 &>&> 運算符,一般是這樣寫的

ostream operator&<&<(ostream os, const TestClass rhs) { os &<&< rhs.number; return os; }

這裡面沒有返回局部對象的引用啊,所謂返回局部對象的引用是像這樣的

int func() {
int dummy = 1;
return dummy;
}

在這裡dummy就是一個「局部對象」。


先回答第一個問題:C++的有些重載運算符為什麼要返回引用?

事實上我們的重載運算符返回void 、返回對象本身、返回對象引用都是可以的,並不是說一定要返回一個引用,只不過在不同的情況下需要不同的返回值

那麼我們什麼情況下要返回對象的引用呢?

原因有兩個:

①允許進行連續賦值

②防止返回對象(返回對象也可以進行連續賦值)的時候調用拷貝構造函數和析構函數導致不必要的開銷,降低賦值運算符等的效率。

對於第二點原因:如果用「值傳遞」的方式,雖然功能仍然正確,但由於return語句要把 *this拷貝到保存返回值的外部存儲單元之中,增加了不必要的開銷,會降低賦值函數的效率。

場景一:

需要返回對象引用或者返回對象(效率沒有返回引用高),需要實現連續賦值,

使重載的運算符更符合C++本身的運算符的語意,=連續賦值, += -= *= /=

&<&<輸出流

先討論兩個例子:

1:

關於賦值 =,我們知道賦值=有連續等於的特性

int x,y,z;

x = y = z = 15;

同樣有趣的是,賦值採用的是右結合律,所以上述連鎖賦值被解析為:

x = (y = (z = 15)); //賦值連鎖形式

這裡15先被賦值給z,然後其結果(更新後的z)再被賦值給y,然後其結果(更新後的y)再被賦值給x。

為了實現「連鎖賦值」,賦值操作符號必須返回一個reference指向操作符號的左側實參(而事實上重載運算符的左側實參就是調用對象本身,比如= += -=等)

這是你為classes實現賦值操作符時應該遵循的協議:

這個協議不僅僅適用於以上的標準賦值形式,也適用於所有賦值運算

class Widght{
public:
.....
Widget operator=(cosnt Widget rhs)
{
...
return* this;
}
Widget operator+=(cosnt Widget rhs)
{
...
return* this;
}

Widget operator-=(cosnt Widget rhs)
{
...
return* this;
}

Widget operator*=(cosnt Widget rhs)
{
...
return* this;
}

Widget operator/=(cosnt Widget rhs)
{
...
return* this;
}
...
};

注意,這只是個協議,並無強制性,如果不遵循它,代碼一樣可以通過編譯,然而這份協議被所有內置類型和標準程序庫提供的類型入string,vector,complex,trl:shared_ptr或者即將提供的類型共同遵守。因此除非你有一個標新立異的好理由,不然還是隨眾吧。

下面看一個賦值運算符號的例子

#ifndef STRING_H
#define STRING_H
using namespace std;
class String
{
private:
char *str;
int len;
public:
String(const char* s);
String operator=(const String another);
void show()
{
cout&<&<"value = "&<&

#include&
#include&
#include"String.h"
using namespace std;

String::String(const char* s)
{
len = strlen(s);
str = new char[len + 1];
strcpy(str,s);
}

String String::operator=(const String other)
{
if(this == other)
return *this;
delete[] str;
len = other.len;
str = new char[len + 1];
strcpy(str,other.str);
return *this;
}

int main()
{
String str1("abc");
String str2("123");
String str3("456");
str1.show();
str2.show();
str3.show();

str3 = str1 = str2;//str3.operator=(str1.operator=str2)
str3.show();
str1.show();
return 0;
}

下面是返回引用的情況String operator+(const String str)

這樣會在賦值運算過程的返回途中調用兩次拷貝構造函數和析構函數(因為return的是個新的對象)

如果採用String operator+(const String str) 這樣就不會有多餘的調用(因為這裡直接return一個已經存在對象的引用)

如果採用return對象 那麼第二次賦值運算調用的情況就是

將一個新的String對象傳遞到operator+()的參數中去 相當於

const Stringstr = returnStringObj;

如果採用return對象引用 那麼第二次賦值運算的情況就是

將一個已經存在的String對象(其實就是str1)的引用傳遞到operator+()的參數中去

const Stringstr = returnReference; (String returnReference = str1;)+=等運算符也是同樣的考慮,比如

int main()
{
String str1("abc");
String str2("123");
String str3("456");
str1.show();
str2.show();
str3.show();

str3 = str1 = str2;//str3.operator=(str1.operator=str2)
str3.show();
str1.show();

int num = 10;
num+=(num+=100);
cout&<&

如果要這樣使用+=或其他上面舉出的運算符,+=運算的返回值一定要是一個對象或者引用才行,不然就會出現錯誤(參數類型不符號)。

當然如果確定不會這麼使用,直接返回void也是可以的

上面的例子也說明了一點,析構函數的調用是在變數作用域結束的時候(以及程序運行結束的時候)

在不需要對返回值進行操作的時候,直接返回void就行了,首先要明白一點,運算符左側的對象就是操作對象比如

ObjectA = ObjectB ObjectA.operator=(ObjectB)

ObjectA+=ObjectB ObjectA.operator+(ObjectB)

下面看返回void

public:
String(const char* s);
void operator=(const String another);

void String::operator=(const String other)
{
if(this == other)
return;
delete[] str;
len = other.len;
str = new char[len + 1];
strcpy(str,other.str);
}

int main()
{
String str1("abc");
String str2("123");
str1.show();
str2.show();

str1 = str2;//str1.operator=str2
str1.show();
return 0;
}

返回void也是可以成功完成賦值運算符賦值的

以上就是答案


題主還是沒理清思路啊。要從兩個角度分析這個問題:為什麼返回應用?以及返回之後發生了什麼?

1、2 兩問實際上是第一個問題:為什麼在重載操作符的時候會返回引用?

實際上這裡的原因很簡單:為了表達正確的語義。C++ 的操作符重載的全部意義就是為了讓所謂」值類型「的操作看起來更像是 int / float 之類基本類型的操作,所以當談論操作符重載的時候,

返回的引用基本上都是綁定在 *this(當實現為成員函數)或者第一個參數(當實現為全局函數,如 operator&<&< (ostream, T) 這種)

目的很明確:為了串聯表達式。而很明顯可以看到:這兩個情況都不存在 NULL 的可能性。如果傳入對象為 NULL,錯誤早在返回引用被訪問之前就已經出了。

一個函數能不能返回引用,應該在這個函數內部進行分析:

  1. 對於一個傳入的對象,生命期由調用者管理的時候,返回它的引用通常是沒問題的(operator&<&< 等通常屬於這種情況)

  2. this 指針實際上等價於被傳入給成員函數的」第一個參數「,生命周期也是由調用者管理的,所以等價於第一個情況
  3. 被調用函數管理的局部對象,由於在函數返回的時候生命周期結束,無法保持,通常不應該返回引用;如果外部需要,可以返回值(進行拷貝)或者分配在堆上並返回指針(需要調用者之後清理)。

第三個問題是基本的引用概念問題。

int i = 0;
int i1 = i;
int r1 = i;
int r2 = i1;
int r3 = r1;
int i2 = r1;

請問 r2 和 r3 有區別么?沒有。當你把一個」引用「放在等號右側的時候,它和」被它引用的對象「是等價的,所以」3. 返回的引用賦給一個變數後,那個變數是不是引用?「 的答案僅取決於左邊這個變數的定義(體會 i2 和 r3 的區別)。

第四個問題,也就是 CSDN 那個問題里的錯誤在於,這個被返回的對象的生命期在當前表達式結束的時候已經結束了。你需要分析的是這個被綁定的對象的生命期;根據我第一段斜體字,以及 @鍾宇騰 的分析,可以很明顯的看出這裡和 CSDN 那段代碼中,對象的生命期是有本質區別的。正確的做法,上面已經分析了。


我只回答問題1,因為問題234,樓上的幾位大牛已經說的很完整了。

問題1:C++中有些重載運算符為什麼要返回引用?

答:因為C++標準中支持很多這種寫法:

int a;
(a+=3)*=4;

這不單發生在+=運算符上,還有=運算符。

當然,大家最熟悉的應該是&<&<運算符:

cout &<&< "hello world!" &<&< endl;

如果這些支持連續寫法的運算符的返回值不是引用的話,

那麼只有第一個運算符能夠正常工作,

後面的操作就都無法得到正常的結果了。


返回引用一個重要目的是實現鏈式調用

a.add().add().test().print()

1.如果不返回引用,那麼a=b=c是無效的

2.返回自身引用不存在NULL的情況

3.引用應當用於初始化引用,而不是付給變數

4.cout &<&< "A" 返回的是cout的引用。


推薦閱讀:

為什麼C語言考試不夠好?
%d的格式不能用來輸出sizeof的返回值嗎?
為什麼學習編程第一課要學習輸出"hello, world"?這是誰規定的?

TAG:編程語言 | 編程 | C編程語言 | C | CC |