- 論壇徽章:
- 0
|
《Programming in C》學(xué)習(xí)筆記
我花了兩個(gè)月時(shí)間精讀《Programming in C》一書, 為的是查缺補(bǔ)漏, 打好基礎(chǔ), 進(jìn)而深刻理解C語(yǔ)言. 現(xiàn)在把書上曾經(jīng)作了標(biāo)記的地方(或者寫過(guò)代碼驗(yàn)證過(guò)的細(xì)節(jié))整理成筆記.
一. 基本數(shù)據(jù)類型
a) 基本數(shù)據(jù)類型和常量
- 基本數(shù)據(jù)類型 常量舉例 printf 如何格式輸出
- ------------------------------------------------------------------------------
- _Bool 0,1 %u %i
- char 'c' 'a' %c
- unsigned char 'c' 'a' %c
- short int -- %hi %ho %hx
- unsigned short int -- %hi %ho %hx
- int 10, -20, 0xff, 0777 %i %o %x
- unsigned int 10u, 0xffu, 0777U %u %o %x
- long 10l
- (這個(gè)字母l還是寫成大寫L更好看),
- 10L0xffL , 077777L %li %lo %lx
- unsigned long 10UL, 0xffffffUL %lu %lo %lx
- long long 10LL, 0xffffffLL %lli %llo %llx
- unsigned long long 10ULL, 0xffffffULL %llu %llo %llx
- float 10.00f,3.14e-7f,0x10.0p20 %f %e %g %a
- double 10.00,3.14e-7,0x10.0p20 %f %e %g %a
- long double 10.00L, 3.14e-7L %Lf %Le %Lg
- float _Complex
- double _Complex
- long double _Complex
- _Imaginary
復(fù)制代碼
仔細(xì)觀察, 找到規(guī)律就可以記住了:
u表示unsigned
i表示int
d表示10進(jìn)制
o表示8進(jìn)制
l表示long
f表示float
e表示科學(xué)計(jì)數(shù)法
g表示啥我不知道(general?),智能輸出浮點(diǎn)數(shù)格式
b) 字符常量
i. 轉(zhuǎn)義字符:
\a \b \f \n \r \t \v \\ \” \’ \?
注意:
\nnn
nnn是八進(jìn)制數(shù), 如果不符合下面的條件,則屬于未定義行為, vc6會(huì)忽略\字符
正則表達(dá)式: \\[0-7]{1,3}
一個(gè)轉(zhuǎn)義字符只能表示一個(gè)8bit字節(jié)所容納的8進(jìn)制數(shù), 即 \000 -- \377
\unnnn \Unnnn
nnnn是十六進(jìn)制數(shù)
正則表達(dá)式: \\[Uu][0-9a-fA-F]{1,n}
具體可以用多大的十六進(jìn)制數(shù),要看編譯器為這個(gè)字符準(zhǔn)備了多大空間(vc6不支持)
\xnn
nn是十六進(jìn)制數(shù), 如果不符合下面的條件,則屬于未定義行為, vc6會(huì)忽略\字符
正則表達(dá)式: \\[0-9a-fA-F]{1,2}
一個(gè)轉(zhuǎn)義字符只能表示一個(gè)8bit字節(jié)所容納的16進(jìn)制數(shù), 即 \x00 -- \xFF
ii. 多個(gè)字符常量
不同的編譯器自己決定如何實(shí)現(xiàn),不推薦使用,比如我見(jiàn)過(guò)有人用vc寫這樣的代碼:
long LL = 'abcd';
printf("%c %c %c %c \n",
((char*)&LL)[0], ((char*)&LL)[1],
((char*)&LL)[2], ((char*)&LL)[3]);
iii. 寬字符常量
寬字符類型名: wchar_t
vc6這樣定義它: typedef unsigned short wchar_t;
我的GCC定義: typedef long wchar_t;
寬字符常量在窄字符常量前加L, 如 L’a’ L’9’
二. 符號(hào)數(shù)和無(wú)符號(hào)數(shù)類型轉(zhuǎn)換陷阱
一般的數(shù)據(jù)類型轉(zhuǎn)換原則大家都知道, 但是一些特殊的情況是C語(yǔ)言沒(méi)有定義的.例如把一個(gè)無(wú)符號(hào)數(shù)賦值給有符號(hào)數(shù),并且超過(guò)了有符號(hào)數(shù)的范圍:
char c = 200; //結(jié)果 c == -56
int i = 0xFFFFFFFF; //結(jié)果 i == -1
因?yàn)檫@是未定義行為, 原則上不同的編譯器會(huì)作出不同的處理, 事實(shí)上vc6是使用”二進(jìn)制復(fù)制”來(lái)賦值的,即把200 (0xC8) 這個(gè)字節(jié)復(fù)制到字符c中; 把0xFFFFFFFF四個(gè)字節(jié)復(fù)制到整型i中.
三. 數(shù)組初始化
int iarr[10] = { 0 }; //這樣顯示初始化第一個(gè)元素為0, 然后默認(rèn)把其他元素初始化為0
//總體效果: 將所有元素初始化為0
int iarr[10] = { [5] = 5, [7] = 1}; //C語(yǔ)言可以指定數(shù)組的索引下標(biāo)進(jìn)行初始化
//注意C++中不能這么用
四. 變量長(zhǎng)度數(shù)組
“變量長(zhǎng)度數(shù)組”是C99新引入的數(shù)組. 我測(cè)試發(fā)現(xiàn)VC6是不支持這個(gè)的,但是GCC支持!我寫了這樣的測(cè)試代碼,發(fā)現(xiàn)程序居然也支持作為i是負(fù)數(shù),而且在負(fù)數(shù)的情況下,GCC的內(nèi)存分配雖然怪異(0索引元素作為數(shù)組物理內(nèi)存中的最后一個(gè)元素,依次向前排列),但也是保證正確的(數(shù)組/下標(biāo)/元素地址/指針計(jì)算不是產(chǎn)生錯(cuò)誤)。
- #include <stdlib.h>
- #include <stdio.h>
- void fun(int i)
- {
- char kk = 'B';
- char buf[ i ];
- char mm = 'E';
- printf("size :: %d %x -- %x\n", sizeof(buf), (size_t)buf, (size_t)&buf[i-1]);
- buf[i-1] = 'a';
- printf("\t\t\t buf[i-1]:%c\t %x:%c \t %x:%c \n", buf[i-1], (size_t)&kk, kk, (size_t)&mm, mm);
- }
- int main(int argc, char * argv[], char * envp[])
- {
- fun(2);
- fun(3);
- fun(4);
- fun(1);
- fun(0);
- fun(-1);
- fun(-10);
- }
復(fù)制代碼
GCC安全的為負(fù)數(shù)長(zhǎng)度的數(shù)組分配了空間,保證了這種數(shù)組的安全使用, 不會(huì)影響棧上的其他變量空間。
下面是輸出:
- size :: 2 bfbfec50 -- bfbfec51
- buf[i-1]:a bfbfec7f:B bfbfec7e:E
- size :: 3 bfbfec50 -- bfbfec52
- buf[i-1]:a bfbfec7f:B bfbfec7e:E
- size :: 4 bfbfec50 -- bfbfec53
- buf[i-1]:a bfbfec7f:B bfbfec7e:E
- size :: 1 bfbfec60 -- bfbfec60
- buf[i-1]:a bfbfec7f:B bfbfec7e:E
- size :: 0 bfbfec60 -- bfbfec5f
- buf[i-1]:a bfbfec7f:B bfbfec7e:E
- size :: -1 bfbfec60 -- bfbfec5e
- buf[i-1]:a bfbfec7f:B bfbfec7e:E
- size :: -10 bfbfec60 -- bfbfec55
- buf[i-1]: bfbfec7f:B bfbfec7e:E
復(fù)制代碼
我估計(jì)這種不通用的東西產(chǎn)品里應(yīng)該很少用。盡量避免使用,以增強(qiáng)移植性。
至于數(shù)組長(zhǎng)度是負(fù)數(shù)的情況,我是這么想的:
GCC就像數(shù)學(xué)家發(fā)現(xiàn)自然數(shù)后又發(fā)現(xiàn)了負(fù)數(shù)那樣,GCC為人們實(shí)現(xiàn)了負(fù)數(shù)數(shù)組長(zhǎng)度,并告訴我們數(shù)組長(zhǎng)度也可以是負(fù)數(shù)。至于負(fù)數(shù)有什么物理意義,數(shù)學(xué)家先不管了;數(shù)組長(zhǎng)度負(fù)數(shù)有什么實(shí)際意義,Gcc就不管了,它只是保證了正確的實(shí)現(xiàn)。
五. 結(jié)構(gòu)的初始化
結(jié)構(gòu)的初始化盡管可以這樣:
- typedef struct
- {
- int a;
- char buf[10];
- } Recode;
- Recode rr = {
- 10,
- {'0','1','2','3','4'}
- };
復(fù)制代碼
但是這樣的隱患是初始化時(shí)必須牢記結(jié)構(gòu)成員的順序, 而且不利于結(jié)構(gòu)聲明以后的修改. 如果編譯器支持,最好使用下面的形式:
- Recode rr = {
- .a = 10,
- .buf = {'0','1','2','3','4'}
- };
-
復(fù)制代碼
六. 0長(zhǎng)度數(shù)組
0長(zhǎng)度數(shù)組是個(gè)奇怪的東西, 下面的代碼(兩種形式之一)是可以通過(guò)編譯的.
char buf[];
或者
char buf[0];
有什么用處呢? 大家知道數(shù)組名其實(shí)是數(shù)組所在內(nèi)存的首地址, 那么0長(zhǎng)度數(shù)組的名字,其實(shí)是在內(nèi)存某個(gè)地方中作了一個(gè)標(biāo)記, 在適合的時(shí)候?qū)⑦@個(gè)標(biāo)記后面的一段內(nèi)存作為這個(gè)數(shù)組的內(nèi)容. 貌似數(shù)組下標(biāo)溢出了,但是善于利用這點(diǎn) 可以實(shí)現(xiàn)一個(gè)”變長(zhǎng)”結(jié)構(gòu)體.
例如下面的代碼:
- #include <stdio.h>
- #include <stdlib.h>
- #include <string.h>
- static const size_t def_name_len = 31 ;
- typedef struct __Name
- {
- size_t index;
- size_t len;
- char buf[0];
- } Name, *PName ;
- Name * createName(size_t index, const char * strname)
- {
- size_t len;
- PName pname = NULL;
-
- if (strname == NULL)
- {
- len = def_name_len;
- }
- else
- {
- len = strlen(strname);
- }
-
- pname = (PName) malloc( sizeof(Name) + len + 1);
-
- if(pname == NULL) return NULL;
- pname->index = index;
- pname->len = len;
- pname->buf[0] = '\0';
- if (strname) strncpy(pname->buf, strname, len+1);
- return pname;
- }
- void freeName(PName pname)
- {
- if(pname == NULL) return;
- free(pname);
- pname = NULL;
- }
- int main()
- {
- int i;
- PName namelist[4] = {
- createName(1, "name1"),
- createName(2, "name2"),
- createName(3, "name3"),
- createName(4, "name4"),
- };
-
- for(i=0; i<4; ++i)
- {
- if(namelist[i])
- printf("index %u \t name: %s \n", namelist[i]->index, namelist[i]->buf);
- }
- for(i=0; i<4; ++i)
- {
- freeName(namelist[i]);
- }
- return 0;
- }
復(fù)制代碼
struct __Name有三個(gè)成員size_t index; size_t len; char buf[0]; 但是sizeof(Name)的結(jié)果是8, 為什么呢?因?yàn)樯厦嬲f(shuō)了,” 0長(zhǎng)度數(shù)組的名字,其實(shí)是在內(nèi)存某個(gè)地方中作了一個(gè)標(biāo)記”,所以不占空間, 上面代碼中的pname = (PName) malloc( sizeof(Name) + len + 1); 一行,申請(qǐng)了一個(gè)Name結(jié)構(gòu)體變量,然后這塊內(nèi)存后面緊跟了一塊長(zhǎng)len+1的內(nèi)存,所以我們就可以用buf[0..len]來(lái)訪問(wèn)這段內(nèi)存了. 圖示如下:
- -------------------------------------------------------------------------
- | index (4byte) | len (4byte) |<------- len+1 byte -------->|
- -------------------------------------------------------------------------
- | <----------Name-------------->|
- |<-----buf[len+1] ------------|
復(fù)制代碼
可見(jiàn), 原理用一句話來(lái)總結(jié),就是利用數(shù)組下標(biāo)”故意”溢出來(lái)訪問(wèn)數(shù)組首地址后的內(nèi)存.
再找一個(gè)實(shí)際應(yīng)用的例子:
在MS GDIPlus 提供的類庫(kù)中,有這樣一個(gè)結(jié)構(gòu)體來(lái)表示調(diào)色板數(shù)據(jù)
- typedef struct {
- UINT Flags;
- UINT Count; //下面數(shù)組Entries的實(shí)際元素?cái)?shù)
- ARGB Entries[1]; //只包含一個(gè)元素的數(shù)組,用法類似0長(zhǎng)度數(shù)組
- } ColorPalette;
復(fù)制代碼
下面的代碼使用GetPalette函數(shù)得到一個(gè)ColorPalette結(jié)構(gòu)體
- UINT size = image->GetPaletteSize();//ColorPalette結(jié)構(gòu)體的實(shí)際長(zhǎng)度.
- printf("The size of the palette is %d bytes.\n", size);
- ColorPalette* palette = (ColorPalette*)malloc(size); //一塊內(nèi)存
- image->GetPalette(palette, size);
- if(size > 0)
- {
- printf("There are %u colors in the palette.\n", palette->Count);
- printf("The first five colors in the palette are as follows:\n");
- for(INT j = 0; j < palette->Count; ++j)
- printf("%x\n", palette->Entries[j]);
- }
復(fù)制代碼
未完待續(xù), 書上還畫了很多地方,看來(lái)明天還要繼續(xù)總結(jié)了...
編輯了很久, 排版效果還是不佳,可以看我的blog:《Programming in C》學(xué)習(xí)筆記
[ 本帖最后由 dulao5 于 2007-1-7 15:29 編輯 ] |
|