左值引用和右值引用

一、基本概念

  1. 左值(lvalue)和右值(rvalue)

    • 左值指的是有確定存儲位置(地址)的對象,通常可以出現在賦值語句左側。例如:變量名、解引用指針得到的對象、數組元素等都屬于左值。

    • 右值一般指臨時對象或字面常量,通常沒有固定的存儲地址,只能出現在賦值語句右側。例如:字面量(42"hello")、表達式求值產生的臨時結果(a + b、函數返回的非引用類型)等都屬于右值。

  2. 左值引用(lvalue reference)T&

    • 語法:T&

    • 含義:引用一個左值,必須綁定到一個具有名字且可尋址的對象上。

    • 作用:可以通過引用直接操作原對象,不產生拷貝;常用于函數參數(接收可修改的實參)或延長臨時對象的生命周期(使用 const T&)。

  3. 右值引用(rvalue reference)T&&(C++11 引入)

    • 語法:T&&

    • 含義:引用一個右值,只能綁定到臨時對象(比如函數返回的非引用類型對象、字面量或者std::move之后的結果)。

    • 作用:為“移動語義(move semantics)”和“完美轉發(perfect forwarding)”提供基礎,通過接收將要被銷毀的臨時對象,可以“竊取”其內部資源而不是拷貝。


二、左值引用與右值引用的區別

特性左值引用 T&右值引用 T&&
可綁定的對象只能綁定到 左值(命名變量等)只能綁定到 右值(臨時對象、字面常量、std::move產生的中間值)
是否可修改可修改所引用的對象可以修改所引用的臨時對象(臨時對象本來就要銷毀)
延長生命周期const T& 可延長臨時對象生命周期T& 不能綁定臨時對象綁定臨時對象后,可操作臨時,直到其生命周期結束
主要用途傳遞可修改的已有對象,避免拷貝實現移動語義、完美轉發,減少不必要的深度拷貝
  • 左值引用 T&

    • 只能引用已有的命名對象。

    • 用途:

      1. 函數參數接收時,可以直接修改傳入的實參(例如 void foo(int& x))。

      2. 避免拷貝開銷(例如 void print(const std::string& s))。

      3. const T& 可以綁定到右值,用于只讀訪問且延長臨時對象生命周期。

  • 右值引用 T&&

    • 只能引用臨時對象(右值)。

    • 用途:

      1. 移動構造/移動賦值:從臨時對象“竊取”內部資源,而不做深拷貝。

      2. 完美轉發:在模板中,通過 T&&(與 std::forward<T>(…))保持函數參數的值類別(左值/右值)不變。

      3. 禁止綁定左值:直接傳遞命名對象到 T&& 參數會編譯錯誤,除非顯式使用 std::move


三、典型示例

下面通過幾個示例來直觀說明它們的使用場景和區別。

1. 直接綁定示例

#include <iostream>
#include <string>int main() {int  a = 10;            // a 是左值int& lref = a;          // 左值引用只能綁定到左值// int& lref2 = 20;     // 錯誤:不能把 int& 綁定到字面量 20(右值)上int&& rref = 20;        // 右值引用只能綁定到右值// int&& rref2 = a;     // 錯誤:不能把 int&& 綁定到左值 a 上std::string s = "Hello";std::string& ls = s;    // 合法,引用已有 std::string 對象const std::string& lsc = "World"; // const std::string& 可以綁定到右值 "World",臨時字符串的生命周期延長至 lsc 作用域結束std::string&& rs = std::string("Temp"); // 右值引用綁定到了臨時 std::string("Temp"),可在后續對 rs 進行修改std::cout << "a: " << a << "\n";              // a: 10std::cout << "lref: " << lref << "\n";        // lref: 10std::cout << "rref: " << rref << "\n";        // rref: 20std::cout << "ls: " << ls << "\n";            // ls: Hellostd::cout << "lsc: " << lsc << "\n";          // lsc: Worldstd::cout << "rs: " << rs << "\n";            // rs: Tempreturn 0;
}
  • int& lref = a;:左值引用 lref 綁定到左值 a

  • int&& rref = 20;:右值引用 rref 綁定到字面量 20(右值)。

  • const std::string& lsc = "World";const T& 可以綁定到右值(臨時字符串),并將其生命周期延長至 lsc 的作用域結束。

  • std::string&& rs = std::string("Temp");:右值引用 rs 綁定到臨時字符串對象,此時可以對該臨時對象進行修改(不過它最終會析構)。


2. 作為函數參數的區別

通常我們會看到如下重載示例,用以區分傳入的是左值還是右值:

#include <iostream>// 重載 1:接受左值引用
void process(int& x) {std::cout << "process(int&): 接收左值引用, x = " << x << "\n";
}// 重載 2:接受右值引用
void process(int&& x) {std::cout << "process(int&&): 接收右值引用, x = " << x << "\n";
}int main() {int a = 5;process(a);      // 調用 process(int&), 因為 a 是左值process(10);     // 調用 process(int&&), 因為 10 是右值process(std::move(a)); // std::move(a) 將 a 轉換成右值, 因此調用 process(int&&)return 0;
}
  • 當實參是一個命名變量a),它是左值,因此會匹配 void process(int&)

  • 當實參是一個字面量臨時表達式(如 10std::move(a)),它們是右值,因此會匹配 void process(int&&)


3. 移動構造 vs 拷貝構造

以一個簡單的自定義類 MyString 為例,演示移動構造函數與拷貝構造函數的區別。

#include <iostream>
#include <cstring>class MyString {
public:char* data;// 構造函數:從 C 風格字符串構造MyString(const char* s) {size_t len = std::strlen(s);data = new char[len + 1];std::strcpy(data, s);std::cout << "構造 MyString(\"" << data << "\")\n";}// 拷貝構造:深拷貝MyString(const MyString& other) {size_t len = std::strlen(other.data);data = new char[len + 1];std::strcpy(data, other.data);std::cout << "調用 拷貝構造, 源 = \"" << other.data << "\"\n";}// 移動構造:竊取指針,避免拷貝MyString(MyString&& other) noexcept {data = other.data;         // 直接竊取資源指針other.data = nullptr;      // 將源置空,避免析構時釋放兩次std::cout << "調用 移動構造\n";}// 析構函數~MyString() {if (data) {std::cout << "析構 MyString(\"" << data << "\")\n";delete[] data;} else {std::cout << "析構 MyString(nullptr)\n";}}
};MyString makeString() {MyString temp("臨時");return temp; // C++11 以后可進行移動構造而非拷貝
}int main() {MyString s1("Hello");       // 普通構造MyString s2 = s1;           // 拷貝構造MyString s3 = makeString(); // 由于 makeString 返回臨時對象,可觸發移動構造(或編譯器優化)// 可以強制使用移動構造:MyString s4 = std::move(s1);return 0;
}
  • 調用 MyString s2 = s1; 時,實參 s1 是一個左值,因此調用的是 拷貝構造,會分配新內存并拷貝字符串內容。

  • 調用 MyString s3 = makeString(); 時,makeString() 返回的臨時對象是一個右值,因此會優先調用 移動構造(如果編譯器沒有做完全優化)。移動構造只會“竊取”臨時對象的內部 char* 指針,而不會再做深拷貝。

  • s1 應用 std::move(s1),即把左值 s1 強制轉換為右值,再傳遞給 MyString s4 = std::move(s1);,也會調用移動構造,將 s1 的內部指針轉移給 s4,此時 s1.data 置為 nullptr


4. 完美轉發(Perfect Forwarding)示例

在模板中,如果想讓函數“如實”地將傳入參數的值類別(左值/右值)傳給另一個函數,可以使用右值引用和 std::forward。示例:

#include <iostream>
#include <utility> // std::forward, std::movevoid target(int& x) {std::cout << "target(int&) 被調用,x = " << x << "\n";
}void target(int&& x) {std::cout << "target(int&&) 被調用,x = " << x << "\n";
}// 通用轉發函數模板
template <typename T>
void wrapper(T&& param) {// 直接傳給 target,但保留 param 本身的值類別// 如果原參數是左值,就調用 target(int&); 如果是右值,就調用 target(int&&)target(std::forward<T>(param));
}int main() {int a = 100;wrapper(a);            // a 是左值 -> 調用 target(int&)wrapper(200);          // 200 是右值 -> 調用 target(int&&)int&& rr = 300;wrapper(rr);           // rr 本身雖然聲明為右值引用,但 rr 名稱是左值 -> 調用 target(int&)wrapper(std::move(rr)); // std::move(rr) 是右值 -> 調用 target(int&&)return 0;
}
  • wrapper(a)a 是左值,模板參數 T 被推斷為 int&T&& 變為 int& &&,折疊后仍為 int&,所以 param 是左值引用,std::forward<T>(param) 仍是左值,調用 target(int&)

  • wrapper(200)200 是右值,T 被推斷為 intT&&int&&param 是右值引用。std::forward<int>(param) 是右值,調用 target(int&&)

  • int&& rr = 300; 聲明了一個右值引用 rr,但 rr 本身是一個命名變量(左值)。所以:

    • wrapper(rr):傳入 rr(是左值),和 wrapper(a) 類似,依然調用 target(int&)

    • wrapper(std::move(rr))std::move(rr)rr 強制轉換為右值,調用 target(int&&)


