一,If,while,switch,do,for語句分析
1》分支語句分析 -- if。
if語句用於根據條件選擇執行語句。
else不能獨立存在且總是與它最近的if相匹配。
else語句后可以接連其他if語句。
if語句中零值比較的注意點。
bool型變量應該直接出現於條件中,不要進行比較。
普通變量和0值比較時,0值應該出現在比較符號左邊。
float型變量不能直接進行0值比較,需要定義精度。
2》分支語句分析 -- switch
switch語句對應單個條件多個分值的情形。
每個case語句分支必須要有break,否則會導致分支重疊。
default語句有必要加上,以處理特殊情況。
case語句中的值只能是整型或字符型。
case語句排列順序分析。
按字母或數字順序排列各條語句。
正常情況放在前面,異常情況放在后面。
default語句只用於處理真正的默認情況。
分支語句分析小結
if語句實用於需要“按片”進行判斷的情形中。
switch語句實用於需要對各個離散值進行分別判斷的情形中。
if語句可以完全從功能上代替switch語句,但switch語句無法代替if語句。
switch語句對於多分支判斷的情形更加簡潔。
3》循環語句分析
1》循環語句的基本工作方式
通過條件表達式判定是否執行循環體。
條件表達式遵循if語句表達式的原則。
2》do,while,for的區別
do語句先執行后判斷,循環體至少執行一次。
while語句先判斷后執行,循環體可能不執行。
for語句先判斷后執行,相比while更簡潔。
break和continue的區別
break表示終止循環的執行。
continue表示終止本次循環體,進入下次循環執行。
Switch不能用continue關鍵字。
二.goto,void,extern,sizeof分析
1》Goto語句分析
高手潛規則:禁用goto。
項目經驗:程序質量與goto的出現次數成反比。
最后的判決:將goto打入冷宮。
2》void的意義
void修飾函數返回值和參數。
如果函數沒有返回值,那么應該將其聲明為void型。
如果函數沒有參數,應該聲明其參數為void。
void修飾函數返回值和參數僅為了表示無。
C語言沒有定義void究竟是多大內存的別名。
void指針的意義
C語言規定只有相同類型的指針才可以相互賦值。
void*指針作為左值用於“接收”任意類型的指針。
void*指針作為右值賦值給其它指針時需要強制類型轉換。
3》extern中隱藏的意義
extern用於聲明外部定義的變量和函數。
extern用於“告訴”編譯器用C方式編譯。
C++編譯器和一些變種C編譯器默認會按“自己”的方式編譯
函數和變量,通過extern關鍵可以命令編譯器“以標准C方
式進行編譯”。
4》sizeof關鍵字分析
Sizeof是編譯器的內置指示符,不是函數。
sizeof用於“計算”相應實體所占的內存大小。
sizeof的值在編譯期就已經確定。
5》const修飾變量
在C語言中const修飾的變量是只讀的,其本質還是變量。
const修飾的變量會在內存占用空間。
本質上const只對編譯器有用,在運行時無用。
const不是真的常量,可以通過指針改變其值。
在C語言中const修飾的數組是只讀的。
const修飾的數組空間不可被改變。
const int* p; //p可變,p指向的內容不可變
int const* p; //p可變,p指向的內容不可變
int* const p; //p不可變,p指向的內容可變
const int* const p; //p和p指向的內容都不可變
口訣:左數右指
當const出現在*號左邊時指針指向的數據為常量。
當const出現在*后右邊時指針本身為常量。
const修飾函數參數和返回值
const修飾函數參數表示在函數體內不希望改變參數的值。
const修飾函數返回值表示返回值不可改變,多用於返回
指針的情形。
6》深藏不漏的volatile
volatile可理解為“編譯器警告指示字”。
volatile用於告訴編譯器必須每次去內存中取變量值。
volatile主要修飾可能被多個線程訪問的變量。
volatile也可以修飾可能被未知因數更改的變量。
三.Struct分析
由結構體產生柔性數組,柔性數組即數組大小待定的數組。
C語言中結構體的最后一個元素可以是大小未知的數組。
C語言中可以由結構體產生柔性數組。
struct占用的內存大小
第一個成員起始於0偏移處。
每個成員按其類型大小和指定對齊參數n中較小的一個進行對齊。
偏移地址和成員占用大小均需對齊。
結構體成員的對齊參數為其所有成員使用的對齊參數的最大值。
結構體總長度必須為所有對齊參數的整數倍。
1》union和struct的區別
struct中的每個域在內存中都獨立分配空間。
union只分配最大域的空間,所有域共享這個空間。
union的使用受系統大小端的影響。
四.枚舉類型的使用方法
enum是一種自定義類型。
enum默認常量在前一個值的基礎上依次加1。
enum類型的變量只能取定義時的離散值。
五.枚舉類型和#define的區別
#define宏常量只是簡單的進行值替換,枚舉常量是真正意義上的常量。
#define宏常量無法被調試,枚舉常量可以。
#define宏常量無類型信息,枚舉常量是一種特定類型的常量。
六.typedef的意義
typedef用於給一個已經存在的數據類型重命名。
typedef並沒有產生新的類型。
typedef重定義的類型不能進行unsigned和signed擴展。
typedef和#define的區別
typedef是給已有類型取別名。
#define為簡單的字符串替換,無別名的概念。
七.注釋符號
編譯器會在編譯過程刪除注釋,但不是簡單的刪除而是用空格代替。
編譯器認為雙引號括起來內容都是字符串,雙斜杠也不例外。
“/*……*/”型注釋不能被嵌套。
注釋應該准確易懂,防止二義性,錯誤的注釋有害而無利 。
注釋是對代碼的提示,避免臃腫和喧賓奪主。
一目了然的代碼避免加注釋。
不要用縮寫來注釋代碼,這樣可能會產生誤解。
注釋用於闡述原因而不是用於描述程序的運行過程。
八.接續符和轉義符
編譯器會將反斜杠剔除,跟在反斜杠后面的字符自動解到前一行。
在接續單詞時,反斜杠之后不能有空格,反斜杠的下一行之
前也不能有空格。
接續符適合在定義宏代碼塊時使用(C語言中定義宏函數的定義只能在 一行內完成)。
C語言中的反斜杠(\)同時具有接續符和轉義符的作用。
當反斜杠作為接續符使用時可直接出現在程序中。
當反斜杠作為轉義符使用時需出現在字符或字符串中。
九.單引號和雙引號
中的單引號用來表示字符常量。
C語言中的雙引號用來表示字符串常量。
‘a’表示字符常量在內存中占1個字節。
’a’+1表示’a’的ASCII碼加1,結果為‘b’。
“a”表示字符串常量在內存中占2個字節。
“a”+1表示指針運算,結果指向“a”結束符’\0’。
本質上單引號括起來的一個字符代表整數。
雙引號括起來的字符代表一個指針。
C編譯器接受字符和字符串的比較,可意義是錯誤的。
C編譯器允許字符串對字符變量賦值,其意義是可笑的。
十.接續符和轉義符
編譯器會將反斜杠剔除,跟在反斜杠后面的字符自動解到前
一行。
在接續單詞時,反斜杠之后不能有空格,反斜杠的下一行之
前也不能有空格。
接續符適合在定義宏代碼塊時使用。
C語言中的反斜杠(\)同時具有接續符和轉義符的作用。
當反斜杠作為接續符使用時可直接出現在程序中。
當反斜杠作為轉義符使用時需出現在字符或字符串中。
十一.位運算符分析
左移運算符<<將運算數的二進制位左移
規則:高位丟棄,低位補0
右移運算符>>把運算數的二進制位右移。
規則:高位補符號位,低位丟棄
小技巧:
左移n位相當於乘以2的n次方,但效率比數學運算符高。
右移n位相當於除以2的n次方,但效率比數學運算符高。
十二.類型轉換分析
算術運算式中,低類型轉換為高類型。
賦值表達式中,表達式的值轉換為左邊變量的類型。
函數調用時,實參轉換為形參的類型。
函數返回值,return表達式轉換為返回值類型。
十三.編譯器做了什么
預編譯
處理所有的注釋,以空格代替。
將所有的#define刪除,並且展開所有的宏定義。
處理條件編譯指令#if, #ifdef, #elif, #else, #endif。
處理#include,展開被包含的文件。
保留編譯器需要使用的#pragma指令。
編譯
對預處理文件進行一系列詞法分析,語法分析和語義分析。
詞法分析主要分析關鍵字,標示符,立即數等是否合法。
語法分析主要分析表達式是否遵循語法規則。
語義分析在語法分析的基礎上進一步分析表達式是否合法。
分析結束后進行代碼優化生成相應的匯編代碼文件。
匯編
匯編器將匯編代碼轉變為機器可以執行的指令。
每個匯編語句幾乎都對應一條機器指令。
十四.定義宏常量
#define定義宏常量可以出現在代碼的任何地方。
#define從本行開始,之后的代碼都可以使用這個宏常量。
#define表達式給有函數調用的假象,卻不是函數。
#define表達式可以比函數更強大。
#define表達式比函數更容易出錯。
宏表達式在預編譯期被處理,編譯器不知道宏表達式的存在。
宏表達式用“實參”完全替代形參,不進行任何運算。
宏表達式沒有任何的“調用”開銷。
宏表達式中不能出現遞歸定義。
====定義完整的日志文件宏
#define LOG(s) do{ \ time_t t; \ struct tm* tim; \ time(&t); \ tim = localtime(&t); \ printf("%s:%d,%s,%s\n",__FILE__,__LINE__,asctime(tim),s); \ }while(0)
十五.條件編譯的行為
1.條件編譯的行為類似於C語言中的if…else。
2. 條件編譯是預編譯指示命令,用於控制是否編譯某段代碼。
3.#include的本質是將已經存在的文件內容嵌入到當前文件中。
4.#include的間接包含同樣會產生嵌入文件內容的動作。
5.條件編譯使得我們可以按不同的條件編譯不同的代碼段,因而可
以產生不同 的目標代碼。
6.#if…#else…#endif被預編譯器處理;而if…else語句被編譯器處
理,必然被編譯進目標代碼。
7.通過編譯器命令行能夠定義預處理器使用的宏。
8.條件編譯可以避免重復包含頭同一個頭文件。
9. 條件編譯是在工程開發中可以區別不同產品線的代碼。
10. 條件編譯可以定義產品的發布版和調試版。
十六.#error和#line的用法
#error用於生成一個編譯錯誤消息,並停止編譯。
用法:#error message
注:message不需要用雙引號包圍
#error編譯指示字用於自定義程序員特有的編譯錯誤消息。
類似的,#warning用於生成編譯警告,但不會停止編譯。
#line用於強制指定新的行號和編譯文件名,並對源程序的代碼重新編號。
? 用法:#line number filename
注:filename可省略
#line編譯指示字的本質是重定義__LINE__和__FILE__。
十七.#pragma簡介
1. #pragma是編譯器指示字,用於指示編譯器完成一些特定的動作。
2.#pragma所定義的很多指示字是編譯器和操作系統特有的。
3.#pragma在不同的編譯器間是不可移植的。
4.預處理器將忽略它不認識的#pragma指令。
5.兩個不同的編譯器可能以兩種不同的方式解釋同一條#pragma指令。
6. #pragma pack能夠改變編譯器的默認對齊方式。
一般用法:#pragma parameter
注:不同的parameter參數語法和意義各不相同
#pragma message
message參數在大多數的編譯器中都有相似的實現。
message參數在編譯時輸出消息到編譯輸出窗口中。
message可用於代碼的版本控制。
什么是內存對齊?#pragma pack
不同類型的數據在內存中按照一定的規則排列;而不是順序的一個
接一個的排放,這就是對齊。
為什么需要內存對齊?
1.CPU對內存的讀取不是連續的,而是分成塊讀取的,塊的大小只能
是1、2、4、8、16字節。
2.當讀取操作的數據未對齊,則需要兩次總線周期來訪問內存,因
3此性能會大打折扣。
4. 某些硬件平台只能從規定的地址處取某些特定類型的數據,否則
拋出硬件異常。
十八.指針本質
1.指針在本質上也是一個變量,需要占用一定的內存空間。指針用於保
存內存地址的值。
*號的意義
1.在指針聲明時,*號表示所聲明的變量為指針。
2.在指針使用時,*號表示取指針所指向的內存空間中的值。
3.號類似一把鑰匙,通過這把鑰匙可以打開內存,讀取內存中的值。
傳值調用與傳址調用
1.指針是變量,因此可以聲明指針參數。
2. 當一個函數體內部需要改變實參的值,則需要使用指針參數。
3.函數調用時實參值將復制到形參。
4.指針適用於復雜數據類型作為參數的函數中。
5. 指針是C語言中一種特別的變量。
6.指針所保存的值是內存的地址。
7.可以通過指針修改內存中的任意地址內容。
十九.數組的概念
1.數組是相同類型的變量的有序集合int a[5]。
2.數組在一片連續的內存空間中存儲元素。
3. 數組元素的個數可以顯示或隱式指定。
4.數組名代表數組首元素的地址。
5.數組的地址需要用取地址符&才能得到。
6.數組首元素的地址值與數組的地址值相同。
7.數組首元素的地址與數組的地址是兩個不同的概念。。
9數組名可以看做一個常量指針。
10. 數組名“指向”的是內存中數組首元素的起始位置。
11. 在表達式中數組名只能作為右值使用。
12. 只有在下列場合中數組名不能看做常量指針。
數組名作為sizeof操作符的參數。
數組名作為&運算符的參數。
13.數組是一片連續的內存空間。
14. 數組的地址和數組首元素的地址意義不同。
15. 數組名在大多數情況下被當成常量指針處理。
16. 數組名其實並不是指針,在外部聲明時不能混淆。
數組的本質
數組是一段連續的內存空間。
數組的空間大小為sizeof(array_type) * array_size。
數組名可看做指向數組第一個元素的常量指針。
指針的運算
指針是一種特殊的變量,與整數的運算規則為:
p + n; (unsigned int)p + n*sizeof(*p);
結論:
當指針p指向一個同類型的數組的元素時:p+1將指向。
當前元素的下一個元素;p-1將指向當前元素的上一個元素。
指針的運算
指針之間只支持減法運算,且必須參與運算的指針類型必須相同
p1 – p2; == ( (unsigned int)p1 - (unsigned int)p2) / sizeof(type);
注意:
只有當兩個指針指向同一個數組中的元素時,指針相減才有意義,
其意義為指針所指元素的下標差。
當兩個指針指向的元素不在同一個數組中時,結果未定義。
指針的比較
指針也可以進行關系運算:
< <= > >=
指針關系運算的前提是同時指向同一個數組中的元素。
任意兩個指針之間的比較運算(==, !=)無限制。
下標 VS 指針
理論上而言,當指針以固定增量在數組中移動時,其效率高於下標產生的代碼
當指針增量為1且硬件具有硬件增量模型時,表現更佳
注意:
現代編譯器的生成代碼優化率已大大提高,在固定增量時,下標形式的
效率已經和指針形式相當;但從可讀性和代碼維護的角度來看,下標形式更優。
a和&a的區別
a為數組是數組首元素的地址。
&a為整個數組的地址。
a和&a的意義不同其區別在於指針運算:
a + 1 (unsigned int)a + sizeof(*a)
&a + 1 (unsigned int)(&a) + sizeof(*&a)
數組參數
C語言中,數組作為函數參數時,編譯器將其編譯成對應的指針
void f(int a[]); void f(int* a);
void f(int a[5]); void f(int* a);
結論:
一般情況下,當定義的函數中有數組參數時,需要定義另一個參數來標示
數組的大小。
指針和數組的對比
數組聲明時編譯器自動分配一片連續內存空間。
指針聲明時只分配了用於容納指針的4字節空間。
在作為函數參數時,數組參數和指針參數等價。
數組名在多數情況可以看做常量指針,其值不能改變。
指針的本質是變量,保存的值被看做內存中的地址。
二十.C語言中的字符串
從概念上講,C語言中沒有字符串數據類型。
在C語言中使用字符數組來模擬字符串。
C語言中的字符串是以’\0’結束的字符數組。
C語言中的字符串可以分配於棧空間,堆空間或者只讀存儲區。
字符串長度
字符串的長度就是字符串所包含字符的個數。
C語言中的字符串長度指的是第一個’\0’字符前出現的字符個數。
C語言中通過’\0’結束符來確定字符串的長度。
strlen的返回值是用無符號數定義的,因此相減不可能產生負數。
一般情況下,千萬千萬不要自行編寫C標准庫已經提供的函數。
標准庫有時會使用匯編語言實現,目的就是為了充分利用機器所提供的
特殊指令以追求最大的速度。
復用已經存在的函數庫會更高效。
長度不受限的字符串函數
不受限制的字符串函數是通過尋找字符串的結束符’\0’來判斷長度
字符串復制: char* strcpy(char* dst, const char* src);
字符串連接: char* strcat(char* dst, const char* src);
字符串比較: int strcmp(const char* s1, const char* s2);
不受限制的字符串函數都是以‘\0’作為結尾標記來進行的,因此輸入參
數中必須包含’\0’。
strcpy和strcat必須保證目標字符數組的剩余空間足以保存整個源字符串。
strcmp以0值表示兩個字符串相等。
第一個字符串大於第二個字符串的時候返回值大於0。
第一個字符串小於第二個字符串的時候返回值小於0。
strcmp不會修改參數值,但依然以’\0’作為結束符。
長度受限的字符串函數
長度受限的字符串函數接收一個顯示的長度參數用於限定操作的字符數
字符串復制: char* strncpy(char* dst, const char* src, size_t len);
字符串連接: char* strncat(char* dst, const char* src , size_t len);
字符串比較: int strncmp(const char* s1, const char*s2 , size_t len)
strncpy只復制len個字符到目標字符串。
當源字符串的長度小於len時,剩余的空間以’\0’填充。
當源字符串的長度大於len時,只有len個字符會被復制,且它將不會以
’\0’ 結束。
strncat最多從源字符串中復制len個字符到目標串中。
strncat總是在結果字符串后面添加’\0’。
strncat不會用’\0’填充目標串中的剩余空間。
strncmp只比較len個字符是否相等。
二十一.指針數組和數組指針分析
數組類型
C語言中的數組有自己特定的類型。
數組的類型由元素類型和數組大小共同決定。
例:int array[5]的類型為int[5]。
定義數組類型
C語言中通過typedef為數組類型重命名typedef type(name)[size];
數組類型: typedef int(AINT5)[5];
typedef float(AFLOAT10)[10];
數組定義: AINT5 iArray;
AFLOAT10 fArray
1.數組指針
數組指針用於指向一個數組。
數組名是數組首元素的起始地址,但並不是數組的起始地址。
通過將取地址符&作用於數組名可以得到數組的起始地址。
可通過數組類型定義數組指針: ArrayType* pointer;
也可以直接定義:type (*pointer)[n];。
pointer為數組指針變量名。type為指向的數組的類型。n為指向的數組的大小。
2.指針數組
指針數組是一個普通的數組。
指針數組中每個元素為一個指針。指針數組的定義:type* pArray[n]。
type*為數組中每個元素的類型。pArray為數組名。 n為數組大小。
main函數的參數
int main()
int main(int argc)
int main(int argc, char *argv[])
int main(int argc, char *argv[], char *env[])
argc –命令行參數個數。
argv –命令行參數數組。
env –環境變量數組。
數組指針本質上是一個指針。
數組指針指向的值是數組的地址。
指針數組本質上是一個數組。
指針數組中每個元素的類型是指針。
二十二.多維數組和多維指針
1.指向指針的指針
指針變量在內存中會占用一定的空間。
可以定義指針來保存指針變量的地址值。
2.指向指針的指針
指針在本質上也是變量。對於指針也同樣存在傳值調用與傳址調用。
3.二維數組與二級指針
二維數組在內存中以一維的方式排布。
二維數組中的第一維是一維數組。
二維數組中的第二維才是具體的值。
二維數組的數組名可看做常量指針。
4.數組名
一維數組名代表數組首元素的地址 ,int a[5] a的類型為int*。
二維數組名同樣代表數組首元素的地址,int m[2][5] m的類型為int(*)[5]。
結論:
1. 二維數組名可以看做是指向數組的常量指針。
2. 二維數組可以看做是一維數組。
3. 二維數組中的每個元素都是同類型的一維數組。
小結
C語言中只有一維數組,而且數組大小必須在編譯期就作為常數確定。
C語言中的數組元素可是任何類型的數據,即,數組的元素可以是另一個數組。
C語言中只有數組的大小和數組首元素的地址是編譯器直接確定的。
#include <stdio.h> #include <assert.h> #include <string.h> #include <malloc.h> typedef int(arrer5)[5]; arrer5 shuzu; int array_int[5]= {1,2,3,4,5}; unsigned char* array[5] = { "aaa", "bbb", "ccc", "ddd", "eee" }; size_t str_len(const char* s) { return (assert(s),(*s ? (str_len(s+1)+1) : 0)) ; } int reset(char** p,int old_size,int new_size) { char* new = NULL; char* pt = NULL; char* pp = *p; int i = 0,ret = 1; int len = 0; if(p != NULL && new_size > 0) { pt = (char*)malloc(new_size); new = pt; len = ((old_size < new_size) ? old_size : new_size); for(i=0;i<len;i++) { *new++ = *pp++; } free(*p); *p = pt; } else ret = 0; return ret; } void str_copy1(char* s1,char* s2) { int size1,size2; int i,j; assert(s1 && s2); size1 = strlen(s1); size2 = strlen(s2); for(i=size1,j = 0;s2[j] != '\0';i++,j++) { s1[i] = s2[j]; } s1[size1+size2] = '\0'; printf("the str1 len is:%d\n",size1); printf("the str2 len is:%d\n",size2); printf("the add s1 len is:%d\n",str_len(s1)); } char* str_copy2(char* s1,char* s2) { assert(s1 && s2); while((*s1++ = *s2++) != '\0'); return s1; } void text_fun() { int i; char str1[100]; char str2[] = "AAAAAAAAAAAAAAA\0"; str_copy2(str1,str2); printf("%s\n",str1); for(i=0;i<5;i++) *(array+0) = (unsigned int)(array_int+0); printf("%0x",array[0]); printf(" %0x\n",&array_int[0]); printf("%s",array[1]); printf(" %0x\n",&array_int[1]); printf("%s",array[2]); printf(" %0x\n",&array_int[2]); printf("%s",array[3]); printf(" %0x\n",&array_int[3]); printf("%s",array[4]); printf(" %0x\n",&array_int[4]); } int main() { int i; char* pint = (char*)malloc(5); printf("old size address:%0x\n",pint); reset(&pint, 5, 7); printf("new size address:%0x\n",pint); }
二十三.數組參數和指針參數分析
退化的意義
C語言中只會以值拷貝的方式傳遞參數。
當向函數傳遞數組時, 將數組名看做常量指針傳數組首元素地址。
二維數組參數
二維數組可以看做是一維數組,二維數組中的每個元素是一維數組。
二維數組參數中第一維的參數可以省略:
void f(int a[5]); == void f(int a[]); == void f(int* a);
void g(int a[3][3]); == void g(int a[][3]); == void g(int (*a)[3]);
等價關系
數組參數 |
等效的指針參數 |
一維數組:float a[5] |
指針:float* a |
指針數組:int* a[5] |
指針的指針:int** a |
二維數組:char a[3][4] |
數組的指針:char (*a)[4] |
注意事項
C語言中無法向一個函數傳遞任意的多維數組
為了提供正確的指針運算,必須提供除第一維之外的所有維長度限制
一維數組參數 – 必須提供一個標示數組結束位置的長度信息
二維數組參數 – 不能直接傳遞給函數
三維或更多維數組參數 – 無法使用
二十四.函數與指針分析
1.函數類型
C語言中的函數有自己特定的類型,函數的類型由返回值,參數類型和參數個數共同決定。
例:int add(int i, int j)的類型為int(int, int)
C語言中通過typedef為函數類型重命名:typedef type name(parameter list)
例: typedef int f(int, int);
typedef void p(int);
2.函數指針
函數指針用於指向一個函數。函數名是執行函數體的入口地址。
可通過函數類型定義函數指針: FuncType* pointer;。
也可以直接定義:type (*pointer)(parameter list);
pointer為函數指針變量名。
type為指向函數的返回值類型。
parameter list為指向函數的參數類型列表。
3.回調函數
回調函數是利用函數指針實現的一種調用機制。
回調機制原理。
調用者不知道具體事件發生的時候需要調用的具體函數。
被調函數不知道何時被調用,只知道被調用后需要完成的任務。
當具體事件發生時,調用者通過函數指針調用具體函數。
回調機制的將調用者和被調函數分開,兩者互不依賴。
4.指針閱讀技巧解析-- 右左法則
1. 從最里層的圓括號中未定義的標示符看起。
2. 首先往右看,再往左看。
3. 當遇到圓括號或者方括號時可以確定部分類型,並調轉方向。
4. 重復2,3步驟,直到閱讀結束。
二十五.動態內存分配
1.C語言中的一切操作都是基於內存的變量和數組都是內存的別名,如何分
配 這些內存由編譯器在編譯期間決定。
2.定義數組的時候必須指定數組長度而數組長度是在編譯期就必須決定的。
需求:程序運行的過程中,可能需要使用一些額外的內存空間。
malloc和free
1.malloc所分配的是一塊連續的內存,以字節為單位,並且不帶任何的類型信息 。2.free用於將動態內存歸還系統。
void* malloc(size_t size);
void free(void* pointer);
注意:
1.malloc實際分配的內存可能會比請求的稍微多一點,但是不能依賴於編譯器
的這個行為。
2.當請求的動態內存無法滿足時malloc返回NULL。
2.當free的參數為NULL時,函數直接返回。
calloc和realloc
void* calloc(size_t num, size_t size);
void* realloc(void* pointer, size_t new_size);
1. calloc的參數代表所返回內存的類型信息。
2. calloc會將返回的內存初始化為0。
3.realloc用於修改一個原先已經分配的內存塊大小。
4.在使用realloc之后應該使用其返回值。
5.當pointer的第一個參數為NULL時,等價於malloc。
小結
1.動態內存分配是C語言中的強大功能。
2. 程序能夠在需要的時候有機會使用更多的內存。
3.malloc單純的從系統中申請固定字節大小的內存。
4.calloc能以類型大小為單位申請內存並初始化為0。
5.realloc用於重置內存大小。
二十六.程序中的三國天下
程序中的棧
1. 棧是現代計算機程序里最為重要的概念之一。
2. 棧在程序中用於維護函數調用上下文,沒有棧就沒有函數,沒有局部變量。
3. 棧保存了一個函數調用所需的維護信息
函數參數,函數返回地址
局部變量
函數調用上下文
程序中的堆
1. 為什么有了棧還需要堆。
2. 棧上的數據在函數返回后就會被釋放掉,無法傳遞到函數外部,局部數組。
3. 堆是程序中一塊巨大的內存空間,可由程序自由使用。
4. 堆中被程序申請使用的內存在程序主動釋放前將一直有效。
5.系統對堆空間的管理方式:空閑鏈表法,位圖法,對象池法等等。
程序中的靜態存儲區
1. 程序靜態存儲區隨着程序的運行而分配空間,直到程序運行結束。
2. 在程序的編譯期靜態存儲區的大小就已經確定。
3. 程序的靜態存儲區主要用於保存程序中的全局變量和靜態變量。
4.與棧和堆不同,靜態存儲區的信息最終會保存到可執行程序中。
小結
1.棧,堆和靜態存儲區是C語言程序常涉及的三個基本內存區。
2.棧區主要用於函數調用的使用。
3. 堆區主要是用於內存的動態申請和歸還。
4. 靜態存儲區用於保存全局變量和靜態變量。
二十七.程序的內存布局
各個段的作用
1.堆棧段在程序運行后才正式存在,是程序運行的基礎。
2. .bss段存放的是未初始化的全局變量和靜態變量。
3. .text段存放的是程序中的可執行代碼。
4..data段保存的是那些已經初始化了的全局變量和靜態變量。
5. .rodata段存放程序中的常量值,如字符串常量。
程序術語對應關系
1. 靜態存儲區通常指程序中的.bss和.data段。
2. 只讀區通常指程序中的.rodata段。
3. 局部變量所占空間為棧上空間。
4..動態空間為堆中的空間。
5.程序可執行代碼存放於.text段。
二十八.野指針和內存操作分析
初識野指針
1. 野指針通常是因為指針變量中保存的值不是一個合法的內存地址而造成的。
2. 野指針不是NULL指針,是指向不可用內存的指針。
3. NULL指針不容易用錯,因為if語句很好判斷一個指針是不是NULL。
野指針的由來
1.局部指針變量沒有被初始化。
2. 使用已經釋放過后的指針。
3. 指針所指向的變量在指針之前被銷毀。
非法內存操作分析
1. 結構體成員指針未初始化。
2. 沒有為結構體指針分配足夠的內存。
3..內存分配成功,但並未初始化。
4..數組越界。
C語言中的交通規則
1. 用malloc申請了內存之后,應該立即檢查指針值是否為NULL,防止使用值為NULL的指針。
2. 牢記數組的長度,防止數組越界操作,考慮使用柔性數組。
3. 動態申請操作必須和釋放操作匹配,防止內存泄露和多次釋放。
4. ? free指針之后必須立即賦值為NULL。
二十九.認清函數的真面目
面向過程的程序設計
1.面向過程是一種以過程為中心的編程思想。
2.首先將復雜的問題分解為一個個容易解決的問題。
3.分解過后的問題可以按照步驟一步步完成。
4.函數是面向過程在C語言中的體現。
5.解決問題的每個步驟可以用函數來實現。
聲明和定義
1.程序中的聲明可理解為預先告訴編譯器實體的存在,如:變量,函數,等等。
2.程序中的定義明確指示編譯器實體的意義。
函數參數
1.函數參數在本質上與局部變量相同,都是在棧上分配空間。
2. 函數參數的初始值是函數調用時的實參值。
程序中的順序點
1. 程序中存在一定的順序點。
2.順序點指的是執行過程中修改變量值的最晚時刻。
3. 在程序達到順序點的時候,之前所做的一切操作必須反映到后續的訪問中。
C語言中的順序點
1. 每個完整表達式結束時。
2. &&, ||, ?:, 以及逗號表達式的每個運算對象計算之后。
3. 函數調用中對所有實際參數的求值完成之后(進入函數體之前)。
4.? C語言會默認沒有類型的函數參數為int。
小結
1. C語言是一種面向過程的語言。
2. 函數可理解為解決問題的步驟。
3. 函數的實參並沒有固定的計算次序。
4. 順序點是C語言中變量改變的最晚時機。
5. 函數定義時參數和返回值的缺省類型為int。
三十.可變參數分析與宏分析
可變參數
1.C語言中可以定義參數可變的函數。
2.參數可變函數的實現依賴於stdarg.h頭文件。
3.va_list變量與va_start, va_end和va_arg配合使用能夠訪問參數值。
可變參數的限制
1. 可變參數必須從頭到尾按照順序逐個訪問。
2. 參數列表中至少要存在一個確定的命名參數。
3. 可變參數宏無法判斷實際存在的參數的數量。
4. 可變參數宏無法判斷參數的實際類型。
小結
1.可變參數是C語言提供的一種函數設計技巧。
2. 可變參數的函數提供了一種更方便的函數調用方式。
3. 可變參數必須順序的訪問。
4. 無法直接訪問可變參數列表中間的參數值。
函數 VS 宏
1. 宏是由預處理直接替換展開的,編譯器不知道宏的存在。
2. 函數是由編譯器直接編譯的實體,調用行為由編譯器決定。
3. 多次使用宏會導致程序代碼量增加。
4. 函數是跳轉執行的,因此代碼量不會增加。
5. 宏的效率比函數要高,因為是直接展開,無調用開銷。
6. 函數調用時會創建活動記錄,效率不如宏。
7.宏的效率比函數稍高,但是其副作用巨大,容易出錯。
函數優點和缺點
1.函數存在實參到形參的傳遞,因此無任何副作用,但是函數需要建立活動對象,效率受影響。
宏無可替代的優勢
1.宏參數可以是任何C語言實體。
宏編寫的_MIN_參數類型可以是int, float等等。
宏的參數可以是類型名。
小結
1. 宏和函數並不是競爭對手。
2. 宏能夠接受任何類型的參數,效率高,易出錯。
3. 函數的參數必須是固定類型,效率稍低,不易出錯。
4. 宏可以實現函數不能實現的功能。
三十一.函數調用行為
活動記錄
1.活動記錄是函數調用時用於記錄一系列相關信息的記錄。
2. 臨時變量域:用來存放臨時變量的值,如k++的中間結果。
3. 局部變量域:用來存放函數本次執行中的局部變量。
4. 機器狀態域:用來保存調用函數之前有關機器狀態的信息,包括
各種寄存器的當前值和返回地址等;。
5. 實參數域:用於存放函數的實參信息。
6. 返回值域:為調用者函數存放返回值。
調用約定
1.當一個函數被調用時,參數會傳遞給被調用的函數,而返回值會被返回給調
用函數。函數的調用約定就是描述參數。是怎么傳遞到棧空間的,以及棧空
間由誰維護。
參數傳遞順序
從右到左依次入棧:__stdcall,__cdecl,__thiscall。
從左到右依次入棧:__pascal,__fastcall。
調用堆棧清理
調用者清除棧。。
被調用函數返回后清除棧。
小結
1..函數調用是C語言的核心機制。
2. 活動記錄中保存了函數調用以及返回所需要的一切信息。
3. 調用約定是調用者和被調用者之間的調用協議,常用於不同開發者編寫的
庫函數之間。
三十二.函數遞歸與函數設計技巧
遞歸概述
1.遞歸是數學領域中概念在程序設計中的應用。
2.遞歸是一種強有力的程序設計方法。
3.遞歸的本質為函數內部在適當的時候調用自身。
遞歸函數
1.C遞歸函數有兩個主要的組成部分。
2.遞歸點 – 以不同參數調用自身。
3. 出口– 不在遞歸調用。
小結
1. C語言中的遞歸函數必然會使用判斷語句。
2.遞歸函數在需要編寫的時候定義函數的出口,否則棧會溢出。
3. 遞歸函數是一種分而治之的思想。
函數設計技巧
1. 不要在函數中使用全局變量,盡量讓函數從意義上是一個獨立的功能模塊。
2. 參數名要能夠體現參數的意義。
3.如果參數是指針,且僅作輸入參數用,則應在類型前加const,以防止該
指針在函數體內被意外修改。
4. 不要省略返回值的類型,如果函數沒有返回值,那么應聲明為void類型。
5. 在函數體的“入口處”,對參數的有效性進行檢查,對指針的檢查尤為重要。
6. 語句不可返回指向“棧內存”的“指針”,因為該內存在函數體結束時被
自動銷毀。
7. 函數體的規模要小,盡量控制在80行代碼之內。
8. 相同的輸入應當產生相同的輸出,盡量避免函數帶有“記憶”功能。
9. 避免函數有太多的參數,參數個數盡量控制在4個以內。
10. 有時候函數不需要返回值,但為了增加靈活性,如支持鏈式表達,可以
附加返回值。
11. 函數名與返回值類型在語義上不可沖突。
三十三.進軍C++的世界
初識OOP
1. 類和對象是面向對象中的兩個基本概念。
2. “類”指的是一類事物,是一個抽象的概念。
3.“對象”指的是屬於某個類的一個實體,是一個具體存在的事物。
4. 類是一種“模板”,可以通過這種模板創建出不同的對象“實例”。
5.對象“實例”是類“模板”的一個具體實現。
6. 一個類可以有很多對象,而一個對象必然屬於某個類。
抽象
1. 抽象的的意義是觀察一群“事物”,並認識它們所具有的一些共同特性。
2. 抽象的本質是忽略不重要的區別,只記錄能表現事物特征的關鍵數據項。
3. 類是抽象在程序設計領域的概念。
4. 類用於抽象的描述一類事物所特有的屬性和行為 如:電腦類的每個對象
都有CPU,內存和硬盤,電腦類的每個對象都可以開機和運行程序。
5. 對象是一個具體的事物,擁有其所屬類的所有屬性,並且每個屬性都
是一個特有的值。
6. 如:老虎的每個對象(也就是每只老虎),都有不同的體重,不同食量以
及不同的性情。
封裝
1.類中描述的事物屬性和行為往往是相關的。
2. 在C++中屬性通過變量來表示,行為通過函數來模擬。
3. 封裝指的是類中的變量只能通過類的函數來訪問。
訪問控制
1.C++的類中有三種訪問權限。
2. public -- 類的外部可以自由的訪問。
3. protected -- 類自身和子類中可以訪問。
4. private -- 類自身中可以訪問。
你也能做富二代
1.在C語言中struct有了自已的含義,雖然在C++中擴展成為了類,但一般情
況還是遵循C中的用法。
2.C++一般情況下用class來做類的關鍵字聲明。
3. 繼承是C++中代碼復用的方式,通過繼承,在子類中可以使用父類中的代碼。
4. 子類可以完全繼承父類中所有的變量和函數,在可以使用父類的地方就
可以用子類代替。
5. 子類從概念上而言是一種特殊的父類。
小結
1. 面向對象是一種新型的軟件開發思想。
2..面向對象將生活中的事物完全映射到程序中。
3.抽象,封裝和繼承是面向對象程序設計的重要特性。
4. 繼承能夠很好的復用已有類的特性。
5.子類是一種特殊化的父類。