C語言深度解剖 關鍵知識總結

偶然看到一本好書《C語言深度解剖》 內容不多,總共100多頁但是內容挺有意思的而且通熟易懂。爲了避免遺忘,現將重要的知識點做個小結歸納:


第一章 關鍵字


1、sizeof --是關鍵字 不是函數


2、聲明和定義的區別 -- 定義創建了對象併爲這個對象分配了內存,聲明沒有分配內存


3、register :請求編譯器儘可能將變量存在cpu內存寄存器中(不能用&來獲取register變量的地址 可能不存在內存中)


4、static :存在內存中的靜態區


5、32位操作系統下:

short  --  2 byte

int       --  4 byte

long    -- 4 byte

float    -- 4 byte

double-- 8byte

char    -- 1byte 


6、 int i;

      sizeof i;  對

      sizeof int; 錯

      sizeof ( int ) ;對


7、

int main()
{
    char a[100];
    int i;
    for(i=0;i<1000;i++)
   {
       a[i]=-1-i;
    }
    printf("%d",strlen(a));
    return 0;
}

兩個原則: ①、原碼+  == 補碼-    ②、上述+、-是不考慮符號位的+、-


8、if中指針的寫法:  if(NULL==p)


9、c語言中不加返回值類型的函數會被作爲返回整型處理


10、const修飾指針----先忽略類型名,const離誰近就修飾誰


第二章 符號


1、不用第三個臨時變量交換兩個變量的值:a=a^b; b=a^b; a=a^b;


2、左移和右移的位數不能大於數據的長度,不能小於0。


3、花括號的作用就是打包,使之形成一個整體,並與外界絕緣


4、逗號表達式,i在遇到每個逗號後,認爲本計算單位已經結束

int x,i=3;

x = (++i, i++, i+10);

x=15


5、C 語言有這樣一個規則:每一個符號應該包含儘可能多的字符。也就是說,編譯器將程序分解成符號的方法是,從左到右一個一個字符地讀入,如果該字符可能組成一個符號,那麼再讀入下一個字符,判斷已經讀入的兩個字符組成的字符串是否可能是一個符號的組成部分;如果可能,繼續讀入下一個字符,重複上述判斷,直到讀入的字符組成的字符串已不再可能組成一個有意義的符號。這個處理的策略被稱爲“貪心法”。需要注意到是,除了字符串與字符常量,符號的中間不能嵌有空白(空格、製表符、換行符等)


6、一些容易出錯的優先級問題:





第三章 預處理


1、ANSI 標準定義的C 語言預處理指令:



2、#define 可以出現在代碼的任何地方,從本行宏定義開始,以後的代碼就就都認識這個宏了;也可以把任何東西


用define 宏定義表達式:

#define SQR(x) x * x

#define SUM(x) (x)+(x)

#define SUM (x) (x)+(x) 編譯器以爲: (x) (x)+(x)  所以在宏定義時注意空格


3、條件編譯定義成宏。

條件編譯的功能使得我們可以按不同的條件去編譯不同的程序部分,因而產生不同的目標代碼文件。這對於程序的移植和調試是很有用的。條件編譯有三種形式:

第一種形式:
#ifdef 標識符
程序段1
#else
程序段2
#endif


第二種形式:
#ifndef 標識符
程序段1
#else
程序段2
#endif


第三種形式:
#if 常量表達式
程序段1
#else
程序段2
#endif


4、字節對齊


①、數據類型的自身對齊值:對於char爲1,short爲2,int float  double 爲4

②、結構體的自身對齊值:成員自身對齊值中最大的那個

③、指定對齊值:#pragma pack (n),編譯器將按照n 個字節對齊


step1:算出結構體自身對齊值(最大的那個)

step2:對每個元素求自身對齊值與結構體自身對齊值比較  取小的那個作爲N----->有效對齊值

step3:對每個元素地址%N=0

step4:最後總長度%結構體自身對齊值=0



第四章 指針和數組



