第12章 創建引用 12.1什麼是引用 引用是一個別名。創建引用時,使用另一個對象(目標)的名稱來初始化它,從此以後該引用就像是目標的另一個名稱,對引用執行的任何操作實際上針對的就是目標。 有些書上說引用就是指針,這不正確。雖然引用常常是使用指針實現的,但是只有編譯器開發人員關心這一點,作為程式員, ...
第12章 創建引用
12.1什麼是引用
引用是一個別名。創建引用時,使用另一個對象(目標)的名稱來初始化它,從此以後該引用就像是目標的另一個名稱,對引用執行的任何操作實際上針對的就是目標。
有些書上說引用就是指針,這不正確。雖然引用常常是使用指針實現的,但是只有編譯器開發人員關心這一點,作為程式員,必須區分這兩種概念。
指針是存儲另一個對象的地址的變數,而引用時對象的別名。
12.2創建引用
要創建引用,需要指定目標對象的類型、引用運算符(&)和引用名。
程式清單12.1 Reference.cpp
#include <iostream>
int main()
{
int intOne;
int &rSomeRef = intOne;
intOne = 5;
std::cout << "intOne: " << intOne << std::endl;
std::cout << "rSomeRef: " << rSomeRef << std::endl;
rSomeRef = 7;
std::cout << "intOne: " << intOne << std::endl;
std::cout << "rSomeRef: " << rSomeRef << std::endl;
return 0;
}
12.3將地址運算符用於引用
如果請求返回引用的地址,就將返回它指向的目標的地址。這是引用的特征:他們是目標的別名。
程式清單12.2 Reference2.cpp
#include <iostream>
int main()
{
int intOne;
int &rSomeRef = intOne;
intOne = 5;
std::cout << "intOne: " << intOne << std::endl;
std::cout << "rSomeRef: " << rSomeRef << std::endl;
std::cout << "&intOne: " << &intOne << std::endl;
std::cout << "&rSomeRef: " << &rSomeRef << std::endl;
return 0;
}
通常,使用引用時,不將地址運算符用於它,而像使用目標變數那樣使用引用。
程式清單12.3 Assignment.cpp
#include <iostream>
int main()
{
int intOne;
int &rSomeRef = intOne;
intOne = 5;
std::cout << "intOne:\t" << intOne << std::endl;
std::cout << "rSomeRef:\t" << rSomeRef << std::endl;
std::cout << "&intOne:\t" << &intOne << std::endl;
std::cout << "&rSomeRef:\t" << &rSomeRef << std::endl;
int intTwo = 8;
rSomeRef = intTwo;
std::cout << "\nintOne:\t" << intOne << std::endl;
std::cout << "intTwo:\t" << intTwo << std::endl;
std::cout << "rSomeRef:\t" << rSomeRef << std::endl;
std::cout << "&intOne:\t" << &intOne << std::endl;
std::cout << "&intTwo:\t" << &intTwo << std::endl;
std::cout << "&rSomeRef:\t" << &rSomeRef << std::endl;
return 0;
}
12.4可引用的目標
因為對象也是一種變數,所以可引用任何對象,包括用戶定義的對象。可以像使用對象那樣使用對象的引用:訪問成員數據和成員函數時,使用類成員訪問運算符(.)與內置類型的引用一樣,指向對象的引用也是對象的別名。
12.5空指針和空引用
指針未初始化或被刪除時,應將其賦為nullptr,但引用不一樣,引用不能為空,讓引用指向空對象的程式是非法的。
12.6按引用傳遞函數參數
前面知道了函數的兩個局限性:參數按值傳遞;return語句只能返回一個值。
通過將值按引用傳遞給函數,可消除這兩種局限性。在c++中,按引用傳遞時通過兩種方式完成的:使用指針和使用引用。他們的語法不同,但效果相同:不是在函數作用域內創建備份(也就是不是值拷貝),而是將原始對象傳遞給函數。
程式清單12.4 ValuePasser.cpp
#include <iostream>
void swap(int x, int y);
int main()
{
int x = 5, y = 10;
std::cout << "Main. Before swap,x: " << x << " y: " << y << std::endl;
swap(x, y);
std::cout << "Main. Before swap,x: " << x << " y: " << y << std::endl;
return 0;
}
void swap(int x, int y)
{
int temp;
std::cout << "Swap. Before swap,x: " << x << " y: " << y << std::endl;
temp = x;
x = y;
y = temp;
std::cout << "Swap. After swap,x: " << x << " y: " << y << std::endl;
}
main()中的值都沒變,可見值拷貝並不能改變原參的值
使用指針實現swap()
程式清單12.5 PointerSwap.cpp
#include <iostream>
void swap(int *x, int *y);
int main()
{
int x = 5, y = 10;
std::cout << "Main. Before swap,x: " << x << " y: " << y << std::endl;
swap(&x, &y);//將地址作為參數傳遞
std::cout << "Main. Before swap,x: " << x << " y: " << y << std::endl;
return 0;
}
void swap(int *px, int *py)//參數聲明為指針
{
int temp;
std::cout << "Swap. Before swap,*px: " << *px << " *py: " << *py << std::endl;
temp = *px;
*px = *py;
*py = temp;
std::cout << "Swap. After swap,*px: " << *px << " *py: " << *py << std::endl;
}
使用引用實現swap()
c++的目標之一時,避免函數的調用者操心函數的工作原理,而將註意力放在函數的功能和返回值上。傳遞指針將負擔轉嫁給了調用方,而這種負擔原本不應該由調用方來承擔:調用方必須知道將要交換的對象的地址傳入。
明白引用語法的負擔應由函數的實現方承擔。為此,可使用引用。
程式清單12.6 ReferenceSwap.cpp
#include <iostream>
void swap(int &x, int &y);
int main()
{
int x = 5, y = 10;
std::cout << "Main. Before swap,x: " << x << " y: " << y << std::endl;
swap(x, y);
std::cout << "Main. Before swap,x: " << x << " y: " << y << std::endl;
return 0;
}
void swap(int &rx, int &ry)//參數聲明為引用
{
int temp;
std::cout << "Swap. Before swap,rx: " << rx << " ry: " << ry << std::endl;
temp = rx;
rx = ry;
ry = temp;
std::cout << "Swap. After swap,rx: " << rx << " ry: " << ry << std::endl;
}
可見兩種方式(指針傳地址、引用傳原參)達到的效果是一樣的,但是引用傳遞中,調用方只需傳遞變數,且在函數內部,需要使用的特殊符號減少了,降低了程式的複雜性。引用將常規變數方便而易於使用的特點和指針的強大融為一體。
12.7理解函數頭和原型
函數原型的另一個重要用途:通過查看原型中聲明的參數(函數原型通常放在頭文件中),程式員知道swap()的參數是按指針還是引用傳遞的,從而將正確調用他們。
在c++中,類的使用者(其他類中使用該類的函數)依賴於頭文件來獲悉需要的所有信息。頭文件相當於類或函數的介面,而實際實現對使用者是隱藏的。這讓程式員能夠將主要精力放在要解決的問題上,而使用類或函數時無需關心它是如何實現的。
12.8返回多個值
(哦,這說和沒說是一樣的,還是只能return一個東西)
一種解決辦法是將多個對象按引用傳入函數,然後在函數中將正確的值賦給這些對象。由於按引用傳遞讓函數能夠修改原始對象,因此這相當於讓函數能夠返回多項信息。這種函數未使用函數的返回值,可將其(指返回值)用於報告錯誤。
另一種辦法是使用指針或引用來實現。
程式清單12.7 ReturnPointer.cpp
#include <iostream>
short factor(int, int *, int *);
int main()
{
int number, squared, cubed;
short error;
std::cout << "Enter a number(0 - 20): ";
std::cin >> number;
error = factor(number, &squared, &cubed);
if (!error)
{
std::cout << "number: " << number << "\n";
std::cout << "square: " << squared << "\n";
std::cout << "cubed: " << cubed << "\n";
}
else
std::cout << "Error encountered!!\n";
return 0;
}
short factor(int n, int *pSquared, int *pCubed)
{
short vaule = 0;
if (n > 20)
{
vaule = 1;
}
else
{
*pSquared = n * n;
*pCubed = n * n * n;
vaule = 0;
}
return vaule;
}
按引用返回值
雖然程式ReturnPointer.cpp可行,但是如果使用引用而不是指針,將更容易理解和維護。
程式清單12.8 ReturnReference.cpp
#include <iostream>
enum ERR_CODE//枚舉
{
SUCCESS,
ERROR
};
ERR_CODE factor(int, int &, int &);
int main()
{
int number, squared, cubed;
ERR_CODE result;
std::cout << "Enter a number(0 - 20): ";
std::cin >> number;
result = factor(number, squared, cubed);
if (result == SUCCESS)
{
std::cout << "number: " << number << "\n";
std::cout << "square: " << squared << "\n";
std::cout << "cubed: " << cubed << "\n";
}
else
std::cout << "Error encountered!!\n";
return 0;
}
ERR_CODE factor(int n, int &pSquared, int &pCubed)
{
short vaule = 0;
if (n > 20)
{
return ERROR;
}
else
{
pSquared = n * n;
pCubed = n * n * n;
return SUCCESS;
}
}