免费注册 查看新帖 |

Chinaunix

  平台 论坛 博客 文库
12下一页
最近访问板块 发新帖
查看: 8093 | 回复: 18
打印 上一主题 下一主题

《Programming in C》学习笔记 [复制链接]

论坛徽章:
0
跳转到指定楼层
1 [收藏(0)] [报告]
发表于 2007-01-06 21:12 |只看该作者 |倒序浏览
《Programming in C》学习笔记

    我花了两个月时间精读《Programming in C》一书, 为的是查缺补漏, 打好基础, 进而深刻理解C语言. 现在把书上曾经作了标记的地方(或者写过代码验证过的细节)整理成笔记.

一.        基本数据类型
a)        基本数据类型和常量

  1. 基本数据类型                常量举例                printf 如何格式输出
  2. ------------------------------------------------------------------------------
  3. _Bool                         0,1                          %u  %i
  4. char                          'c' 'a'                      %c
  5. unsigned char                 'c' 'a'                      %c
  6. short int                       --                         %hi  %ho  %hx
  7. unsigned short int              --                         %hi  %ho  %hx
  8. int                           10, -20, 0xff, 0777          %i  %o  %x
  9. unsigned int                  10u, 0xffu, 0777U            %u  %o  %x
  10. long                          10l
  11.                               (这个字母l还是写成大写L更好看),  
  12.                               10L0xffL ,  077777L          %li  %lo  %lx
  13. unsigned long                 10UL,  0xffffffUL            %lu  %lo  %lx
  14. long long                     10LL,  0xffffffLL            %lli  %llo  %llx
  15. unsigned long long            10ULL, 0xffffffULL           %llu  %llo  %llx
  16. float                         10.00f,3.14e-7f,0x10.0p20    %f  %e  %g  %a
  17. double                        10.00,3.14e-7,0x10.0p20      %f  %e  %g  %a
  18. long double                   10.00L, 3.14e-7L             %Lf  %Le  %Lg
  19. float _Complex               
  20. double _Complex      
  21. long double _Complex      
  22. _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’

二.        符号数和无符号数类型转换陷阱
        一般的数据类型转换原则大家都知道, 但是一些特殊的情况是C语言没有定义的.例如把一个无符号数赋值给有符号数,并且超过了有符号数的范围:
                char c = 200;                        //结果 c == -56
                int i = 0xFFFFFFFF;        //结果 i == -1
        因为这是未定义行为, 原则上不同的编译器会作出不同的处理, 事实上vc6是使用”二进制复制”来赋值的,即把200 (0xC8) 这个字节复制到字符c中; 把0xFFFFFFFF四个字节复制到整型i中.

三.        数组初始化
        int iarr[10] = { 0 };  //这样显示初始化第一个元素为0, 然后默认把其他元素初始化为0
                                //总体效果: 将所有元素初始化为0
        int iarr[10] = { [5] = 5, [7] = 1}; //C语言可以指定数组的索引下标进行初始化
                                                        //注意C++中不能这么用
        
四.        变量长度数组
        “变量长度数组”是C99新引入的数组. 我测试发现VC6是不支持这个的,但是GCC支持!我写了这样的测试代码,发现程序居然也支持作为i是负数,而且在负数的情况下,GCC的内存分配虽然怪异(0索引元素作为数组物理内存中的最后一个元素,依次向前排列),但也是保证正确的(数组/下标/元素地址/指针计算不是产生错误)。

  1. #include <stdlib.h>
  2. #include <stdio.h>

  3. void fun(int i)
  4. {
  5.     char kk = 'B';
  6.     char buf[ i ];
  7.     char mm = 'E';

  8.     printf("size :: %d %x -- %x\n", sizeof(buf), (size_t)buf, (size_t)&buf[i-1]);
  9.     buf[i-1] = 'a';
  10.     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);
  11. }

  12. int main(int argc, char * argv[], char * envp[])
  13. {
  14.     fun(2);
  15.     fun(3);
  16.     fun(4);
  17.     fun(1);
  18.     fun(0);
  19.     fun(-1);
  20.     fun(-10);
  21. }
复制代码


        GCC安全的为负数长度的数组分配了空间,保证了这种数组的安全使用, 不会影响栈上的其他变量空间。
        下面是输出:

  1. size :: 2 bfbfec50 -- bfbfec51
  2.                          buf[i-1]:a      bfbfec7f:B      bfbfec7e:E
  3. size :: 3 bfbfec50 -- bfbfec52
  4.                          buf[i-1]:a      bfbfec7f:B      bfbfec7e:E
  5. size :: 4 bfbfec50 -- bfbfec53
  6.                          buf[i-1]:a      bfbfec7f:B      bfbfec7e:E
  7. size :: 1 bfbfec60 -- bfbfec60
  8.                          buf[i-1]:a      bfbfec7f:B      bfbfec7e:E
  9. size :: 0 bfbfec60 -- bfbfec5f
  10.                          buf[i-1]:a      bfbfec7f:B      bfbfec7e:E
  11. size :: -1 bfbfec60 -- bfbfec5e
  12.                          buf[i-1]:a      bfbfec7f:B      bfbfec7e:E
  13. size :: -10 bfbfec60 -- bfbfec55
  14.                          buf[i-1]:       bfbfec7f:B      bfbfec7e:E
复制代码


        我估计这种不通用的东西产品里应该很少用。尽量避免使用,以增强移植性。

        至于数组长度是负数的情况,我是这么想的:
        GCC就像数学家发现自然数后又发现了负数那样,GCC为人们实现了负数数组长度,并告诉我们数组长度也可以是负数。至于负数有什么物理意义,数学家先不管了;数组长度负数有什么实际意义,Gcc就不管了,它只是保证了正确的实现。

五.        结构的初始化
        结构的初始化尽管可以这样:


  1.         typedef struct
  2.         {
  3.                 int a;
  4.                 char buf[10];
  5.         } Recode;

  6.         Recode rr = {
  7.                 10,
  8.                 {'0','1','2','3','4'}
  9.                 };
复制代码


        但是这样的隐患是初始化时必须牢记结构成员的顺序, 而且不利于结构声明以后的修改. 如果编译器支持,最好使用下面的形式:


  1.         Recode rr = {
  2.                 .a = 10,
  3.                 .buf = {'0','1','2','3','4'}
  4.                 };
  5.        
复制代码


六.        0长度数组

        0长度数组是个奇怪的东西, 下面的代码(两种形式之一)是可以通过编译的.
                char buf[];
        或者
                char buf[0];
        有什么用处呢? 大家知道数组名其实是数组所在内存的首地址, 那么0长度数组的名字,其实是在内存某个地方中作了一个标记, 在适合的时候将这个标记后面的一段内存作为这个数组的内容. 貌似数组下标溢出了,但是善于利用这点                可以实现一个”变长”结构体.

例如下面的代码:

  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. #include <string.h>

  4. static const size_t def_name_len = 31 ;
  5. typedef struct __Name
  6. {
  7.         size_t index;
  8.         size_t len;
  9.         char buf[0];
  10. } Name, *PName ;

  11. Name * createName(size_t index, const char * strname)
  12. {
  13.         size_t len;       
  14.         PName pname = NULL;
  15.        
  16.         if (strname == NULL)
  17.         {
  18.                 len = def_name_len;
  19.         }       
  20.         else
  21.         {
  22.                 len = strlen(strname);
  23.         }
  24.        
  25.         pname = (PName) malloc( sizeof(Name) + len + 1);  
  26.        
  27.         if(pname == NULL) return NULL;

  28.         pname->index = index;
  29.         pname->len = len;
  30.         pname->buf[0] = '\0';
  31.         if (strname)  strncpy(pname->buf, strname, len+1);
  32.         return pname;
  33. }

  34. void freeName(PName pname)
  35. {
  36.         if(pname == NULL) return;
  37.         free(pname);
  38.         pname = NULL;
  39. }

  40. int main()
  41. {
  42.         int i;

  43.         PName namelist[4] = {
  44.                 createName(1, "name1"),
  45.                 createName(2, "name2"),
  46.                 createName(3, "name3"),
  47.                 createName(4, "name4"),
  48.         };
  49.        
  50.         for(i=0; i<4; ++i)
  51.         {
  52.                 if(namelist[i])
  53.                         printf("index %u \t name: %s \n", namelist[i]->index, namelist[i]->buf);
  54.         }
  55.         for(i=0; i<4; ++i)
  56.         {
  57.                 freeName(namelist[i]);
  58.         }
  59.         return 0;
  60. }