1、我們可以簡單的這麼理解:一個基本的數據類型(包括結構體等自定義類型)加上“*”號就構成了一個指針類型的模子。這個模子的大小是一定的,與“*”號前面的數據類型無
關。“*”號前面的數據類型只是說明指針所指向的內存裏存儲的數據類型。所以,在32 位系統下,不管什麼樣的指針類型,其大小都爲4byte。


2、在使用NULL 的時候誤寫成null 或Null 等。這些都是不正確的


3、很多初學者弄不清指針和數組到底有什麼樣的關係。我現在就告訴你:他們之間沒有任何關係!只是他們經常穿着相似的衣服來逗你玩罷了。
指針就是指針,指針變量在32 位系統下,永遠佔4 個byte,其值爲某一個內存的地址。指針可以指向任何地方,但是不是任何地方你都能通過這個指針變量訪問到。
數組就是數組,其大小與元素的類型和個數有關。定義數組時必須指定其元素的類型和個數。數組可以存任何類型的數據,但不能存函數。


4、說以下標的形式訪問在本質上與以指針的形式訪問沒有區別,只是寫法上不同罷了。


5、指針和數組根本就是兩個完全不一樣的東西。只是它們都可以“以指針形式”或“以下標形式”進行訪問。一個是完全的匿名訪問,一個是典型的具名+匿名訪問。


6、指針和數組的區別總結:





7、數組

a[1000 ]

a 不能作爲左值!這個錯誤幾乎每一個學生都犯過。編譯器會認爲數組名作爲左值代表的意思是a 的首元素的首地址,但是這個地址開始的一塊內存是一個總體,我們只能訪問數組的某個元素而無法把數組當一個總體進行訪問。所以我們可以把a[i]當左值,而無法把a當左值。其實我們完全可以把a 當一個普通的變量來看,只不過這個變量內部分爲很多小塊,我們只能通過分別訪問這些小塊來達到訪問整個變量a 的目的。


8、指針數組和數組指針

指針數組:首先它是一個數組,數組的元素都是指針,數組佔多少個字節由數組本身決定。它是“儲存指針的數組”的簡稱。
數組指針:首先它是一個指針,它指向一個數組。在32 位系統下永遠是佔4 個字節,至於它指向的數組佔多少字節,不知道。它是“指向數組的指針”的簡稱。


9、

#include <stdio.h>
intmain(int argc,char * argv[])
{
int a [3][2]={(0,1),(2,3),(4,5)};
int *p;
p=a [0];
printf("%d",p[0]);
}
問打印出來的結果是多少?
很多人都覺得這太簡單了,很快就能把答案告訴我:0。不過很可惜,錯了。答案應該是1。如果你也認爲是0,那你實在應該好好看看這個題。花括號裏面嵌套的是小括號,而
不是花括號!這裏是花括號裏面嵌套了逗號表達式!其實這個賦值就相當於int a [3][2]={ 1, 3,5};


10、二維數組本質上還是一維數組


void fun(char a[10])
{
int i = sizeof(a);
char c = a[3];
}
如果數組b 真正傳遞到函數內部,那i 的值應該爲10。但是我們測試後發現i 的值竟然爲4!爲什麼會這樣呢?難道數組b 真的沒有傳遞到函數內部?是的,確實沒有傳遞過去,
這是因爲這樣一條規則:
C 語言中,當一維數組作爲函數參數的時候,編譯器總是把它解析成一個指向其首元素首地址的指針。

也就是說傳入堆棧的只是一個指針而不是整個數組。

這麼做是有原因的。在C 語言中,所有非數組形式的數據實參均以傳值形式(對實參做一份拷貝並傳遞給被調用的函數,函數不能修改作爲實參的實際變量的值,而只能修改
傳遞給它的那份拷貝)調用。然而,如果要拷貝整個數組,無論在空間上還是在時間上,其開銷都是非常大的。更重要的是,在絕大部分情況下,你其實並不需要整個數組的拷貝,你只想告訴函數在那一刻對哪個特定的數組感興趣

