您好,登錄后才能下訂單哦!
這篇文章主要介紹C++中如何使用const、volatile、mutable關鍵字,文中介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們一定要看完!
const的基本使用
const的用法我覺得對于一個以后想著做一個優秀的程序員來說,這是必須熟練掌握的技能。因為網上有好多的文章介紹它的寫的非常好,有的我就直接拿過來了~,現在我們來看看他的用法。
const 要求他所修飾的對象為常量,不可被改變,不可被賦值,不可作為左值.
1、函數體內修飾局部變量
例:
void func(){const int a=0;}
const作為一個類型限定詞,和int有相同的地位。
const int a; int const a;
是等價的。于是此處我們一定要清晰的明白,const修飾的對象是誰,是a還是intconst要求他所修飾的對象為常量,不可被改變,不可被賦值,不可作為左值(l-value)。所以很明顯它修飾的是a。這是一個很常見的使用方式:
const double pi=3.14;
在程序的后面如果企圖對pi再次賦值或者修改就會出錯。然后看一個稍微復雜的例子。
const int* p;
因為int* p;和 int *p;是等價的。所以const int (*p)和int const (*p)是等價的。現在一目了然const 修飾的是誰? 是*p.所以p+=1;是合法的
*p+=1;是非法的因為const修飾了你。
int* const p;那這個什么意思?
看const修飾的是什么? 它修飾的p。但是p是一個int型的指針,所以這個指針的地址沒有辦法修改。
p+=1; //這就是非法的*p+=1; //這個是合法的
再看一個更復雜的例子,它是上面二者的綜合
const int* const p;說明p自己是常量,且p指向的變量也是常量。于是
p+=1; //非法*p+=1; //非法
const 還有一個作用就是用于修飾常量靜態字符串。例如:
const char* name=David;
如果沒有const,我們可能會在后面有意無意的寫name[4]='x'這樣的語句,這樣會導致對只讀內存區域的賦值,然后程序會立刻異常終止。有了 const,這個錯誤就能在程序被編譯的時候就立即檢查出來,這就是const的好處。讓邏輯錯誤在編譯期被發現。
2、在函數聲明時修飾參數舉個例子void * myMemMove(void *dst,constvoid *src,intcount )這是我寫的memmove函數的聲明,這個函數的意思就是(任意類型)把*src的內容復制給*dst,我們現在很明顯的看到*src它只讓你復制,你不能修改它的值,所以怕你在以后的函數的定義里出現問題現在在聲明里限制你。
3、全局變量我們的原則依然是,盡可能少的使用全局變量。我們的第二條規則 則是,盡可能多的使用const。如果一個全局變量只在本文件中使用,那么用法和前面所說的函數局部變量沒有什么區別。如果它要在多個文件間共享,那么就牽扯到一個存儲類型的問題。
有兩種方式。
1.使用extern例如/* test.h */extern const double pi;/* test.c */const double pi=3.14;
然后其他需要使用pi這個變量的,包含test.h
#include test.h
或者,自己把那句聲明復制一遍就好。
這樣做的結果是,整個程序鏈接完后,所有需要使用pi這個變量的共享一個存儲區域。
2.使用static,靜態外部存儲類
/* constant.h */static const pi=3.14;
需要使用這個變量的*.c文件中,必須包含這個頭文件。
前面的static一定不能少。否則鏈接的時候會報告說該變量被多次定義。這樣做的結果是,每個包含了constant.h的*.c文件,都有一份該變量自己的copy,該變量實際上還是被定義了多次,占用了多個存儲空間,不過在加了static關鍵字后,解決了文件間重定義的沖突。壞處是浪費了存儲空間,導致鏈接完后的可執行文件變大。但是通常,這個,小小幾字節的變化,不是問題。好處是,你不用關心這個變量是在哪個文件中被初始化的。其實const我覺得更多是程序員自己限制自己,自己告訴自己后面哪里不能出現錯誤
舉個例子吧。
#include<stdio.h>#include<Windows.h>int main(){int *p;const int a = 0;p = &a;*p = 3;printf("a= %d \n", a);system("pause");return 0;}
現在看看運行結果
現在我要說一個const操作里面比較騷的一些做法,
舉個例子我們以前寫過的一個類,我們會使用operator[]來返回一個reference的指向,這個一般情況我們都會寫一個
const的也會寫一個非const的opeartor[].這是我們最常見的一個代碼:
T& operator[](int position) { return xxx[position]; } const T& operator[](int position) const { return xxx[position]; }
這是我們平時寫的初級的代碼,但是現在當我們要寫一個TextBlock內的opeartor[]不單只返回一個referencr了,也可能執行邊界檢查,日志訪問信息,還有什么數據完善性檢驗等等一大堆繁瑣的代碼,這個時候當你實現operator[] const和operator[]()const,的時候兩份代碼大部分都一樣,這里伴隨的是代碼重復,編譯時間變長,維護代碼膨脹等等頭疼的問題.當然啦,你可以讓上述那些繁瑣的函數全部封裝的別的函數中,然后分別在operator[]()和operator[]()const當中調用但是你還說重復了一些代碼比如兩次return語句,函數調用.真正該做的是實現operator[]的機能一次并使用它兩次。也就是你只需要寫一個函數,令另外一個調用這個,這促使我們將常量性轉移.接下來見證奇跡我們來看看下面這個代碼是怎么實現的上述的操作的:
class TextBlock { public: ... const char& operator[](std::size_t position) const { ... ... ... return text[position]; } char& operator[](std::size_t position) { return const_cast<char&>(static_cast<const TextBlock&>(*this)[position]); } };
來仔細看這個操作;return const_cast<char&>(static_cast<const TextBlock&>(*this)[position]);首先把*this強制轉換為const TextBlock,再然后調用const的operator[],最后再把const的operator[]的返回值的const常量性取消,然后返回一個非const的值.這里的調用實在是太妙了,我們可以思考一下,好好想想這里的深意.但是會有人說,為什么不用const operator[]調用operator[]呢,這樣強制兩個都可以行的通啊.這樣想是錯的!
令const版本調用調用no-const版本以避免重復并不是你該做的事情.記住const所修飾函數的承諾就是我絕對不會修改你,no-const函數可沒有這種承諾,所以你讓一個const函數去調用一個no-const函數是不現實的. over其實const有很多可以玩的屬性,只要我們想到就可以去實現,這里就說這么一個就ok.接下來我們來瞧瞧另外兩個關鍵字.
C++中的mutable關鍵字
mutalbe的中文意思是“可變的,易變的”,跟constant(既C++中的const)是反義詞。
在C++中,mutable也是為了突破const的限制而設置的。被mutable修飾的變量,將永遠處于可變的狀態,即使在一個const函數中。
我們知道,被const關鍵字修飾的函數的一個重要作用就是為了能夠保護類中的成員變量。即:該函數可以使用類中的所有成員變量,但是不能修改他們的值。然而,在某些特殊情況下,我們還是需要在const函數中修改類的某些成員變量,因為要修改的成員變量與類本身并無多少關系,即使修改了也不會對類造成多少影響。當然,你可以說,你可以去掉該函數的const關鍵字呀!但問題是,我只想修改某個成員變量,其余成員變量仍然希望被const保護。
經典的應用場景比如說:我要測試一個方法的被調用次數。
class Person {public: Person(); ~Person(); int getAge() const; /*調用方法*/ int getCallingTimes() const; /*獲取上面的getAge()方法被調用了多少次*/private: int age; char *name; float score; int m_nums; /*用于統計次數*/};
最普遍的作法就是在getAge()的方法體內對m_nums這個變量進行加+1,但是getAge()方法又是const方法,無法修改m_nums這個變量,我又不想去掉const關鍵字讓別人能夠修改age等成員變量,這個時候mutable關鍵字就派上用場了:
#include <iostream>class Person {public: Person(); ~Person(); int getAge() const; /*調用方法*/ int getCallingTimes() const; /*獲取上面的getAge()方法被調用了多少次*/private: int age; char *name; float score; mutable int m_nums; /*用于統計次數*/};Person::Person(){ m_nums = 0;}Person::~Person(){}int Person::getAge() const{ std::cout << "Calling the method" << std::endl; m_nums++; // age = 4; 仍然無法修改該成員變量 return age;}int Person::getCallingTimes()const{ return m_nums;}int main(){ Person *person = new Person(); for (int i = 0; i < 10; i++) { person->getAge(); } std::cout << "getAge()方法被調用了" << person->getCallingTimes() << "次" << std::endl; delete person; getchar(); return 0;}
運行結果:
Calling the methodCalling the methodCalling the methodCalling the methodCalling the methodCalling the methodCalling the methodCalling the methodCalling the methodCalling the method
getAge()方法被調用了10次
這樣我們既保護了別的成員變量,又能夠使計數器的值進行累加。
需要注意的是:mutable不能修飾const 和 static 類型的變量。
接著補充
實際運用起來也非常容易,就是你想改變的元素被const修飾了,你就往它前面加上mutable那么你就無敵了..
我就舉一個最簡單的例子,我定一個AA類,我在AA類中定義一個MT()函數,該函數屬性為const屬性,再然后我
想在MT()函數中添加該函數執行多少次時,程序編不過去了.因為const修飾的函數里面的所有值都不能修改.
class AA{public:void MT() const{i++;cout << "hehe";cout << "執行了" << i << "次該程序";} private:int i = 0;};
但是這樣編不過去啊,因為MT()函數為const函數,所以不能修改i的值,但是如果我在這里使用mutable關鍵字的
話,現在我們把i加上mutable關鍵字,這樣它永遠就是一個可變的了. 來我們加上去試一試,
class AA{public:void MT() const{i++;cout << "hehe" << " ";cout << "執行了" << i << "次該程序" << endl;;} private:mutable int i = 0;}; int main(){AA a;a.MT();a.MT();a.MT();a.MT();return 0;}
運行結果:
這就是mutable的最簡單的一個應用,以后可以根據需求來使用~
volatile
為什么使用volatile ?C/C++中的 volatile 關鍵字 和const對應,用來修飾變量,通常用于建立語言級別的memory barrier。這是BS在“The C++ Programming Language”對volatile修飾詞的解釋:A volatile specifier is a hint to a compiler that an object may change its value in ways not specified by the language so that aggressive optimizations must be avoided.
volatile 關鍵字是一種類型修飾符,用它聲明的類型變量表示可以被某些編譯器未知的因素更改,比如:操作系統,硬件或者其他線程等。
遇到這個關鍵字聲明的變量,編譯器對訪問該變量的代碼就不再進行優化,從而可以提供對特殊地址的穩定訪問。聲明時語法:int volatile vInt; 當要求使用 volatile 聲明的變量的值的時候,系統總是重新從它所在的內存讀取數據,即使它前面的指令剛剛從該處讀取過數據。而且讀取的數據立刻被保存。例如:
volatile int i=10; int a = i; ... // 其他代碼,并未明確告訴編譯器,對 i 進行過操作 int b = i; volatile int i=10;int a = i;...// 其他代碼,并未明確告訴編譯器,對 i 進行過操作int b = i;
volatile 指出 i 是隨時可能發生變化的,每次使用它的時候必須從 i的地址中讀取,因而編譯器生成的匯編代碼會重新從i的地址讀取數據放在 b 中。而優化做法是,由于編譯器發現兩次從 i讀數據的代碼之間的代碼沒有對 i 進行過操作,它會自動把上次讀的數據放在 b 中。而不是重新從 i 里面讀。這樣以來,如果 i是一個寄存器變量或者表示一個端口數據就容易出錯,所以說 volatile 可以保證對特殊地址的穩定訪問。注意,在 VC 6 中,一般調試模式沒有進行代碼優化,所以這個關鍵字的作用看不出來。下面通過插入匯編代碼,測試有無 volatile 關鍵字,對程序最終代碼的影響:
輸入下面的代碼:
#include <stdio.h> void main() { int i = 10; int a = i; printf("i = %d", a); // 下面匯編語句的作用就是改變內存中 i 的值 // 但是又不讓編譯器知道 __asm{ mov dword ptr [ebp-4], 20h } int b = i; printf("i = %d", b); } #include <stdio.h> void main(){ int i = 10; int a = i; printf("i = %d", a); // 下面匯編語句的作用就是改變內存中 i 的值 // 但是又不讓編譯器知道 __asm{ mov dword ptr [ebp-4], 20h } int b = i; printf("i = %d", b);}
然后,在 Debug 版本模式運行程序,輸出結果如下:
i = 10i = 32然后,在 Release 版本模式運行程序,輸出結果如下:
i = 10i = 10輸出的結果明顯表明,Release 模式下,編譯器對代碼進行了優化,第二次沒有輸出正確的 i 值。下面,我們把 i 的聲明加上 volatile 關鍵字,看看有什么變化:
#include <stdio.h> void main() { volatile int i = 10; int a = i; printf("i = %d", a); __asm { mov dword ptr [ebp-4], 20h } int b = i; printf("i = %d", b); } #include <stdio.h> void main(){ volatile int i = 10; int a = i; printf("i = %d", a); __asm { mov dword ptr [ebp-4], 20h } int b = i; printf("i = %d", b);}
分別在 Debug 和 Release 版本運行程序,輸出都是:
i = 10i = 32
這說明這個 volatile 關鍵字發揮了它的作用。其實不只是“內嵌匯編操縱棧”這種方式屬于編譯無法識別的變量改變,另外更多的可能是多線程并發訪問共享變量時,一個線程改變了變量的值,怎樣讓改變后的值對其它線程 visible。一般說來,volatile用在如下的幾個地方: 1) 中斷服務程序中修改的供其它程序檢測的變量需要加volatile; 2) 多任務環境下各任務間共享的標志應該加volatile; 3) 存儲器映射的硬件寄存器通常也要加volatile說明,因為每次對它的讀寫都可能由不同意義;
2.volatile 指針
和 const 修飾詞類似,const 有常量指針和指針常量的說法,volatile 也有相應的概念:
修飾由指針指向的對象、數據是 const 或 volatile 的:
const char* cpch; volatile char* vpch; const char* cpch;volatile char* vpch;
注意:對于 VC,這個特性實現在 VC 8 之后才是安全的。指針自身的值——一個代表地址的整數變量,是 const 或 volatile 的:
char*const pchc; char*volatile pchv; char*const pchc;char*volatile pchv;
注意:
(1) 可以把一個非volatile int賦給volatile int,但是不能把非volatile對象賦給一個volatile對象。
(2) 除了基本類型外,對用戶定義類型也可以用volatile類型進行修飾。(3) C++中一個有volatile標識符的類只能訪問它接口的子集,一個由類的實現者控制的子集。用戶只能用const_cast來獲得對類型接口的完全訪問。此外,volatile向const一樣會從類傳遞到它的成員。
3. 多線程下的volatile 有些變量是用volatile關鍵字聲明的。當兩個線程都要用到某一個變量且該變量的值會被改變時,應該用volatile聲明,該關鍵字的作用是防止優化編譯器把變量從內存裝入CPU寄存器中。如果變量被裝入寄存器,那么兩個線程有可能一個使用內存中的變量,一個使用寄存器中的變量,這會造成程序的錯誤執行。volatile的意思是讓編譯器每次操作該變量時一定要從內存中真正取出,而不是使用已經存在寄存器中的值,如下:
volatile BOOL bStop = FALSE; // 在一個線程中: while( !bStop ) { ... } bStop = FALSE; return; //在另外一個線程中,要終止上面的線程循環: bStop = TRUE; while( bStop ); //等待上面的線程終止, volatile BOOL bStop = FALSE; // 在一個線程中: while( !bStop ) { ... } bStop = FALSE; return; //在另外一個線程中,要終止上面的線程循環: bStop = TRUE; while( bStop ); //等待上面的線程終止,
如果bStop不使用volatile申明,那么這個循環將是一個死循環,因為bStop已經讀取到了寄存器中,寄存器中bStop的值永遠不會變成FALSE,加上volatile,程序在執行時,每次均從內存中讀出bStop的值,就不會死循環了。這個關鍵字是用來設定某個對象的存儲位置在內存中,而不是寄存器中。因為一般的對象編譯器可能會將其的拷貝放在寄存器中用以加快指令的執行速度,例如下段代碼中:
在此段代碼中,nMyCounter的拷貝可能存放到某個寄存器中(循環中,對nMyCounter的測試及操作總是對此寄存器中的值進行),但是另外又有段代碼執行了這樣的操作:nMyCounter -= 1;這個操作中,對nMyCounter的改變是對內存中的nMyCounter進行操作,于是出現了這樣一個現象:nMyCounter的改變不同步。
下面是volatile變量的幾個例子:
1.并行設備的硬件寄存器(如:狀態寄存器
2.一個中斷服務子程序中會訪問到的非自動變量(Non-automatic variables)
3.多線程應用中被幾個任務共享的變量
看下面例題:
int square(volatile int *ptr){ return *ptr * *ptr;}
這個程序有什么問題嗎? 如果我們不去關心volatile關鍵字的話,那么這個程序你怎么看都會覺得沒多大問題.但是這里
面問題大這ne, 首先參數聲明為volatile就是表明*ptr可能會隨時改變.上述代碼運行時,編譯器可能產生這樣的代碼:
int square(volatile int *ptr){ int a,b; a = *ptr; b = *ptr; return a * b;}
因為你的*ptr是隨時都可以意想不到的變化,所以有可能a*b的時候,a b的值不相同. 這樣你就得到一個錯誤的結果
改正后的程序:
int square(volatile int *ptr){ int a; a = *ptr; return a * a;}
第二個問題,看如下代碼:
#include<iostream> #include<Windows.h> #include<assert.h> using namespace std; int main() { const int a = 2; int *p = const_cast<int*>(&a); *p = 3; cout << a << endl; system("pause"); return 0; }
我們有理由的認為在內存當中a的值被修改為3,但是結果呢? 我們來看一看
這不科學啊?? 我們再打開監視窗口看一下a的值.
我們都知道監視窗口看到的都是從內存當中拿到的,但是為什么內存當中為3,打印出來就是2呢? 我來解釋一下.
C++編譯器具有優化功能,當你定一個const的常量的時候,系統覺得它不會被改變了,于是做一個優化把該常量存到寄
存器當中,下次訪問的過程更快速一點. 所以當顯示窗口讀取數據的時候,他會直接去寄存器當中讀取數據.而不是去
內存,所以導致我們明明該掉了a的值,卻打印不出來.
這個時候該我們的volatile出馬了,往i前面加一個volatile之后就會解決這個問題。
以上是“C++中如何使用const、volatile、mutable關鍵字”這篇文章的所有內容,感謝各位的閱讀!希望分享的內容對大家有幫助,更多相關知識,歡迎關注億速云行業資訊頻道!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。