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

史上最全 C/C++ 指針避坑指南:八年老鳥整理的 20 個(gè)致命錯(cuò)誤

開發(fā)
指針就是個(gè)地址,搞清楚這個(gè)地址指向哪,什么時(shí)候有效,什么時(shí)候無效,基本就能避免大多數(shù)問題了。?

大家好,我是小康,一個(gè)在 C++ 的坑里摸爬滾打了 8 年的開發(fā)者。今天我要和大家聊聊那些讓每個(gè)程序員都頭疼的指針錯(cuò)誤。

寫了這么久C++,指針還是經(jīng)常讓你頭大?代碼莫名其妙崩潰,調(diào)試半天發(fā)現(xiàn)是指針出問題?面試官隨便問個(gè)指針問題就把你問懵了?

放心,不是你一個(gè)人!今天我們就用最通俗的語言,聊聊 C++ 指針那些"坑"。

記得我剛開始學(xué)習(xí)的時(shí)候,光是看到 int *p 這樣的代碼就覺得腦袋瓜子嗡嗡的。但是,指針這個(gè)東西吧,就像自行車,一旦掌握了要領(lǐng),那騎起來就是享受!今天我就把這些年踩過的坑都給大家分享出來,保證說人話,不說教科書!

錯(cuò)誤一:野指針-這是個(gè)沒拴繩的野狗??!

int* p;  // 聲明一個(gè)指針,但沒有初始化
*p = 10; // 完蛋,這就是傳說中的野指針!

這就好比你養(yǎng)了條狗,但是沒給它栓繩子,它想跑哪跑哪,最后把鄰居家的花園給禍禍了...

正確做法是啥? 要么給它一個(gè)合法的地址,要么直接給 nullptr:

int* p = nullptr;  // 現(xiàn)代C++推薦用nullptr
// 或者
int x = 5;
int* p = &x;

錯(cuò)誤二:忘記刪除堆內(nèi)存 - 這是在浪費(fèi)資源??!

void leakMemory() {
    int* p = new int(42);
    // 函數(shù)結(jié)束了,但是忘記delete
}  // 內(nèi)存泄漏!這塊內(nèi)存永遠(yuǎn)要不回來了

這就像你上廁所占了個(gè)坑,但是用完不沖水就走了,后面的人都沒法用了。正確的做法是:

void noLeak() {
    int* p = new int(42);
    // 用完了記得delete
    delete p;
    p = nullptr;  // 刪除后最好置空
}

更好的辦法是直接用智能指針,這就相當(dāng)于給廁所裝了個(gè)自動(dòng)沖水裝置:

#include <memory>
void modern() {
    auto p = std::make_unique<int>(42);
    // 函數(shù)結(jié)束會(huì)自動(dòng)釋放內(nèi)存,不用操心
}

錯(cuò)誤三:解引用空指針 - 這不是自己給自己挖坑嗎?

int* p = nullptr;
*p = 100;  // 程序崩潰!這就像試圖往一個(gè)不存在的盒子里放東西

在使用指針之前,一定要檢查:

int* p = nullptr;
if (p != nullptr) {
    *p = 100;
} else {
    std::cout << "哎呀,指針是空的,可不能用!" << std::endl;
}

錯(cuò)誤四:delete指針后繼續(xù)使用 - 這是在玩火??!

int* p = new int(42);
delete p;  // 釋放內(nèi)存
*p = 100;  // 災(zāi)難!這塊內(nèi)存已經(jīng)不屬于你了

這就像你退了房租,但還硬要住在人家房子里,這不是找打嗎?

正確做法:

int* p = new int(42);
delete p;
p = nullptr;  // 刪除后立即置空
// 后面要用需要重新分配
p = new int(100);

錯(cuò)誤五:數(shù)組使用單個(gè)delete刪除 - 這是在瞎搗亂??!

int* arr = new int[10];
delete arr;    // 錯(cuò)!這是在用單個(gè)delete刪除動(dòng)態(tài)數(shù)組

數(shù)組要用delete[ ]:

int* arr = new int[10];
delete[] arr;  // 對!這才是刪除動(dòng)態(tài)數(shù)組的正確姿勢
arr = nullptr;

