《Programming in C》学习笔记

1557阅读 0评论2011-02-19 A13433758072
分类:C/C++

Programming in C》学习笔记

 

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

 

一.       基本数据类型

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更好看),  10L

0xffL ,  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’

 

二. 符号数和无符号数类型转换陷阱
   一般的数据类型转换原则大家都知道, 但是一些特殊的情况是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索引元素作为数组物理内存中的最后一个元素,依次向前排列),但也是保证正确的(数组/下标/元素地址/指针计算不是产生错误)。

CODE:
#include
#include

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

CODE:
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就不管了,它只是保证了正确的实现。

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

CODE:
        typedef struct
        {
                int a;
                char buf[10];
        } Recode;

        Recode rr = {
                10,
                {'0','1','2','3','4'}
                };

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

CODE:
        Recode rr = {
                .a = 10,
                .buf = {'0','1','2','3','4'}
                };
       

六. 0长度数组

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

例如下面的代码:

CODE:
#include
#include
#include

static const size_t def_name_len = 32 ;
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;
}

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

CODE:
-------------------------------------------------------------------------
|  index (4byte) | len (4byte)  |<------- len+1 byte -------->|
-------------------------------------------------------------------------
| <----------Name-------------->|                     
                                |<-----buf[len+1] ------------|

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

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

CODE:
typedef struct {
    UINT Flags;
    UINT Count; //下面数组Entries的实际元素数
    ARGB Entries[1]; //只包含一个元素的数组,用法类似0长度数组
} ColorPalette;

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

CODE:
   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]);
   }

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

 

 --eof--

 

 

 

 

 

 

 

http://dulao5.blog.hexun.com/7215174_d.html

 

 

 

上一篇:出现频率最高的笔试题strcpy写法
下一篇:double和float类型数据所对应输入输出格式