天天看點

C++的那些事:const用法面面觀

在 c/c++ 語言中,const關鍵字是一種修飾符。所謂“修飾符”,就是在編譯器進行編譯的過程中,給編譯器一些“要求”或“提示”,但修飾符本身,并不産生任何實際代碼。就 const 修飾符而言,它用來告訴編譯器,被修飾的這些東西,具有“隻讀”的特點。在編譯的過程中,一旦我們的代碼試圖去改變這些東西,編譯器就應該給出錯誤提示。

是以,const修飾符的作用主要是利用編譯器幫助我們檢查自己代碼的正确性。我們使用const在源碼中标示出“不應該改變”的地方,然後利用編譯器,幫助我們檢查這些地方是否真的沒有被改變過。如果我們不小心去修改了這些地方,編譯器就會報錯,進而幫助我們糾正錯誤。使用const和不使用const,對于最終編譯産生的代碼并沒有影響。

雖然const對于最終代碼沒有影響,但是盡可能使用const,将幫助我們避免很多錯誤,提高程式正确率。

在上面已經提到過了,const是一種修飾符,那它可以作為哪些對象的修飾符呢?下面列舉了一些c/c++中用到const的地方。

1,const變量 2,const指針 3,const引用 4,const類 5,類的const成員變量 6,類的const成員函數 7,const修飾函數的形參與傳回值

下面我們分别讨論上面幾種情況下,const的用法。

當一個變量被const修飾後,具有以下幾個特點:

1)該變量隻能讀取不能修改。(編譯器進行檢查) 2)定義時必須初始化。 3)c++中喜歡用const來定義常量,取代原來c風格的預編譯指令define。

上面代碼中第一行和第三行都有錯誤,注釋便是編譯器給出的錯誤提示。

另外注意,在使用const變量作為數組的下标時,變量的值一定要是一個常量表達式(在編譯階段就能計算得到結果)。

我們知道,引用必須在定義的時候指派,這樣就會所引用的變量綁定在一起并作為它的一個别名,在程式中的其他地方,是不能讓引用再與其他對象綁定。這個特性,讓引用看起來就像是const對象一樣,一旦定義後将不能更改。是以并不存在const的引用。

但是我們卻可以引用一個const的對象(變量),我們稱之為對常量的引用,與普通的引用不同的時,對常量的引用不能被用作修改它所綁定的對象。

我們知道,引用的類型必須與其所引用對象的類型一緻,如下面的代碼:

上述代碼為何不行?

此處ri引用了一個int型的整數。對于ri的操作數應該是整數運算,但是dval卻是一個雙精度的浮點數而非整數。是以為了確定讓ri綁定一個整數,編譯器把上述代碼變成了如下形式:

其中temp是一個臨時變量,而ri綁定了一個臨時量,是以當ri改變時,并沒有改變davl的值,是以這種引用是無效的。

也許你注意到了,當我們把double變量綁定在一個int&類型上時,編譯器提示後有個括号:非常量限定。這說明如果是一個常量的引用,則有可能是通過的,顯然下面的代碼就沒有任何問題:

因為在這裡,ri是一個常量引用,我們并不想通過ri改變dval的值,隻要能讀到dval對應的int型的值就行。

 我們知道,指針與引用不同,指針本身是一個對象,是以存在常量指針,這種指針在定義并初始化後,便不能再指向其他變量。用來修飾這種常量指針的const,我們稱之為"頂層const"。

與頂層指針對應的是底層指針,這種指針指向一個const修改的對象,這一點上就有點像是常量的引用。

對于指向常量的指針或引用,都有以下規則:

1)可以将一個非const對象的位址賦給一個指向const對象的指針 2)可以将一個非const對象的位址賦給一個指向非const對象的指針 3)可以将一個const對象的位址賦給一個指向const對象的指針 4)不可以将一個const對象的位址賦給一個指向非const對象的指針。

 還有一種指向const對象的const指針,這種指針首先表明,本身是一個const指針,一旦初始化後不能指向其他對象;其次,它本身所指向的對象也是一個常量,即不能通過指針修改對象的值。

這裡再強調一點,const隻是給編譯器看的,我們可以很輕松的騙過編譯器,并看看編譯器都做了什麼:

我們在代碼的第2行,用一個類型轉換強制的,把一個非const指針指向了一個const對象。

