關於C/C++中const關鍵字的思考

const關鍵字不僅可以用來定義常量,還可以修飾函數的形參、返回值,以及成員函數的this指針。適當的使用const,可以有效避免由於程序員的疏忽造成的變量的改動,很好地提高程序的健壯性。下面分情況討論const在不同場景下的作用:

一、定義常量

const int x = 1;   //定義常量x

const定義的常量和用宏定義#define定義的常量不同之處在於:const常量是有類型的;而宏定義的常量無類型,使用的時候僅僅是簡單的字符串替換(這一操作由預處理器執行)。
在使用const常量時,應該注意和非const變量之間的賦值操作,具體情況如下:

1.1 簡單的賦值(無引用或指針)

    const int x1 = 1;
    int y1 = x1;    //ok

    int x2 = 1;
    const int y2 = x2;   //ok

簡單賦值(傳值方式,無引用或指針)操作過程中,實際上是將右操作數的值拷貝一份至左操作數地址,因此兩個操作數所指的數據是獨立的,互不影響。

1.2 常量=變量(引用或指針)

    int x1 = 1;
    const int &y1 = x1;    //ok

    int value = 1;
    int *x2 = &value;
    const int *y2 = x2;    //ok

允許常量的引用(指針)指向一個變量。如此一來,不可以通過y1(或*y2)更改對應內存空間的值1,但仍可通過x1(或 *x2)更改。

1.3 變量=常量(引用或指針)(重要原則)

    const int x1 = 1;
    int &y1 = x1;    //error

    int value = 1;
    const int *x2 = &value;
    int *y2 = x2;    //error

不允許變量的引用(指針)指向一個常量,因爲編譯器會認爲程序員可能通過這個變量更改常量的值,這是不被允許的。

tips: (以下觀點是作者自己理解的,如有誤,望指正)變量與常量的區別是在“語言層次”上而言的,在程序員設計程序時,通過適當地設計常量和變量,完成相應的功能。可變量或常量所指向的內存地址,沒有常量或者變量之分,任何變量都可以對其進行更改。正如例1.2的代碼示例中,x1和y1都指向同一內存地址,y1是常量,不能修改其值,但可以通過x1修改,實際上也對y1的值修改了。

    int x1 = 1;
    const int &y1 = x1;

    x1 = 2;   //ok

二、修飾函數形參

關於函數傳參問題,請參照博客文章:C/C++中函數傳參方式簡述,此處不再贅述。

如果函數內部無修改形參操作,則最好將形參定義爲const。

2.1 非const引用形參

如果函數形參定義爲非const引用的話,則形參實際上是實參的一個別名,不產生實參的副本。但這種定義形參方式在使用時不太靈活,因爲其即不能用const對象初始化,也不能用字面值或產生右值的表達式實參初始化。(《C++ Primer》 P205)因此“應將不需修改的引用形參定義爲const引用”。(類似上面1.3所述)

2.2 const引用形參

如果函數形參定義爲const引用的話,不產生實參的副本的同時,函數不能修改形參。

void StringCopy(char *strDestination, const char *strSource);
void swap ( int * const p1 , int * const p2 );  //限制在函數swap內修改指針p1和p2的指向

三、修飾函數返回值

用const修飾函數返回值並不常用,在這裏簡單的列舉一下幾種情況:

3.1 “傳值”返回值

如果返回值以“傳值”方式返回給主調函數,這種情況下添加const修飾毫無意義。

如果函數返回值採用“值傳遞方式”,由於函數會把返回值複製到外部臨時的存儲單元中,加const 修飾沒有任何價值。例如把函數int GetInt(void) 寫成const int GetInt(void)是沒有意義的。

3.2 “傳址”返回值

如果返回值以“傳址”方式返回給主調函數,即返回值爲指針,那麼返回值(指針指向內容)不能被修改,且只能被賦值給const修飾的常量指針。(常量指針與指針常量的區別,在文章末尾有解釋)

const char * GetString(void);
char *str = GetString();   //error
const char *str = GetString();   //ok

3.3 “傳引用”返回值

被調函數以“傳引用”方式將返回值返回主調函數,類似3.2。

tips:對函數返回值使用 const 的另一個目的在於:限制不能將函數調用表達式作爲左值使用。示例如下:

int & min ( int &i, int &j);
可以對函數調用進行賦值,因爲它返回的是左值: min ( a , b )=4;
但是,如果對函數的返回值限定爲 const 的,即定義:const int & min ( int & i, int &j );
那麼,就不能對 min ( a, b ) 調用進行賦值了。

