C++:左值(引用)右值(引用)

〇、前言

本文會討論C++中的左值,右值,左值引用,右值引用,以及會理清它們之間的關系。

一、左值與右值

(一)概述

1. 左值是一般指表達式結束后依然存在的持久化對象。右值指表達式結束時就不再存在的臨時對象。便捷的判斷方法:能對表達式取地址、有名字的對象為左值。反之,不能取地址、匿名的對象為右值。

2. C++ 表達式(運算符帶上其操作數、字面量、變量名等)有兩種獨立的屬性:類型值類別 。類型指變量聲明時的類型,而值類別是表達式結果的類型,它必屬于左值、純右值或將亡值三者之一。如int&& x;其中 x 的類型為右值引用,但作為表達式使用時其值類別為左值(因為有名字,可以取址)。比如:

#include <iostream>
void func(int &&b) {std::cout << "b: " << b << " addr: " << &b << std::endl;b++;std::cout << b << std::endl;
}
int main() {int c = 10;func(std::move(c));std::cout << "c: " << c << " addr: " << &c << std::endl;return 0;
}

上例中的 b 就是一個右值引用,它接受了一個右值 c。在 main() 中,c 被轉化為右值引用傳入 func(),因此,func() 中的 b 其實就是 main() 中的 c 別名,它們指向的是同一塊內存區域。需要注意的是,func() 中的 b 其實是一個左值,換句話說,右值引用如果綁定了一個右值,它會延長這個右值的生命周期。 這種生命周期的延長意味著,盡管原始表達式產生的值是一個右值,一旦它被一個右值引用所綁定,它就不再是一個"即將銷毀的臨時值",而更像是一個普通的變量。 這允許開發者在保證效率的同時,也能夠更靈活地控制這些值。

運行結果:

g++ 1.cxx -o main -std=c++14
./main
b: 10 addr: 0x16ba16e28
11
c: 11 addr: 0x16ba16e28

我們需要注意的是,main() 中的 c 被轉換成了右值引用,但是它的狀態沒有發生任何改變。它在 func 中被修改,并在 func 返回后仍然保持有效。std::move 只是告訴編譯器一個對象可以被“安全地”當作右值來使用,這樣就允許在移動語義上下文中使用該對象。

(二)右值的分類

1. 純右值(prvalue):用于識別臨時變量和一些不與對象關聯的值。函數返回值為非引用類型、表達式臨時值(如1+3)、lambda表達式等。

2. 將亡值(xvalue):是與右值引用相關的表達式,通常指將要被移動的對象。如,函數返回類型為T&&std::move的返回值、轉換為T&&的類型轉換函數的返回值(注意,這些都是與右值引用相關的表達式)或臨時對象。

表格形式:

類型分類特征綁定規則和類型安全轉換實例和特殊情況
左值 (lvalue)存在變量、函數調用產生的對象,有固定地址。可綁定到左值引用,例如 int& x = a;變量 int a = 10; 函數調用(返回引用)例如 int& foo();
純右值 (prvalue)不適宜被移動,不具有可識別的地址。可綁定到右值引用或 const 左值引用,例如 int&& x = 5;字面量例如 42,表達式例如 a + b,函數調用(返回非引用)例如 int foo();
將亡值 (xvalue)適宜被移動,具有地址但對象即將被銷毀。可綁定到右值引用,例如使用 std::move 例如 int&& x = std::move(a);通過 std::move 生成的將亡值,例如 std::move(a);使用移動構造函數或移動賦值操作時,傳遞的實際參數。
泛左值 (glvalue)概括左值和將亡值,即擁有特定身份(地址)且可能即將被銷毀。可以綁定到左值引用或右值引用,取決于上下文。例如使用 decltype 捕獲引用時,decltype((a)) x = a; 捕獲的是 a 的引用。

二、左值引用與右值引用

(一)概述

1. 左值引用和右值引用都屬于引用類型。無論是聲明一個左值引用還是右值引用都必須立即進行初始化(今天考試剛考到)。

2. 左值引用都是左值。但具名的右值引用是左值,而匿名的右值引用是右值(比如上面的 func 中的 b,雖然它是右值引用,但是它是一個左值)。

(二)可綁定的值類型(設T是個具體類型)

1. 左值引用(T&):只能綁定到左值(非常量左值)

2. 右值引用(T&&):只能綁定到右值(非常量右值)

