【C/C++高质量编程 笔记】
1.C語言標(biāo)準(zhǔn)的本質(zhì):標(biāo)準(zhǔn)C語言沒有提供實現(xiàn),只是定義了標(biāo)準(zhǔn)的函數(shù)接口,所有工作都是通過庫函數(shù)完成的。
2.什么是語言實現(xiàn):
具體實現(xiàn)一種語言的各種特征并支持特定編程模式的技術(shù)和工具,具體說就是編譯器和連接器或者是解釋器。
3. 基于應(yīng)用程序框架(比如MFC),生成的源代碼往往沒有main(),并不是說這些程序不需要main函數(shù),而只是Application Framework將main的實現(xiàn)隱藏起來了,并且它的實現(xiàn)具有固定的模式,所以不需要程序員來編寫,在應(yīng)用程序的連接階段,框架會將包含main() 實現(xiàn)的Library加進來一起連接。
4.內(nèi)部名稱:C/C++都會按照特定的規(guī)則把程序員定義的標(biāo)識符轉(zhuǎn)換為相應(yīng)的內(nèi)部名稱——在前邊 添加下劃線 " _ ",在C語言中,所有函數(shù)不是局部于編譯單元(文件作用域)的static函數(shù),就是具有extern連接類型和global作用域的全局函數(shù),從唯一識 別函數(shù)上看并沒有大的不同,但在C++中,允許用戶在不同的作用域定義同名的函數(shù),作用域不單單是文件,可能是class namespace等,甚至在同一作用域中也可以定義同名的函數(shù)——重載。在源碼級別,通過它們各自的對象和成員標(biāo)識符區(qū)分,但是在連接器層面,所有函數(shù) 都是全局函數(shù),能夠用來區(qū)分不同函數(shù)調(diào)用的除了作用域外就是函數(shù)名稱了,C++中使用Name-Mangling避免連接二義性。
5.變量初始化需要注意的事項:
a. 在C++/C中,全局變量(extern或static的)存放在程序的靜態(tài)數(shù)據(jù)區(qū)中,在程序進入main()之前創(chuàng)建,在main()結(jié)束后銷毀,因此 在代碼中根本沒有機會初始化它們,語言及其實現(xiàn)提供了一個默認(rèn)的全局初始化器0——沒有明確初始化全局變量則將0轉(zhuǎn)換為所需類型來完成初始化。函數(shù)內(nèi)?的static局部變量和類的static數(shù)據(jù)成員都具有static存儲類型,因此最終也被移動到程序的靜態(tài)數(shù)據(jù)區(qū)中,因此也會默認(rèn)初始化為0.除非你明確的提供了初值。
b.在一個編譯單元中定義的全局變量的初始值不要依賴定義于另一個編譯單元中的全局變量的初始值,因為編譯器和連接器無法確定兩個編譯單元連接在一起時哪一個全局變量的初始化優(yōu)先于另一個編譯單元的全局變量的初始化。
c.存儲在靜態(tài)數(shù)據(jù)區(qū)的變量會在程序剛開始運行時就完成初始化,也是唯一的一次初始化。共有兩種變量存儲在靜態(tài)存儲區(qū):全局變量和static變量,只不過和全局變量比起來,static可以控制變量的可見范圍,說到底static還是用來隱藏的。這兩種變量都是保持變量內(nèi)容持久性的方法。它們默認(rèn)初始化都為0。
6.區(qū)別編譯時和運行時的不同
編譯時指的是編譯預(yù)處理器、編譯器和連接器工作的階段,只對基本的規(guī)范和語法進行處理,而像容器越界訪問、虛函數(shù)動態(tài)決議、函數(shù)動態(tài)連接、動態(tài)內(nèi)存分配等需要在運行時才能確定的問題是運行時。
下邊這個例子就說明了C++的訪問控制策略是為了防止意外時間而不是防止對編譯器的惡意欺騙。
?* =====================================================================================
?*
?*?????? Filename:? test.cpp
?*
?*??? Description:? 區(qū)分編譯時和運行時的不同
?*
?*??????? Version:? 1.0
?*??????? Created:? 02/26/2010 09:24:06 AM
?*?????? Revision:? none
?*?????? Compiler:? gcc
?*
?*???????? Author:? gnuhpc (http://www.gnuhpc.info), warmbupt@gmail.com
?*??????? Company:? IBM CDL
?*
?* =====================================================================================
?*/
#include <iostream>
using namespace std;
class Base
{
??? public:
??? ??? virtual void Say(){
??? ??? ??? cout<<"Base::Say()was invoked!/n";
??? ??? }
}; /* -----? end of class Base? ----- */
class Derived:public Base{
??? private:
??? ??? virtual void Say(){
??? ??? ??? cout << "Derived::Say() was invoked!/n";
??? ??? }
};
int main(int argc, char *argv[])
{
??? Base *p = new Derived;
??? p->Say();
}
輸入:Derived::Say() was invoked!? 這違背了private 本來的意愿。
7.字節(jié)是內(nèi)存編制的最小單位,所以最小的對象(包括空對象)也至少會占據(jù)一個字節(jié)的內(nèi)存空間。一個bool變量也占據(jù)了1字節(jié)內(nèi)存,只是浪費了
8.void 是空類型,意思是這種類型的大小無法確定,不存在void類型的對象,void指針可以作為通用指針,因為它可以指向任何類型的對象。void類型指針和 NULL指針的區(qū)別,NULL是可以賦值給任何類型的指針的值0,在C中它為(void *)0,而在C++中,由于允許從0到任何指針類型的隱式轉(zhuǎn)化,NULL就是整數(shù)0.一個Void *類型的指針是一個合法的指針,常用于函數(shù)參數(shù)來傳遞一個函數(shù)與其調(diào)用者約定好類型的對象地址,如線程函數(shù)。在C中允許任何非void類型指針和void 類型的指針之間進行直接的相互轉(zhuǎn)化,但是在C++中只允許任何類型的指針向void類型的指針轉(zhuǎn)化,而不允許反過來將void類型的指針直接指派給任何非 void類型指針,除非進行強制轉(zhuǎn)換。這樣就避免了內(nèi)存擴張和截斷的安全問題。
9.默認(rèn)類型:C中為int,C++沒有默認(rèn)類型,但是在模板中有“默認(rèn)類型參數(shù)”的概念。
10.高低地址存放:
所 謂自然對齊,基本數(shù)據(jù)類型(主要是short、int和double)的變量不能簡單的存儲在內(nèi)存中的任意地址處,它們的起始地址必須能夠被它們的大小整 除。RiSC的都是Big Endian存儲,即高字節(jié)高字在低地址存放,要求自然對齊。而Intel的都是Little Endian,即高字節(jié)高字在高地址存放,不要求自然對齊。
11.類型轉(zhuǎn)換:
一般占用內(nèi)存比較少的類型會隱式的轉(zhuǎn)換為表達(dá)式中占 用內(nèi)存最多的操作數(shù)類型,類型轉(zhuǎn)換并不是改變原來的類型和值,而是生成了新的臨時變元:char is-a int, int is-a long, long is-a float, lfoat is-a double.
從內(nèi)存的角度,一個類型轉(zhuǎn)換過的指針?biāo)軌蛟L問的范圍受到其類型的限制,
例如,這實際上就是內(nèi)存的截斷,因為int指針能訪問的范圍小于Double型:
double d1= 1000.25100212;
*pInt =(int*)(&d1);
cout << *pInt <<endl;
注意,這里要區(qū)分值的截斷和內(nèi)存的截斷,下邊是值的截斷:
?double d2 = 10.20;
?int i2 = (int)d2;
?cout << i2 << endl;
而下邊的這個例子就是內(nèi)存的擴張,因為double指針能訪問的范圍大于int型:
int i1 = 1023;
double *pDouble = (double*)(&i1);
cout << *pDouble <<endl;
同理,在OO中,不能把基類的對象,直接轉(zhuǎn)換為派生類對象,無論是直接賦值還是強制轉(zhuǎn)換,因為這不是“自然的”。
12.++ --的效率問題:當(dāng)單獨使用時前置后置都一樣,而當(dāng)復(fù)雜的表達(dá)式中使用時,比如當(dāng)應(yīng)用于用戶定義類型,尤其是大隊想的時候,前置版本會比后置版本效率高許 多,原因是后置版本,比如b=a++, 其實質(zhì)并非某些教科上所寫的“先使用其操作數(shù)的值,然后再進行加1運算”,而是首先創(chuàng)建一個臨時變量temp存儲a的值,然后做a+=1的運算,隨后把 temp的值賦給b,最后銷毀這個臨時變量(若是對象則還會調(diào)用其拷貝構(gòu)造函數(shù)),所有這些是有代價的。所以在可以選擇的情況下,盡量使用前置版本。下邊 就寫一個重載++運算符的例子:
/*
?* =====================================================================================
?*
?*?????? Filename:? test.cpp
?*
?*??? Description: 重載++運算符
?*
?*??????? Version:? 1.0
?*??????? Created:? 02/26/2010 09:24:06 AM
?*?????? Revision:? none
?*?????? Compiler:? gcc
?*
?*???????? Author:? gnuhpc (http://www.gnuhpc.info), warmbupt@gmail.com
?*??????? Company:? IBM CDL
?*
?* =====================================================================================
?*/
#include <iostream>
using namespace std;
class Integer{
??? public:
??? ??? Integer(double data):m_data(data){}
??? ??? Integer& operator++(){
??? ??? ??? cout << "前置版本,返回引用" <<endl;
??? ??? ??? m_data++;
??? ??? ??? return *this;
??? ??? }
??? ??? Integer operator++(int){
??? ??? ??? cout << "后置版本,返回對象的值" <<endl;
??? ??? ??? Integer temp = *this;
??? ??? ??? m_data++;
??? ??? ??? return temp;
??? ??? }
??? ??? int getData(){
??? ??? ??? return m_data;
??? ??? }
??? private:?
??? ??? double m_data;
};
int main(int argc, char *argv[])
{
??? Integer x=1;
??? ++x;
??? cout <<x.getData() <<endl;
??? x++;
??? cout <<x.getData() <<endl;
}
13.bool類型:c++中0->false,而任何非0值為true,所以應(yīng)該總是和false比較。
14.不建議使用==和!=來比較浮點數(shù)是否相等(用abs比較),但是可以直接比較浮點數(shù)誰大誰小。
15.遍歷數(shù)組的效率:
對于多維數(shù)組而言,高效率的遍歷方法是看語言以什么順序來安排數(shù)組元素的存儲空間,我們看看c/c++是用什么方式存儲的:
?* =====================================================================================
?*
?*?????? Filename:? test.cpp
?*
?*??? Description:? C/C++多維數(shù)組存儲是以先行后列的方式存儲的,所以遍歷時外循環(huán)是行,內(nèi)循環(huán)是列效率較高
?*
?*??????? Version:? 1.0
?*??????? Created:? 02/26/2010 09:24:06 AM
?*?????? Revision:? none
?*?????? Compiler:? gcc
?*
?*???????? Author:? gnuhpc (http://www.gnuhpc.info), warmbupt@gmail.com
?*??????? Company:? IBM CDL
?*
?* =====================================================================================
?*/
#include <iostream>
using namespace std;
int main(int argc, char *argv[])
{
??? int a[5][5];
??? for( int i=0 ; i<5 ; i++ )
??? {
??? ??? for( int j=0 ; j<5 ; j++ )
??? ??? {
??? ??? ??? cout << "a[" <<i <<"][" <<j <<"]=" << &a[i][j] <<" ";
??? ??? }
??? ??? cout <<endl;????
??? }
????
}
影響效率的實際上是大數(shù)組遍歷時來回跳轉(zhuǎn)導(dǎo)致的內(nèi)存頁面交換次數(shù)以及cache命中率的高低,而不是循環(huán)次數(shù)本身。
16.循環(huán)體內(nèi)存在邏輯判斷,并且循環(huán)次數(shù)很大時,最好將邏輯判斷移到循環(huán)體外,雖然看起來很羅嗦,但是編譯器可以對循環(huán)進行優(yōu)化處理:
?
17.字面常量,比如char? c ='a', 只能引用不能修改,其保存在程序的符號表中而不是一般的數(shù)據(jù)區(qū),為只讀。在編譯時通常把合并常量的開關(guān)打開可優(yōu)化程序效率。
18. 在標(biāo)準(zhǔn)的C語言中,const符合常量默認(rèn)是extern的,也就是說你不能在兩個或以上的編譯單元中同時定義一個同名的const符號常量,或者把一個 const符號常量定義放在一個頭文件中而多個編譯單元同時包含該文件。但是在標(biāo)準(zhǔn)的C++中,const為內(nèi)連接,可以定義在頭文件中。當(dāng)在不同的編譯 單元中同時包含該文件時,編譯器認(rèn)為它們是不同的符號常量,因為每個編譯單元獨立編譯時分別為它們分配空間,連接時進行合并。
19.標(biāo)準(zhǔn)C++/C中的枚舉常量的值可以很大,比如300000000000000000
20.在C++中應(yīng)該盡可能使用const定義符號常量。
21.C++需要對外公開的常量放在頭文件中,不需要對外公開的常量定義在文件的頭部。為便于管理可以把不同模塊的常量集中存放在一個公用的頭文件中。
22.const?定義的常量在函數(shù)執(zhí)行之后其空間會被釋放,而?static?定義的靜態(tài)常量在函數(shù)執(zhí)行后不會被釋放其空間。
static?表示的是靜態(tài)的。類的靜態(tài)成員函數(shù),成員變量是和類相關(guān)的,不是和類的具體對象相關(guān),即使沒有具體的對象,也能調(diào)用類的靜態(tài)成員函數(shù),成員變量。一般的靜態(tài)函數(shù)幾乎就是一個全局函數(shù),只不過它的作用域限于包含它的文件中。
?
在?c++?中,?static?靜態(tài)成員變量不能在類內(nèi)部初始化。在?c++?中,?const?常量成員變量也不能在類定義處初始化,只能通過構(gòu)造函數(shù)初始化列表進行,并且必須有構(gòu)造函數(shù)。
?
const?數(shù)據(jù)成員只在?某個對象生存期內(nèi)是常量?,?而對于整個類而言卻是可變的?。因為類可以創(chuàng)建多個對象,不同的對象其?const?數(shù)據(jù)成員的值可以不同。所以不能在類聲明中初始化?const?數(shù)據(jù)成員,因為類的對象未被創(chuàng)建時,編譯器不知道const?
數(shù)據(jù)成員的值是什么。
const?數(shù)據(jù)成員的初始化?只能在類的構(gòu)造函數(shù)的初始化表中進行?。?要想建立在整個類中都恒定的常量?,應(yīng)該用類中的枚舉常量來實現(xiàn),或者?static
const。
?
如:
class
Test
{
public:
???? Test():
a(0){}?
?? enum {size1=100, size2 = 200 };
private:
???? const
int a;? //?
只能在構(gòu)造函數(shù)初始化列表中初始化
???? static
int b?;
???? const
static int c; //?與?static const int
c;?相同,可以?在這里定義?(如果以后在類中需要使用該變量的話?).
}
int Test?::?b = 0;
??//?不能以成員列表初始化,不能在定義處初始化,因為不屬于某個對象。
const
int?Test::?c?=?0?;?//?注意:給靜態(tài)成員變量賦值時,不在需要加?static?修飾。但?const?要加。
?
?
在這轉(zhuǎn)載一篇寫的比較清晰的文字:
全局變量/常量幾種方法的區(qū)別(C/C++)?
在討論全局變量之前我們先要明白幾個基本的概念:
1. 編譯單元(模塊):
???
在IDE開發(fā)工具大行其道的今天,對于編譯的一些概念很多人已經(jīng)不再清楚了,很多程序員最怕的就是處理連接錯誤(LINK ERROR),
因為它不像編譯錯誤那樣可以給出你程序錯誤的具體位置,你常常對這種錯誤感到懊惱,但是如果你經(jīng)常使用gcc,makefile等工具在linux或者嵌
入式下做開發(fā)工作的話,那么你可能非常的理解編譯與連接的區(qū)別!當(dāng)在VC這樣的開發(fā)工具上編寫完代碼,點擊編譯按鈕準(zhǔn)備生成exe文件時,VC其實做了兩
步工作,第一步,將每個.cpp(.c)和相應(yīng).h文件編譯成obj文件;第二步,將工程中所有的obj文件進行LINK生成最終的.exe文件,那么錯
誤就有可能在兩個地方產(chǎn)生,一個是編譯時的錯誤,這個主要是語法錯誤,另一個是連接錯誤,主要是重復(fù)定義變量等。我們所說的編譯單元就是指在編譯階段生成
的每個obj文件,一個obj文件就是一個編譯單元,也就是說一個cpp(.c)和它相應(yīng)的.h文件共同組成了一個編譯單元,一個工程由很多個編譯單元組
成,每個obj文件里包含了變量存儲的相對地址等 。
2. 聲明與定義的區(qū)別
??? 函數(shù)或變量在聲明時,并沒有給它實際的物理內(nèi)存空間,它有時候可以保證你的程序編譯通過,
但是當(dāng)函數(shù)或變量定義的時候,它就在內(nèi)存中有了實際的物理空間,如果你在編譯模塊中引用的外部變量沒有在整個工程中任何一個地方定義的話,
那么即使它在編譯時可以通過,在連接時也會報錯,因為程序在內(nèi)存中找不到這個變量!你也可以這樣理解,
對同一個變量或函數(shù)的聲明可以有多次,而定義只能有一次!
3. extern的作用
??? extern有兩個作用,第一個,當(dāng)它與"C"一起連用時,如: extern "C" void
fun(int a, int b); 則告訴編譯器在編譯fun這個函數(shù)名時按著C的規(guī)則去翻譯相應(yīng)的函數(shù)名而不是C++的,
C++的規(guī)則在翻譯這個函數(shù)名時會把fun這個名字變得面目全非,可能是fun@aBc_int_int#%$?也可能是別的,這要看編譯器的"脾氣"了(不同的編譯器采用的方法不一樣),為什么這么做呢,因為C++支持函數(shù)的重載啊,在這里不去過多的論述這個問題,如果你有興趣可以去網(wǎng)上搜索,相信你可以得到滿意的解釋!
???
當(dāng)extern不與"C"在一起修飾變量或函數(shù)時,如在頭文件中: extern int g_Int;
它的作用就是聲明函數(shù)或全局變量的作用范圍的關(guān)鍵字,其聲明的函數(shù)和變量可以在本模塊活其他模塊中使用,記住它是一個聲明不是定義!也就是說B模塊(編譯
單元)要是引用模塊(編譯單元)A中定義的全局變量或函數(shù)時,它只要包含A模塊的頭文件即可,
在編譯階段,模塊B雖然找不到該函數(shù)或變量,但它不會報錯,它會在連接時從模塊A生成的目標(biāo)代碼中找到此函數(shù)。
??? 如果你對以上幾個概念已經(jīng)非常明白的話,那么讓我們一起來看以下幾種全局變量/常量的使用區(qū)別:
1. 用extern修飾的全局變量
??? 以上已經(jīng)說了extern的作用,下面我們來舉個例子,如:?
??? 在test1.h中有下列聲明:
??? #ifndef TEST1H
??? #define TEST1H
??? extern char g_str[]; // 聲明全局變量g_str
??? void fun1();
??? #endif
??? 在test1.cpp中
??? #include "test1.h"
????
????char g_str[] = "123456"; // 定義全局變量g_str?
????
??? void fun1()
??? {
??????? cout << g_str << endl;
??? }
????
??? 以上是test1模塊, 它的編譯和連接都可以通過,如果我們還有test2模塊也想使用g_str,只需要在原文件中引用就可以了
??? #include "test1.h"
??? void fun2()
??? {
??????? cout << g_str << endl;
??? }
???
以上test1和test2可以同時編譯連接通過,如果你感興趣的話可以用ultraEdit打開test1.obj,你可以在里面著"123456"這
個字符串,但是你卻不能在test2.obj里面找到,這是因為g_str是整個工程的全局變量,在內(nèi)存中只存在一份,
test2.obj這個編譯單元不需要再有一份了,不然會在連接時報告重復(fù)定義這個錯誤!
??? 有些人喜歡把全局變量的聲明和定義放在一起,這樣可以防止忘記了定義,如把上面test1.h改為
??? extern char g_str[] = "123456"; // 這個時候相當(dāng)于沒有extern
???
然后把test1.cpp中的g_str的定義去掉,這個時候再編譯連接test1和test2兩個模塊時,會報連接錯誤,這是因為你把全局變量g_str的定義放在了頭文件之后,test1.cpp這個模塊包含了test1.h所以定義了一次g_str,而test2.cpp也包含了test1.h所以再一次定義了g_str,這個時候連接器在連接test1和test2時發(fā)現(xiàn)兩個g_str。
如果你非要把g_str的定義放在test1.h中的話,那么就把test2的代碼中#include "test1.h"去掉 換成:
??? extern char g_str[];
??? void fun2()
??? {
??????? cout << g_str << endl;
??? }
???
這個時候編譯器就知道g_str是引自于外部的一個編譯模塊了,不會在本模塊中再重復(fù)定義一個出來,但是我想說這樣做非常糟糕,因為你由于無法在test2.cpp中使用#include "test1.h",那么test1.h中聲明的其他函數(shù)你也無法使用了,除非也用都用extern修飾,這樣的話你光聲明的函數(shù)就要一大串,而且頭文件的作用就是要給外部提供接口使用的,所以 請記住,?只在頭文件中做聲明,真理總是這么簡單。
2. 用static修飾的全局變量
???
首 先,我要告訴你static與extern是一對“水火不容”的家伙,也就是說extern和static不能同時修飾一個變量;其次,static修飾 的全局變量聲明與定義同時進行,也就是說當(dāng)你在頭文件中使用static聲明了全局變量后,它也同時被定義了;最后,static修飾全局變量的作用域只 能是本身的編譯單元,也就是說它的“全局”只對本編譯單元有效,其他編譯單元則看不到它?。利用這一特性可以在不同的文件中定義 同名函數(shù)和同名變量,而不必?fù)?dān)心命名沖突。如:
??? test1.h:
??? #ifndef TEST1H
??? #define TEST1H
????static char g_str[] = "123456";?
??? void fun1();
??? #endif
??? test1.cpp:
??? #include "test1.h"
????
??? void fun1()
??? {
??????? cout << g_str << endl;
??? }
????
??? test2.cpp
??? #include "test1.h"
????
??? void fun2()
??? {
??????? cout << g_str << endl;
??? }
????
???
以上兩個編譯單元可以連接成功, 當(dāng)你打開test1.obj時,你可以在它里面找到字符串"123456",
同時你也可以在test2.obj中找到它們,它們之所以可以連接成功而沒有報重復(fù)定義的錯誤是因為雖然它們有相同的內(nèi)容,但是存儲的物理地址并不一樣,就像是兩個不同變量賦了相同的值一樣,而這兩個變量分別作用于它們各自的編譯單元。
???
也許你比較較真,自己偷偷的跟蹤調(diào)試上面的代碼,結(jié)果你發(fā)現(xiàn)兩個編譯單元(test1,test2)的g_str的內(nèi)存地址相同,于是你下結(jié)論static修飾的變量也可以作用于其他模塊,但是我要告訴你,那是你的編譯器在欺騙你,大多數(shù)編
譯器都對代碼都有優(yōu)化功能,以達(dá)到生成的目標(biāo)程序更節(jié)省內(nèi)存,執(zhí)行效率更高,當(dāng)編譯器在連接各個編譯單元的時候,它會把相同內(nèi)容的內(nèi)存只拷貝一份,比如上面的"123456", 位于兩個編譯單元中的變量都是同樣的內(nèi)容,那么在連接的時候它在內(nèi)存中就只會存在一份了,如果你把上面的代碼改成下面的樣子,你馬上就可以拆穿編譯器的謊言:
??? test1.cpp:
??? #include "test1.h"
????
??? void fun1()
??? {
??????? g_str[0] = ''a'';
??????? cout << g_str << endl;
??? }
??? test2.cpp
??? #include "test1.h"
????
??? void fun2()
??? {
??????? cout << g_str << endl;
??? }
????
??? void main()
??? {
??????? fun1(); // a23456
??????? fun2(); // 123456
??? }
????
??? 這個時候你在跟蹤代碼時,就會發(fā)現(xiàn)兩個編譯單元中的g_str地址并不相同,因為你在一處修改了它,所以編譯器被強行的恢復(fù)內(nèi)存的原貌,在內(nèi)存中存在了兩份拷貝給兩個模塊中的變量使用。
??? 正是因為static有以上的特性,所以一般定義static全局變量時,都把它放在原文件中而不是頭文件,這樣就不會給其他模塊造成不必要的信息污染,同樣記住這個原則吧!
????
3 const修飾的全局常量
??? const修飾的全局常量用途很廣,比如軟件中的錯誤信息字符串都是用全局常量來定義的。const修飾的全局常量據(jù)有跟static相同的特性,即它們只能作用于本編譯模塊中,但是const可以與extern連用來聲明該常量可以作用于其他編譯模塊中?,?如
??? extern const char g_str[];
??? 然后在原文件中別忘了定義:
??? const char g_str[] = "123456";
???
所以當(dāng)const單獨使用時它就與static相同,而當(dāng)與extern一起合作的時候,它的特性就跟extern的一樣了!所以對const我沒有什么可以過多的描述,我只是想提醒你,const char* g_str = "123456" 與 const char g_str[] =
"123465"是不同的, 前面那個const 修飾的是char *而不是g_str,它的g_str并不是常量,它被看做是一個定義了的全局變量(可以被其他編譯單元使用)?, 所以如果你像讓char*g_str遵守const的全局常量的規(guī)則,最好這么定義const char* const g_str="123456".
比較常用的在多個文件的工程中定義全局常量的方法:
方法1:在某個公用頭文件中將符號常量定義為static(c++有無static無所謂),并初始化,例如:
//CommDef.h
static const int MAX=1024;
然后每一個使用它的編譯單元包含該頭文件即可。
方法2:在某個公用的頭文件中將符號常量聲明?為extern,例如
//CommDef.h
extern const int MAX;
并且在某一個源文件中定義一次:
const int MAX=1024;
然 后每一個使用它的編譯單元包含上述頭文件即可。
方法1的優(yōu)點是維護方便,但是由于每一個符號常量在每一個包含了它們的編譯單元內(nèi)都存在一份獨立的拷貝,若修改常量的初值則將影響到多個編譯單元而導(dǎo)致必須重新編譯,而且浪費空間。
方法2的優(yōu)點是節(jié)約存儲、編譯后修改再編譯節(jié)省時間,但維護比較不便。
23.C++/C語言,要取得一個變量或?qū)ο蟮膬?nèi)存地址的通用方法是:強制轉(zhuǎn)換為void*,然后輸出。
24.若輸入?yún)?shù)以值傳遞的方式傳遞對象,則宜改用"const &"方式來傳遞,因為引用的創(chuàng)建和銷毀不會調(diào)用對象的構(gòu)造和析構(gòu)函數(shù),從而可提高效率。若函數(shù)的返回值是一個對象,有些場合可以使用“返回引用”替換“返回對象值”。而有時只能返回對象值。
25.不要將正常值和錯誤標(biāo)志混在一起返回,建議正常值用輸出參數(shù)獲得,而錯誤標(biāo)志用return語句返回,另外一種方法是將正常情況下的返回值和錯誤標(biāo)志綁定成一個鍵值對<value,bool>,例如std::map的insert()方法。
26.有時候函數(shù)原本不需要返回值,但是為了增加靈活性,如支持鏈?zhǔn)奖磉_(dá)可以附加返回值,比如strcpy。
27.標(biāo)準(zhǔn)C語言有4種存儲類型,即:extern ,auto , static , register ,分為永久生存——extern和static,以及臨時生存期限——auto和register。一個變量或函數(shù)只能有一種存儲類型。
28. 連接類型有三種:外、內(nèi)、無,表明了一個標(biāo)識符的可見性,所以常常和作用域的概念混淆。所謂外連接,就是這個標(biāo)識符可以在其他編譯單元中或者在定義它的編 譯單元中的其他范圍內(nèi)被調(diào)用。它需要在運行時分配空間。所外內(nèi)連接指一個標(biāo)識符能在定義它的編譯單元中的其他范圍內(nèi)被調(diào)用,但是不能在其他的編譯單元中被 調(diào)用。無連接指的是只能在聲明它的范圍內(nèi)被調(diào)用。
29.assert不是函數(shù)而是宏,是一個完全無害的測試手段。?斷言出錯是程序員的錯誤,比如說程序員誤傳進了一個NULL指針,傳進去了一個NULL的窗口句柄,或者編寫不當(dāng),而不是程序使用者(用
戶)的操作錯誤。在發(fā)行版本(Release)中,可以定義NDEBUG宏來取消所有斷言。所以,斷言不能夠完全代替參數(shù)檢查。
30.?const 只能修飾函數(shù)的輸入?yún)?shù),輸入?yún)?shù)若是使用“指針傳遞”則使用const進行保護,例如void Stringcpy(char *strDest, const char *strSrc),若是也想保護指針本身則可以在指針前加const:void Stringcpy(char *strDest, const char* const strSrc)。若是“值傳遞”,但是多次使用到傳遞進來的初值,則也可以加上const,保證代碼不會無意修改它。而定義諸如void Func1(A a)這樣的函數(shù)一定是效率比較低的,因為函數(shù)體內(nèi)將產(chǎn)生A類型的臨時變量來拷貝a,而臨時變量的構(gòu)造、拷貝和析構(gòu)都有const,所以可以使用傳引用—— 只借助參數(shù)的別名,本質(zhì)上是傳遞地址,此時需要加上const來進行保護:void Vunc1(const A &a),對于基本數(shù)據(jù)類型,這樣的操作完全是沒有必要的。另外,若是給返回指針的函數(shù)返回值前加上const,則返回值是一個契約性常量,不能被 直接修改,返回值只能被賦值給有const修飾的同類型指針。
總結(jié)
以上是生活随笔為你收集整理的【C/C++高质量编程 笔记】的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: C++和Java的属性访问和方法调用 效
- 下一篇: C++强制类型转换:dynamic_ca