自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

別再寫出會(huì)爆炸的代碼了!這才是 C++ 引用的正確打開方式

開發(fā)
在這篇指南中,我們將通過學(xué)習(xí)如何正確返回引用和指針,揭示常見的陷阱以及如何避免它們。

你是否曾經(jīng)遇到過這樣的情況:代碼看起來完全正確,但程序卻莫名其妙地崩潰了? 或者更糟 - 程序看似正常運(yùn)行,卻時(shí)不時(shí)出現(xiàn)一些詭異的行為? 很可能你遇到了 C++ 中最臭名昭著的問題之一:懸空引用(dangling references) 。

在這篇指南中,我們將:

  • 通過生動(dòng)的故事講解危險(xiǎn)的局部引用
  • 學(xué)習(xí)如何正確返回引用和指針
  • 揭示常見的陷阱以及如何避免它們
  • 掌握編寫安全代碼的最佳實(shí)踐
  • 掌握實(shí)用的調(diào)試技巧

讓我們開始這段充滿驚險(xiǎn)的探索之旅吧! 

危險(xiǎn)的局部引用:一個(gè)驚心動(dòng)魄的故事

想象一下,你正在寫一個(gè)溫馨的小故事程序...

class Story {
    string content_;    // ?? 存儲(chǔ)故事的內(nèi)容
public:
    // ?? 構(gòu)造新的故事
    Story(string text) : content_(text) {} 
    
    // ?? 安全地讀取故事內(nèi)容
    // 使用 const 修飾確保故事內(nèi)容不會(huì)被修改 ??
    string getContent() const { return content_; }
};

突然有一天,你天真爛漫地寫下了這樣的代碼:

Story* createMagicStory() {
    // ??? 在棧上創(chuàng)建一個(gè)臨時(shí)的故事對(duì)象
    Story localStory{"從前有座山..."}; 
    
    // ?? 危險(xiǎn)操作!返回棧上對(duì)象的地址
    // ?? 函數(shù)返回后 localStory 會(huì)被銷毀
    // ?? 這將導(dǎo)致懸空指針
    return &localStory;                 
} // ?? 噗!localStory 已經(jīng)消失在風(fēng)中...

哎呀!這就像是想用相機(jī)拍下肥皂泡,但等你按下快門時(shí)泡泡已經(jīng)破了 ??。當(dāng)函數(shù)返回時(shí),我們可憐的 localStory 就像童話里的南瓜馬車一樣消失不見了!

讓我們看看另一個(gè)同樣令人心碎的場(chǎng)景:

Story& getBestStory() {
    // ??? 在棧上創(chuàng)建臨時(shí)故事對(duì)象
    Story epicStory{"一個(gè)充滿 bug 的世界..."};
    
    // ?? 危險(xiǎn)操作!返回臨時(shí)對(duì)象的引用
    // ?? 函數(shù)結(jié)束時(shí) epicStory 會(huì)被銷毀
    // ?? 返回的引用將指向已經(jīng)不存在的對(duì)象
    // ?? 這會(huì)導(dǎo)致未定義行為
    return epicStory;    
} // ?? 轟!epicStory 已經(jīng)消失,但引用還在死死地指向那片虛無

這就像是你試圖用一張快遞單追蹤一個(gè)已經(jīng)送達(dá)的包裹 - 地址是對(duì)的,但包裹早就不在那里了!

拯救我們的故事:正確的方式

來看看如何讓我們的故事永遠(yuǎn)流傳 :

// 方式一:讓故事安全地飛向遠(yuǎn)方 ??
unique_ptr<Story> createSafeStory() {
    // ??? 在堆上創(chuàng)建新的故事對(duì)象
    // ?? 使用智能指針自動(dòng)管理內(nèi)存
    return make_unique<Story>("這是一個(gè)安全的故事~");
    
    // ? 函數(shù)結(jié)束時(shí):
    // ?? 故事對(duì)象安全地存儲(chǔ)在堆內(nèi)存中
    // ?? unique_ptr 負(fù)責(zé)管理對(duì)象的生命周期
    // ?? 直到最后一個(gè)使用者結(jié)束才會(huì)被銷毀
} 