錯(cuò)誤六:指針運(yùn)算越界 - 這是要翻車的節(jié)奏!

int arr[5] = {1, 2, 3, 4, 5};
int* p = arr;
for(int i = 0; i <= 5; i++) {  // 錯(cuò)!數(shù)組只有5個(gè)元素
    cout << *p++ << endl;  // 最后一次訪問越界了
}

正確做法:

int arr[5] = {1, 2, 3, 4, 5};
int* p = arr;
for(int i = 0; i < 5; i++) {  // 對!只訪問有效范圍
    cout << *p++ << endl;
}

錯(cuò)誤七:返回局部變量的指針 - 這是在玩火!

int* getLocalPtr() {
    int x = 42;
    return &x;  // 危險(xiǎn)!x是局部變量,函數(shù)結(jié)束就沒了
}

這就像你要借別人的東西,但是人家已經(jīng)搬家了,你上哪借去?

正確做法:

int* getSafePtr() {
    int* p = new int(42);
    return p;  // 返回堆內(nèi)存的指針
}
// 或者更好的做法
std::unique_ptr<int> getSaferPtr() {
    return std::make_unique<int>(42);
}

錯(cuò)誤八:指針類型不匹配 - 強(qiáng)扭的瓜不甜啊!

double d = 3.14;
int* p = &d;  // 錯(cuò)!類型不匹配

正確做法:

double d = 3.14;
double* p = &d;  // 對!類型要匹配

錯(cuò)誤九:多重指針不打基礎(chǔ) - 這是在疊積木不打底!

int** pp;  // 指向指針的指針
*pp = new int(42);  // 危險(xiǎn)!底下一塊積木都沒放就想往上疊

正確的搭法:

// 一層一層來,穩(wěn)穩(wěn)當(dāng)當(dāng)
int* p = new int(42);     // 先放好底層積木
int** pp = &p;            // 再往上疊一塊
cout << **pp << endl;     // 現(xiàn)在這積木穩(wěn)當(dāng),可以安全使用了

記住:多重指針就像搭積木,得從底層開始,一層一層穩(wěn)妥地往上搭,跳著搭就容易倒塌!

錯(cuò)誤十:const 和指針的位置擺錯(cuò) - 這是在挖坑自己跳??!

最常見的三種指針和const組合:

int value = 10, other = 20;

// 三種基本組合
const int* p1 = &value;      // ? *p1 = 100;    ? p1 = &other;
int* const p2 = &value;      // ? *p2 = 100;    ? p2 = &other;
const int* const p3 = &value;// ? *p3 = 100;    ? p3 = &other;

常見錯(cuò)誤:

void onlyRead(int* const data) {   // 錯(cuò)誤用法!
    *data = 100;   // 竟然能改值!
    data = &other; // 這個(gè)才報(bào)錯(cuò)
}

void onlyRead(const int* data) {   // 正確用法!
    *data = 100;   // 編譯報(bào)錯(cuò),保護(hù)數(shù)據(jù)不被修改
    data = &other; // 允許改變指向
}

記憶技巧:

  • const int* : const 在 * 左邊,鎖住值
  • int* const : const 在 * 右邊,鎖住指向
  • 要保護(hù)數(shù)據(jù)不被改,就用 const int*

錯(cuò)誤十一:構(gòu)造函數(shù)漏初始化指針 - 這是在埋定時(shí)炸彈啊

class MyClass {
    int* ptr;
public:
    MyClass() {
        // 完蛋,忘記初始化ptr了
    }
};  // 使用ptr時(shí)可能崩潰

正確做法:

class MyClass {
    int* ptr;
public:
    MyClass() : ptr(nullptr) {  // 構(gòu)造時(shí)就初始化
        // 或者分配內(nèi)存
        ptr = new int(42);
    }
};

錯(cuò)誤十二:函數(shù)參數(shù)傳遞指針沒聲明const - 這是在裸奔啊!

// 下面這種寫法,數(shù)據(jù)像裸奔一樣毫無保護(hù)
void printData(int* data) {  
    cout << *data << endl;  // 雖然只是讀數(shù)據(jù),但是沒人知道??!
}

正確做法:

