C++的引用

目錄

引用

常引用

指針與引用的關系

小拓展

引用的價值

做形參

?傳值、傳引用的效率比較

做返回值

函數傳值返回

函數傳引用返回(錯誤示范)

野引用(錯誤示范)

引用的正常應用

值和引用作為返回值類型的性能比較

引用和指針的區別

語法上

底層(匯編)上


引用

基本概念:引用是給已存在變量取了一個別名

李逵,在家稱為“鐵牛”,江湖上人稱“黑旋風”

特點:和原變量共用同一塊內存空間(地址相同,且牽一發而動全身),同一變量可以有多個別名

格式:類型& 引用變量名(對象名) = 引用實體

注意事項:

1、C++中的&依然可以表示取地址運算符和按位與運算符

2、引用類型必須和引用實體是同種類型的

3、當引用實體和它的別名屬于不同的域時,別名和引用實體的名字可以相同但是不建議這樣做

#include <iostream>
using namespace std;int main()
{int a = 10;int& b = a;cout << &a << endl;cout << &b << endl << endl;a++;b++;cout << a << endl;cout << b << endl << endl;return 0;
}

4、 引用必須在開始就初始化(說明自己是誰的別名)

int a = 0;//wrong
int& b;
b = a;  //right
int &b = a;

5、引用定義后,不能改變指向(別名與綁定后無法修改,對別名的任何操作就是對實體的操作)

int a = 0;
int& b = a;
int c = 1;
b = c;    // 這里不是讓b指向c,而是將1賦值給了引用實體a

常引用

定義:使用?const?修飾的引用

特點:被const修飾的引用不能被修改

void printValue(const int& value) {// 這里無法通過value來修改原始值,如果輸入value += 10;編譯器就會報錯cout << "Value: " << value << endl;
}int main() {int num = 10;// 使用常量引?來傳遞numprintValue(num);return 0;
}

指針與引用的關系

基本概念:指針和引用是類似的,指針找到并修改你,別名直接修改你(二者存在依賴關系)

????????雖然C++的引用可以對使用指針后比較復雜的場景進行一些替換,讓代碼更簡單易懂,但是因

為引用在定義后不能改變指向(刪除雙向鏈表的一個結點時,?需要用改變前去指針和后繼指針,

用不能做到這一點),而指針可以,所以在C++中引用并不能替代指針:

struct Node
{struct Node* next;struct Node* prev;int val;
};

??????之前我們在寫單鏈表時,用二級指針pphead接收頭指針的地址,而當我們有了引用的概念后,

可以以一種更好理解的方式實現下列單鏈表的(偽)代碼:

#include <stdio.h> 
struct Node
{struct Node* next;struct Node* prev;int val;
};//二級指針版本
void PushBack(struct Node** pphead,int x)
{*pphead = newnode;
}//引用版本
void PushBack(struct Node*& phead, int x)//為指針取別名,plist指針的別名是phead
{phead = newnode;
}int main()
{struct Node* plist = NULL;PushBack(plist,0);return 0;
}

????????我們將原來的二級指針pphead換為struct Node*& phead,phead是plist的別名,對phead的操

作就是對plist的操作,這使得代碼看起來更加簡單。單鏈表使用二級指針的原因就是為了能夠向

頭指針中存入新節點的值(不用的話對于值得修改不能被帶出尾插函數),這里直接通過引用就實

現了這一目的所以不需要再去考慮傳值調用的問題

關于單鏈表的內容可以查看:單鏈表的實現(全注釋promax版)

小拓展

#include <stdio.h> typedef struct Node
{struct Node* next;struct Node* prev;int val;
}LNode,*PNode;//引用版本
void PushBack(PNode& phead, int x)//為指針取別名,plist指針的別名是phead
{phead = newnode;
}int main()
{PNode plist = NULL;PushBack(plist,0);return 0;
}
  • LNode: 是?struct Node?的別名(當你使用?LNode?時,就相當于使用了?struct Node)

  • PNode: 是結構體類型的指針struct Node*的別名

C++百分之八十的場景都在使用引用,剩下的才會用指針

引用的價值

做形參

????????原來我們在交換兩個變量的時候,向Swap函數中傳遞的是地址,形參是實參的拷貝。實參必

須傳遞的是地址,否則交換后的結果無法傳遞回去,有了別名后就不需要傳遞地址了,形參可以直

接寫成實參的別名即可,對于別名的修改就相當于原來的實參的修改:

#include <iostream>
using namespace std;//原版本,傳遞地址
void Swap(int* left, int* right)
{int tmp;tmp = *left;*left = *right;*right = tmp;
}int main()
{int i = 10;int j = 20;Swap(&i, &j);cout << i << endl;cout << j << endl;return 0;
}//現版本,引用
void Swap(int& left, int& right)
{int tmp = left;left = right;right = tmp;
}int main()
{int i = 10;int j = 20;Swap(i, j);cout << i << endl;cout << j << endl;return 0;
}

從語法上來講,left和right是i和j的拷貝,int& left和int& right是i和j的別名

🤡從底層上來講,這里先不講,我不會🤡?

?傳值、傳引用的效率比較

??????? ?以值作為參數和返回值類型,在傳參和返回期間,函數不會直接傳遞實參或者將變量本身
直接返回,而是傳遞實參或者返回變量的一份臨時拷貝,因此用值作為參數或者返回值類型,效
率是非常低下的,尤其是當參數或者返回值類型非常大時,效率就更低:
#include <stdio.h>
#include <time.h>
#include <iostream>
using namespace std;struct A { int a[10000]; };
void TestFunc1(A a) {}
void TestFunc2(A& a) {}int main()
{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;return 0;
}

結論:引用做形參時是輸出型參數,且當對象較大時,減少拷貝提高效率

這些效果指針也可以實現,但是沒引用方便?

輸出型參數:傳遞給函數的參數數據,函數可以使用且能修改這些數據

輸入型參數:傳遞給函數的參數數據,函數可以使用但不能修改這些數據

做返回值

函數傳值返回

? ? ? ? main函數開辟了一塊棧幀,接著又調用了func函數,再開辟一塊幀棧,然后func返回a的

值并結束,然后main函數又想接收func函數的返回值a,但是返回值a已經在func函數結束后被銷

毀了,a空間中所存放的值已經不再被保證有效,所以此時ret得到是隨機(也有可能是原來的

值,這相當于你雞蛋碎在大街上了,你記著位置回去找,還能在地上找到蛋黃。如果等久一點,再

回去找,那就不一定能找到啥了,但是在大多數情況下,編譯器在棧幀銷毀時會將返回存儲在

寄存器中(對象比較小時)或者其他適當位(除了函數返回值之外,編譯器還可以選擇使用寄存

器來保存一些重要的局部變量或者臨時變量,以減少對內存的讀寫作)最后器中存放的原來返回值的值會交給ret

????????在函數結束后硬要把局部變量搞成一個隨機值是一件沒有意義的事情。只能說函數結束后局部變量的值【不再保證有效】

????????銷毀是需要花點力氣的,函數銷毀后,原來函數的那一片空間變成空閑區域,隨時會再次被使用。大部分情況下,你還能輸出,是因為沒人用到了那片空閑區域!然而機器也懶得去銷毀,重置內存也得費電。

函數傳引用返回(錯誤示范)

????????func函數的返回值是一個int&類型的引用,即返回值是局部變量a的引用(別名),而當函數

返回該引用時,a的值已經不再保證有效了(隨機值或者原值)

結論:函數傳值返回的是返回變量的拷貝,函數傳引用返回的是返回變量的引用(別名)

野引用(錯誤示范)

????????func函數返回的是a的引用(別名),那么ret就是a的引用的引用,而a的那片空間在func函

數銷毀時已經不再保證有效了,所以ret就是一個野引用(在程序運行過程中無法保證該內存空間

仍然有效或包含原始值(因為它可能已被其他數據覆蓋),因此稱之為野引用)

結論:返回變量(局部變量)出了函數作用域就被銷毀時,不能用引用返回(薛定諤的🐱)

引用的正常應用

全局變量、靜態變量、堆上分配對象等內容可以用引用返回:

  • 全局變量:全局變量在程序運行期間始終存在,因此可以安全地通過引用返回
  • 靜態變量:靜態變量也類似于全局常駐內存,在程序整個執行周期中都存在
  • 堆上分配對象:如果一個對象是通過?new?運算符在堆上動態分配內存創建的,則其生命周期由?new?和?delete?控制,并不受限于函數作用域
①int a = 10;
int& func()
{②static int a = 0;return a;
}int main()
{int& ret = func();cout << ret << endl;return 0;
}

堆上分配對象的例子我不會🤡

這是C語言(參雜了一點C++)實現順序表的簡化代碼:

#include <iostream>
#include <assert.h>
#include <stdio.h>using namespace std;struct SeqList
{int* a;int size;int capacity;
};//初始化
void SLInit(SeqList& sl)//利用引用了
{sl.a = (int*)malloc(sizeof(int) * 4);//... sl.size = 0;sl.capacity = 4;
}void SLPushBack(SeqList& sl, int x)
{//...(擴容)sl.a[sl.size++] = x;
}//修改
void SLModity(SeqList& sl, int pos, int x)
{assert(pos >= 0);assert(pos <= sl.size);sl.a[pos] = x;
}//獲取pos位置的值
int SLGet(SeqList& sl, int pos)
{assert(pos >= 0);assert(pos <= sl.size);return sl.a[pos];
}int main()
{SeqList s;//C++將stryct SeqList變為了類,所以可以直接用SeqListSLInit(s);SLPushBack(s, 1);SLPushBack(s, 2);SLPushBack(s, 3);SLPushBack(s, 4);for (int i = 0; i < s.size; i++){cout << SLGet(s,i) << " ";}cout << endl;for (int i = 0; i < s.size; i++){int val = SLGet(s,i);if (val % 2 == 0){SLModity(s, i, val * 2);}}cout << endl;for (int i = 0; i < s.size; i++){cout << SLGet(s, i) << " ";}cout << endl;return 0;
}

這是完全使用C++語法寫出的順序表代碼:

#include <iostream>
#include <assert.h>
#include <stdio.h>using namespace std;struct SeqList
{//成員變量int* a;int size;int capacity;//成員函數//C++的結構體(類)除了可以定義變量還可以定義函數void Init()//可以不寫前綴{a = (int*)malloc(sizeof(int) * 4);//... size = 0;capacity = 4;}void PushBack(int x){//...(擴容)a[size++] = x;}int& Get(int pos)//加上一個引用就可以代替原來的SLModify和SLGet兩個函數的作用{assert(pos >= 0);assert(pos <= size);return a[pos];//由于數組中pos位置的空間是由malloc開辟的,如果不主動釋放它就一直都在,所以即使函數銷毀,該空間也不會銷毀,該空間中的值也會被保留?}
};int main()
{SeqList s;//C++將stryct SeqList變為了類,所以可以直接用SeqLists.Init();s.PushBack(1);s.PushBack(2);s.PushBack(3);s.PushBack(4);for (int i = 0; i < s.size; i++){cout << s.Get(i) << " ";}cout << endl;//將滿足條件的數組pos位置的值進行修改for (int i = 0; i < s.size; i++){if (s.Get(i) % 2 == 0){s.Get(i) *= 2;}}cout << endl;for (int i = 0; i < s.size; i++){cout << SLGet(s, i) << " ";}cout << endl;return 0;
}
  • C語言:數據與函數分離,想要訪問數據就要將數據作為參數傳遞給函數
  • C++:數據和函數不分離,都處于一個類中(實際上也傳了編譯器做的但是現在沒學到)可以直接用(不需要再傳一個結構體類型的指針將存儲在結構體中的數據傳遞)

????????在這段代碼中我們不僅僅需要關注的是將原本放在外部的尾插、初始化等函數放在了結構體

,還需要注意在這里我們用Get一個函數就可以實現原來SLGet和SLModify兩個函數的作用(讀

寫pos位置的數據)這是因為C++規定臨時變量具有常性(特指存儲在寄存器中的變量,雖然

pos位置的值除非主動釋放否則不會銷毀,但是可以將該值拷貝一份放入寄存器中作為函數的返回

值),臨時變量默認被const修飾無法修改,如果在試圖修改pos位置的值時就會出現不可修改的

左值的報錯,但是即使C++沒有做出這一規定,我們仍然不能做到對數組pos位置的值進行修改,

因為臨時變量只是原來該位置數組的一個拷貝,對拷貝內容的修改無法影響到原來位置的數值,這

兩種情況都只能對pos位置的值進行讀取而不能進行修改,而當我們加上了一個引用,就可以實現

對pos位置的值的修改,此時Get函數返回的就不是一個臨時變量(函數返回引用不會產生臨時變

量),而是a[pos]的別名,至于誰的別名作者不知道就不做解釋,只需要此時我們既可以讀取到

pos位置的值也可以對該值進行修改?

????????完成對數組pos位置的值的修改還有一個前提就是數組pos位置存放值的空間在函數結束后仍然可以保證值得有效,否則就會出現傳引用返回(錯誤示范)中出現得值不保證有效的問題,對于這一點由于數組空間是由mallo開辟的,除非主動銷毀否則不會釋放,即使函數結束數組pos位置存放值得空間仍然存在,值仍然保證有效,所以可以放心引用該實體對象

值和引用作為返回值類型的性能比較

#include <iostream>
#include <time.h>
using namespace std;struct A { int a[10000]; };
A a;
// 值返回
A TestFunc1() { return a; }
// 引用返回
A& TestFunc2() { return a; }int main()
{// 以值作為函數的返回值類型size_t begin1 = clock();for (size_t i = 0; i < 100000; ++i)TestFunc1();size_t end1 = clock();// 以引用作為函數的返回值類型size_t begin2 = clock();for (size_t i = 0; i < 100000; ++i)TestFunc2();size_t end2 = clock();// 計算兩個函數運算完成之后的時間cout << "TestFunc1 time:" << end1 - begin1 << endl;cout << "TestFunc2 time:" << end2 - begin2 << endl;
}

結論:引用做返回值,可以修改和讀取返回對象,減少拷貝(臨時變量)提高效率

引用和指針的區別

一個東西在語法上表達得意思和底層實現它得方式是不一樣得(魚香肉絲里沒有🐟)

語法上

①、引用是別名,不開空間,指針是地址,需要開空間存地址

int main()
{int a = 10;int& ra = a;//引用語法上不開空間ra = 20;int* pa = &a;//指針語法上開空間*pa = 20;return 0;
}

②、引用必須初始化,指針可以初始胡也可以不初始化(所以指針更容易出現野的情況)

③、引用不可以改變指向,指針可以改變指向

④、引用相對安全,沒有空引用,但是有空指針,容易出現野指針,但是不容易出現野引用

⑤、在sizeof中含義不同引用結果為引用類型的大小,但指針始終是地址空間所占字節個數

⑥、引用自加即引用的實體增加1,指針自加即指針向后偏移一個類型的大小

⑦、有多級指針,但是沒有多級引用

⑧、訪問實體方式不同,指針需要顯式解引用,引用編譯器自己處理

⑨、引用比指針使用起來相對更安全

底層(匯編)上

對于①中的引用,在底層(匯編)上的情況是這樣的:

????????“003F2026 lea eax,[a]”:取a的地址放在eax寄存器,故引用在底層層面需要開空間

結論:

  • 引用底層是用指針實現的
  • 語法含義和底層實現是背離的(魚香肉絲沒有🐟)

匯編層面上,沒有引用,都是指針,引用編譯后也轉換成指針了

~over~

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/713482.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/713482.shtml
英文地址,請注明出處:http://en.pswp.cn/news/713482.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

spring-boot-starter-parent和spring-boot-dependencies介紹

springboot項目的pom文件中&#xff0c;我們經常看見這樣(下圖)兩種springboot的版本依賴管理方式&#xff1b;圖片中的這兩種依賴聲明方式任意用其中一種都可以。文章后面會簡單闡述一下區別和使用場景。 事例中完整的pom文件 <?xml version"1.0" encoding&quo…

阿爾卡特Adixen ADP/ADS 系列 2 干泵使用說明

阿爾卡特Adixen ADP/ADS 系列 2 干泵使用說明

HTML教程(3)——常用標簽(1)

一、圖片標簽 1.場景&#xff1a;在網頁中顯示圖片 2.基本寫法&#xff1a; <img src""> 3.特點&#xff1a;單標簽&#xff0c;img標簽需要展示對應的效果&#xff0c;需要借助其屬性進行設置 4常用屬性&#xff1a; src&#xff1a;其屬性值為目標圖片…

【框架】Spring 框架重點解析

Spring 框架重點解析 1. Spring 框架中的單例 bean 是線程安全的嗎&#xff1f; 不是線程安全的 Spring 框架中有一個 Scope 注解&#xff0c;默認的值是 singleton&#xff0c;即單例的&#xff1b;因為一般在 Spring 的 bean 對象都是無狀態的&#xff08;在生命周期中不被…

解決Mybatis報Type interface *.*Mapper is not known to the MapperRegis

解決Mybatis報Type interface *.*Mapper is not known to the MapperRegis 問題發現問題解決方法一&#xff1a;檢查Mapper文件的namespace路徑是否正確方法二&#xff1a;使用其他方法是否正確 問題發現 在學習MyBatis框架的時候&#xff0c;不使用 XML 構建 SqlSessionFacto…

字符串函數 sscanf() 詳解

什么是 sscanf() 函數&#xff1f; sscanf() 函數是 C 語言中的一個標準庫函數&#xff0c;它的作用是從一個字符串中按照指定的格式提取數據&#xff0c;并將其存儲到對應的變量中。它的原型如下&#xff1a; int sscanf(const char *str, const char *format, ...);其中&am…

Project_Euler-44 題解

Project_Euler-44 題解 題目 思路 題目給出了一個性質&#xff0c;讓我在對應性質的數據中找出目標值&#xff0c;這種問題首先想到的就是枚舉。 我們可以枚舉 P k P_k Pk? &#xff0c;對于每一個 P k P_k Pk? &#xff0c;我們再枚舉 P j P_j Pj?&#xff0c; P j P_…

【ue5】滑鏟系統藍圖筆記

大致邏輯如下&#xff1a; 一、導入動畫 滑鏟蹲待機蹲行走 導入到文件夾中 可以右鍵設置顏色&#xff0c;便于區分。 二、調整動畫 1.啟動根運動 啟動根運動后&#xff0c;人物才可以位移&#xff0c;不然只能在原地。 打開動畫序列&#xff0c;勾選啟用根運動Enabled…

用node或者vscode開啟一個簡單的本地server服務器,加載html網頁

使用Live Server 想要加載本地html頁面可以快速能讓它在你本地瀏覽器中打開&#xff0c;可以有好多種方式&#xff0c;如果你有使用vscode&#xff0c;可以安裝一個插件&#xff1a;Live Server&#xff0c;然后直接在vscode中直接右鍵就可以開啟這個服務&#xff1a; 安裝好之…

C++基于多設計模式下的同步異步日志系統day2

&#x1f4df;作者主頁&#xff1a;慢熱的陜西人 &#x1f334;專欄鏈接&#xff1a;C基于多設計模式下的同步&異步日志系統 &#x1f4e3;歡迎各位大佬&#x1f44d;點贊&#x1f525;關注&#x1f693;收藏&#xff0c;&#x1f349;留言 主要內容實現了日志代碼設計的實…

在 Spring Boot 3.x 中使用 SpringDoc 2 / Swagger V3

SpringDoc V1 只支持到 Spring Boot 2.x springdoc-openapi v1.7.0 is the latest Open Source release supporting Spring Boot 2.x and 1.x. Spring Boot 3.x 要用 SpringDoc 2 / Swagger V3, 并且包名也改成了 springdoc-openapi-starter-webmvc-ui SpringDoc V2 https://s…

select,poll和epoll有什么區別

它們都是NIO中多路復用的三種實現機制&#xff0c;是由linux操作系統提供的。 用戶空間和內核空間&#xff1a;操作系統為了保證系統安全&#xff0c;將內核分為兩個部分&#xff0c;一個是用戶空間&#xff0c;一個是內核空間。用戶空間不能直接訪問底層的硬件設備&#xff0…

IT廉連看——Uniapp——配置文件pages

IT廉連看——Uniapp——配置文件pages [IT廉連看] 本堂課主要為大家介紹pages.json這個配置文件 一、打開官網查看pages.json可以配置哪些屬性。 下面邊寫邊講解 新建一個home頁面理解一下這句話。 以下一些頁面的通用配置 通用設置里我們可以對導航欄和狀態欄進行一些設…

Android修行手冊-集成Python開發環境

Unity3D特效百例案例項目實戰源碼Android-Unity實戰問題匯總游戲腳本-輔助自動化Android控件全解手冊再戰Android系列Scratch編程案例軟考全系列Unity3D學習專欄藍橋系列ChatGPT和AIGC &#x1f449;關于作者 專注于Android/Unity和各種游戲開發技巧&#xff0c;以及各種資源分…

Debezium發布歷史161

原文地址&#xff1a; https://debezium.io/blog/2023/09/13/debezium-2-4-beta2-released/ 歡迎關注留言&#xff0c;我是收集整理小能手&#xff0c;工具翻譯&#xff0c;僅供參考&#xff0c;筆芯筆芯. Debezium 2.4.0.Beta2 Released September 13, 2023 by Chris Cranfo…

Apache Flink連載(三十五):Flink基于Kubernetes部署(5)-Kubernetes 集群搭建-1

?? 個人主頁:IT貧道-CSDN博客 ?? 私聊博主:私聊博主加WX好友,獲取更多資料哦~ ?? 博主個人B棧地址:豹哥教你學編程的個人空間-豹哥教你學編程個人主頁-嗶哩嗶哩視頻 目錄 ?編輯

Python爬蟲——Urllib庫-2

編解碼 問題引入 例如&#xff1a; https://www.baidu.com/s?wd章若楠 https://www.baidu.com/s?wd%E7%AB%A0%E8%8B%A5%E6%A5%A0 第二部分的一串亂碼就是章若楠 如果這里是寫的章若楠就會 產生這樣的錯誤 所以我們就可以使用get請求方式的quote方法了 get請求方式的q…

laravel ApiResponse接口統一響應封裝

一&#xff0c;新增接口返回碼配置文件 在config中新增配置文件apicode.php <?phpreturn [ apicodes>[/*** Message("OK")* 對成功的 GET、PUT、PATCH 或 DELETE 操作進行響應。也可以被用在不創建新資源的 POST 操作上*/HTTP_OK > 200,/*** Message(&qu…

使用el-form之表單校驗自動定位到報錯位置問題,,提升用戶體驗

需求描述 由于需要填寫的表單項太多&#xff0c;提交的時候校驗不通過&#xff0c; 如果沒填寫的表單項在最上面&#xff0c;用戶看不到不知道發生了啥&#xff0c; 所以需要將頁面滾動定位到第一個報錯的表單項位置&#xff0c;提升用戶體驗實現步驟 1. 給form表單添加ref …

數據中心GPU集群高性能組網技術分析

數據中心GPU集群組網技術是指將多個GPU設備連接在一起&#xff0c;形成一個高性能計算的集群系統。通過集群組網技術&#xff0c;可以實現多個GPU設備之間的協同計算&#xff0c;提供更大規模的計算能力&#xff0c;適用于需要大規模并行計算的應用場景。 常用的組網技術&…