博客 / 詳情

返回

每日一個C++知識點|const和static的區別

const和 static是C++編程語言中的常用關鍵字,對於初學者來説可能會混淆,畢竟一個代表“常量”,一個代表“靜態”,都是靜止類的詞彙。

其實這兩者並沒有本質的聯繫,其中const是類型限定符,聚焦於修飾變量的 “不可修改性”;

static是存儲類説明符,管控變量的存儲位置、生命週期與作用域。

由於const和static是性質不同的關鍵字,下面就分開對兩者進行講解~

const

如果沒有const的場景會是怎麼樣呢?下面通過簡單代碼舉例~

普通變量無const

當普通變量沒有const修飾符時:

#include <iostream>
using namespace std;

// 定義系統最大連接數(核心配置,本應只讀)
int MAX_CONN = 100;

void initServer() {
    // 開發人員誤操作修改了核心常量(編譯無報錯,運行時才發現問題)
    MAX_CONN = 200; 
    cout << "初始化服務器:最大連接數=" << MAX_CONN << endl;
}

int main() {
    initServer();
    // 業務邏輯依賴MAX_CONN,誤改後導致連接數超限、系統異常
    if (150 > MAX_CONN) {
        cout << "連接數未超限" << endl;
    } else {
        cout << "連接數超限,系統崩潰" << endl;
    }
    return 0;
}

由上可知,若變量沒有約束,會導致無意識的篡改,會導致業務邏輯錯亂,如果代碼量過大,就導致問題難以定位。此時const的作用就顯現出來了,加上const的解決方案如下:

#include <iostream>
using namespace std;

// 加const,強制MAX_CONN只讀
const int MAX_CONN = 100;

void initServer() {
    // MAX_CONN = 200; // 編譯直接報錯:只讀變量不可賦值,從源頭攔截錯誤
    cout << "初始化服務器:最大連接數=" << MAX_CONN << endl;
}

int main() {
    initServer();
    if (150 > MAX_CONN) {
        cout << "連接數未超限" << endl;
    } else {
        cout << "連接數超限,系統崩潰" << endl;
    }
    return 0;
}

加上const,變量變常量了,此時MAX_CONN不可修改,如果修改編譯器就會報錯,就可以從源頭攔截問題,這就是const的價值。

函數參數無 const

除了普通變量的篡改引起的錯誤之外,函數參數無 const也會誤修改傳入的實參,尤其是要注意指針和引用~

#include <iostream>
#include <string>
using namespace std;

// 僅用於打印字符串的函數,無const修飾引用參數
void printString(string& s) {
    // 開發人員誤操作修改了參數(本意只是打印,卻篡改了外部實參)
    s = "被篡改的字符串"; 
    cout << "打印內容:" << s << endl;
}

int main() {
    string original = "原始核心數據";
    printString(original);
    // 外部實參被函數意外修改,導致後續邏輯出錯
    cout << "外部原始數據:" << original << endl; // 輸出:被篡改的字符串
    return 0;
}

函數參數為非 const 引用時,函數內部可直接修改外部實參;即便函數本意是 “只讀操作”,也無語法約束阻止修改,導致外部數據被無意識篡改。那麼,加 const 修飾參數就會解決以上問題。

#include <iostream>
#include <string>
using namespace std;

// 加const修飾引用參數,明確函數不會修改參數
void printString(const string& s) {
    // s = "被篡改的字符串"; // 編譯報錯:無法修改const引用參數
    cout << "打印內容:" << s << endl;
}

int main() {
    string original = "原始核心數據";
    printString(original);
    // 外部實參未被修改,邏輯正常
    cout << "外部原始數據:" << original << endl; // 輸出:原始核心數據
    return 0;
}

對函數參數加上了const限定符,編譯期禁止函數內部修改參數,保障外部實參安全。

類成員無const

除了普通變量和函數參數沒加const會引起篡改之外,類成員無const也會造成失誤修改:

#include <iostream>
using namespace std;

class Student {
private:
    string name;
    int score;
public:
    Student(string n, int s) : name(n), score(s) {}

    // 僅用於讀取分數的函數,無const修飾
    int getScore() {
        // 開發人員誤操作修改了成員變量(本意只是返回分數)
        score = 0; 
        return score;
    }
};

int main() {
    Student s("張三", 90);
    // 調用讀取函數後,分數被意外修改
    cout << "張三的分數:" << s.getScore() << endl; // 輸出:0(本該是90)
    return 0;
}

類成員函數無 const 修飾時,即便函數本意是 “只讀操作”,也可隨意修改成員變量,違背 “只讀接口” 的設計初衷。下面就是該問題的解決方案:

#include <iostream>
using namespace std;

class Student {
private:
    string name;
    int score;
public:
    Student(string n, int s) : name(n), score(s) {}

    // 加const修飾成員函數,承諾不修改成員變量
    int getScore() const {
        // score = 0; // 編譯報錯:const成員函數禁止修改成員變量
        return score;
    }
};

int main() {
    Student s("張三", 90);
    // 讀取函數僅返回值,對象狀態未被修改
    cout << "張三的分數:" << s.getScore() << endl; // 輸出:90
    return 0;
}

const 成員函數getScore()被強制約束為 “只讀函數”,無法修改類的非靜態成員變量,保障對象狀態的穩定性