// 加個(gè)const,數(shù)據(jù)就穿上了防護(hù)服
void printData(const int* data) {  
    cout << *data << endl;
}

記?。褐皇亲x數(shù)據(jù)不修改時(shí),一定要加const!不加const就像把數(shù)據(jù)扔在大馬路上,誰都能改。

錯(cuò)誤十三:指針移動(dòng)導(dǎo)致內(nèi)存釋放失敗 - 這是在玩火!

int* p = new int[5];
for(int i = 0; i < 5; i++) {
    cout<<*p<<endl;
    p++;  // 完蛋,循環(huán)結(jié)束后p已經(jīng)不指向數(shù)組起始位置了
}
delete[] p;  // 錯(cuò)誤!p已經(jīng)移動(dòng)了

正確做法:

int* p = new int[5];
int* temp = p;  // 用臨時(shí)指針做移動(dòng)
for(int i = 0; i < 5; i++) {
    cout<<*temp<<endl;
    temp++;
}
delete[] p;  // 正確!p還在起始位置

錯(cuò)誤十四:指針和引用混用 - 這是在給自己找麻煩!

void func(int*& ptr) {  // 指針的引用,看著就頭大
    ptr = new int(42);
}

更清晰的做法:

std::unique_ptr<int>& func() {  // 返回智能指針的引用
    static auto ptr = std::make_unique<int>(42); // 返回 static 對象
    return ptr;
}

錯(cuò)誤十五:不安全的指針向下轉(zhuǎn)換 - 這是在蠻干??!

class Base {};
class Derived : public Base {};

Derived* d = new Derived();
Base* b = d;        // 向上轉(zhuǎn)換,安全
Derived* d2 = b;    // 錯(cuò)誤!向下轉(zhuǎn)換需要 dynamic_cast

正確做法:

Derived* d2 = dynamic_cast<Derived*>(b);  // 安全的向下轉(zhuǎn)換
if( d2 != nullptr ) {  // 檢查轉(zhuǎn)換是否成功
    // 使用d2
}

錯(cuò)誤十六:函數(shù)指針調(diào)用前未檢查 - 這是在冒險(xiǎn)?。?/h4>
// 錯(cuò)誤示例
void (*fp)(int) = nullptr;
fp(42);  // 災(zāi)難!沒檢查就直接調(diào)用

// 或者更糟的情況
void (*fp)(int);  // 未初始化就使用
fp(42);  // 更大的災(zāi)難!

正確做法:

void (*fp)(int) = nullptr;  // 明確初始化為nullptr
// 或者賦值一個(gè)具體函數(shù)
void foo(int x) { cout << x << endl; }
fp = foo;

// 使用前檢查
if(fp!=nullptr) {
    fp(42);  // 安全!
} else {
    cout << "函數(shù)指針無效" << endl;
}

錯(cuò)誤十七:在類里 delete this 指針 - 簡直是自殺!

// 錯(cuò)誤示例
class Player {
public:
    int score;
public:    
    void killSelf() {
        delete this;       // 自己把自己刪了
    }
};

Player* player = new Player();
player->killSelf();      // 這下好了,后面的代碼都懸了
resetGame();      // 慘!死人也想重開一局

正確的做法:

class Player {
    // 方法1:讓外面的代碼來管理生命周期
    void cleanup() {
        score = 0;
        // 只做清理工作,不要自己刪自己
    }
};

// 外部代碼負(fù)責(zé)刪除
Player* player = new Player();
player->cleanup();  // 先清理
delete player;      // 再刪除
player = nullptr;   // 最后置空

// 方法2:更現(xiàn)代的方式 - 使用智能指針
class Player {
    // 類里面該做啥做啥,不用操心刪除的事
};

// 讓智能指針來管理生命周期
auto player = make_shared<Player>();
// 不用管刪除,超出作用域自動(dòng)清理

記住:

  • 在類的方法里刪除 this指針就像自殺,死了還想干活那肯定不行
  • 對象的生命周期最好交給外部代碼或智能指針管理
  • 如果非要在類里面刪除自己,那刪完就立即返回,別做其他操作

錯(cuò)誤十八:智能指針互相引用 - 這是在手拉手繞圈圈!