C 語言中,當一維數組作爲函數參數的時候,編譯器總是把它解析成一個指向其首元素首地址的指針。這條規則並不是遞歸的,也就是說只有一維數組纔是如此,當數組超過一維時,將第一維改寫爲指向數組首元素首地址的指針之後,後面的維再也不可改寫。比如:a[3][4][5]作爲參數時可以被改寫爲(*p)[4][5]。


11、數組指針

int (*)[10] p;


12、函數指針

char * (*fun1)(char * p1,char * p2);這裏fun1 不是什麼函數名,而是一個指針變量,它指向一個函數。這個函數有兩個指針類型的參數,函數的返回值也是一個指針。

函數指針使用的例子:
上面我們定義了一個函數指針,但如何來使用它呢?先看如下例子:

#include <stdio.h>
#include <string.h>
char * fun(char * p1,char * p2)
{
int i = 0;
i = strcmp(p1,p2);
if (0 == i)
{
return p1;
}
else
{
return p2;
}
}
int main()
{
char * (*pf)(char * p1,char * p2);
pf = &fun;
(*pf) ("aa","bb");
return 0;
}

*(int*)&p=(int)Function;表示將函數的入口地址賦值給指針變量p。

使用函數指針的好處在於,可以將實現同一功能的多個模塊統一起來標識,這樣一來更容易後期的維護,系統結構更加清晰。或者歸納爲:便於分層設計、利於系統抽象、降低耦合度以及使接口與實現分開。


第五章 內存管理


1、

靜態區:保存自動全局變量和static 變量(包括static 全局和局部變量)。靜態區的內容在總個程序的生命週期內都存在,由編譯器在編譯的時候分配。
棧:保存局部變量。棧上的內容只在函數的範圍內存在,當函數運行結束,這些內容也會自動被銷燬。其特點是效率高,但空間大小有限。
堆:由malloc 系列函數或new 操作符分配的內存。其生命週期由free 或delete 決定。在沒有釋放之前一直存在,直到程序結束。其特點是使用靈活,空間比較大,但容易錯。


2、結構體指針未初始化

struct student
{
char *name;
int score;
}stu,*pstu;
intmain()
{
strcpy(stu.name,"Jimy");
stu.score = 99;
return 0;
}

這裏定義了結構體變量stu,但是他沒想到這個結構體內部char *name 這成員在定義結構體變量stu 時,只是給name 這個指針變量本身分配了4 個字節。name 指針並沒有指向一個合法的地址,這時候其內部存的只是一些亂碼。所以在調用strcpy 函數時,會將字符串"Jimy"往亂碼所指的內存上拷貝,而這塊內存name 指針根本就無權訪問,導致出錯。解決的辦法是爲name 指針malloc 一塊空間。


3、malloc函數

函數原型:(void *)malloc(int size)

malloc 函數的返回值是一個void 類型的指針,參數爲int 類型數據,即申請分配的內存大小,單位是byte。內存分配成功之後,malloc 函數返回這塊內存的首地址。你需要一個指針來接收這個地址。但是由於函數的返回值是void *類型的,所以必須強制轉換成你所接收的類型。

char *p = (char *)malloc(100);


使用malloc函數同樣要注意這點:如果所申請的內存塊大於目前堆上剩餘內存塊(整塊),則內存分配會失敗,函數返回NULL。

malloc 函數申請的是連續的一塊內存


4、用malloc 函數申請0 字節內存

另外還有一個問題:用malloc 函數申請0 字節內存會返回NULL 指針嗎?可以測試一下,也可以去查找關於malloc 函數的說明文檔。申請0 字節內存,函數並不返回NULL,而是返回一個正常的內存地址。但是你卻無法使用這塊大小爲0 的內存。這好尺子上的某個刻度,刻度本身並沒有長度,只有某兩個刻度一起才能量出長度。



第六章 函數


1、










第七章 文件結構


 1、



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章