但是後面我們通過這個指針來修改這個值,卻沒有生效,原因呢?

那是因為編譯器在編譯階段發現var是一個常量,是以在編譯目标代碼時已經将var的地方都用42進行了替換。

其實類定義的對象,與普通的變量是一樣的,用const修飾時,說明這個類是一個常量類對象,這個對象有下面2個特點:

1)不能改變其成員變量(非mutalbe成員) 2)不能調用其非const成員函數

const 成員變量指的是類中的成員變量為隻讀,不能夠被修改(包括在類外部和類内部)。

1)const 成員變量必須在類的構造函數初始化表達式中被初始化,即使在構造函數體内也不可以。 2)靜态 const 成員變量需要在類外部單獨定義并初始化(可定義在頭檔案)

類對象的執行個體化過程可以了解為包含以下步驟:首先,開辟整個類對象的記憶體空間。之後,根據類成員情況,配置設定各個成員變量的記憶體空間,并通過構造函數的初始化清單進行初始化。最後,執行構造函數中的代碼。由于 const 成員變量必須在定義(配置設定記憶體空間)時,就進行初始化。是以需要在夠在函數的初始化清單中初始化。const成員在初始化之後,其值就不允許改變了,即便在構造内部也是不允許的。

靜态成員變量并不屬于某個類對象,而是整個類共有的。靜态成員變量可以不依附于某個執行個體化後的類對象進行通路。那麼,靜态成員變量的值,應該在任何執行個體化操作之前,就能夠進行改變(否則,隻有執行個體化至少一個對象,才能通路靜态成員)。是以,靜态成員變量不能夠由構造函數進行記憶體配置設定,而應該在類外部單獨定義,在執行個體化任何對象之前,就開辟好空間。又由于 const 成員變量 必須初始化,是以靜态成員變量必須在定義的時候就初始化。

const成員函數指的是,此函數不應該修改任何成員變量。

1)傳給const成員函數的this指針,是指向 const 對象 的 const 指針。 2)const成員函數,不能夠修改任何成員變量,除非成員變量被 mutable 修飾符修飾。

在成員函數調用的過程中,都有一個 this 指針被當做參數隐性地傳遞給成員函數(可能通過棧,也可能通過cpu寄存器)。這個this指針,指向調用這個函數的對象(這樣,成員函數才能找到成員變量的位址,進而對其進行操作)。這個this指針,是個 const指針,不能修改其指向(你不希望這個對象的函數,修改了那個對象的成員變量,對吧?)。

傳遞給const成員函數的this指針,指向一個const對象。也就是說,在const成員函數内部,這個this指針是一個指向const對象的const指針。

mutable 修飾符使得const函數的行為有了一些靈活性。相當于提醒編譯器,這個成員變量比較特殊,就不要進行任何隻讀檢查了。

為什麼 const 對象隻能夠調用const成員函數呢?,其實是這樣的。由于對象本身通過 const 修飾,那麼指向這個對象的指針也就是指向const對象的const指針了。換句話說,指向這個對象的this指針就是指向const對象的const指針。一般成員函數要求的this指針為:指向對象的const指針。是以此處發生了參數不比對,無法進行調用。而 const 成員函數要求的this指針,恰恰是 指向const對象的const指針。是以依然能夠調用。

将函數的形參用const修飾是希望實參在函數内部不被修改,而一般函數接口可能會遇到以下三種情況:

1,const對象 2,指向const對象的指針 3,綁定const對象的引用 4,傳回值是一個const對象

首先,我們看const對象的形參,這種接口用const修飾實際上沒有任何意義,因為實參在傳遞給實參時是傳遞了一份副本,原實參是不會變化的。

通過上面代碼可以看出,實參如果隻能過值進行傳遞,函數接口不用const修改,也不會令實參的值改變。

而通過指針或引用傳遞給函數時,函數就可以通過形參來改變實參的值,這裡如果需要對實參進行保護,則需要在函數接口聲明形參為指向const類型的指針或引用。

有的時候,我們需要函數的傳回值是一個const對象,比如我們考慮一個有理資料類,我們給類定義了一個*的重載。

如果上面代碼中重載操作符傳回對象不是const類型,則a*b=c這個式子就成立,實際上這與我們的内置類型的算術運算原則違背了,而我們希望我們設計的類的操作意義要像内置内類一樣。

參考博文:

[1]:

[2]:

繼續閱讀