C語言深度解剖學習筆記


第一章關鍵字

什麼是定義?什麼是聲明?

什麼是定義:所謂的定義就是(編譯器)創建一個對象,爲這個對象分配一塊內存並給它取上一個名字,這個名字就是我們經常所說的變量名或對象名。但注意,這個名字一旦和這塊內存匹配起來,它們就同生共死,終生不離不棄。並且這塊內存的位置也不能被改變。一個變量或對象在一定的區域內(比如函數內,全局等)只能被定義一次,如果定義多次,編譯器會提示你重複定義同一個變量或對象。

什麼是聲明:有兩重含義,如下

第一重含義:告訴編譯器,這個名字已經匹配到一塊內存上了,下面的代碼用到變量或對象是在別的地方定義的。聲明可以出現多次。

第二重含義:告訴編譯器,我這個名字我先預定了,別的地方再也不能用它來作爲變量名或對象名。

定義聲明最重要的區別:定義創建了對象併爲這個對象分配了內存,聲明沒有分配內存

C語言標準定義的 32個關鍵字

關鍵字

auto 聲明自動變量,缺省時編譯器一般默認爲 auto

int 聲明整型變量

double 聲明雙精度變量

long 聲明長整型變量

char 聲明字符型變量

float 聲明浮點型變量

short 聲明短整型變量

這六個關鍵字代表 C 語言裏的六種基本數據類型

32位的系統上 short內存大小是2 byteint內存大小是4bytelong內存大小是4bytefloat內存大小是4bytedouble內存大小是 8 bytechar內存大小是 1 byte。(注意這裏指一般情況,可能不同的平臺還會有所不同,具體平臺可以用 sizeof關鍵字測試一下)

signed 聲明有符號類型變量

unsigned 聲明無符號類型變量

正負數:最高位如果是 1,表明這個數是負數。如果最高位是 0,表明這個數是正數

struct 聲明結構體變量

不要認爲結構體內不能放函數\

union 聲明聯合數據類型

union只配置一個足夠大的空間以來容納最大長度的數據成員

enum 聲明枚舉類型

成員都是常量,也就是我們平時所說的枚舉常量(常量一般用大寫)。enum變量類型還可以給其中的常量符號賦值,如果不賦值則會從被賦初值的那個常量開始依次加 1,如果都沒有賦值,它們的值從 0開始依次遞增 1

static 聲明靜態變量

第一個作用:修飾變量。

1靜態全局變量,作用域僅限於變量被定義的文件中,其他文件即使用 extern聲明也沒法使用他

2靜態局部變量,在函數體裏面定義的,就只能在這個函數裏用。由於是存在內存的靜態區,所以即使這個函數運行結束,這個靜態變量的值還是不會被銷燬,函數下次使用時仍然能用到這個值。

第一個作用:修飾函數

函數前加 static使得函數成爲靜態函數。但此處static的含義不是指存儲方式,而是指對函數的作用域僅侷限於本文件(所以又稱內部函數)。使用內部函數的好處是:不同的人編寫不同的函數時,不用擔心自己定義的函數,是否會與其它文件中的函數同名。

switch 用於開關語句

case 開關語句分支

default 開關語句中的其他分支

break 跳出當前循環

如果分支很多……請用switchcase

case 後面只能是整型或字符型的常量或常量表達式【C語言中,字符常量的字面值是整型,表達式運算時,字符型也會自動提升爲整型。這也就是說,在switchcase中寫的表達式,其值是整型:】

register聲明寄存器變量

這個關鍵字請求編譯器儘可能的將變量存在 CPU 內部寄存器中而不是通過內存尋址訪問以提高效率。注意是儘可能,不是絕對

register變量必須是能被 CPU寄存器所接受的類型。意味着 register變量必須是一個單個的值,並且其長度應小於或等於整型的長度

const 聲明只讀變量

volatile 說明變量在程序執行中可被隱含地改變

用它修飾的變量表示可以被某些編譯器未知的因素更改,比如操作系統、硬件或者其它線程等。遇到這個關鍵字聲明的變量,編譯器對訪問該變量的代碼就不再進行優化,從而可以提供對特殊地址的穩定訪問。

typedef 用以給數據類型取別名(當然還有其他作用)

