C/C++中的const

Dabric發表於2021-04-17

1 C中的const

C中const修飾的變數是隻讀變數,在使用const關鍵字宣告定義變數時會給該變數分配記憶體空間。

const修飾的全域性變數預設是外部連結的,即其它原始檔可以直接使用該變數。

const修飾的區域性變數儲存在棧區中,不能通過變數名直接修改該變數的值,但是可以通過指標的方式修改該變數對應的值,從某種意義上來說,C中const修飾的變數不是真正意義上的常量,可以將其當作一種只讀變數。

C中const示例:

// fun.c
// c中const修飾的全域性變數預設是外部連結的
const int num = 100;  // a的本質是隻讀的變數,存放在文字常量區(記憶體空間只讀)

// main.c
#include <stdio.h>

// 對fun.c中的a進行宣告(不要賦值)
extern const int num;

void test()
{
    printf("num = %d\n", num);
    // num = 200;  // error: assignment of read-only variable 'num'
}

int main(int argc, char *argv[])
{
    test();
    return 0;
}
#include <stdio.h>

void test()
{
    const int data = 200;  // 區域性只讀變數,通過指標的形式修改data變數的值
    printf("data = %d\n", data);  // 200

    int* p = (int*)&data;
    *p = 300;
    printf("data = %d\n", data);  // 300
}

int main(int argc, char *argv[])
{
    test();
    return 0;
}

總結:

  • const修飾全域性變數時,該變數儲存在文字常量區(只讀),不能通過指標的方式修改其內容

  • const修飾區域性變數是,該變數儲存在棧區中(可讀可寫),通過指標的方式可以修改其內同

2 C++中的const

C++中const修飾的變數不需要建立記憶體空間,比如定義常量const int data = 10;,C++會在一張符號表中新增namedatavalue為10的一條記錄,如下圖所示:

既然,const修飾的變數沒有記憶體空間,所以在C++中const修飾的變數才是真正意義上的常量。

C++中定義宣告的全域性常量是內部連結的,只能作用於當前的整個檔案中,如果想讓其它原始檔對該常量進行訪問的,必須加extern關鍵字將該常量轉換成外部連結。

在C++中,是否為const常量分配記憶體空間依賴於如何使用,以下情況會對常量進行記憶體空間的分配:

  • const常量取地址時
  • 使用變數的形式初始化const修飾的變數時
  • const修飾自定義資料型別(結構體、物件)時

C++中const示例:

// fun.c
// const修飾的全域性變數,預設時內部連結,不能直接被其它原始檔使用
//const int num = 100;

// 可以通過將num轉換成外部連結的方式,供其它原始檔對num的訪問
extern const int num = 100;

// main.c
#include <iostream>
using namespace std;

extern const int num;

struct Person{
    int num;
    char name[32];
};

void test()
{
    cout << "全域性num = " << num << endl;  // error: undefined reference to `num'

    // 1. c++中對const修飾的基礎型別的變數不會開闢記憶體空間,只是將其放到符號表中
    const int data = 100;
    // data = 200; // error: 只讀
    cout << "data = " << data << endl;

    // 2. 對data取地址時,系統會給data開闢空間
    int* p = (int*)&data;
    *p = 2000;
    cout << "*p = " << *p << endl;  // 2000

    cout << "data = " << data << endl; // 100

    // 3. 通過變數的形式初始化 const修飾的變數,系統會為其開闢空間
    int a = 200;
    const int b = a;  // 系統直接為b開闢空間,不會把b放入到符號表中
    p = (int*)&b;
    *p = 3000;
    cout << "*p = " << *p << endl;  // 3000
    cout << "b = " << b << endl;  // 3000

    // 4. const修飾自定義型別的變數,系統會分配空間
    const Person per = { 100, "viktor" };
    // p1.num = 1000;  // error
    cout << "num = " << per.num << ", name = " << per.name << endl;  // 100, viktor

    Person* p1 = (Person*)&per;
    p1->num = 2000;
    cout << "num = " << per.num << ", name = " << per.name << endl;  // 2000, viktor
}

int main(int argc, char *argv[])
{
    test();
    return 0;
}

總結:

  • C++中,使用const定義宣告變數時,該變數會先放入到符號表中,不會開闢記憶體空間;
  • const修飾的全域性變數預設是內部連結的;
  • const修飾的變數進行取地址操作,系統會對該變數開闢空間;
  • 使用其它的變數對const修飾的變數進行初始化時,系統會對該變數開闢空間;
  • const修飾自定義的資料型別,系統為自定義資料開闢空間。

3 C/C++中const異同總結

3.1 const修飾全域性變數

全域性變數儲存在只讀的文字常量區,所以C/C++中const修飾的全域性變數都不可以更改變數對應的內容。

C中const修飾的全域性變數預設是外部連結的,而C++中預設的是內部連結,如果想使得其變為外部連結可以在const修飾的全域性變數前加extern關鍵字。

3.2 const修飾區域性變數

const在修飾基礎資料型別的區域性變數時,在C中會給該變數在棧中開闢記憶體空間,可以通過指標的方式修改變數的內容;而C++對於const修飾基礎型別的變數是在符號表中新增一條記錄,不會在棧中開闢空間,所以不能通過指標的方式修改變數的值。

C++中對const修飾的區域性變數是在如何使用的情況下才分配記憶體,如果對const修飾基礎資料型別的區域性變數進行取地址操作,會對變數分配記憶體;使用一個變數初始化const變數時會分配記憶體;const修飾的自定義資料型別(結構體、物件)會分配記憶體。

4 const和#define

在舊版本C中,如果想建立一個常量,必須使用前處理器#define MAX 1024;。使用巨集定義的MAX對於編譯器來說不知其的存在,因為在程式預處理的過程中,所有的MAX已經被替換為1024,於是MAX並沒有將其加入到符號表中。

如果使用這個常量獲得一個編譯錯誤資訊時,可能會帶來一些困擾,因為這個資訊可能會提到1024,但是並沒有提到MAX

此外MAX如果被定義在另外一個標頭檔案中,當前可能並不知道1024代表什麼,也許解決這個問題要花很長時間。解決辦法就是用一個常量替換上面的巨集。

const#define的區別:

  • const有型別,可進行編譯器型別安全檢查。#define無型別,不能進行型別檢查

示例:

// 巨集沒有型別,const有
#define MAX 1024
const short my_max = 1024;

void func(short i)
{
    cout << "short函式" << endl;
}

void func(int i)
{
    cout << "int函式" << endl;
}

void test()
{
	func(MAX);  // int函式
    
    func(my_max);  // short函式
}
  • const有作用域,而#define不重視作用域,預設定義出到檔案結尾。如果定義在指定作用域下有效的常量,那麼#define就不適用

示例:

// 巨集的作用域是當前的整個檔案,const的作用域以定義的情況決定
void my_func(void)
{
	// 作用範圍是當前複合語句
    const int my_num = 10;
    
    // 作用範圍是當前位置到檔案結束
    #define MY_NUM 10
}

void test()
{
    // cout << "my_num = " << my_num << endl;  // err 不識別
    cout << "MY_NUM = " << MY_NUM << endl;  // ok
}


  • const可以作為名稱空間的成員,而如果將巨集作為名稱空間的成員,失去了名稱空間的意義,因為巨集作用於當前的檔案,而不是隻屬於某個名稱空間的

5 總結

const由C++採用,並加進標準C中,儘管它們很不一樣,在C中,編譯器對待const如同對待變數一樣,只不過帶有一個特殊的標記,意識是”你不能改變我“。在C++中定義const時,編譯器為它建立空間,所以如果兩個不同檔案定義多個同名的const,連結器將發生連結錯誤。簡而言之,const在C++中用的更好。

在C++中儘量使用const來替換巨集定義,避免不必要的麻煩。

另外在C++中可以使用變數定義陣列。對於C,在支援C99標準的編譯器中,可以使用變數定義陣列。

示例:

#include <iostream>
using namespace std;

void test()
{
    int a = 10;
    int arr[a];
    int i = 0;
    for(; i < 10; i++)
        arr[i] = i;
    i = 0;
    for(; i < 10; i++)
        cout << arr[i] << " ";
    cout << endl;
}

int main(int argc, char *argv[])
{
    test();
    return 0;
}

相關文章