3. 常量左值引用(const T&):常量左值引用是個“萬能”的引用類型:它既可以綁定到左值也可以綁定到右值,它像右值引用一樣可以延長右值的生命期。不過相比于右值引用所引用的右值,常量左值引用的右值在它的“余生”中只能是只讀的。對于這點,可以參考這個例子:

#include <iostream>
void func(const int &b) {std::cout << "b: " << b << " addr: " << &b << std::endl;b++; // 會報錯std::cout << b << std::endl;
}
int main() {int c = 10;func(std::move(c));std::cout << "c: " << c << " addr: " << &c << std::endl;return 0;
}

編譯出錯:

g++ 2.cxx -o main -std=c++14
2.cxx:4:6: error: cannot assign to variable 'b' with const-qualified type 'const int &'b++;~^
2.cxx:2:22: note: variable 'b' declared const here
void func(const int &b) {~~~~~~~~~~~^
1 error generated.

4. 常量右值引用(const T&&):可綁定到右值或常量右值。由于移動語義需要右值可以被修改,因此常量右值引用沒有實際用處。如果需要引用右值且讓其不可更改,則常量左值引用就足夠了。

三、萬能引用(universal reference)

(一)T&&的含義

1. 當T是一個具體的類型時,T&& 表示右值引用,只能綁定到右值。

2. 當涉及T類型推導時,T&& 為萬能引用。若用右值初始化萬能引用,則T&&為右值引用。若用左值初始化萬能引用,則T&&為左值引用。但不管哪種情況,T&&都是一種引用類型

(二)萬能引用

1. T&&是萬能引用的兩個條件:

(1)必須涉及類型推導

(2)聲明的形式也必須正好形如 T&&并且該形式被限定死了,任何對其修飾都將剝奪T&&成為萬能引用的資格

2. 萬能引用使用的場景

(1)函數模板形參
    
(2)auto&&

一個例子:

#include <iostream>
#include <vector>using namespace std;class Widget {};void func1(Widget &&param){}; // param為右值引用類型(不涉及類型推導)template <typename T>
void func2(T &&param) {} // param為萬能引用(涉及類型推導)template <typename T>
void func3(std::vector<T> &&param) {
}template <typename T>
void func4(const T &&param) {}
template <class T> class MyVector {public:void push_back(T &&x) {} // x為右值引用。因為當定義一個MyVector對象后,T己確定。當調用該函數時T的類型不用再推導!template <class... Args>void emplace_back(Args&&...args){}; // args為萬能引用,因為Args獨立于T的類型,當調用該函數時,需推導Args的類型。
};int main() {Widget w;func2(w);            // 萬能引用, func2(T&& param),param為Widget&(左值引用)func2(std::move(w)); // 萬能引用, param為Widget&&,是個右值引用。int x = 0;Widget &&var1 = Widget(); // var1為右值引用(不涉及類型推導)auto &&var2 = var1; //萬能引用,auto&&被推導為Widget&(左值引用)auto &&var3 = x; //萬能引用,被推導為int&;(左值引用)// 3. 計算任意函數的執行時間:auto&&用于lambda表達式形參(C++14)auto timefunc = [](auto &&func, auto &&...params) {//計時器啟動//調用func(param...)函數std::forward<decltype(func)>(func)( //根據func的左右值特性來調用相應的重載&或&&版本的成員函數std::forward<decltype(params)>(params)... //保持參數的左/右值特性);//計時器停止并記錄流逝的時間};timefunc(func1, std::move(w)); //計算func1函數的執行時間return 0;
}

在你提供的代碼中,main 函數涉及到萬能引用的語句如下:

  1. func2(w);func2(std::move(w)); — 這里的 func2 函數模板參數 T&& 是一個萬能引用。它可以綁定到左值和右值。在這兩個調用中,第一次調用時 T 被推導為 Widget&(因為 w 是一個左值),而第二次調用時 T 被推導為 Widget(因為 std::move(w) 產生一個右值)。

  2. auto &&var2 = var1; — 這里使用了 auto&&,它也是一個萬能引用。var1 是一個左值(盡管它本身是一個綁定到臨時對象的右值引用),所以 var2 被推導為 Widget&

  3. auto &&var3 = x; — 這同樣使用了 auto&&,這是一個萬能引用。由于 x 是一個左值,var3 被推導為 int&

  4. timefunc lambda 表達式的定義中,參數列表 (auto &&func, auto &&...params) 使用了萬能引用。這里 auto&& 用于單個參數和參數包,允許這個 lambda 接受任意數量的任意類型的參數,并保持他們的值類別(左值或右值)。

這些例子展示了萬能引用在模板類型推導中的強大功能,尤其是在泛型編程和函數重載解析中的應用。通過萬能引用,可以寫出更靈活的函數和模板,使得它們能夠同時接受左值和右值,而無需重載函數。

四、參考

這里。

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

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

相關文章

334_C++_std::bind中使用shared_from_this()

std::bind(&HttpClient::getPwd, shared_from_this(), std::placeholders::_1, std::placeholders::_2);[ HttpClient繼承自NetObj,NetObj是父類,NetObj受到std::shared_pt

分析 vs2019 c++ 中的 decltype 與 declval

&#xff08;1&#xff09; decltype 可以讓推斷其參數的類型。按住 ctrl 點擊 decltype &#xff0c;會發現無法查閱 其定義 &#xff1a; &#xff08;2&#xff09; 但 STL 庫里咱們可以查閱函數 declval 的 定義&#xff0c;很短&#xff0c;摘抄如下&#xff1a; templat…

【氮化鎵】高溫GaN HEMTs大信號模型——ASM-HEMT

這篇文章的標題是《An ASM-HEMT for Large-Signal Modeling of GaN HEMTs in High-Temperature Applications》&#xff0c;由Nicholas C. Miller等人撰寫&#xff0c;發表于2023年9月29日。文章的主要內容是關于一種適用于高溫應用的GaN HEMTs&#xff08;高電子遷移率晶體管&…

Java 高級面試問題及答案1

Java 高級面試問題及答案 問題1: 請解釋Java中的垃圾回收機制&#xff0c;并描述其工作原理。 答案&#xff1a; Java中的垃圾回收&#xff08;Garbage Collection, GC&#xff09;是一種自動內存管理機制&#xff0c;用于識別和回收不再使用的對象&#xff0c;從而釋放內存資…

使用System.Drawing繪制基本幾何圖形

1.使用System.Drawing繪制一個正方形 using System; using System.Drawing; using System.Windows.Forms;public partial class MyForm : Form {public MyForm(){// 你可以在這里設置Form的雙緩沖&#xff0c;以避免繪制時出現的閃爍 this.DoubleBuffered true;}protected o…

LeetCode 每日一題 ---- 【1553.吃掉 N 個橘子的最少天數】

LeetCode 每日一題 ---- 【1553.吃掉 N 個橘子的最少天數】 1553.吃掉N個橘子的最少天數方法&#xff1a;記憶化搜索 1553.吃掉N個橘子的最少天數 方法&#xff1a;記憶化搜索 前兩天給樹澆水&#xff0c;原來澆的是橘子樹哇 今天直接來了個大的【困難】 class Solution {Ma…

Linux——緩沖區

一、問題引入 我們先來看看下面的代碼&#xff1a;我們使用了C語言接口和系統調用接口來進行文件操作。在代碼的最后&#xff0c;我們還使用fork函數創建了一個子進程。 #include<stdio.h> #include<stdlib.h> #include<unistd.h> #include<sys/wait.h&…

將jar打包成exe可安裝程序,并在html頁面喚醒

一、exe4j將jar打包成exe 1.exe4j下載 下載地址&#xff1a;https://www.ej-technologies.com/download/exe4j/files 2.exe4j打包jar 2.1. welcome 可以選擇歷史配置&#xff0c;新增則直接下一步 2.2. project type選擇“jar in exe” mode 2.3. application info設置應用…

【接口測試_03課_-接口自動化思維梳理及Requests庫應用】

一、通過代碼&#xff0c;實現Jmeter 1、項目要放在虛擬環境里面&#xff0c;解釋器要使用虛擬環境的 上面是虛擬環境&#xff0c;下面是系統環境。2選一 venv目錄 查看當前虛擬環境已存在的依賴包 2、安裝Requests依賴包 1&#xff09;安裝命令 pip install requests 如果…

防火墻技術的演進,什么是下一代防火墻(NGFW)?

防火墻技術的演進 防火墻技術的演進經歷了不同階段&#xff0c;從包過濾防火墻到狀態檢測防火墻&#xff0c;再到集成多種安全功能的UTM&#xff08;統一威脅管理&#xff09;設備&#xff0c;最終發展到具備應用識別能力的NGFW&#xff08;下一代防火墻&#xff09;。 包過濾…

DTAS 尺寸公差分析及尺寸鏈計算-建模神器 — 用戶DIY裝配

工業互聯網&#xff08;工業4.0) 是未來智能制造的核心&#xff0c;工業軟件是智能制造的靈魂。 相關工業軟件及系統的自主研發是智能制造和質量升級轉型亟需解決的卡脖子環節&#xff0c;而公差分析軟件系統是前期質量研發精準設計、降本增效的關鍵。 數字化時代&#xff0…

知了匯智副總經理趙懋駿出席“走進阿里”CEO聯席會,共話AI大模型新趨勢

在智能科技日新月異的今天&#xff0c;匯智知了堂副總經理趙懋駿于3月28日受邀出席了在天府軟件園舉行的“走進阿里–2024年CEO聯席會”&#xff0c;會議聚焦阿里云AI技術的最新進展與行業應用&#xff0c;特別是“AI技術正在加速變革&#xff1a;大模型的歷史、現在與趨勢”&a…

手撕C語言題典——環形鏈表的約瑟夫問題

目錄 前言 一.故事背景 二.題目 ?編輯三.思路 1&#xff09;數組 ?編輯2&#xff09; 循環鏈表 四.代碼實現 搭配食用更佳哦~~ 數據結構之單單單——鏈表-CSDN博客 數據結構之單鏈表的基本操作-CSDN博客 前面學了單鏈表的相關知識&#xff0c;我們來嘗試做一下關于…

centos 把nginx更新到最新版本

yum install epel-release # 添加 EPEL 軟件倉庫&#xff0c;這是 Nginx 官方軟件倉庫的依賴項 yum install yum-utils # yum-utils 包含了 yum-config-manager 工具&#xff0c;它可以讓您輕松地啟用、禁用或配置 yum 軟件倉庫 vi /etc/yum.repos.d/nginx.repo # 增加以下內容…

灌區信息化管理平臺系統包含哪些內容?(全面介紹)

政策背景 2022年12月29日&#xff0c;水利部啟動48處大中型灌區開展數字孿生灌區先行先試建設。 2023年2月24日&#xff0c;《2023年農村水利水電工作要點》:2023年農村水利水電工作的總體思路包括:緊盯保障國家糧食安全&#xff0c;加快推進大中型灌區現代化改造&#xff0c;…

Linux repo包安裝Nginx

Linux repo包安裝Nginx 1. 將nginx.repo 文件拷貝到 /etc/yum.repos.d 目錄2.找到原來的NGINX配置文件打包備份3.執行Nginx安裝命令4. 重啟 nginx -s reload5. 查看Nginx版本 1. 將nginx.repo 文件拷貝到 /etc/yum.repos.d 目錄 cp nginx.repo /etc/yum.repos.d2.找到原來的…

jQuery 入門:輕松創建與插入節點

在Web開發中&#xff0c;動態地創建和管理DOM&#xff08;文檔對象模型&#xff09;節點是一項基本且強大的技能。jQuery&#xff0c;作為JavaScript的一個流行庫&#xff0c;以其簡潔的API簡化了這一過程。本文將通過一個簡單的示例&#xff0c;介紹如何使用jQuery來創建新的D…

【力扣一輪】鏈表-刪除鏈表指定值的元素

刪除鏈表指定元素 力扣鏈接 代碼隨想錄題解 分為兩個版本&#xff0c;一個是帶有虛擬頭節點&#xff0c;一個是不帶。 無論是帶有還是不帶有&#xff0c;我都遇到了這幾個問題&#xff1a; ①while循環時的判斷&#xff0c;首先要判斷當前節點是否為空&#xff0c;接著才能…

bmi088-linux驅動(I2C)

電氣特性&#xff1a; 在正常工作時&#xff0c;gyro 工作電流為5mA&#xff0c;acc 工作電流為150uA。 SPI 時鐘和數據電平范圍 0 -3.6 結構框圖如下&#xff1a; 硬件連接圖如下&#xff1a; note&#xff1a; 1. 通過PS引腳選擇通訊協議&#xff0c;上拉引腳則選擇的是I2C…

系統定期執行命令的方法

系統定期執行命令的方法 一、進入超級用戶下 執行命令&#xff1a;sudo su 二、添加要執行的命令 例子&#xff1a;每天0點執行一次myapp.sh命令 先后輸入&#xff1a;crontab -e、 1、 回車 設置每天0點執行一次myapp.sh操作&#xff0c;需要寫絕對路徑 含義&#xff1…