四、修飾成員函數(函數體/對象/*this)

C++中,const還可以用來修飾成員函數,即修飾該類實例化的對象的this指針。

關於this指針,《C++ Primer》P377中有如下描述:

在普通的非const成員函數中,this的類型是一個指向類類型對象的const指針:可以改變this所指向的值,但不能改變this所保存的地址。
在const成員函數中,this的類型是一個指向const類類型對象的const指針:既不能改變this所指向的對象,也不能改變this所保存的地址。

上述描述的意思就是,const修飾的成員函數實際上可以理解爲Type const *this,即此成員函數中的對象被視爲const對象,不能更改數據成員。

不過const的位置很特殊,在聲明的尾部,估計是語言設計者發現,其它的地方都被佔用了吧~~
const修飾的成員函數,不能修改類/對象的數據成員。因此,任何不會修改數據成員的成員函數都應該聲明爲const 類型

如果在編寫const 成員函數時,不慎修改了數據成員,或者調用了其它非const 成員函數,編譯器將指出錯誤,這無疑會提高程序的健壯性。

class MyClass {
    int member ;
    public:
    int getCount ( ) const;
};

classname :: getCount( )
{   member =4 ;   //error,“只讀成員函數”,不能修改數據成員
    return member;
}

關於const成員函數須注意以下幾點:
a. const成員函數不可以修改對象的數據,不管對象是否具有const性質。它在編譯時,以是否修改成員數據爲依據,進行檢查;
b. const成員函數不能在函數中調用其它非const成員函數;(非const成員函數可能修改數據成員)
c. const對象只能訪問const成員函數;而非const對象既可以訪問非const成員函數,也可以訪問const成員函數;(const對象的數據成員不能被修改,而非const成員函數可能對其修改)

五、關於const的其它知識點

5.1 const的鏈接屬性

關於變量或函數的鏈接屬性,請轉至:鏈接屬性與存儲類型

const對象默認的鏈接屬性爲internal,即文件的局部變量。

a. 在全局作用域裏定義非const變量時,它在整個程序中都可以訪問。

//《C++ Primer》 P50
//全局變量具有外部鏈接屬性
//file1.cc
int counter;  //define
//file2.cc
extern int counter;   //uses counter from file1
++counter;    //increments counter defined in file1

b. 在全局作用域聲明的const變量是定義該對象的文件的局部變量。此變量值存在於那個文件中,不能被其它文件訪問。通過執行const變量爲extern(顯式修改鏈接屬性),就可以在整個程序中訪問const對象。

//又如(《C++ Primer》 P50例子)
//const變量具有內部鏈接屬性,默認值存在於定義的文件中,不能被其它文件訪問
//file_1.cc
//defines and initializes a const that is accessible to other files
extern const int bufSize = fcn();   //用extern修改const變量的鏈接屬性

//file_2.cc
extern const int bufSize;  //uses bufSize from file_1
for(int index=0;index!=bufSize;++index);    //uses bufSize defined int file_1
    //...

5.2 const定義的常量轉換至非const變量

我們先來看一段示例代碼:

    int i = 0;
    const int &j = i;  //ok,隱式轉換,int -> const int

    const int x = 1;
    int &y = x;     //error
    int &y = const_cast<int&>(x);    //ok,顯式轉換,const int -> int

通過上述代碼可見,const引用變量可以與非const變量綁定,反之則報錯(非const引用變量可能修改const變量的值)。此時需要藉助C++中的const_cast進行強制類型轉換。
const_cast,顧名思義,就是修改表達式的const屬性。const_cast僅能去掉(或添加,不常用)變量的const屬性,如果用它來完成其它類型的強制轉換,都會引起編譯錯誤。

C++幾種強制類型轉換可見另一篇文章:C++四種強制類型轉換運算符的聯繫與區別

tips:
a. const_cast不會改變原變量的const屬性,引用C++類型轉換詳解–const_cast的一段話:

使用const_cast去掉const屬性,其實並不是真的改變原類類型(或基本類型)的const屬性,它只是又提供了一個接口(指針或引用),使你可以通過這個接口來改變類型的值。也許這也是const_case只能轉換指針或引用的一個原因吧。

    int i = 0;
    const int &j = const_cast<const int&>(i);
    i = 1;    //ok

    const int x = 1;
    int &y = const_cast<int&>(x);
    x = 0;   //error

b. const_cast可能出現“常量重疊”問題,即同一個地址具有兩個不同的值(關於const變量與const_cast),使用時要注意。

5.3 常量指針與指針常量

從字面意思看,“常量指針”側重指針:指向常量的指針;而“指針常量”側重常量:指針類型的常量。

5.3.1 常量指針

const int *p;
int const *p;

這個被指向對象不能通過指針更改,但可以通過原來的聲明更改;可以改變該指針指向的對象(對象地址,指針變量的值)。

int a=1;
const int* p=&a;
*p=3;   //error
a=3;  //ok

5.3.2 指針常量

int* const p;

可以通過該指針修改指向對象的值,但不能更改指針指向的對象。

int b=2;
int* const q=&b;
q=&a;  //error
*q=3;   //ok

tips: const修飾誰,誰不能變

參考資料:
[C/C++] const 詳解(修飾變量、輸入參數、返回值、成員函數)
修飾函數和函數返回值的const的差別
const參數,const返回值與const函數
const修飾符總結
const的內部鏈接屬性

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章