C/C++中的const

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;
}

Tags: