【c++】const關鍵字解析

首先我們來看一下爲什麼要使用const呢?因爲採用符號常量寫出的代碼更容易維護;指針常常是邊讀邊移動,許多函數參數都是隻讀不謝的,const最常見的用法就是作爲數組的界和switch分情況標記(也可以用枚舉符代替)。
分類:
常變量:const 類型說明符 變量名
常引用: const 類型說明符 & 引用名
常對象:類名::fun(形參) const
常數組:類型說明符 const 數組名[大小]
常指針:const 類型說明符* 指針名 ,類型說明符* const 指針名
注意:在常變量(const 類型說明符 變量名)、常引用(const 類型說明符 & 引用名)、常對象(類名 const 對象名)、常數組(類型說明符 const 數組名[大小]),const”與“類型說明符”或類名(其實類名是一種自定義的類型說明符)的位置可以互換
比如:const int a = 5;與int const a = 5;等同
類名 const 對象名 與 const 類名 對象名 等同
1、用法1:常量
取代了c中的宏定義,聲明時必須進行初始化(c++中則不然)。const限制了常量的使用方式,並沒有描述常量應該如何分配。如果編譯器知道了某const的所有使用,它甚至可以不爲該const分配空間。最簡單的常見情況就是常量的值在編譯時已知,而且不需要分配存儲。
用const聲明的變量雖然增加了分配空間,但是可以不保證類型安全。
c標準中,const定義的常量是全局的,c++中視聲明位置而定
2、用法2:指針和常量
使用指針時涉及到兩個對象:改指針本身和被它所指的對象而不是使這個指針成爲常量。要將指針本身而不是被指對象聲明爲常量,必須使用聲明運算符*const
所以出現在*之前的const是作爲基礎類型的一部分,
char* const cp;//到char的const指針
pc不能指向別的字符串,但可以修改其指向的字符串的內容(指向不能變)
char const *pc1;//到const char的指針
內容不能改變,可以改變指向
const char* pc2;//到const char 的指針(後兩個聲明是等同的)
內容不能改變可以改變指向
從右往左讀的記憶方式:
注意:
允許把非const對象的地址賦給指向const對象的指針,不允許把一個const對象的地址賦給一個普通的、非const對象的指針
3、用法3:const修飾函數傳入參數
將函數傳入參數聲明爲const,以指明使用這種參數僅僅是爲了效率的原因,而不是想讓調用函數能夠修改對象的值。同理,將指針參數聲明爲const,函數將不修改由這個參數所指的對象,通常修飾指針參數和引用參數:
void Fun(const A * in);//修飾指針型傳入參數
void Fun(const A& in);//修飾引用型傳入參數
4、用法4修飾函數返回值
可以組織用戶修改返回值,返回值也要相應的付給一個常量或常指針
5、用法5:const修飾成員函數(c++特性)
const對象只能訪問const成員函數,而非const對象可以訪問任意的成員函數,包括const成員函數
const對象的成員是不能修改的,而通過指針維護的對象確實可以修改的
const成員函數不可以修改對象的數據,不管對象是否具有const性質。編譯時以是否修改成員數據爲依據進行檢查。
具有展開來說:
(一)常量與指針
常量與指針放在一起很容易讓人難以區分,,對於常量指針和指針常量也不是所有學習c/c++的人都能說清的:
例如:
const int *m1 = new int(10);
int * const m2 = new int(20);
const 到底是修飾指針還是修飾指針指向的區域呢:實際上const只對它左邊的東西起作用,唯一的例外就是const本身就是最左邊的修飾符,那麼它纔對右邊的東西起作用,根據這個規則來判斷,m1應該是常量指針(不能通過m1來修改它指向的內容),而m2應該是指針常量(不能讓m2指向其它的內存模塊)
1、對於常量指針,不能通過該指針來改變所指的內容。即下面的操作是錯誤的。
這裏寫圖片描述
因爲你在試圖通過pi改變它所指向的內容,但是還是可以通過其它方式修改的,例如:
這裏寫圖片描述
第二種方法也是同樣效果。
實際上,在將程序載入內存的時候,會有專門的一塊內存區域來存放常量。但是,上面的i本身並不是常量,還是存放在堆或者棧中的。仍然可以對值進行修改,我們說的不能修改指向是編譯器的一種限制。
2、根據上面的const的規則,const int* m1 = new int(10);
int const *m1 = new int (10);
3、在函數參數中指針常量表示不允許將該指針指向其他內容
這裏寫圖片描述
4、在函數參數中使用常量指針時表示在函數中不能改變指針所指向的內容
這裏寫圖片描述
我們還可以這樣來防止調用者改變參數的值。但是,這樣的限制是爲參數調用這,我們也不要試圖去改變參數的值
這裏寫圖片描述
(二)常量與引用
引用就是另一個變量的別名,它本身就是一個常量,就是說不能再讓一個引用成爲另外一個變量的別名,那麼他們只剩下代表的內存區域是否可變。
這裏寫圖片描述
在系統加載程序的時候,系統會將內存分爲4個區域:堆區 棧區 全局區(靜態) 和代碼區,對於常量來說系統沒有專門劃分區域來保護其中的數據不能被修改。也就是說,使用常量的方式對數據進行保護是通過編譯器做語法限制來實現的。我們仍然可以繞過編譯器去修改被定義爲常量的內區域,
這裏寫圖片描述
(三)常量函數
常量函數是c++的一種擴展,它更好的確保了c++中類的封裝性,在c++中,爲了防止類的數據成員被非法訪問,將類的成員函數分成了兩類,一類是常量成員函數,另一類是非常量成員函數。在一個函數的簽名後面加上const後該函數就變成了常量函數。對於常量函數,最關鍵的不同是編譯器不允許其修改類的數據成員,
這裏寫圖片描述
上面的代碼中,常量函數func內試圖去修改數據成員intvalue的值,因此將在編譯的時候引發異常
當然對於非常量的成員函數,我們可以根據需要讀取或者修改數據成員的值。但是,這要依賴調用函數的對象是否是常量,如果我們把一個類定義爲常量,我們的本意是希望它的狀態不會被改變,那麼如果一個常量的對象調用它的非常亮函數會發生什麼後果呢?
這裏寫圖片描述
從上面的代碼可以看出,由於常量對象的狀態不允許被修改,因此通過常量對象調用非常量函數時將會產生語法錯誤。實際上,我們知道每個成員函數都有一個隱含的指向對象本身的this指針,而常量函數則包含一個this的常量指針
如下:
void inspect(const Fres* this) const;
void mutate(Fred* this);
也就是說對於常量函數,我們不能通過this指針區修改對象對應的內存塊。但是,在上面我們已經知道,這僅僅是編譯器的限制,我們仍然可以繞過編譯器的限制,去改變對象的狀態。

