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