[size=4] 《Programming in C》学习笔记 [/size]
我花了两个月时间精读《Programming in C》一书, 为的是查缺补漏, 打好基础, 进而深刻理解C语言. 现在把书上曾经作了标记的地方(或者写过代码验证过的细节)整理成笔记.
[size=3]一. 基本数据类型[/size]
a) 基本数据类型和常量
基本数据类型 常量举例 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
(这个字母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
仔细观察, 找到规律就可以记住了:
u表示unsigned
i表示int
d表示10进制
o表示8进制
l表示long
f表示float
e表示科学计数法
g表示啥我不知道(general?),智能输出浮点数格式
b) 字符常量
i. 转义字符:
\a \b \f \n \r \t \v \\ \” \’ \?
注意:
\nnn
nnn是八进制数, 如果不符合下面的条件,则属于未定义行为, vc6会忽略\字符
正则表达式: \\[0-7]{1,3}
一个转义字符只能表示一个8bit字节所容纳的8进制数, 即 \000 -- \377
\unnnn \Unnnn
nnnn是十六进制数
正则表达式: \\[Uu][0-9a-fA-F]{1,n}
具体可以用多大的十六进制数,要看编译器为这个字符准备了多大空间(vc6不支持)
\xnn
nn是十六进制数, 如果不符合下面的条件,则属于未定义行为, vc6会忽略\字符
正则表达式: \\[0-9a-fA-F]{1,2}
一个转义字符只能表示一个8bit字节所容纳的16进制数, 即 \x00 -- \xFF
ii. 多个字符常量
不同的编译器自己决定如何实现,不推荐使用,比如我见过有人用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’
[size=3]二. 符号数和无符号数类型转换陷阱[/size]
一般的数据类型转换原则大家都知道, 但是一些特殊的情况是C语言没有定义的.例如把一个无符号数赋值给有符号数,并且超过了有符号数的范围:
char c = 200; //结果 c == -56
int i = 0xFFFFFFFF; //结果 i == -1
因为这是未定义行为, 原则上不同的编译器会作出不同的处理, 事实上vc6是使用”二进制复制”来赋值的,即把200 (0xC8) 这个字节复制到字符c中; 把0xFFFFFFFF四个字节复制到整型i中.
[size=3]三. 数组初始化[/size]
int iarr[10] = { 0 }; //这样显示初始化第一个元素为0, 然后默认把其他元素初始化为0
//总体效果: 将所有元素初始化为0
int iarr[10] = { [5] = 5, [7] = 1}; //C语言可以指定数组的索引下标进行初始化
//注意C++中不能这么用
[size=3]四. 变量长度数组[/size]
“变量长度数组”是C99新引入的数组. 我测试发现VC6是不支持这个的,但是GCC支持!我写了这样的测试代码,发现程序居然也支持作为i是负数,而且在负数的情况下,GCC的内存分配虽然怪异(0索引元素作为数组物理内存中的最后一个元素,依次向前排列),但也是保证正确的(数组/下标/元素地址/指针计算不是产生错误)。
#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);
}
GCC安全的为负数长度的数组分配了空间,保证了这种数组的安全使用, 不会影响栈上的其他变量空间。
下面是输出:
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
我估计这种不通用的东西产品里应该很少用。尽量避免使用,以增强移植性。
至于数组长度是负数的情况,我是这么想的:
GCC就像数学家发现自然数后又发现了负数那样,GCC为人们实现了负数数组长度,并告诉我们数组长度也可以是负数。至于负数有什么物理意义,数学家先不管了;数组长度负数有什么实际意义,Gcc就不管了,它只是保证了正确的实现。
[size=3]五. 结构的初始化[/size]
结构的初始化尽管可以这样:
typedef struct
{
int a;
char buf[10];
} Recode;
Recode rr = {
10,
{'0','1','2','3','4'}
};
但是这样的隐患是初始化时必须牢记结构成员的顺序, 而且不利于结构声明以后的修改. 如果编译器支持,最好使用下面的形式:
Recode rr = {
.a = 10,
.buf = {'0','1','2','3','4'}
};
[size=3]六. 0长度数组[/size]
0长度数组是个奇怪的东西, 下面的代码(两种形式之一)是可以通过编译的.
char buf[];
或者
char buf[0];
有什么用处呢? 大家知道数组名其实是数组所在内存的首地址, [color=red]那么0长度数组的名字,其实是在内存某个地方中作了一个标记, 在适合的时候将这个标记后面的一段内存作为这个数组的内容.[/color] 貌似数组下标溢出了,但是善于利用这点 可以实现一个”变长”结构体.
例如下面的代码:
#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)
printf("index %u \t name: %s \n", namelist->index, namelist->buf);
}
for(i=0; i<4; ++i)
{
freeName(namelist);
}
return 0;
}
struct __Name有三个成员size_t index; size_t len; char buf[0]; 但是sizeof(Name)的结果是8, 为什么呢?因为上面说了,” 0长度数组的名字,其实是在内存某个地方中作了一个标记”,所以不占空间, 上面代码中的pname = (PName) malloc( sizeof(Name) + len + 1); 一行,申请了一个Name结构体变量,然后这块内存后面紧跟了一块长len+1的内存,所以我们就可以用buf[0..len]来访问这段内存了. 图示如下:
-------------------------------------------------------------------------
| index (4byte) | len (4byte) |<------- len+1 byte -------->|
-------------------------------------------------------------------------
| <----------Name-------------->|
|<-----buf[len+1] ------------|
可见, 原理用一句话来总结,就是利用数组下标”故意”溢出来访问数组首地址后的内存.
再找一个实际应用的例子:
在MS GDIPlus 提供的类库中,有这样一个结构体来表示调色板数据
typedef struct {
UINT Flags;
UINT Count; //下面数组Entries的实际元素数
ARGB Entries[1]; //只包含一个元素的数组,用法类似0长度数组
} ColorPalette;
下面的代码使用GetPalette函数得到一个ColorPalette结构体
UINT size = image->GetPaletteSize();//ColorPalette结构体的实际长度.
printf("The size of the palette is %d bytes.\n", size);
ColorPalette* palette = (ColorPalette*)malloc(size); //一块内存
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]);
}
未完待续, 书上还画了很多地方,看来明天还要继续总结了...
编辑了很久, 排版效果还是不佳,可以看我的blog:[url=http://dulao5.blog.hexun.com/7215174_d.html][color=Red][u]《Programming in C》学习笔记[/u][/color]
[ 本帖最后由 dulao5 于 2007-1-7 15:29 编辑 ]
langue 回复于:2007-01-06 21:18:50
支持一下。
dulao5 回复于:2007-01-06 21:30:40
谢谢这位版姐:D
assiss 回复于:2007-01-06 21:33:32
int iarr[10] = { [5] = 5, [7] = 1}; //C语言可以指定数组的索引下标进行初始化
=====
这是GCC对C89的扩展,
不过C99标准支持了这一初始化方法。
0长度数组
===
c89只支持长度至少为1的数组。
gcc扩展为可以支持0长度的数组,
c99使用flexible array (不知道怎么翻译),即[],里面不出现数字。
flexible array 的许多用法都是受到限制的,但GCC的扩展让限制少了很多。
详见:
http://gcc.gnu.org/onlinedocs/gcc-4.1.1/gcc/Zero-Length.html#Zero-Length
dulao5 回复于:2007-01-06 21:40:23
看了楼上的链接,我明白了,int y[0]和int y[]还是不同的。
这样的代码能运行我还是比较惊奇,没研究过。。。
struct f1 {
int x; int y[];
} f1 = { 1, { 2, 3, 4 } };
手头没有gcc,有机会再研究:D
linyue 回复于:2007-01-06 21:47:06
引用:原帖由 dulao5 于 2007-1-6 21:40 发表
看了楼上的链接,我明白了,int y[0]和int y[]还是不同的。
这样的代码能运行我还是比较惊奇,没研究过。。。
struct f1 {
int x; int y[];
} f1 = { 1, { 2, 3, 4 } };
手头没有gcc,有 ...
第二个f1肯定写错了,怎么能跟结构同名……
langue 回复于:2007-01-06 21:51:24
引用:原帖由 linyue 于 2007-1-6 21:47 发表
第二个f1肯定写错了,怎么能跟结构同名……
struct inode inode;
Linux 的内核代码中也用到了。
poize 回复于:2007-01-06 22:22:55
引用:原帖由 dulao5 于 2007-1-6 21:30 发表
谢谢这位版姐:D
女的啊:shock:
toiby 回复于:2007-01-06 22:26:54
打击下LZ,暂时觉得没意思,没啥新意
[ 本帖最后由 toiby 于 2007-1-6 22:33 编辑 ]
meiyuhan 回复于:2007-01-06 22:36:19
引用:原帖由 poize 于 2007-1-6 22:22 发表
女的啊:shock:
漂亮女老师
新杂人 回复于:2007-01-07 00:28:40
谢谢楼主, 辛苦了!
dlms 回复于:2007-01-07 10:29:59
不错支持
ghostwwl 回复于:2007-01-07 11:53:44
变长结构体 c99支持了 0长度数组是很久很久以前 老一辈革命家经验方法
zwylinux 回复于:2007-01-07 12:15:45
引用:原帖由 dulao5 于 2007-1-6 21:12 发表
[size=4] 《Programming in C》学习笔记 [/size]
我花了两个月时间精读《Programming in C》一书, 为的是查缺补漏, 打好基础, 进而深刻理解C语言. 现在把书上曾经作了标记的地方(或者写过代码验证过的细节 ...
不错阿,up
tyc611 回复于:2007-01-07 13:40:45
> strncpy(pname->buf, strname, len+1);
友情提醒:前面检查了NULL,这儿没有,看到就说了哈
dulao5 回复于:2007-01-07 15:30:44
谢谢tyc611 同志的提醒,我在一楼修改了这个错误。
kesine 回复于:2007-01-08 10:03:04
支持共享
Northwindrocker 回复于:2007-01-08 10:36:46
收藏一下!!!
|