Chapter_03 分析C语言的声明

  • 3.1 只有编译器才会喜欢的语法

C语言的声明语法(声明模型)很晦涩,容易成为程序员的障碍,正是由于在组合类型方面的笨拙行为,C语言显得很复杂。

造成这种情况的原因是因为“类型模型(type model)”这个概念对于当时的编程语言理论而言尚属陌生。BCPL(C语言前身)语言几乎没有类型,其以二进制字作为唯一的数据类型。
C语言的设计哲学之一:对象的声明形式与它的使用形式要尽可能相似
int *p[3]是一个int类型的指针数组。
其使用方法如下:
    // p 是一个指针数组,其中的每个元素都是一个指向 int类型的指针
    int * p[3];

    // 声明三个 int数组
    int a0[] = {2, 3, 4};
    int a1[] = {0, 1};
    int a2[] = {-8, 9, 0, 1, 3};

    // 赋值
    p[0] = a0;
    p[1] = a1;
    p[2] = a2;

    int i;
    printf( "a0:\t");
    for( i = 0; i < 3; i++, p[0]++)
    {
        printf("%d\t" , *p[0]);
    }
    printf( "\na1:\t");
    for( i = 0; i < 2; i++, p[1]++)
    {
        printf("%d\t" , *p[1]);
    }
    printf( "\na2:\t");
    for( i = 0; i < 4; i++, p[2]++)
    {
        printf("%d\t" , *p[2]);
    }
其执行结果如下:
 
 
C语言的声明所存在的最大问题是你无法以一种习惯性的从左向右的方式阅读一个声明
 
关于const
指针所指向的对象是只读的两种声明:
    const int i [] = {1024, 1045};

    // 指针所指向的对象是只读的
    const int * grape ;     // 等同于 const int (*grape)
    int const * orange ;    // 等同于 int const (*orange)
    grape = & i ;
    orange = & i ;

    printf( "%d\t" , *grape );  // grape: 1024
    grape++;                    // 合法,改变指针本身的值,grape指向i[1]
    printf( "%d\n" , *grape );  // grape: 1045
    //(*grape) += 4;           // 非法,改变指针所指向的 int 对象的值

 

指针本身是只读的声明:
    int i = 1024;

    // 指针本身是只读的
    int * const apple = &i;

    *apple += 4;   // 合法,改变指针所指向的对象的值

    // apple += 4;  // 非法,改变指针本身的值
       
    printf("i: %d\n", *apple );  // i的值变为1028

指针本身是只读
且指针所指向的对象也是只读的两种声明:
    const int i = 1024;

    // 指针本身只读 && 所指向的对象也只读
    const int * const grape_jam_1 = & i;
    int const * const grape_jam_2 = & i;

    grape_jam_1 += 4;   // 非法,不能改变指针本身的值
    *grape_jam_1 += 4;  // 非法,不能改变指针所指向对象的值
上面这段代码所产生的错误如下:
 
总结的规则:在指针的声明中,只要const“紧挨着”指针变量就说明该指针本身是只读的,否则就是该指针所指向的对象是只读的
 
  • 3.2 声明是如何形成的
声明器(declarator)是所有声明的核心。
声明器:标识符以及与它组合在一起的任何指针(*)、函数括号(())、数组下标([])等。
 
关于结构(struct)
 
结构就是一种把一些数据项组合在一起的数据结构。其语法为:
struct { 内容 ... };
结构的定义后可以跟一些变量名,表示这些变量的类型是这个结构:
struct { 内容 ... } plum, pomegranate, pear ;
struct关键字后可以加一个可选的“结构标签”,可以作为结构的简写形式(struct fruit_tag)用于以后定义变量:
struct fruit_tag { 内容... } plum, pomegranate , pear;
因此,结构的通常形式:
struct 结构标签 (可选){
    类型1 标识符1;
    类型2 标识符2;
    ....
} 变量定义( 可选);
作者建议不要把结构的声明和变量的定义混合在一起,而是分开,使代码更容易阅读。
变量的声明应该与类型的声明分开。
 
两个跟结构有关的参数传递问题:
  1. 函数在传递参数时,首先尽可能地存放在寄存器中(追求速度)。——一个int型参数一般会被传递到寄存器中,而结构参数则很可能被传递到堆栈中。
  2. 在结构中放置数组,可将该结构(内包含数组)当做第一等级的类型(赋值语句拷贝整个数组、作为函数的返回类型、传值调用)。
第二点如下代码所示:
struct s_tag
{
    int a[100];   // 结构内包含数组
};