#include<stdio.h>
#include<iostream>
using namespace std;
class Fred
{
public:
    void inspect()const;
    void mutate();
private:
    int intvalue;

};
void Fred::inspect()const
{
    cout << "start initvalue" << intvalue << endl;
    //這裏我們根據this指針重新定義了一個指向桶一塊內存地址的指針
    //通過這個新定義的指針,我們仍然可以修改對象的狀態
    Fred* pFred = (Fred*)this;
    pFred->intvalue = 50;
    cout << "After initvalue" << intvalue << endl;
}
int main()
{
    Fred fred;
    fred.inspect();
    return 0;
}

這裏寫圖片描述
以上代碼說明只要我們願意還是可以同規格常量函數修改對象的狀態。
也可以通過在數據成員前面加上mutable,以允許該成員可以砸常量函數中被修改。

#include<stdio.h>
#include<iostream>
using namespace std;
class Fred
{
public:
    void inspect()const;
    void mutate();
private:
    mutable int intvalue;

};
void Fred::inspect()const
{
    intvalue = 100;
}
int main()
{
    Fred fred;
    fred.inspect();
    return 0;
}

這裏寫圖片描述
注意:並不是所有的編譯器都支持mutable.
關於常量函數還有一個問題是重載

#include<stdio.h>
#include<string>
#include<iostream>
using namespace std;
class Fred
{
public:
    void func()const;
    void func();
private:
    mutable int intvalue;

};
void Fred::func()const
{
    cout << "add const" << endl;
}
void Fred::func()
{
    cout << "no const" << endl;
}
void UserCode(Fred& fred, const  Fred& cfred)
{
    fred.func();
    cfred.func();
}
int main()
{
    Fred fred;
    UserCode(fred, fred);
    return 0;
}

這裏寫圖片描述
當存在同名同參數和返回值的常量函數和非常量函數時,具體調用哪個函數是根據調用對象是常量對象還是非常量對象決定的。常量對象調用常量成員,非常量對象調用非常量成員。
(四)常量返回值
很多時候我們的函數中會返回一個地址或者引用。調用這得到這個返回的地址或者引用後就可以修改所指或者代表的對象。這個時候如果我們不希望這個函數的調用這修改這個返回的內容,就應該返回一個常量。


c和c++const的區別:
1、c++中的const正常情況下是看成編譯器的常量,編譯器並不爲const分配空間,只是在編譯期間將其值保存在名字表中,並在合適的時候摺合在代碼中,

#include<iostream>
using namespace std;
int main()
{
    const int a = 1;
    const int b = 2;
    int array[a + b] = { 0 };
    for (int i = 0; i < sizeof(array) / sizeof(*array); i++)
    {
        cout << *array << endl;
    }
    return 0;
}

這裏寫圖片描述
可以看到可以通過編譯,並且可以正常運行,但稍加修改後放在c編譯器中就會出錯。
這裏寫圖片描述
出現這種情況的原因是:在C中,const默認是內部連接,如果想在C++中達到以上的效果,必須要用extern關鍵字,即C++中,const默認使用內部連接,而C中使用外部連接,所以編譯器不知道編譯時的值,而且數組定義時的下標必須是常量。
2、在C語言中:const int size這個語句是正確的,因爲它被C編譯器看作爲一個聲明,指明在別的地方分配存儲空間,但在C++中這樣是不對的,C++中const默認是內部連接,如果在C++中想達到以上的效果,必須使用extern關鍵字,即C++中const默認使用內部連接,而C中默認使用外部連接
(1)內連接:
編譯器只對正在編譯的文件創建存儲空間,別的文件可以使用相同的表示符或全局變量,C/C++中內連接使用關鍵字static指定
(2)外連接:所有被編譯過得文件創建一片單獨存儲空間,一旦空間被創建,連接器必須解決對這片空間的引用,全局變量和函數使用外部連接,通過extern關鍵字聲明,可以從其他文件訪問相應的變量和函數
3、C++中,是否爲const分配空間要看具體情況,如果加上關鍵字extern或者取const變量地址,則編譯器就要爲const分配存儲空間。
4、C++中定義常量的時候不再採用define,因爲define只做簡單的宏替換,並不提供類型檢查

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