就像給故事找了一個(gè)溫暖的家,它會(huì)一直住在那里,直到我們說再見。

或者更簡(jiǎn)單的方式:

// 方式二:直接返回故事的副本 ??
Story getStoryDirectly() {
    // ?? 創(chuàng)建一個(gè)新的故事對(duì)象
    // ?? 通過返回值優(yōu)化(RVO)避免不必要的拷貝
    return Story{"一個(gè)值得傳頌的故事"};
    
    // ? 函數(shù)結(jié)束時(shí):
    // ?? 故事內(nèi)容被安全復(fù)制
    // ?? 調(diào)用者獲得完整的故事副本
}

這就像是把故事刻在了石頭上,誰拿到都是完整的一份!

引用返回的妙用 - 來看看這些生活小場(chǎng)景

讓我們用一個(gè)簡(jiǎn)單的家庭住址簿來理解引用返回,保證讓你一看就懂! 

首先,來看看我們的住址簿類:

class AddressBook {
    vector<string> addresses_;  // ?? 存儲(chǔ)所有居民的地址簿
public:
    // ?? 安全地獲取指定位置的地址引用
    // ?? 因?yàn)榈刂反鎯?chǔ)在地址簿的vector中,所以返回引用是安全的
    // ?? index: 要查找的地址索引
    // ?? 返回: 對(duì)應(yīng)地址的引用,可以直接修改
    string& getAddress(size_t index) {
        // ??? 檢查索引是否越界
        Expects(index < addresses_.size());
        
        // ?? 返回地址引用 - 就像在實(shí)體地址簿上直接修改地址一樣
        return addresses_[index];
    }
};

這就像是在翻開一本實(shí)體地址簿 - 你直接看到的就是那個(gè)地址,而不是地址的復(fù)印件。很直觀吧? 

來看看如何使用:

void updateAddress(AddressBook& book) {
    // ?? 從地址簿中獲取第一個(gè)地址的引用
    // ?? 因?yàn)槭且?所以不會(huì)產(chǎn)生復(fù)制
    string& oldAddress = book.getAddress(0);      
    
    // ?? 直接修改地址內(nèi)容
    // ?? 因?yàn)槭且?所以修改會(huì)直接影響原始數(shù)據(jù)
    // ?? 更新為新的地址信息
    oldAddress = "新地址: 幸福小區(qū)88號(hào)";          
    
} // ?? 函數(shù)結(jié)束時(shí)地址簿保持更新后的狀態(tài)
  // ? 因?yàn)槲覀冃薷牡氖窃紨?shù)據(jù),所以更改會(huì)永久保存

但是!千萬不要這樣做 :

string& getTemporaryAddress() {
    // ?? 在棧上創(chuàng)建臨時(shí)地址字符串
    string addr = "臨時(shí)地址";    
    
    // ?? 危險(xiǎn)操作!返回棧上臨時(shí)變量的引用
    // ?? 函數(shù)返回后 addr 會(huì)被銷毀
    // ?? 返回的引用將指向已釋放的內(nèi)存
    // ?? 這會(huì)導(dǎo)致未定義行為
    return addr;               
    
} // ?? 噗!addr已經(jīng)消失在風(fēng)中...

這就像是把地址寫在便利貼上,等你要用的時(shí)候便利貼已經(jīng)被風(fēng)吹走了! 

再來看一個(gè)溫度計(jì)的例子:

class Thermometer {
    double current_temp_;    // ??? 存儲(chǔ)當(dāng)前溫度值
public:
    // ?? 安全的溫度讀取方法
    // ?? 返回溫度的常量引用,確保溫度值不會(huì)被修改
    // ?? 因?yàn)?current_temp_ 是類成員,所以返回其引用是安全的
    // ?? 返回: 當(dāng)前溫度的只讀引用
    const double& getCurrentTemp() const {
        return current_temp_;  // ?? 只允許查看溫度,不能修改
    }
    
