1、const 修飾普通變量

C/C++中常量用於記錄程序中不可更改的數據,在數據類型前加const,就得到了一個不可更改的常量。

  • 常量聲明const用於定義常量
const int MAX = 100;//MAX的值在程序運行期間無法被修改,所以必須在定義時初始化。

2、const與指針的組合

如果被const修飾的數據類型是指針,要關心下const的位置,const在*左邊,表示指向的數據是常量;const在*右邊,表示指針本身是常量。口訣:左定值,右定向

  • 常量指針:這種指針不能修改它所指向的內存中的值,但可以改變指針指向其他地址。可以用於函數參數,避免函數修改傳入的數據。
int a = 10;
int b = 20;
const int *ptr = &a; // 非常量指針轉換為指向常量的指針(轉換規則)
int const *ptrr; // 等價上面
// *ptr = 20;  // 錯誤,不能修改x的值
ptr = &b; // ok
// int* p = ptr; // 錯誤,指向常量的指針不能隱式轉換為非常量指針,需要const_cast
int* pp = const_cast<int*>(ptr); // ok,但使用需謹慎,
// const_cast只能去除指針或引用的const屬性,不能去除對象本身的const屬性
  • 指針常量:指針本身是常量,必須初始化,不能改變指針的指向,但指向的值可變。用於需要固定指向某個對象,但可能需要修改該對象內容的情況。
int a = 10;
int b = 20;
int* const ptr = &a;  // ptr是一個常指針,不能改變ptr指向的地址
*ptr = 50; // ok
// ptr = &b;  // 錯誤,ptr不能指向其他變量
  • 完全常量指針:指向常量的常量指針,指針本身是常量,指針指向的數據也是常量。指針和值都不可變,是最嚴格的const指針。可以用於配置參數、固定常量等絕對不能修改的情況。
int x = 10;
const int* const ptr = &x;
//ptr = &y; // 錯
//*ptr = 20; // 錯
  • 多層指針與const:指向上述指針的指針
const int** pp1;
int* const* pp2;
int const** pp3;
const int* const* pp4;

3、const與引用

  • 常量引用:引用一個常量,不能通過引用修改數據。可以用於函數參數和返回值,避免拷貝,同時保證不修改原數據,且同時支持常量和變量參數。
int a = 10;
const int& ref = a;
// ref = 20; //錯
double d = 3.14;
//int& rj = d; // 錯誤
const int& ri = d; //ok,創建臨時int變量3,綁定ri到臨時變量(轉換規則)
  • 綁定字面量:只有常量引用可以綁定字面量
const int& a = 10; // 可以用右值(字面量)或同類型的對象初始化(初始化規則)
const double& b = 5; // 可以用不同但相關的對象初始化,會創建臨時對象
const int& c = 5.5; // double隱式轉換int會丟失精度

4、const在類中的應用

  • 常量成員變量:const成員變量必須在構造函數的初始化列表中初始化,不能在構造函數體內賦值
class Myclass{
private:
    const int m_v;
public:
    Myclass(int value):m_v(value){}// ok
    // 錯誤
    //Myclass(int value){
    //    m_v = value;
    //}
};
  • const成員函數:1、const成員函數不會修改類的非mutable成員變量,這是一種接口契約。2、const成員函數在編譯時會將'this'指針轉換為‘const T*'類型,因此不能修改成員變量。3、C++允許const和非const版本的成員函數重載,編譯器會根據對象是否為const來選擇調用哪個版本。4、const對象只能調用const成員函數,不能調用非const成員函數,非const對象可以調用const和非const成員函數。5、const對象的非mutable成員變量被視為const,可以通過const成員函數獲取成員變量的值。
// 對於非const成員函數,this的類型是 T*
// 對於const成員函數,this的類型是 const T*
class StringBuffer{
private:
    int m_count;
    string m_buffer;
    mutable int m_accessCount;
public:
    const char& operator[](size_t index) const{
        //m_count++; // 錯
        m_accessCount++; // ok
        cout << "const char&" << endl;
        return m_buffer[index];
    }
    char& operator[](size_t index){
        cout << "char&" << endl;
        return m_buffer[index];
    }
    int getCount() {return m_count;}
    int constGetCount() const {return m_count;}
};
int main(){
    StringBuffer buffer;
    const StringBuffer constBuffer;
    
    buffer[0] = 'A'; // char& 調用非const版本
    //constBuffer[0] = 'B'; // 錯誤,const版本返回const引用,不能修改
    char c1 = buffer[0]; // char& 調用非const版本
    char c2 = constBuffer[0];// const char& 調用const版本

    int count = constBuffer.constGetCount(); // ok
    // count = constBuffer.getCount(); // 錯
    count = buffer.constGetCount(); //ok
    count = buffer.getCount(); //ok
}

類的設計原則:

對於不修改成員變量的函數,聲明為const,提供const和非const重載版本,以支持不同使用場景。合理使用const來約束接口,提高代碼的安全性。

實現高效的接口:

返回成員變量的const引用,避免拷貝,允許只讀訪問,同時保護內部數據。

5、const在實際開發中的最佳實踐詳解

  • 優先使用const的原則:儘早const,儘可能const,對於不需要修改的數據,從設計初期就聲明為const
const double PI = 3.1415926535;
const std::string COMPANY_NAME = "TechCorp";
  • 函數參數的最佳實踐:對於內置類型:對於簡單類型(int,double等),直接傳值即可。對於指針,根據是否需要修改指針指向的數據決定是否使用const;對於自定義類型和容器
void processValue(int value){}
void printValue(const int* valuePtr){}