// 可将该结构作为第一等级的类型
struct s_tag orange, lime, lemon ;

// 结构作为函数参数及函数返回值
struct s_tag twofold( struct s_tag s)
{
    int j;
    for( j = 0; j < 100; j++)
    {
        s.a [j] *= 2;
    }

    return s;
}

int main ()
{
    int i;
    for( i = 0; i < 100; i++)
    {
        lime.a [i] = 1;
    }

    lemon = twofold(lime );
    orange = lemon;      // 给整个结构赋值
}
结构中包含一个指向结构本身的指针,这种方法常用于列表(list)、树(tree)等。
 
关于联合(union)
 
结构中每个成员依次存储,而在联合中,所有成员都成偏移量为0处开始存储,在某一时刻,只有一个成员真正存储于该地址。联合因此也叫“变体记录”。
联合的一般形式:
union 可选的标签
{
    类型  标识符;
    类型  标识符;
    ...
    类型N   标识符N ;
}可选的变量定义;
联合一般作为大型结构的一部分存在。用以节省存储空间,因为某些数据项不可能同时出现
联合也可以把同一个数据解释成两种不同的东西,而不是把两个不同的数据解释为同一种东西。
作者最后列举数据(15W行与机器无关的OS源代码),说明结构出现的次数大约是联合的一百倍。
 
关于枚举(enum)
 
枚举通过一种简单的途径,把一串名字与一串整型值联系在一起。
对于C来说,很少有什么事只能考枚举来完成而无法用#define解决的。
枚举的一版形式:
enum 可选标签
{
    内容...
}可选变量定义;
缺省情况下,整型值从0开始。如果对列表中的某个标识符进行了赋值,那么后面的标识符依次大1。
枚举与#define的一个不同:#define定义的名字一般在编译时被丢弃,而枚举名字通常在调试器中一直可见。
 
  • 3.3 优先级规则
要理解一个声明,必须要懂得其中的优先级规则。
理解C语言声明的优先级规则:
A    声明从它的名字开始读取,然后按照优先级依次读取。
B    优先级顺序:
    B.  1    声明中被括号括起来的部分
    B.  2    后缀操作符:   
               ()  表示这是一个函数
               []  表示这是一个数组
    B.  3    前缀操作符: * 表示“指向...的指针”。
C     如果const、volatile关键字的后面紧跟类型说明符(int long float),那么它作用于类型说明符。在其他情况下,其作用于它紧邻的指针星号。
 
  • 3.5 关于typedef
在某些方面,typedef类似于宏文本替换——它没有引入新类型,而是为现有类型取个新名字。
一般情况下,typedef用于简洁地表示指向其他东西的指针。
不要在一个typedef中放入几个声明器;不要把typedef嵌入到声明的中间部分。
在同一个代码块中,typedef引入的名字不能与其他标识符同名。
 
  • 3.6  typedef和define的区别
typedef和宏文本替换之间存在的两个方面的区别:
1. 可以用其他类型说明符对宏类型名进行扩展,但对typedef定义的类型名就不能这样做;
#define   peach   int
typedef   int   orange;

unsigned peach i;    /* 合法,相当于 unsigned int i */
unsigned orange j;   /* 不合法*/
2. 在连续几个变量的声明语句中,用typedef定义的类型能够保证声明中所有的变量均为同一种类型,而#define定义的类型则无法保证。
#define d_string char*
d_string str1 , str2;   /* str1 的类型为char *, str2 的类型为 char */

typedef char * t_string;
t_string str3 ,  str4;   /* str3 和str4 的类型均为 char* */

 

  • 3.7  C语言中的名字空间
C语言中的名字空间:
  1. 标签名(label name)
  2. 标签(tag)。——用于所有的结构、枚举、联合;
  3. 成员名:每个结构或联合都有自己的名字空间;
  4. 其他
在同一个名字空间里,任何名字都必须具有唯一性,但在不同的名字空间里可以存在相同的名字。
typedef struct fruit
{
    int weight ;
    int price_per_lb ;
} fruit;  /* 结构标签与结构类型具有了相同的名字 */

struct fruit mandarin;  /* 使用结构标签 fruit */
fruit orange ;      /* 使用结构类型 fruit */
如上这种情况,结构标签与typedef的结构别名具有了相同的名字,作者在此处建议:当你有两个不同的东西时,一个比较好的原则就是用不同的名字来称呼它们。这样做减少了混淆的危险(这始终是软件的一个重要准则)。比如此处可以在结构标签后加一个"_tag"后缀。
posted @ 2013-12-27 11:56  qdsclove  阅读(445)  评论(6编辑  收藏  举报