c cin.get()的用法小結_C/C++每日一問求職筆試常考的extern、const、volatile三個關鍵字...
技術標籤:c cin.get()的用法小結c++全域性變數c++全域性變數怎麼定義c函式引用全域性變量出錯undefined referencec語言 externc語言volatile
C/C++每日一問
點選上方「嵌入式龍憨憨」,選擇「置頂/星標公眾號」第一時間檢視程式設計筆記!
特別設定【每日一問】專欄,對專業課--C/C++的複習的學習成果進行實踐檢驗以及知識拓展,裂變自己接觸和學習過的知識及技巧;主要是理論和實踐相結合,以基礎知識為主,實踐操作為輔,保證學習效果,和大家一起回顧知識,共同進步,加油!
特別提示:【每日一問】欄目包括但不限於【今日主題】、【實踐演練】、【知識裂變】等模組,內容比較基礎,適合新手學習以及熟手進行知識回顧,大神勿噴,請自動繞道,謝謝!
1
今日主題
在校招以及社招應聘嵌入式軟體工程師、微控制器軟體工程師、C/C++軟體工程師等崗位的C語言筆試中,大概有70-80%的機率遇到這個題目:
說說extern、const、volatile三個關鍵字的作用。
在微控制器實際應用中,一般使用“extern”比較多,另外兩個關鍵字使用較少,很多人可能都沒接觸過,但是這仍然是我們需要重點掌握的。
首先我們先了解一下三個關鍵字的英文釋義:
extern:外面的;外來的;
從釋義我們可以瞭解到extern關鍵字所修飾的內容是一個外部的,不是本檔案當中的;
const:恆定的;不變的;恆量;常數;
從釋義我們可以瞭解到const關鍵字所修飾的內容是一個不能改變的
volatile:易變的;不穩定的;
從釋義我們可以瞭解到volatile關鍵字所修飾的內容是一個容易被改變的,隨時可能發生變化的。
從字面意思我們已經能大概瞭解到它們所起的一個作用,接下來我們通過實際講解進一步進行了解它們的作用。
2
實踐演練
1、extern關鍵字
1.1、extern放在全域性變數或者函式之前,表示全域性變數或者函式的定義在別的檔案中,提示編譯器遇到此變數和函式時在其他模組中尋找其定義;即所謂的全域性宣告。
比如:
externinti;//全域性變數宣告extern void led();// 全域性函式宣告
如果全域性變數不在檔案的開頭定義,有效的作用範圍將只限於其定義處到檔案結束
在標頭檔案中: extern int i;它的作用就是宣告全域性變數或函式的作用範圍的關鍵字,其宣告的函式和變數可以在本模組或其他模組中使用,記住它是一個宣告不是定義。
也就是說B模組如果引用A模組中定義的全域性變數或函式時,它只要包含A模組的標頭檔案即可,在編譯階段,模組B雖然找不到該函式或變數,但它不會報錯,它會在連線時從模組A生成的目的碼中找到此函式。
接下來我們進行具體舉例:
在我們實際微控制器應用程式設計中,如果你是模組化程式設計的話,程式不全是寫在main.c中的,每個模組對應了一個.c和.h檔案,比如led.c和led.h,led.c檔案中定義了一個函式void led();和一個全域性變數i,具體如下:
// 檔案led.cinti=0;void led(){ i = 1;}
此時,你在led.h中進行函式宣告和定義宣告:
//檔案led.h#ifndef__LED_H__#define __LED_H__int i ;voidled();#endif
此時你覺得可以在mian.c中呼叫led.c中的函式和變數i了,在mian.c中包含標頭檔案,然後開始呼叫:
//檔案main.c#include"led.h"int main(){i=8; led(); return0;}
然而編譯後會報錯,原因是全域性變數i的作用域是led.c,它並不是相對於整個工程檔案的全域性,你並沒有進行變數外部宣告就在main.c檔案中呼叫,同時檔案中沒有定義此變數,編譯器是找不到這個變數的。所以就算在led.h中聲明瞭還是無法呼叫,需要用extern進行修飾。
此時在main.c檔案或者在led.h中通過extern修飾變數進行外部宣告,如:extern int i;
告訴編譯器這個變數:“你現在編譯的檔案中,有一個識別符號雖然沒有在本檔案中定義,但是它是在別的檔案中定義的全域性變數,你要放行!”
//檔案led.h#ifndef__LED_H__#define __LED_H__extern int i ;voidled();#endif
1.2、extern的另一種用法:extern “C”
extern "C"的主要作用就是為了能夠正確實現C++程式碼呼叫其他C語言程式碼。加上extern "C"後,會指示編譯器這部分程式碼按C語言(而不是C++)的方式進行編譯。
由於C++支援函式過載,因此編譯器編譯函式的過程中會將函式的引數型別也加到編譯後的程式碼中,而不僅僅是函式名;而C語言並不支援函式過載,因此編譯C語言程式碼的函式時不會帶上函式的引數型別,一般只包括函式名。
extern "C"的使用要點總結:
1,可以是如下的單一語句:
extern "C" double sqrt(double);
2,可以是複合語句, 相當於複合語句中的宣告都加了extern "C"
extern "C"{doubleadd(double,double); int min(int, int);}
3,可以包含標頭檔案,相當於標頭檔案中的宣告都加了extern "C"
extern "C"{ #include <string>}
此功能的用法主要用在C++和C混合使用的情況下,比如STM32和DSP都是可用C++進行程式開發的,但是有很多庫函式以及程式都是C語言開發的,所以為了能夠有些程式按照C語言的編譯規則進行編譯就需要加上extern "C"。
注意事項:
不可以將extern "C" 新增在函式內部
如果函式有多個宣告,可以都加extern "C", 也可以只出現在第一次宣告中,後面的宣告會接受第一個連結指示符的規則。
除extern "C", 還有extern "FORTRAN" 等。
2、const關鍵字
使用const修飾的變數、物件等,意味著它的資料可以被訪問,不能被修改,即只讀性質。注意:是隻讀,可以修飾變數,一定不要說是常量!
只讀意味著使用const修飾時,一定要初始化,若不初始化,後面就無法對其初始化。
接下來看看一下幾行程式碼:
constintp=1;intconstp=1;const int *p;int * const p;int const * p const;
1、前2行程式碼的作用一樣,定義變數p為常整型數,意味著不可修改;
2、第4行程式碼,定義一個指向常整形變數的指標變數p,意味著指標變數p指向的變數不可修改,指標變數p可以改變指向;
3、第6行程式碼,定義一個指向整形變數的常指標變數p,意味著指標變數p可以修改,指標變數p指向的變數不可修改;
4、第8行程式碼,定義一個指向常整形變數的常指標變數p,意味著指標指向的整型數是不可修改的,同時指標也是不可修改的。
小結:
1、const用來說明所定義的變數是隻讀的,這些在編譯期間完成,編譯器可能使用常量直接替換掉對此變數的引用;
2、合理地使用關鍵字const可以使編譯器很自然地保護那些不希望被改變的引數,防止其被無意的程式碼修改。簡而言之,這樣可以減少bug的出現;
3、對指標來說,可以指定指標本身為const,也可以指定指標所指的資料為const,或二者同時指定為const;
4、在一個函式宣告中,const可以修飾形參,表明它是一個輸入引數,在函式內部不能改變其值;
5、對於類的成員函式,若指定其為const型別,則表明其是一個常函式,不能修改類的成員變數;
3、volatile關鍵字
定義:volatile提醒編譯器它後面所定義的變數隨時都有可能改變,因此編譯後的程式每次需要儲存或讀取這個變數的時候,告訴編譯器對該變數不做優化,都會直接從變數記憶體地址中讀取資料,從而可以提供對特殊地址的穩定訪問。
3.1、告訴編譯器不能做任何優化
比如要往某一地址送兩指令:
int *ip =...; //裝置地址*ip = 1; //第一個指令*ip = 2; //第二個指令
以上程式compiler可能做優化而成:
int *ip = ...;*ip = 2;
結果第一個指令丟失。如果用volatile, compiler就不允許做任何的優化,從而保證程式的原意:
volatile int *ip = ...;*ip = 1;*ip = 2;
即使你要compiler做優化,它也不會把兩次賦值語句簡化為一句。它只能做其它的優化。
3.2、直接從記憶體中讀取
用volatile定義的變數會在程式外被改變,每次都必須從記憶體中讀取,而不能重複使用放在cache(高速緩衝儲存器)或暫存器中的備份。
由於訪問暫存器要比訪問記憶體單元快的多,編譯器在存取變數時,為提高存取速度,編譯器優化有時會先把變數讀取到一個暫存器中;以後再取變數值時就直接從暫存器中取值。
當變數值在本執行緒裡改變時,會同時把變數的新值copy到該暫存器中,以便保持一致。
當該暫存器在因為別的執行緒改變了值,但原變數的值不會改變,從而造成應用程式讀取的值和實際的變數值不一致。
在編寫多執行緒的程式時,同一個變數可能被多個執行緒修改,而程式通過該變數同步各個執行緒,如下程式:
//執行緒1中的函式void dothing(void){...//其他語句 while (1) { if(i) { dosomething(); }}/*執行緒2中的函式 */voidISR(void){ i=1;}
程式的本意是希望通過ISR在中改變i的值,在dothing函式中呼叫dosomething函式;但是,由於編譯器並不知道在別的地方修改了i,因此可能只執行一次對從i到某暫存器的讀操作,然後每次if判斷都只使用這個暫存器裡面的“i副本”,導致dosomething永遠也不會被呼叫。
小結:volatile變數的幾個場景:
1、中斷服務程式中修改的供其它程式檢測的變數需要加volatile;
2、多執行緒應用中被幾個任務共享的變數;
3、並行裝置的硬體暫存器。
3
知識裂變
1、編譯器優化
記憶體訪問速度遠不及CPU處理速度,為提高機器整體效能,作如下處理:
1)硬體級別的優化:在硬體上引入硬體快取記憶體Cache,加速對記憶體的訪問。另外在現代CPU中指令的執行並不一定嚴格按照順序執行,沒有相關性的指令可以亂序執行,以充分利用CPU的指令流水線,提高執行速度。
2)軟體一級的優化:一種是在編寫程式碼時由程式設計師優化,另一種是由編譯器進行優化。編譯器優化常用的方法有:將記憶體變數快取到暫存器;調整指令順序充分利用CPU指令流水線,常見的是重新排序讀寫指令。
編譯器有一種技術叫做資料流分析,分析程式中的變數在哪裡賦值、在哪裡使用、在哪裡失效,分析結果可以用於常量合併,常量傳播等優化,進一步可以消除一些程式碼。但有時這些優化不是程式所需要的,這時可以用volatile關鍵字禁止做這些優化。
2、關於volatile的幾個問題
1)一個引數既可以是const還可以是volatile嗎?解釋為什麼。
2)一個指標可以是volatile 嗎?解釋為什麼。
3)下面的函式有什麼錯誤:
int square(volatile int *ptr){ return *ptr * *ptr;}
下面是答案:
1)是的。一個例子是隻讀的狀態暫存器。它是volatile因為它可能被意想不到地改變。它是const因為程式不應該試圖去修改它。
2)是的。儘管這並不很常見。一個例子是當一箇中服務子程式修該一個指向一個buffer的指標時。
3)這段程式碼的有個惡作劇。這段程式碼的目的是用來返指標*ptr指向值的平方,但是,由於*ptr指向一個volatile型引數,編譯器將產生類似下面的程式碼:
intsquare(volatileint*ptr){ int a,b; a = *ptr; b = *ptr; return a * b;}
由於*ptr的值可能被意想不到地該變,因此a和b可能是不同的。結果,這段程式碼可能返不是你所期望的平方值!正確的程式碼如下:
long square(volatile int *ptr){ int a; a = *ptr; return a * a;}
3、const與define的區別
1)編譯器處理方式不同
define在預編譯階段進行替換;
const在編譯時確定其值。
2)型別檢查
define無型別,不會進行型別安全檢查,可能會發生錯誤;
const有資料型別,編譯時會進行型別檢查。
3)記憶體空間
define不分配記憶體,有多少次使用就進行多少次替換,在記憶體中會有多個拷貝,消耗記憶體大;
const在靜態儲存區中分配記憶體,在程式執行過程中只有一次拷貝。
關注不迷路分享技術,碼字不易
轉發、在看就是我最大的動力
置頂/星標公眾號,和我一起學習吧