四、總結

  1. 綁定限制

    • T& 只能綁定到左值,不能綁定到右值(除非加上 const,即 const T& 可以綁定到右值,用于只讀)。

    • T&& 只能綁定到右值,不能直接綁定到左值(除非對左值使用 std::movestd::forward 強制轉換為右值)。

  2. 主要用途

    • 左值引用 T&:主要用于接收已有對象并進行讀取/修改,避免拷貝開銷。

    • 右值引用 T&&:主要用于實現“移動語義”,在可以銷毀的臨時對象上直接竊取資源;同時在模板中用于“完美轉發”以保留參數的值類別。

  3. 性能意義

    • 使用 T&& 實現移動構造/移動賦值,可以顯著減少對大對象(如容器、字符串等)的深拷貝,從而提升性能。

    • std::forward<T>T&& 配合可以讓模板函數在轉發參數時不丟失值類別,避免不必要的拷貝或移動。

  4. 注意事項

    • 雖然 T&& 只能綁定右值,但當 T 已推斷為引用類型(如 int&)時,T&& 會出現“引用折疊”(reference collapsing)現象:

      • 如果 TU&,那么 T&& 等價于 U&

      • 如果 TU,那么 T&& 就是 U&&

    • 在函數重載中,若同時存在 void f(int&)void f(int&&),傳入的實參的值類別會直接影響調用哪個重載。

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

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

相關文章

django入門-orm數據庫操作

一&#xff1a;下載數據庫依賴項mysqlclient pip install mysqlclient 二&#xff1a;django配置文件配置數據庫鏈接 路徑&#xff1a;mysite2\mysite2\settings.py DATABASES {default: {ENGINE: django.db.backends.mysql,NAME: data, # 數據庫名稱USER: root, …

國標GB28181設備管理軟件EasyGBS視頻平臺筑牢文物保護安全防線創新方案

一、方案背景? 文物作為人類文明的珍貴載體&#xff0c;具有不可再生性。當前&#xff0c;盜竊破壞、游客不文明行為及自然侵蝕威脅文物安全&#xff0c;傳統保護手段存在響應滯后、覆蓋不全等局限。隨著5G與信息技術發展&#xff0c;基于GB28181協議的EasyGBS視頻云平臺&…

NetSuite Bundle - Dashboard Refresh

兒童節快樂&#xff01; 今朝發一個Bundle&#xff0c;解決一個NetSuite Dashboard的老問題。出于性能上的考慮&#xff0c;NetSuite的Dashboard中的Portlet&#xff0c;只能逐一手工刷新。有人基于瀏覽器做了插件&#xff0c;可以進行自動刷新。但是在我們做項目部署時&#…

<PLC><socket><西門子>基于西門子S7-1200PLC,實現手機與PLC通訊(通過websocket轉接)

前言 本系列是關于PLC相關的博文,包括PLC編程、PLC與上位機通訊、PLC與下位驅動、儀器儀表等通訊、PLC指令解析等相關內容。 PLC品牌包括但不限于西門子、三菱等國外品牌,匯川、信捷等國內品牌。 除了PLC為主要內容外,PLC相關元器件如觸摸屏(HMI)、交換機等工控產品,如…

【AI論文】推理語言模型的強化學習熵機制

摘要&#xff1a;本文旨在克服將強化學習擴展到使用 LLM 進行推理的主要障礙&#xff0c;即策略熵的崩潰。 這種現象在沒有熵干預的RL運行中一直存在&#xff0c;其中策略熵在早期訓練階段急劇下降&#xff0c;這種探索能力的減弱總是伴隨著策略性能的飽和。 在實踐中&#xff…

手動刪除網頁上的禁止復制事件

以Edge瀏覽器為環境、以網絡上一個文檔為例。 右擊頁面&#xff0c;打開【檢查】工具。選擇元素&#xff0c;打開【事件偵聽器】&#xff1a; 展開copy&#xff0c;刪除里面的事件&#xff1a; 選中文字&#xff0c;進行復制

element級聯地址選擇器

一、實現過程總覽 組件替換&#xff1a;將原有的輸入框&#xff08;el-input&#xff09;替換為級聯選擇器&#xff08;el-cascader&#xff09;&#xff0c;并配置基礎屬性。數據適配&#xff1a;引入 JSON 地址數據&#xff0c;通過cascaderProps映射數據字段&#xff08;如…

【leetcode-兩數之和】

一、題目 二、題解 &#xff08;1&#xff09;雙指針 這里要注意j<length的 //聲明兩個指針int length nums.length;for (int i 0; i < length; i) {for (int j i 1; j<length;j){if (j<length && nums[i]nums[j]target){return new int[]{i,j};}}}r…

CLion社區免費后,使用CLion開發STM32相關工具資源匯總與入門教程

Clion下載與配置 Clion推出社區免費&#xff0c;就是需要注冊一個賬號使用&#xff0c;大家就不用去找破解版版本了&#xff0c;jetbrains家的IDEA用過的都說好&#xff0c;這里嵌入式領域也推薦使用。 CLion官網下載地址 安裝沒有什么特別&#xff0c;下一步就好。 啟動登錄…