上面分別從普通變量、函數參數引用、類成員函數三種情況分析const關鍵字的防篡改作用,我們可以看出,當我們希望我們的變量、參數引用、成員函數不被錯誤修改的時候,我們會加上const修飾符,把變量變成常量,此時就無法修改了,修改編譯器就會報錯。

const 的本質是編譯期約束:不會增加運行時開銷,僅在編譯階段檢查非法修改,既避免拷貝,又保障數據安全,是 “零成本的安全保障”。

static

static是存儲類的説明符,在變量前面加上static就成為靜態變量,在函數前面加上static就成為靜態函數。其核心作用是改變修飾對象的存儲位置,將其固定為全局 / 靜態區(內存區域有五大分區),在同一作用域內,同名變量會觸發重定義錯誤。同時延長生命週期,但不限制對象的可修改性(和const不同)

static 的典型應用場景分別是修飾局部變量、修飾類成員變量、修飾全局變量 / 函數:

修飾局部變量

存儲位置轉為全局 / 靜態區,生命週期延長至程序全程。因為內存分區分為五大區(棧區、堆區、全局 / 靜態區、常量區、代碼區),局部變量是存儲在棧區,棧區的特點是編譯器自動分配和釋放內存,具體表現為某個函數的局部變量,當這個函數執行完成後就釋放內存,所以生命週期較短,將局部變量的存儲位置轉為全局 / 靜態區,有利於延長生命週期,在程序運行的全過程都可以使用,不僅僅是函數內(棧內)有效。

具體代碼如下:

void countCall() {
    static int callNum = 0; // 僅初始化一次
    callNum++;
    std::< "調用次數:< std::endl;
}

以上代碼中多次調用countCall(),callNum值持續累加

修飾類成員變量

static修飾的類成員變量屬於類本身而非實例,所有對象共享該變量。怎麼理解這句話呢?傳統的成員變量或者成員函數想要被調用,就需要先對這個類進行實例化對象,然後通過該對象進行調用,如下代碼所示:

class Demo {
public:
    int normalValue; // 非靜態成員變量
};
int main() {
    Demo obj1; // 棧上實例化對象(無new)
    obj1.normalValue = 10; 
    return 0;
}

如果是static修飾的成員變量的話,可以直接通過類來訪問,對所有對象共享:

class Demo {
public:
    static int sharedValue; // 類內聲明
};
int Demo::sharedValue = 0; // 類外初始化

static修飾成員變量打破 “實例綁定” 的限制,實現類級數據共享,避免冗餘存儲。

修飾全局變量 / 函數

static修飾全局變量和全局函數可以將鏈接屬性改為內部鏈接,僅當前編譯單元可見,避免多文件重定義衝突,因為在同一作用域內,同名變量會觸發重定義錯誤。

static int globalStaticVar = 5; // 僅當前.cpp文件可見
static void staticFunc() { /* 函數實現 */ } // 僅當前.cpp文件可調用

const和static的組合

上面分別講了const和static的作用,其實也可以將const和static組合使用,將二者組合可實現 “只讀 + 共享” 的靜態常量,存儲於只讀數據區(常量區),程序全程存在且所有實例共享,是定義系統配置常量的最優解。代碼如下所示:

class Config {
public:
    // C++17後可直接類內初始化,無需類外定義
    static const int MAX_CONN = 100; 
};
// 訪問方式:Config::MAX_CONN,無需創建Config實例

定義類級 / 全局級的只讀共享常量必須用const和static的組合,如果僅用static,數據可被修改,破壞配置的穩定性;僅用const的話每個實例獨立存儲一份,冗餘且無法脱離實例訪問。

補充:const修飾指針

以上分別對const和static關鍵字的作用和使用場景進行分析,但const除了修飾普通變量之外,在修飾指針上也有一番考究,分別是常量指針指針常量

常量指針

常量指針是針指向的內存內容是常量(不可修改),但指針本身可指向其他地址

int a=10, b=20;
const int* p = &a; // 常量指針
p = &b; 指針可指向新地址(指針可變)

記憶:常量指針是 “指向常量的指針” ,指向的內存內容是常量,指針本身是變量,const遠離指針

使用場景:函數參數傳遞訪問只讀內存區域

指針常量

指針常量是指針本身的地址值不可修改,但指向的內存內容可修改

int a=10, b=20;
int* const p = &a; // 指針常量
*p = 30;  可修改指向的內容(內容可變)

記憶:指針常量:指針本身是常量,指向的內容是變量,const靠近指針

使用場景:硬件 / 底層編程單例模式

雙重 const

語法為const 類型* const 指針名,指針和內容均不可改。

const int* const p = &a;
// *p = 30; 錯誤: 內容不可改
// p = &b; 錯誤:指針不可改

使用場景:訪問全局只讀配置多線程只讀共享資源

總結

上述內容主要分為以下四點:

  1. const修飾普通變量、函數參數、類成員三種情況,主要是為了防篡改。
  2. static修飾局部變量,成員變量,全局變量和全局函數三種情況,主要是為了改變存儲位置方便數據共享, 減少數據冗餘, 延長生命週期
  3. const和static的結合通常定義系統配置常量
  4. const修飾指針形成常量指針、指針常量和雙重 const三種情況。

以上就是本文的所有內容,如果本文對你有幫助的話歡迎點贊收藏哦~

感興趣的朋友也歡迎關注喲~我將會持續輸出編程開發的內容~

user avatar
0 位用戶收藏了這個故事!

發佈 評論

Some HTML is okay.