循環(huán)引用示例:

// 錯(cuò)誤示例:兩個(gè)朋友互相拉手不放
class Student {
    shared_ptr<Student> bestFriend;  // 我有個(gè)好朋友
public:
    void makeFriend(shared_ptr<Student> other) {
        bestFriend = other;  // 我拉著我朋友
    }
};

// 兩個(gè)學(xué)生互相成為好朋友
auto tom = make_shared<Student>();
auto jerry = make_shared<Student>();
tom->makeFriend(jerry);    // tom拉住jerry
jerry->makeFriend(tom);    // jerry也拉住tom
// 完蛋!他們互相拉著對方不放手,
// 即使放學(xué)了也走不了(內(nèi)存不能釋放)

正確的做法:

// 正確示例:一個(gè)人拉手,一個(gè)人輕拉
class Student {
    weak_ptr<Student> bestFriend;  // 用weak_ptr,不牢牢抓住對方
public:
    void makeFriend(shared_ptr<Student> other) {
        bestFriend = other;  // 輕輕拉住朋友就好
    }
};

auto tom = make_shared<Student>();
auto jerry = make_shared<Student>();
tom->makeFriend(jerry);    
jerry->makeFriend(tom);    
// 現(xiàn)在好了,放學(xué)后可以松手回家了(正常釋放內(nèi)存)

記?。?/p>

  • 兩個(gè)對象用shared_ptr互相引用,就像兩個(gè)人死死拉住對方的手不放,誰都走不了
  • 要解決這個(gè)問題,讓一方改用weak_ptr,就像輕輕牽手就好,需要的時(shí)候隨時(shí)可以松開
  • 智能指針循環(huán)引用會(huì)導(dǎo)致內(nèi)存泄漏,就像兩個(gè)人一直拉著手,永遠(yuǎn)不能回家

注意:智能指針的循環(huán)引用很容易把人繞暈,我用兩張手繪小圖,帶大家一步步理解這個(gè)過程:

循環(huán)引用圖解:

說明:智能指針對象 tom 和 jerry 的引用計(jì)數(shù)值 count 都變成 2,導(dǎo)致在 main 程序退出時(shí),各自的 count 都無法減為 0 ,從而造成內(nèi)存泄漏。

使用 weak_ptr 避免循環(huán)引用:

說明:tom 和 jerry 的引用計(jì)數(shù)值 count 始終都是 1,main 程序退出時(shí),各自的 count 都減到 0 ,內(nèi)存正常釋放。

錯(cuò)誤十九:指針成員的深淺拷貝 - 很容易翻車!

class Resource {
    int* data;
public:
    Resource() { data = newint(42); }
    ~Resource() { delete data; }
    
    // 默認(rèn)拷貝構(gòu)造函數(shù)和賦值運(yùn)算符會(huì)導(dǎo)致災(zāi)難
    // Resource(const Resource& other) = default;  // 淺拷貝!
    // Resource& operator=(const Resource& other) = default;  // 淺拷貝!
};

void disasterExample() {
    Resource r1;
    Resource r2 = r1;    // 淺拷貝:r1和r2的data指向同一內(nèi)存
    // 函數(shù)結(jié)束時(shí),r1和r2都會(huì)delete同一個(gè)data!程序崩潰
}

正確做法:

class Resource {
    int* data;
public:
    Resource() { data = newint(42); }
    ~Resource() { delete data; }
    
    // 實(shí)現(xiàn)深拷貝
    Resource(const Resource& other) {
        data = newint(*other.data);  // 復(fù)制數(shù)據(jù)本身
    }
    Resource& operator=(const Resource& other) {
        if (this != &other) {
            delete data;
            data = newint(*other.data);
        }
        return *this;
    }
    
    // 或者更好的方案:使用智能指針
    // unique_ptr<int> data;  // 禁止拷貝
    // shared_ptr<int> data;  // 共享所有權(quán)
};

人人都知道要深拷貝,但實(shí)際寫代碼時(shí)很容易忽略,尤其是在類有多個(gè)指針成員時(shí)?,F(xiàn)代 C++ 建議優(yōu)先使用智能指針來避免這類問題。