extern 聲明變量是在其他文件正聲明(也可以看做是引用變量)

return 子程序返回語句(可以帶參數,也可不帶參數)

void 聲明函數無返回值或無參數,聲明空類型指針

void* 任何類型的指針都可以直接賦值給它,無需進行強制類型轉換

continue 結束當前循環,開始下一輪循環

do 循環語句的循環體

while 循環語句的循環條件

for 一種循環語句(可意會不可言傳)

if 條件語句

else 條件語句否定分支( if 連用)

C 語言有這樣的規定:else始終與同一括號內最近的未匹配的 if語句結合

goto 無條件跳轉語句

goto 語句可以靈活跳轉,如果不加限制,它的確會破壞結構化設計風格;其次,goto 語句經常帶來錯誤或隱患。它可能跳過了變量的初始化、重要的計算等語句

sizeof 計算對象所佔內存空間大小

sizeof是關鍵字不是函數。sizeof在計算變量所佔空間大小時,括號可以省略,而計算類型(模子)大小時不能省略

第二章符號

國際 C 語言亂碼大賽(IOCCC)這是IOCCC 1988年獲獎作品,作者是 IanPhillipps

#include<stdio.h>

main(t,_,a)char*a;{return!0<t?t<3?main(-79,-13,a+main(-87,1-_,

main(-86,0,a+1)+a)):1,t<_?main(t+1,_,a):3,main(-94,-27+t,a)&&t==2?_<13?

main(2,_+1,"%s%d%d\n"):9:16:t<0?t<-72?main(_,t,

"@n'+,#'/*{}w+/w#cdnr/+,{}r/*de}+,/*{*+,/w{%+,/w#q#n+,/#{l+,/n{n+,/+#n+,/#\

;#q#n+,/+k#;*+,/'r:'d*'3,}{w+Kw'K:'+}e#';dq#'l\

q#'+d'K#!/+k#;q#'r}eKK#}w'r}eKK{nl]'/#;#q#n'){)#}w'){){nl]'/+#n';d}rw'i;#\

){nl]!/n{n#';r{#w'rnc{nl]'/#{l,+'K {rw' iK{;[{nl]'/w#q#n'wknw'\

iwk{KK{nl]!/w{%'l##w#'i;:{nl]'/*{q#'ld;r'}{nlwb!/*de}'c\

;;{nl'-{}rw]'/+,}##'*}#nc,',#nw]'/+kd'+e}+;#'rdq#w!nr'/') }+}{rl#'{n'')# \

}'+}##(!!/")

:t<-50?_==*a?putchar(31[a]):main(-65,_,a+1):main((*a=='/')+t,_,a+1)

:0<t?main(2,2,"%s"):*a=='/'||main(0,main(-61,*a,

"!ek;dci@bK'(q)-[w]*%n+r3#l,{}:\nuwloca-O;m.vpbks,fxntdCeghiry"),a+1);}

接續符和轉義符

C語言裏以反斜槓(\)表示斷行。編譯器會將反斜槓剔除掉,跟在反斜槓後面的字符自動接續到前一行。但是注意:反斜槓之後不能有空格,反斜槓的下一行之前也不能有空格。

花括號

char a[10]={“abcde”};[正確] char a[10]{=“abcde”}; [錯誤]

用花括號是爲了把一些語句或代碼打個包包起來,使之形成一個整體,並與外界絕緣,

++--操作符

inti =3

++i+++i+++i);

表達式的值爲多少?15嗎?16嗎?18嗎?其實對於這種情況,C語言標準並沒有作出規定。有點編譯器計算出來爲 18,因爲 i經過 3次自加後變爲 6,然後3 6 相加得18;而有的編譯器計算出來爲 16(比如VisualC++6.0),先計算前兩個i 的和,這時候i 自加兩次,2 i的和爲 10,然後再加上第三次自加的i 16。其實這些沒有必要辯論,用到哪個編譯器寫句代碼測試就行了。但不會計算出 15的結果來的。

貪心法

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

按照這個規則可能很輕鬆的判斷 a+++b表達式與 a+++b一致

第三章預處理

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

另外 ANSI 標準 C還定義瞭如下幾個宏:

_LINE_ 表示正在編譯的文件的行號

_FILE_ 表示正在編譯的文件的名字

_DATE_ 表示編譯時刻的日期字符串,例如: "25 Dec 2007"

_TIME_ 表示編譯時刻的時間字符串,例如: "12:30:55"

_STDC_ 判斷該文件是不是定義成標準 C程序

如果編譯器不是標準的,則可能僅支持以上宏的一部分,或根本不支持。當然編譯器也有可能還提供其它預定義的宏名。注意:宏名的書寫由標識符與兩邊各二條下劃線構成。

第四章 指針和數組

三個問題:

A),什麼是指針?