Rust 學習筆記:關于 Cargo 的練習題

Rust 學習筆記&#xff1a;關于 Cargo 的練習題 Rust 學習筆記&#xff1a;關于 Cargo 的練習題問題一問題二問題三問題四問題五問題六問題七 Rust 學習筆記&#xff1a;關于 Cargo 的練習題 參考視頻&#xff1a; https://www.bilibili.com/video/BV1xjAaeAEUzhttps://www.b…

高速收發器

一、高速收發器 1.FPGA高速收發器&#xff1a;GTP,GTX,GTH,GTZ 2.每個Quad有4對高速收發器GT(4個TX和4個RX)和一個COmmon 3.走差分&#xff0c;提高抗干擾性 4.CPLL是每個lane私有的&#xff0c;QPLL是整個Quad的所有通道共享的 5.每個MGT的bank有兩對差分參考時鐘 6.CPLL的時鐘…

Rust 變量與可變性

文章目錄 變量與可變性常量遮蔽&#xff08;Shadowing&#xff09; 變量與可變性 Rust中變量默認是不可變的&#xff0c;這是 Rust 鼓勵你編寫更安全、易于并發代碼的眾多方式之一。不過&#xff0c;你仍然可以選擇讓變量可變。讓我們來探討 Rust 為什么鼓勵你優先使用不可變性…

sourcetree無法獲取遠程所有的tag

2025年5月29日11:30:17 sourcetree widnwos v3.4.23版本 突然發現線上的代碼庫里有很多新打的tag&#xff0c;但是sourcetree死活無法拉去所有的tag&#xff0c;嘗試卸載重新安裝也不行&#xff0c;全網也找了還是不知道&#xff0c;但是mac版本好像沒有這個問題 方法1&…

《深度探索C++對象模型》閱讀筆記(完整版)

《深度探索C對象模型》閱讀筆記&#xff08;完整版&#xff09; 文章目錄 《深度探索C對象模型》閱讀筆記&#xff08;完整版&#xff09;1. 關于對象&#xff08;Object Lessons&#xff09;1.1 C對象模型&#xff08;The C Object Model&#xff09;1.1.1 語言中的對象模型1.…

從Docker拉取鏡像一直失敗超時解決辦法

項目場景&#xff1a; 在ubuntu中&#xff0c;使用docker拉去鏡像時&#xff0c;一直超時&#xff0c;拉去失敗。 問題描述 原因分析&#xff1a; 國外服務器網絡不好導致。 解決方案&#xff1a; 解決方案1 設置國內源 我這邊測試&#xff0c;更改以后仍然失敗 阿里云提供…

KONG根據請求參數限流

背景 價格接口 /search 同時支持緩存查價和實時查價&#xff0c;主要通過searchType字段區分這兩種請求。 searchType 為空時為緩存查價&#xff0c;QPS很高。searchType 不為空時為實時查價&#xff0c;但QPS遠低于普通查價。 如果直接對該接口限流&#xff0c;當流量波動超…

通俗易懂解析:@ComponentScan 與 @MapperScan 的異同與用法

在 Spring 和 MyBatis 集成開發中&#xff0c;ComponentScan 和 MapperScan 是兩個核心注解&#xff0c;但它們的用途和工作機制截然不同。本文將通過通俗的語言和示例代碼&#xff0c;帶您輕松掌握它們的區別和使用方法。 一、基礎概念 ComponentScan&#xff1a;Spring 的“通…

39. 自動化異步測試開發之編寫異步業務函數、測試函數和測試類(函數寫法)

39. 自動化異步測試開發之編寫異步業務函數、測試函數和測試類&#xff08;函數寫法&#xff09; 一、異步業務函數解析 1.1 頁面導航函數 async def get(async_driver, url: str http://secure.smartbearsoftware.com/samples/testcomplete12/WebOrders/Login.aspx):await…

Qt 無邊框窗口實現拖動與窗口控制(最小化/最大化/關閉)

在 Qt 中&#xff0c;使用 Qt::FramelessWindowHint 可以創建無邊框窗口&#xff0c;但這樣會導致窗口無法拖動&#xff0c;并且系統默認的標題欄按鈕&#xff08;最小化、最大化、關閉&#xff09;也會消失。本文將介紹如何實現無邊框窗口的鼠標拖動功能&#xff0c;并添加自定…

Linux中的System V通信標準-共享內存、消息隊列以及信號量

在Linux系統中&#xff0c;System V IPC&#xff08;Inter-Process Communication&#xff09;提供了一系列進程間通信的機制&#xff0c;包括共享內存、消息隊列和信號量。這些機制在系統中發揮了重要作用&#xff0c;幫助進程之間進行數據交換和同步。本文將詳細介紹這些機制…