C++中的引用傳參和指針傳參的區(qū)別小結(jié)
核心概念對(duì)比
| 概念 | C++ | 前端 (JS/TS) |
|---|---|---|
| 值傳遞 | 默認(rèn)方式,完整拷貝 | 基本類(lèi)型 (number, string等) |
| 引用傳遞 | 需顯式使用 & 或指針 | 對(duì)象、數(shù)組、函數(shù)等引用類(lèi)型 |
| 指針傳遞 | 傳遞內(nèi)存地址 | 類(lèi)似傳遞對(duì)象的引用 |
1. 值傳遞(默認(rèn))- 完整拷貝
#include <iostream>
using namespace std;
struct Point {
int x, y;
Point(int a, int b) : x(a), y(b) {
cout << "構(gòu)造: (" << x << "," << y << ")" << endl;
}
Point(const Point& p) : x(p.x), y(p.y) {
cout << "拷貝構(gòu)造: (" << x << "," << y << ")" << endl;
}
~Point() {
cout << "析構(gòu): (" << x << "," << y << ")" << endl;
}
};
// ? 值傳遞:發(fā)生完整拷貝
void byValue(Point p) {
p.x = 100; // 修改的是副本,不影響原對(duì)象
cout << "函數(shù)內(nèi): (" << p.x << "," << p.y << ")" << endl;
}
int main() {
Point p1(1, 2);
cout << "--- 調(diào)用函數(shù) ---" << endl;
byValue(p1);
cout << "--- 函數(shù)返回 ---" << endl;
cout << "原對(duì)象: (" << p1.x << "," << p1.y << ")" << endl; // 仍是 (1,2)
return 0;
}
輸出:
構(gòu)造: (1,2)
--- 調(diào)用函數(shù) ---
拷貝構(gòu)造: (1,2)
函數(shù)內(nèi): (100,2)
析構(gòu): (100,2)
--- 函數(shù)返回 ---
原對(duì)象: (1,2)
析構(gòu): (1,2)
?? 性能問(wèn)題:大對(duì)象會(huì)發(fā)生昂貴的深拷貝
2. 引用傳遞(類(lèi)似前端引用類(lèi)型)
// ? 引用傳遞:不拷貝,直接操作原對(duì)象(類(lèi)似前端的對(duì)象引用)
void byReference(Point& p) {
p.x = 100; // 直接修改原對(duì)象!
cout << "函數(shù)內(nèi): (" << p.x << "," << p.y << ")" << endl;
}
// ? const 引用:只讀訪問(wèn),不拷貝也不能修改
void byConstReference(const Point& p) {
// p.x = 100; // ? 編譯錯(cuò)誤:不能修改const引用
cout << "只讀訪問(wèn): (" << p.x << "," << p.y << ")" << endl;
}
int main() {
Point p1(1, 2);
cout << "=== 引用傳遞 ===" << endl;
byReference(p1);
cout << "原對(duì)象已被修改: (" << p1.x << "," << p1.y << ")" << endl; // (100,2)
cout << "\n=== const引用 ===" << endl;
byConstReference(p1);
return 0;
}
與前端對(duì)比:
// JavaScript - 對(duì)象天然是引用傳遞
function modify(obj) {
obj.x = 100; // 直接修改原對(duì)象
}
let p = {x: 1, y: 2};
modify(p);
console.log(p.x); // 100 ?
// C++ 必須顯式加 & 才能實(shí)現(xiàn)同樣效果
3. 指針傳遞(C風(fēng)格,更靈活但危險(xiǎn))
// 指針傳遞:傳遞內(nèi)存地址
void byPointer(Point* p) {
if (p != nullptr) { // 必須檢查空指針!
p->x = 200; // 使用 -> 訪問(wèn)成員
}
}
// 指針的const版本
void byConstPointer(const Point* p) {
// p->x = 200; // ? 不能修改
cout << p->x << endl; // ? 可以讀取
}
int main() {
Point p1(1, 2);
byPointer(&p1); // 必須取地址
cout << "修改后: (" << p1.x << "," << p1.y << ")" << endl;
// 可以傳 nullptr(這是與引用的重要區(qū)別)
byPointer(nullptr); // ? 合法
return 0;
}
引用 vs 指針對(duì)比:
| 特性 | 引用 & | 指針 * |
|---|---|---|
| 語(yǔ)法 | 更簡(jiǎn)潔,像原對(duì)象 | 需解引用 -> 或 * |
| 空值 | 不能為 null | 可以為 nullptr |
| 重新綁定 | 不能改指向其他對(duì)象 | 可以指向不同對(duì)象 |
| 安全性 | 更高(必須初始化) | 需檢查空指針 |
4. 返回值優(yōu)化(RVO)與移動(dòng)語(yǔ)義
傳統(tǒng)返回值(有拷貝)
// ? C++11前:返回時(shí)會(huì)發(fā)生拷貝(或編譯器RVO優(yōu)化)
Point createPoint() {
Point p(3, 4);
return p; // 理論上會(huì)拷貝,但編譯器通常優(yōu)化掉
}
// 接收時(shí)再次拷貝
Point p2 = createPoint();
現(xiàn)代C++:移動(dòng)語(yǔ)義(C++11起)
struct BigData {
int* data;
size_t size;
// 移動(dòng)構(gòu)造函數(shù)(轉(zhuǎn)移資源所有權(quán),不拷貝數(shù)據(jù))
BigData(BigData&& other) noexcept
: data(other.data), size(other.size) {
cout << "移動(dòng)構(gòu)造(偷資源)" << endl;
other.data = nullptr; // 置空源對(duì)象
other.size = 0;
}
// 移動(dòng)賦值
BigData& operator=(BigData&& other) noexcept {
cout << "移動(dòng)賦值" << endl;
if (this != &other) {
delete[] data;
data = other.data;
size = other.size;
other.data = nullptr;
}
return *this;
}
};
BigData createBigData() {
BigData bd(1000000); // 分配大量?jī)?nèi)存
return bd; // 觸發(fā)移動(dòng)語(yǔ)義,而非深拷貝!
}
int main() {
BigData bd1 = createBigData(); // 移動(dòng)構(gòu)造,幾乎零開(kāi)銷(xiāo)
BigData bd2;
bd2 = createBigData(); // 移動(dòng)賦值
return 0;
}
5. 最佳實(shí)踐總結(jié)
// ? 最佳實(shí)踐速查表 // 1. 小對(duì)象(int, double, 小結(jié)構(gòu)體):直接值傳遞 void f(int x, Point p); // 2. 大對(duì)象只讀訪問(wèn):const 引用(性能+安全) void process(const BigData& data); // 3. 需要修改原對(duì)象:非const引用 void modify(Point& p); // 4. 參數(shù)可選(可能為空):指針 void maybeProcess(BigData* data); // data可以是nullptr // 5. 轉(zhuǎn)移所有權(quán)(C++11):右值引用 void takeOwnership(BigData&& data); // 6. 返回值:優(yōu)先返回值(依賴(lài)RVO/移動(dòng)),而非輸出參數(shù) BigData create(); // ? 現(xiàn)代C++推薦 void create(BigData& out); // 舊式風(fēng)格,避免
類(lèi)比總結(jié)
// C++ 顯式控制傳遞方式 void f(Point p); // 值傳遞(拷貝) ← 像 JS 的 structuredClone void f(Point& p); // 引用傳遞(別名) ← 像 JS 的對(duì)象引用 void f(const Point& p); // const引用 ← 像 JS 的只讀引用 void f(Point* p); // 指針傳遞 ← 像 JS 的弱引用/可空引用 void f(Point&& p); // 右值引用(移動(dòng)) ← 像 JS 的 對(duì)象轉(zhuǎn)移/解構(gòu)賦值
關(guān)鍵區(qū)別:C++ 默認(rèn)是值語(yǔ)義(拷貝),必須顯式使用 & 才能獲得引用語(yǔ)義;而前端(JS)對(duì)象默認(rèn)就是引用語(yǔ)義。
以下深入剖析 C++ 中引用傳遞和指針傳遞的本質(zhì)差異。
核心對(duì)比表
| 維度 | 引用傳遞 T& | 指針傳遞 T* |
|---|---|---|
| 本質(zhì) | 別名(alias),必須綁定有效對(duì)象 | 內(nèi)存地址,可獨(dú)立存在 |
| 空值 | ? 不能為 null | ? 可以為 nullptr |
| 重新綁定 | ? 終身綁定,不能改指向 | ? 可隨時(shí)指向其他對(duì)象 |
| 語(yǔ)法 | 使用原對(duì)象語(yǔ)法 . | 需解引用 -> 或 * |
| 初始化 | 必須初始化 | 可以延遲初始化 |
| 內(nèi)存占用 | 通常優(yōu)化為無(wú)開(kāi)銷(xiāo) | 占用指針大?。?/8字節(jié)) |
本質(zhì)差異圖解
引用 (&) 指針 (*) ┌─────────┐ ┌─────────┐ │ 引用變量 │ ──→ 對(duì)象 │ 指針變量 │ ──→ 對(duì)象 │ (別名) │ (必須存在) │ (地址) │ 或 ──→ nullptr └─────────┘ └─────────┘ 語(yǔ)法糖,編譯器處理 真正的內(nèi)存實(shí)體
代碼層面深度對(duì)比
1. 基礎(chǔ)語(yǔ)法差異
#include <iostream>
using namespace std;
struct Config {
int port;
string host;
};
// ========== 引用版本 ==========
void setupByRef(Config& cfg) {
// 語(yǔ)法:直接使用對(duì)象
cfg.port = 8080;
cfg.host = "localhost";
// ? 不能檢查是否為空(假設(shè)一定存在)
// if (&cfg == nullptr) {} // 無(wú)意義,引用不能為null
}
// ========== 指針版本 ==========
void setupByPtr(Config* cfg) {
// 語(yǔ)法:必須先檢查空指針
if (cfg == nullptr) {
cerr << "錯(cuò)誤:配置為空" << endl;
return;
}
// 解引用方式1:->
cfg->port = 8080;
// 解引用方式2:*(獲取引用后再用.)
(*cfg).host = "localhost";
}
int main() {
Config c{0, ""};
// 引用調(diào)用:簡(jiǎn)單直接
setupByRef(c);
// 指針調(diào)用:必須取地址
setupByPtr(&c);
setupByPtr(nullptr); // ? 可以傳空,函數(shù)內(nèi)部處理
return 0;
}
2. 重新綁定能力(關(guān)鍵差異)
void demonstrateRebinding() {
int a = 10, b = 20;
// ========== 引用:終身綁定 ==========
int& ref = a;
ref = b; // ? 這不是讓ref指向b!而是把b的值賦給a
// 結(jié)果:a = 20, ref仍是a的別名
// 想改指向?不可能!
// &ref = b; // 編譯錯(cuò)誤!
// ========== 指針:靈活重定向 ==========
int* ptr = &a;
cout << *ptr; // 10
ptr = &b; // ? 隨時(shí)改指向
cout << *ptr; // 20
ptr = nullptr; // ? 可以指向空
}
3. 多級(jí)間接訪問(wèn)
// 指針可以指向指針,引用不行
void pointerChain() {
int x = 42;
int* p = &x;
int** pp = &p; // ? 指針的指針
int*** ppp = &pp; // ? 可以無(wú)限套娃
cout << ***ppp; // 42
// 引用只有一級(jí)(引用的引用被折疊)
int& r = x;
// int&& rr = r; // 這是右值引用,不是引用的引用!
}
本質(zhì)差異:編譯器視角
// 源代碼
void byRef(int& r) { r = 10; }
void byPtr(int* p) { *p = 10; }
int main() {
int x = 5;
byRef(x);
byPtr(&x);
}
編譯后的偽代碼:
; 引用版本(編譯器優(yōu)化后,與指針相同)
byRef:
mov eax, [esp+4] ; 取地址(與指針一樣?。?
mov dword [eax], 10 ; 解引用賦值
; 指針版本
byPtr:
mov eax, [esp+4] ; 取地址
test eax, eax ; ? 檢查空指針(編譯器可能優(yōu)化掉)
je .null_handler
mov dword [eax], 10
; 結(jié)論:引用是指針的語(yǔ)法糖,但編譯器保證非空
本質(zhì)真相:引用在底層就是指針,但編譯器添加了非空約束和自動(dòng)解引用的語(yǔ)法糖。
適用場(chǎng)景詳解
? 引用傳遞適用場(chǎng)景
// 場(chǎng)景1:參數(shù)必須存在(業(yè)務(wù)邏輯要求)
void connectDatabase(const ConnectionConfig& config) {
// 配置必須提供,不存在"無(wú)配置"的情況
// 無(wú)需檢查空,簡(jiǎn)化代碼
}
// 場(chǎng)景2:操作符重載(只能用引用)
class Vector {
public:
// 必須返回引用才能鏈?zhǔn)劫x值:v1 = v2 = v3
Vector& operator=(const Vector& other) {
// ...
return *this; // 解引用返回引用
}
// 下標(biāo)運(yùn)算符
int& operator[](size_t index) {
return data[index]; // 必須返回引用才能:v[0] = 10
}
};
// 場(chǎng)景3:函數(shù)式編程風(fēng)格(明確所有權(quán))
void sort(vector<int>& data); // 修改原數(shù)據(jù)
void print(const vector<int>& data); // 只讀訪問(wèn)
? 指針傳遞適用場(chǎng)景
// 場(chǎng)景1:參數(shù)可選(可為空)
void logMessage(const string* prefix = nullptr) {
if (prefix) {
cout << "[" << *prefix << "] ";
}
cout << "日志內(nèi)容" << endl;
}
// 調(diào)用:logMessage(); 或 logMessage(&debugTag);
// 場(chǎng)景2:動(dòng)態(tài)內(nèi)存管理(所有權(quán)轉(zhuǎn)移)
void processLargeFile(unique_ptr<FileData>* filePtr) {
// 可能轉(zhuǎn)移所有權(quán),或釋放內(nèi)存
if (filePtr && *filePtr) {
auto data = std::move(*filePtr); // 接管所有權(quán)
// filePtr 現(xiàn)在指向空unique_ptr
}
}
// 場(chǎng)景3:C接口兼容/多態(tài)數(shù)組
void drawShapes(Shape** shapes, size_t count) {
// 指針數(shù)組,支持運(yùn)行時(shí)多態(tài)
for (size_t i = 0; i < count; ++i) {
shapes[i]->draw(); // 虛函數(shù)調(diào)用
}
}
// 場(chǎng)景4:需要重新指向(迭代、遍歷)
Node* findNode(Node* head, int value) {
Node* current = head; // 從head開(kāi)始
while (current) {
if (current->val == value) return current;
current = current->next; // ? 指針可以遍歷
}
return nullptr;
}
// 如果用引用:Node& current = head; current = ... 會(huì)修改原h(huán)ead!
現(xiàn)代C++的最佳實(shí)踐
// ?? 參數(shù)傳遞決策樹(shù) // 1. 小對(duì)象(<= 2個(gè)指針大?。褐祩鬟f void f(int x, Point p); // 2. 只讀大對(duì)象:const 引用(首選) void process(const BigData& data); // 3. 必須修改原對(duì)象:非const 引用 void modify(Config& cfg); // 4. 參數(shù)可選/可為空:指針 void render(const Camera* cam = nullptr); // 5. 轉(zhuǎn)移所有權(quán)(C++11):右值引用 void consume(vector<int>&& data); // 6. 動(dòng)態(tài)數(shù)組/多態(tài):智能指針 void loadModel(shared_ptr<Mesh> mesh); // 共享所有權(quán) void takeData(unique_ptr<Buffer> buffer); // 獨(dú)占所有權(quán)
一句話(huà)總結(jié)
引用是"承諾存在的別名",指針是"可能為空的地址"。
用引用表達(dá)契約(必須存在),用指針表達(dá)可選性(可能為空)。
到此這篇關(guān)于C++中的引用傳參和指針傳參的區(qū)別小結(jié)的文章就介紹到這了,更多相關(guān)C++ 引用傳參和指針傳參內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
C語(yǔ)言實(shí)現(xiàn)BMP轉(zhuǎn)換JPG的方法
這篇文章主要介紹了C語(yǔ)言實(shí)現(xiàn)BMP轉(zhuǎn)換JPG的方法,涉及C#圖片格式轉(zhuǎn)換的相關(guān)技巧,具有一定參考借鑒價(jià)值,需要的朋友可以參考下2015-07-07
C語(yǔ)言代碼實(shí)現(xiàn)學(xué)生成績(jī)管理系統(tǒng)
這篇文章主要為大家詳細(xì)介紹了C語(yǔ)言代碼實(shí)現(xiàn)學(xué)生成績(jī)管理系統(tǒng),文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-06-06
詳解C語(yǔ)言中的內(nèi)存四區(qū)模型及結(jié)構(gòu)體對(duì)內(nèi)存的使用
這篇文章主要介紹了C語(yǔ)言中的內(nèi)存四區(qū)模型及結(jié)構(gòu)體對(duì)內(nèi)存的使用,包括結(jié)構(gòu)體中內(nèi)存泄漏情況的注意點(diǎn)提醒,需要的朋友可以參考下2016-03-03
c語(yǔ)言:基于函數(shù)指針的兩個(gè)示例分析
本篇文章是對(duì)c語(yǔ)言中函數(shù)指針的兩個(gè)示例做了詳細(xì)的分析介紹,需要的朋友參考下2013-05-05
詳解C++中如何將構(gòu)造函數(shù)或析構(gòu)函數(shù)的訪問(wèn)權(quán)限定為private
這篇文章主要介紹了詳解C++中如何將構(gòu)造函數(shù)或析構(gòu)函數(shù)的訪問(wèn)權(quán)限定為private的方法,文中還解釋了構(gòu)造函數(shù)與虛函數(shù)的區(qū)別,需要的朋友可以參考下2016-03-03
C++?OpenCV技術(shù)實(shí)戰(zhàn)之身份證離線識(shí)別
OpenCV身份證離線識(shí)別技術(shù)的主要技術(shù)就是通過(guò)OpenCV找到身份證號(hào)碼區(qū)域,然后通過(guò)OCR進(jìn)行數(shù)字識(shí)別該區(qū)域的截圖即可得到身份證號(hào)碼。感興趣的可以了解一下2021-12-12