B),什麼是數組?

C),數組和指針之間有什麼樣的關係?

指針

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

如何將數值存儲到指定的內存地址

int *p = (int*)0x12ff7c;需要注意的是將地址0x12ff7c 賦值給指針變量p 的時候必須強制轉換

左值和右值

簡單而言,出現在賦值符“=”右邊的就是右值,出現在賦值符“=”左邊的就是左值

C 語言引入一個術語-----“可修改的左值”。意思就是,出現在賦值符左邊的符號所代

表的地址上的內容一定是可以被修改的。換句話說,就是我們只能給非只讀變量賦值

指針與數組

A),char *p =“abcdef”;

B),char a[] =“123456”;

例子A)定義了一個指針變量pp 本身在棧上佔4 bytep 裏存儲的是一塊內存的首地址。這塊內存在靜態區,其空間大小爲7 byte,這塊內存也沒有名字。對這塊內存的訪問完全是匿名的訪問

1)以指針的形式:*(p+4)。

2)以下標的形式:p[4]

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

對指針進行加1 操作,得到的是下一個元素的地址,而不是原有地址值直接加1。所以,一個類型爲T 的指針的移動,以sizeof(T) 爲移動單位

指針數組和數組指針

指針數組首先它是一個數組,數組的元素都是指針,數組佔多少個字節由數組本身決定。它是“儲存指針的數組”的簡稱。

數組指針首先它是一個指針,它指向一個數組。在32 位系統下永遠是佔4 個字節,至於它指向的數組佔多少字節,不知道。它是“指向數組的指針”的簡稱。

下面到底哪個是數組指針,哪個是指針數組呢:

A),int *p1[10];

B),int (*p2)[10];

這裏需要明白一個符號之間的優先級問題。[]的優先級比*要高

P1是一個數組,其包含10 個指向int 類型數據的指針,即指針數組

p2 是一個指針,它指向一個包含10 int 類型數據的數組,即數組指針

二級指針

char **p;

A)p = NULL;

B)char *p2; p = &p2;

給p 賦值沒有問題,但怎麼使用p 呢?這就需要我們前面多次提到的鑰匙(“*”)。

第一步:根據p 這個變量,取出它裏面存的地址。

第二步:找到這個地址所在的內存。

第三步:用鑰匙打開這塊內存,取出它裏面的地址,*p 的值。

第四步:找到第二次取出的這個地址。

第五步:用鑰匙打開這塊內存,取出它裏面的內容,這就是我們真正的數據,**p 的值。

我們在這裏用了兩次鑰匙(“*”)才最終取出了真正的數據。也就是說要取出二級指針所真正指向的數據,需要使用兩次兩次鑰匙(*

第五章 內存管理

棧、堆和靜態區

對於程序員,一般來說,我們可以簡單的理解爲內存分爲三個部分:靜態區,棧,堆

靜態區保存自動全局變量和static 變量(包括static 全局和局部變量)。靜態區的內容在總個程序的生命週期內都存在,由編譯器在編譯的時候分配。

保存局部變量。棧上的內容只在函數的範圍內存在,當函數運行結束,這些內容也會自動被銷燬。其特點是效率高,但空間大小有限。

malloc 系列函數或new 操作符分配的內存。其生命週期由free 或delete 決定。在沒有釋放之前一直存在,直到程序結束。其特點是使用靈活,空間比較大,但容易出錯。

內存泄漏

會產生泄漏的內存就是堆上的內存(這裏不討論資源或句柄等泄漏情況),也就是說由malloc 系列函數或new操作符分配的內存。如果用完之後沒有及時free 或delete,這塊內存就無法釋放,直到整個程序終止。

 

發佈了27 篇原創文章 · 獲贊 19 · 訪問量 4萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章