复制代码


        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]来访问这段内存了. 图示如下:

  1. -------------------------------------------------------------------------
  2. |  index (4byte) | len (4byte)  |<------- len+1 byte -------->|
  3. -------------------------------------------------------------------------
  4. | <----------Name-------------->|                     
  5.                                 |<-----buf[len+1] ------------|
复制代码


        可见, 原理用一句话来总结,就是利用数组下标”故意”溢出来访问数组首地址后的内存.

再找一个实际应用的例子:
        在MS GDIPlus 提供的类库中,有这样一个结构体来表示调色板数据


  1. typedef struct {
  2.     UINT Flags;
  3.     UINT Count; //下面数组Entries的实际元素数
  4.     ARGB Entries[1]; //只包含一个元素的数组,用法类似0长度数组
  5. } ColorPalette;
复制代码


        下面的代码使用GetPalette函数得到一个ColorPalette结构体

  1.    UINT size = image->GetPaletteSize();//ColorPalette结构体的实际长度.
  2.    printf("The size of the palette is %d bytes.\n", size);
  3.    ColorPalette* palette = (ColorPalette*)malloc(size); //一块内存
  4.    image->GetPalette(palette, size);
  5.    if(size > 0)
  6.    {
  7.       printf("There are %u colors in the palette.\n", palette->Count);
  8.       printf("The first five colors in the palette are as follows:\n");
  9.       for(INT j = 0; j < palette->Count; ++j)
  10.          printf("%x\n", palette->Entries[j]);
  11.    }
复制代码


        未完待续, 书上还画了很多地方,看来明天还要继续总结了...

编辑了很久, 排版效果还是不佳,可以看我的blog:《Programming in C》学习笔记

[ 本帖最后由 dulao5 于 2007-1-7 15:29 编辑 ]

论坛徽章:
0
2 [报告]
发表于 2007-01-06 21:18 |只看该作者
支持一下。

论坛徽章:
0
3 [报告]
发表于 2007-01-06 21:30 |只看该作者
谢谢这位版姐

论坛徽章:
0
4 [报告]
发表于 2007-01-06 21:33 |只看该作者
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/gc ... th.html#Zero-Length

论坛徽章:
0
5 [报告]
发表于 2007-01-06 21:40 |只看该作者
看了楼上的链接,我明白了,int y[0]和int y[]还是不同的。
这样的代码能运行我还是比较惊奇,没研究过。。。
     struct f1 {
       int x; int y[];
     } f1 = { 1, { 2, 3, 4 } };

手头没有gcc,有机会再研究

论坛徽章:
0
6 [报告]
发表于 2007-01-06 21:47 |只看该作者
原帖由 dulao5 于 2007-1-6 21:40 发表
看了楼上的链接,我明白了,int y[0]和int y[]还是不同的。
这样的代码能运行我还是比较惊奇,没研究过。。。
     struct f1 {
       int x; int y[];
     } f1 = { 1, { 2, 3, 4 } };

手头没有gcc,有 ...


第二个f1肯定写错了,怎么能跟结构同名……

论坛徽章:
0
7 [报告]
发表于 2007-01-06 21:51 |只看该作者
原帖由 linyue 于 2007-1-6 21:47 发表


第二个f1肯定写错了,怎么能跟结构同名……


struct inode inode;

Linux 的内核代码中也用到了。

论坛徽章:
0
8 [报告]
发表于 2007-01-06 22:22 |只看该作者
原帖由 dulao5 于 2007-1-6 21:30 发表
谢谢这位版姐

女的啊

论坛徽章:
0
9 [报告]
发表于 2007-01-06 22:26 |只看该作者
打击下LZ,暂时觉得没意思,没啥新意

[ 本帖最后由 toiby 于 2007-1-6 22:33 编辑 ]

论坛徽章:
0
10 [报告]
发表于 2007-01-06 22:36 |只看该作者
原帖由 poize 于 2007-1-6 22:22 发表

女的啊


漂亮女老师
您需要登录后才可以回帖 登录 | 注册

本版积分规则 发表回复

  

北京盛拓优讯信息技术有限公司. 版权所有 京ICP备16024965号-6 北京市公安局海淀分局网监中心备案编号:11010802020122 niuxiaotong@pcpop.com 17352615567
未成年举报专区
中国互联网协会会员  联系我们:huangweiwei@itpub.net
感谢所有关心和支持过ChinaUnix的朋友们 转载本站内容请注明原作者名及出处

清除 Cookies - ChinaUnix - Archiver - WAP - TOP