    // ?? 溫度校準(zhǔn)方法
    // ?? 返回溫度的非常量引用,允許調(diào)整溫度值
    // ?? 用于校準(zhǔn)或修正溫度讀數(shù)
    // ?? 返回: 可修改的溫度引用
    double& calibrateTemp() {
        return current_temp_;  // ??? 可以調(diào)整溫度值
    }
};

使用起來就像這樣:

void checkTemperature() {
    Thermometer thermo;        // ??? 創(chuàng)建一個(gè)溫度計(jì)實(shí)例
    
    // ?? 獲取當(dāng)前溫度的只讀引用
    // ?? const引用確保溫度值不會(huì)被意外修改
    constdouble& temp = thermo.getCurrentTemp();  
    
    // ?? 獲取可調(diào)整的溫度引用
    // ?? 用于校準(zhǔn)溫度值
    double& adjustable = thermo.calibrateTemp();   
    
    // ?? 對(duì)溫度進(jìn)行補(bǔ)償調(diào)整
    // ?? 直接修改原始溫度值
    // ?? 因?yàn)槭褂靡?所以修改會(huì)直接影響溫度計(jì)中的實(shí)際值
    adjustable += 0.5;                            
} // ? 函數(shù)結(jié)束時(shí)溫度計(jì)保持校準(zhǔn)后的狀態(tài)

記住這些簡(jiǎn)單的原則:

  • 只返回那些確實(shí)存在的對(duì)象的引用(比如類的成員變量)
  • 像對(duì)待你的錢包一樣關(guān)注對(duì)象的生命周期
  • 需要只讀訪問時(shí),記得用 const

這樣,引用返回就不再可怕啦! 就像是給朋友指路 - 只要路還在,指向它就沒問題! 

常見陷阱大揭秘

啊哈!讓我們來看看 C++ 中最容易掉進(jìn)去的幾個(gè)可愛的"陷阱" :

1.Lambda 捕獲的小把戲

想象一下,你在寫一個(gè)可愛的小游戲,需要保存玩家的最高分:

// ?? 危險(xiǎn)示例:返回局部變量的指針
int* getHighScore() {
    // ?? 在棧上創(chuàng)建局部變量
    int score = 100;  // 創(chuàng)造了新紀(jì)錄!
    
    // ?? 創(chuàng)建一個(gè) lambda 表達(dá)式
    // ?? 危險(xiǎn):通過引用捕獲局部變量 score
    auto saveScore = [&]() { 
        return &score;  // ?? 返回局部變量的地址
    };
    
    // ?? 調(diào)用 lambda 并返回已經(jīng)失效的指針
    // ??? score 變量即將離開作用域被銷毀
    return saveScore();  
    
} // ?? 此時(shí) score 已被銷毀
// ?? 返回的指針變成了懸空指針

這就像是想用快門拍下彩虹 ??,等你按下快門時(shí)彩虹已經(jīng)消失不見了。正確的做法應(yīng)該是:

// ? 安全的做法:使用智能指針
shared_ptr<int> saveHighScoreSafely() {
    // ??? 在堆上創(chuàng)建數(shù)據(jù)
    // ?? 使用智能指針管理內(nèi)存
    return make_shared<int>(100);
    
    // ? 函數(shù)結(jié)束時(shí):
    // ?? 數(shù)據(jù)安全存儲(chǔ)在堆上
    // ?? 由 shared_ptr 管理生命周期
}

(2) 集合里的幽靈指針

再來看看這個(gè)經(jīng)典場(chǎng)景 - 想要收集可愛的小動(dòng)物名字:

// ?? 危險(xiǎn)示例:這是一個(gè)會(huì)導(dǎo)致未定義行為的代碼!
class PetCollection {
    vector<string*> pets;  // ?? 存儲(chǔ)指向字符串的指針(這是一個(gè)危險(xiǎn)的設(shè)計(jì))
public:
    void addPet() {
        // ?? 在棧上創(chuàng)建臨時(shí)字符串
        string kitty = "喵喵";       
        
        // ?? 嚴(yán)重錯(cuò)誤:存儲(chǔ)了棧上臨時(shí)變量的地址
        // ?? 當(dāng)函數(shù)返回時(shí),kitty 會(huì)被銷毀
        // ?? vector 中存儲(chǔ)的指針將變成懸空指針
        pets.push_back(&kitty);     
        
    } // ?? 到這里 kitty 已經(jīng)被銷毀了
      // ??? pets 中的指針指向了已釋放的內(nèi)存
};

這就像是用相機(jī)拍下了一只正在逃跑的貓咪 ??,等你再看照片的時(shí)候...咦?貓咪怎么不見了?

讓我們改成正確的方式:

// ? 正確的實(shí)現(xiàn)方式:
class SafePetCollection {
    vector<string> pets;  // ?? 直接存儲(chǔ)字符串,而不是指針
public:
    void addPet() {
        // ?? 創(chuàng)建新的字符串并存儲(chǔ)其副本
        // ?? 安全地將數(shù)據(jù)復(fù)制到 vector 中
        pets.push_back("喵喵");  
    }
};

記住這些可愛的小技巧:

  • 不要讓 lambda 捕獲局部變量的引用(除非你確定不會(huì)在變量消失后使用)
  • 容器里存儲(chǔ)實(shí)際的值而不是指針(除非你真的需要指針的特性)
  • 返回值優(yōu)先用值返回,需要指針時(shí)用智能指針
  • 如果一定要用引用,確保引用的對(duì)象生命周期足夠長(zhǎng)

這樣,你的代碼就會(huì)像一只訓(xùn)練有素的小貓咪,不會(huì)到處亂跑,也不會(huì)突然消失不見啦!

溫馨提示

(1) 永遠(yuǎn)不要返回棧上對(duì)象的指針或引用

(2) 注意函數(shù)返回值可能通過多種方式泄露局部變量:

  • 直接返回指針/引用
  • 通過輸出參數(shù)
  • 作為返回對(duì)象的成員
  • 作為返回容器的元素

(3) static 局部變量是例外,可以安全返回它們的指針/引用

(4) 使用現(xiàn)代 C++ 特性(智能指針、optional 等)來避免這些問題

(5) 如果需要返回大對(duì)象,考慮移動(dòng)語義而不是指針

讓我們的代碼更安全,遠(yuǎn)離懸空指針的困擾!

責(zé)任編輯:趙寧寧 來源: everystep
相關(guān)推薦

2021-11-25 07:43:56

CIOIT董事會(huì)

2021-11-10 16:03:42

Pyecharts Python可視化

2019-03-17 16:48:51

物聯(lián)網(wǎng)云計(jì)算數(shù)據(jù)信息

2022-08-16 08:33:06

DevOps實(shí)踐

2020-05-09 10:35:06

遞歸面試算法

2025-03-12 11:14:45

2018-10-29 15:20:03

2021-10-09 15:49:00

5G網(wǎng)絡(luò)技術(shù)

2021-06-07 10:05:56

性能優(yōu)化Kafka

2016-03-01 14:51:18

云計(jì)算DevOps

2016-01-08 11:00:14

OpenStack云計(jì)算

2019-02-20 14:35:57

區(qū)塊鏈數(shù)字貨幣比特幣

2022-03-22 07:37:04

FeignSpringRibbon

2023-07-10 09:38:06

兼容性測(cè)試方案

2017-08-02 10:43:39

深度學(xué)習(xí)TensorFlowRNN

2025-01-10 06:30:00

2022-06-22 09:06:54

CSS垂直居中代碼

2020-07-05 09:17:20

云桌面

2020-06-04 15:16:46

云計(jì)算

2021-06-21 09:36:44

微信語音轉(zhuǎn)發(fā)
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)