您好,登錄后才能下訂單哦!
本文小編為大家詳細介紹“C++引用的特性是什么”,內容詳細,步驟清晰,細節處理妥當,希望這篇“C++引用的特性是什么”文章能幫助大家解決疑惑,下面跟著小編的思路慢慢深入,一起來學習新知識吧。
引用不是新定義一個變量,而是給已存在變量取了一個別名,語法理解上程序不會為引用變量開辟內存空間,它和它引用的變量共用同一塊內存空間
比如:李逵,在家稱為"鐵牛",江湖上人稱"黑旋風"
類型& 引用變量名(對象名) = 引用實體
int main() { //有一塊空間a,后面給a取了三個別名b、c、d int a = 10; int& b = a; int& c = a; int& d = b; //char& d = a;//err,引用類型和引用實體不是同類型(這里有爭議————char a = b[int類型],留個懸念,下面會解答) //會被修改 c = 20; d = 30; return 0; }
注意
引用類型必須和引用實體是同種類型
注意區分 ‘&’ 取地址符號
引用在定義時必須初始化
一個變量可以有多個引用
引用一旦引用一個實體,再不能引用其他實體
int main() { //int& e;//err int a = 10; int& b = a; //這里指的是把c的值賦值于b int c = 20; b = c; return 0; }
void TestConstRef() { const int a = 10; //int& ra = a; //該語句編譯時會出錯,a為常量;由const int到int const int& ra = a;//ok int b = 20; const int& c = b; //ok,由int到const int //b可以改,c只能讀不能寫 b = 30; //c = 30;//err //b、c分別起的別名的權限可以是不變或縮小 int& d = b;//ok //int& e = c//err const int& e = c;//ok //int& f = 10; // 該語句編譯時會出錯,b為常量 const int& g = 10;//ok int h = 10; double i = h;//ok //double& j = h;//err const double& j = h;//ok //?為啥h能賦值給i了(隱式類型轉換),而給h起一個double類型的別名卻不行————如果是僅僅是類型的問題那為啥加上const就行了? //double i = h;并不是直接把h給i,而是在它們中間產生了一個臨時變量(double類型、常量),并利用這個臨時變量賦值 //也就是說const double& j = h;就意味著j不是直接變成h的別名,而是變成臨時變量(doublde類型)的別名,但是這個臨時變量是一個常量,這也解釋了為啥需要加上const }
小結
1.我能否滿足你變成別名的條件:可以不變或者縮小你讀寫的權限 (const int -> const int 或 int -> const int),而不能放大你讀寫的權限 (const int -> int)
2.別名的意義可以改變,并不是每個別名都跟原名有一樣的權限
3.不能給類型不同的變量起別名的真正原因不是類型不同,而是隱式類型轉換后具有常性了
常引用的意義 (舉例棧)
typedef struct Stack { int* a; int top; int capacity; }ST; void InitStack(ST& s)//傳引用是為了形參的改變影響實參 {//...} void PrintStack(const ST& s)//1.傳引用是為了減少拷貝 2. 同時保護實參不會被修改 {//...} void Test(const int& n)//即可以接收變量,也可以接收常量 {//...} int main() { ST st; InitStack(st); //... PrintStack(st); int i = 10; Test(i); Test(20); return 0; }
小結
1.函數傳參如果想減少拷貝使用引用傳參,如果函數中不改變這個參數最好使用 const 引用傳參
2.const 引用的好處是保護實參,避免被誤改,且它可以傳普通對象也可以傳 const 對象
void Swap1(int* p1, int* p2) { int temp = *p1; *p1 = *p2; *p2 = temp; } void Swap2(int& rx, int& ry) { int temp = rx; rx = ry; ry = temp; } int main() { int x = 3, y = 5; Swap1(&x, &y);//C傳參 Swap2(x, y);//C++傳參 return 0; }
在 C++ 中形參變量的改變,要影響實參,可以用指針或者引用解決
意義:指針實現單鏈表尾插 || 引用實現單鏈表尾插
void SListPushBack(SLTNode*& phead, int x) { //這里phead的改變就是plist的改變 } void TestSList2() { SLTNode* plist = NULL; SListPushBack(plist, 1); SListPushBack(plist, 2); }
有些書上喜歡這樣寫 (不推薦)
typedef int SLTDataType; typedef struct SListNode { SLTDataType data; struct SListNode* next; }SLTNode, *PSLTNode; void SListPushBack(PSLTNode& phead, int x) { //... }
//傳值返回 int Add(int a, int b) { int c = a + b; return c;//需要拷貝 } int main() { int ret = Add(1, 2);//ok, 3 Add(3, 4); cout << "Add(1, 2) is :"<< ret <<endl; return 0; }
Add 函數里的 return c; —— 傳值返回,臨時變量作返回值。如果比較小,通常是寄存器;如果比較大,會在 main 函數里開辟一塊臨時空間
怎么證明呢
int Add(int a, int b) { int c = a + b; return c; } int main() { //int& ret = Add(1, 2);//err const int& ret = Add(1, 2);//ok, 3 Add(3, 4); cout << "Add(1, 2) is :"<< ret <<endl; return 0; }
從上面就可以驗證 Add 函數的返回值是先存儲在臨時空間里的
//傳引用返回 int& Add(int a, int b) { int c = a + b; return c;//不需要拷貝 } int main() { int ret = Add(1, 2);//err, 3 Add(3, 4); cout << "Add(1, 2) is :"<< ret <<endl; return 0; }
結果是不確定的,因為 Add 函數的返回值是 c 的別名,所以在賦給 ret 前,c 的值到底是 3 還是隨機值,跟平臺有關系 (具體是平臺銷毀棧幀時是否會清理棧幀空間),所以這里的這種寫法本身就是越界的 (越界抽查不一定報錯)、錯誤的
發現這樣也能跑,但詭異的是為啥 ret 是 7
//傳引用返回 int& Add(int a, int b) { int c = a + b; return c; } int main() { int& ret = Add(1, 2);//err, 7 Add(3, 4); cout << "Add(1, 2) is :"<< ret <<endl; return 0; }
在上面我們在 VS 下運行,可以得出編譯器并沒有清理棧幀,那么這里進一步驗證引用返回的危害
雖然能正常運行,但是它是有問題的
1.出了 TEST 函數的作用域,ret 變量會銷毀,就不能引用返回
2. 出了 TEST 函數的作用域,ret 變量不會銷毀,就可以引用返回
3.引用返回的價值是減少拷貝
觀察并剖析以下代碼
int main()int main() { int x = 3, y = 5; int* p1 = &x; int* p2 = &y; int*& p3 = p1; *p3 = 10; p3 = p2; return 0; }
#include <time.h>#include<iostream>using namespace std;struct A { int a[10000]; };A a;void TestFunc1(A a) {}void TestFunc2(A& a) {}A TestFunc3() { return a; }A& TestFunc4() { return a; }void TestRefAndValue(){A a;// 以值作為函數參數size_t begin1 = clock();for (size_t i = 0; i < 10000; ++i)TestFunc1(a);size_t end1 = clock();// 以引用作為函數參數size_t begin2 = clock();for (size_t i = 0; i < 10000; ++i)TestFunc2(a);size_t end2 = clock();// 分別計算兩個函數運行結束后的時間cout << "TestFunc1(A)-time:" << end1 - begin1 << endl;cout << "TestFunc2(A&)-time:" << end2 - begin2 << endl;}void TestReturnByRefOrValue(){// 以值作為函數的返回值類型size_t begin1 = clock();for (size_t i = 0; i < 100000; ++i)TestFunc3();size_t end1 = clock();// 以引用作為函數的返回值類型size_t begin2 = clock();for (size_t i = 0; i < 100000; ++i)TestFunc4();size_t end2 = clock();// 計算兩個函數運算完成之后的時間cout << "TestFunc1 time:" << end1 - begin1 << endl;cout << "TestFunc2 time:" << end2 - begin2 << endl;}int main(){//傳值、傳引用效率比較TestRefAndValue();cout << "----------cut----------" << endl;//值和引用作為返回值類型的性能比較TestReturnByRefOrValue();return 0;}#include <time.h> #include<iostream> using namespace std; struct A { int a[10000]; }; A a; void TestFunc1(A a) {} void TestFunc2(A& a) {} A TestFunc3() { return a; } A& TestFunc4() { return a; } void TestRefAndValue() { A a; // 以值作為函數參數 size_t begin1 = clock(); for (size_t i = 0; i < 10000; ++i) TestFunc1(a); size_t end1 = clock(); // 以引用作為函數參數 size_t begin2 = clock(); for (size_t i = 0; i < 10000; ++i) TestFunc2(a); size_t end2 = clock(); // 分別計算兩個函數運行結束后的時間 cout << "TestFunc1(A)-time:" << end1 - begin1 << endl; cout << "TestFunc2(A&)-time:" << end2 - begin2 << endl; } void TestReturnByRefOrValue() { // 以值作為函數的返回值類型 size_t begin1 = clock(); for (size_t i = 0; i < 100000; ++i) TestFunc3(); size_t end1 = clock(); // 以引用作為函數的返回值類型 size_t begin2 = clock(); for (size_t i = 0; i < 100000; ++i) TestFunc4(); size_t end2 = clock(); // 計算兩個函數運算完成之后的時間 cout << "TestFunc1 time:" << end1 - begin1 << endl; cout << "TestFunc2 time:" << end2 - begin2 << endl; } int main() { //傳值、傳引用效率比較 TestRefAndValue(); cout << "----------cut----------" << endl; //值和引用作為返回值類型的性能比較 TestReturnByRefOrValue(); return 0; }
以值作為參數或者返回值類型,在傳參和返回期間,函數不會直接傳遞實參或者將變量本身直接返回,而是傳遞實參或者返回變量的一份臨時的拷貝,因此用值作為參數或者返回值類型,效率是非常低下的,尤其是當參數或者返回值類型非常大時,效率就更低
引用就是一個別名,沒有獨立空間,和其引用實體共用同一塊空間
指針變量是開辟一塊空間,存儲變量的地址
int main() { int a = 10; int& ra = a; cout<<"&a = "<<&a<<endl; cout<<"&ra = "<<&ra<<endl; int b = 20; int* pb = &b; cout<<"&b = "<<&b<<endl; cout<<"&pb = "<<&pb<<endl; return 0; }
引用和指針是一樣的,因為引用是按照指針方式來實現的
int main() { int a = 10; int& ra = a; ra = 20; int* pa = &a; *pa = 20; return 0; }
這里我們對比一下 VS 下引用和指針的匯編代碼可以看出來他倆是同根同源
引用和指針的不同點:
1、引用在定義時必須初始化,指針沒有要求
2、引用在初始化時引用一個實體后,就不能再引用其他實體,而指針可以在任何時候指向任何一個同類型實體
3 、沒有 NULL 引用,但有 NULL 指針
4、在 sizeof 中含義不同:引用結果為引用類型的大小,與類型有關;但指針始終是地址空間所占字節個數 (32 位平臺下占 4 個字節,64 位平臺下占 8 個字節),與類型無關
5、引用自加即引用的實體增加 1,與類型無關,指針自加即指針向后偏移一個類型的大小,與類型有關
6、有多級指針,但是沒有多級引用
7、訪問實體方式不同,指針需要解引用,引用編譯器自己處理
8、引用比指針使用起來相對更安全,指針容易出現野指針、空指針等非法訪問問題
讀到這里,這篇“C++引用的特性是什么”文章已經介紹完畢,想要掌握這篇文章的知識點還需要大家自己動手實踐使用過才能領會,如果想了解更多相關內容的文章,歡迎關注億速云行業資訊頻道。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。