1.儲存型別
標準c語言為變數、常量、函式定義了4種儲存型別:extern,auto,static,register,它們分別用乙個關鍵字(儲存型別說明符)來說明。這4種儲存型別可分為兩種生存期限:永久的(即整個程式執行期間都存在)【extern和static】和臨時的(即暫時儲存在堆疊和暫存器中)【auto和register】。
全域性變數和全域性函式預設儲存型別為extern,能夠被定義在它們之後的同乙個編譯單元內的函式所呼叫,如果變數和函式被顯式地加上extern宣告,那麼其他編譯單元中的函式也能呼叫它們。若變數和函式被顯式地加上static宣告,那麼它們具有static儲存型別,只能被同乙個編譯單元內的函式呼叫。
區域性變數預設auto儲存型別,除非用static和register來定義。但它們的作用域都是程式塊作用域,連線型別都是內連線,進入函式時建立,退出函式時銷毀。register和auto只能用於宣告區域性變數和區域性常量。
全域性常量預設儲存型別為static,除非在定義了它的編譯單元之外的其他編譯單元中顯示地用extern宣告,否則不能被訪問。
區域性符號常量預設auto,除非顯式定義為static或register。
函式的形參是區域性變數,一般為auto。
用register修飾的變數會被直接載入到cpu暫存器中,提高程式執行效率。
2.作用域規則
作用域是乙個識別符號能夠起作用的程式範圍。
標準c語言中,這些範圍包括檔案、函式、程式塊、函式原型。
標準c++中,作用域型別還有類、名字空間。
標號是具有函式作用域的唯一一種識別符號,能夠在函式體內的任何乙個地方被訪問到,一般用在goto語句中。
區域性變數具有程式塊塊作用域(由{}決定),建立在函式堆疊上,但是由於在巢狀的程式塊中內層程式塊定義的變數不能在其外層程式塊中訪問,即如果在函式中巢狀的程式塊可以定義相同名字的變數,內層變數會遮蔽外層的同名變數。
當區域性變數與乙個全域性變數同名時,在函式內部將遮蔽該全域性變數。此時在函式內部可用一元作用域解析運算子(::)來引用全域性變數,例如:::g_icount++。
在任何函式、型別定義及名字空間定義之外的識別符號具有檔案作用域,包括函式定義、型別定義本身,它們從其定義位置開始直到所在檔案結尾都是可見的,也叫檔案級的識別符號。
函式原型中的形參名稱是具有函式原型作用域的唯一一種識別符號,不會與其他函式的同名形參名稱衝突,該形參名會被編譯器忽略。但是函式定義中的形參是區域性變數,具有程式塊作用域。
c++的類是有行為能力的抽象資料型別(adt),在類作用域中,類的非靜態成員函式可以直接訪問類的其他任何成員;但在類的作用域外,只能通過類的物件、物件指標及物件引用來訪問類成員。成員函式中定義的區域性變數具有程式塊作用域,會遮蔽同名的類的資料成員,此時訪問同名的資料成員方法:this->flag=flag;或classname::flag=flag;
3.連線型別
表明了乙個識別符號的可見性,分為
外連線:識別符號能夠在其他編譯單元中或者在定義它的編譯單元中的其他範圍內被呼叫。需要分配執行時的儲存空間。例如:
void f(bool flag) // 函式定義是外連線的
int g_int; //全域性變數g_int是外連線的
extern const int max_length=1024;// max_length變成外連線的
namespace ns_h
內連線:識別符號能夠在定義它的編譯單元中的其他範圍內被呼叫。但不能在其他編譯單元中被呼叫。例如:
static void f2() //f2為內連線的
union //匿名聯合的成員為內連線的
;class me; //me是內連線的
const int max_length=1024; // 常量是內連線的
typedef long integer; //typedef為內連線的
無連線:僅能夠在宣告它的範圍內被呼叫。例如:
void f()
; //區域性類是無連線的,具有程式塊作用域
}4.**總結
程式元素的儲存型別、作用域、生存期限及連線型別
程式元素
儲存型別
作用域生存期限
連線型別
全域性adt/udt定義 檔案
內連線巢狀的adt/udt定義 類
外連線區域性adt/udt定義
程式塊無連線
非靜態全域性函式和全域性變數
extern 檔案
永久外連線
靜態全域性函式和全域性變數
static 檔案
永久內連線
區域性非靜態變數/常量
auto
程式塊 臨時
無連線區域性靜態變數/常量
static
程式塊 永久
無連線靜態全域性常量
static 檔案
永久內連線
非靜態全域性常量
c和c++有所不同
類的靜態成員
static 類
永久內連線
類的非靜態成員 類
內連線名字空間的成員
不確定名字空間
不確定外連線
外部函式原型 檔案
內連線程式塊中的函式原型
程式塊內連線
巨集定義 檔案
內連線
5.使用斷言
斷言(assert)語義:如果表示式的值為0(假),則輸出錯誤訊息並終止程式的執行(一般還會出現對話方塊,說明在什麼地方引發了assert);如果表示式為真,則不進行任何操作。因此斷言失敗就表明程式存在乙個bug。
c++/c的巨集assert(expression)就是斷言,表示式為假時,呼叫庫函式abort()終止程式。
程式一般分為debug版本和release版本,debug版本用於內部除錯,release版本發行給使用者使用。由於assert(expression)的巨集體全部被條件編譯偽指令#ifdef_debug 和#endif所包含,因此assert()只在debug版本內有效。
在函式入口處,建議使用斷言來檢查引數的有效性。例如記憶體拷貝函式mencpy,如果assert的表示式為假,那麼程式就會中止。
void *memcpy(void *pvto,const void*pvfrom,size_t size)
return pvto;
}但上述情況也不能保證萬無一失,如果給memcpy()傳入兩個未初始化的野指標(位址不為0但是沒有指向合法的記憶體塊),那麼assert()就失去了作用。
使用斷言的目的是捕捉在執行時不應該發生的非法情況,不同於錯誤情況,後者是程式執行過程中自然存在的並且是一定要做出主動處理的。
例如用malloc申請動態記憶體時,如果系統沒有足夠的記憶體可用,那麼malloc返回null。動態申請記憶體失敗不是非法情況,而是錯誤情況。所以要用if語句捕捉錯誤情況並給出錯誤處理**,而不應該用assert。
//錯誤的示例
int *pbuf=(int*)malloc(sizeof(int)*1000);
assert(pbuf!=null);//錯誤的使用assert
...//do something
//正確的示例
int *pbuf=(int*)malloc(sizeof(int)*1000);
if(pbuf==null)
else
要區分斷言(assert)和跟蹤語句(tracer)的不同,後者是指一些用於報告程式執行過程中當前狀態的輸出語句,但它們並不一定就是bug。
6.使用cons提高函式的健壯性
被const修飾的東西都受到c++/c語言實現的靜態型別安全檢查機制的保護,可以預防意外修改,能提高程式的健壯性。
例如:void stringcopy(char *strdest,const char *strsrc);
給strsrc加上const修飾符後,如果函式體內的語句試圖改動strsrc指向的記憶體單元,編譯器將指出錯誤。
如果還想保護指標本身,則宣告指標本身為常量,防止該指標的值被改變:
void outoutstring(const char* const pstr);
如果輸入引數採用值傳遞,一般不用const修飾,因為函式是用實參的拷貝初始化形參,及時在函式內部修改的該引數,改變的也只是堆疊上的拷貝而不是實參。
對於adt/udt的輸入引數,應該將「值傳遞」改為「const&傳遞」,提高效率。對於基本資料型別的輸入引數,不要這樣改,會費解。
如果給「指標傳遞」的函式返回值加const修飾符,那麼函式返回值是一種契約性常量,不能被直接修改,並且該返回值只能被賦值給加const修飾的同型別指標(除非強制轉型)。
例如函式:const char *getstring(void);
呼叫時:const char *str=getstring();
C,C 學習筆記
1.求a的n次方 標頭檔案pow a,n a,n都為double 2.關於指標的一些小測試 includeusing namespace std int main int a 8 int m 3 cout 3.sort位於 algorithm 中,sort p,p n 預設公升序排列。4.inclu...
C C 學習筆記 三
到目前為止,我們看到的所有函式中,傳遞到函式中的引數全部是按數值傳遞的 by value 也就是說,當我們呼叫乙個帶有引數的函式時,我們傳遞到函式中的是變數的數值而不是變數本身。但在某些情況下你可能需要在乙個函式內控制乙個函式以外的變數。要實現這種操作,我們必須使用按位址傳遞的引數 argument...
C C 學習筆記 四
到目前為止,我們定義的所有函式都是在它們第一次被呼叫 通常是在main中 之前,而把main 函式放在最後。如果重複以上幾個例子,但把main 函式放在其它被它呼叫的函式之前,你就會遇到編譯錯誤。原因是在呼叫乙個函式之前,函式必須已經被定義了,就像我們前面例子中所做的。但實際上還有一種方法來避免在m...