錯(cuò)誤二十:函數(shù)內(nèi)修改指針實(shí)參 - 這是在玩障眼法!

// 錯(cuò)誤示例
void resetPointer(int* ptr) {
    ptr = nullptr;  // 以為這樣就能把外面的指針置空
}

int* p = new int(42);
resetPointer(p);    // 調(diào)用函數(shù)
cout << *p;         // 糟糕!p根本沒變成nullptr,還在指向原來的地方

正確做法:

// 方法1:使用指針的指針
void resetPointer(int** ptr) {  // 傳入指針的地址
    *ptr = nullptr;  // 現(xiàn)在可以修改原始指針了
}

int* p = newint(42);
resetPointer(&p);   // 傳入p的地址
// 現(xiàn)在p確實(shí)被置空了

// 方法2:使用引用
void resetPointer(int*& ptr) {  // 使用指針的引用
    ptr = nullptr;
}

int* p = newint(42);
resetPointer(p);    // p會(huì)被置空

記?。?/p>

  • 函數(shù)參數(shù)是傳值的,修改指針形參不會(huì)影響外面的指針
  • 要修改外部指針,必須傳入指針的指針
  • 這個(gè)問題在做指針操作時(shí)特別常見,很多人都會(huì)犯這個(gè)錯(cuò)

實(shí)戰(zhàn)小貼士

(1) 優(yōu)先使用智能指針

// 不推薦
MyClass* ptr = new MyClass();
// 推薦
unique_ptr<MyClass> ptr = make_unique<MyClass>();

(2) 指針安全法則

  • 用完指針及時(shí)置空 nullptr
  • 分配內(nèi)存后立即考慮釋放的時(shí)機(jī)和方式
  • 涉及指針的函數(shù),第一步就是檢查指針是否為 nullptr
  • 使用智能指針時(shí),要注意循環(huán)引用

(3) 關(guān)于指針和引用的選擇:

// 需要修改指針指向時(shí),必須傳遞指針
void updatePtr(int*& ptr); // 通過引用修改指針 - 這種情況很少見
void updatePtr(int** ptr); // 通過指針修改指針 - 更常見的做法

// 只需要訪問或修改指針指向的數(shù)據(jù)時(shí)
void process(const int* ptr);  // 不修改數(shù)據(jù)時(shí)用const
void modify(int* ptr);

(4) 代碼規(guī)范建議

// 指針聲明時(shí)緊跟類型
int* ptr;  // 推薦
int *ptr;  // 不推薦

// 多重指針超過兩層就要考慮重構(gòu)
int*** ptr;  // 需要重新設(shè)計(jì)

// const的一致性
void process(const std::string* data);  // 參數(shù)不修改就用const

總結(jié)

看完這些指針的坑,是不是覺得其實(shí)也沒那么可怕?記住一點(diǎn):指針就是個(gè)地址,搞清楚這個(gè)地址指向哪,什么時(shí)候有效,什么時(shí)候無效,基本就能避免大多數(shù)問題了。

責(zé)任編輯:趙寧寧 來源: 跟著小康學(xué)編程
相關(guān)推薦

2024-04-03 12:30:00

C++開發(fā)

2018-01-20 20:46:33

2025-04-27 00:04:00

C#異步編程

2025-03-20 12:00:00

C++this指針匯編

2023-11-22 13:22:51

C++函數(shù)

2024-08-26 08:29:55

2022-03-04 18:11:16

信服云

2023-11-01 15:32:58

2011-04-11 11:09:50

this指針

2021-06-18 12:30:36

C++函數(shù)指針編程語言

2021-12-21 15:31:10

C++語言指針

2022-01-23 14:29:25

C語言編程語言

2010-02-02 15:01:59

C++成員函數(shù)指針

2024-05-15 16:01:04

C++編程開發(fā)

2024-07-03 12:04:42

C++this?

2014-01-24 09:49:01

C++指針

2020-06-12 11:03:22

Python開發(fā)工具

2010-01-26 13:42:28

C++指針

2021-02-26 00:46:11

CIO數(shù)據(jù)決策數(shù)字化轉(zhuǎn)型

2025-02-24 00:10:00

點(diǎn)贊
收藏

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