《C专家编程》读书笔记(三)

《C专家编程》第三章笔记

第三章 - 分析C语言的声明

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

Kernighan和Ritchie承认 - C语言声明的语法有时会带来严重的问题
1."声明的形式和使用的形式相似"这种用法可能是C语言的独创,其他语言并没有采取这种做法。
   -- int *p[3] 和 使用*p[i]这样的表达式引用或使用指针

   C语言的声明所存在的最大问题是你无法以一种人们所习惯的自然方式从左向右阅读一个声明
     --> 介个...我现在已经习惯了

   关于指向数组的指针 -- 强制转换
   char (*j)[20]; /* j是一个指向数组的指针,数组内有20个char元素*/
   j = (char (*)[20])malloc(20); /*如果去掉星号两边的括号,代码会变成非法*/

   涉及指针和const的声明可能会出现几种不同的顺序
   const int *var;
   int const *var;
   上面两种情况下,指针所指向的对象是只读的
   int *const var;   -->这种情况下,指针是只读的

3.2声明是如何形成的

1.声明器 - 所有声明的核心
  简单地说,就是标识符以及与它组合在一起的任何指针、函数括号、数组下标等。
2.不能像下面那样声明 ??????????????
  * 函数的返回值不能是函数,所以如foo()()是非法的
  * 函数的返回值不能是数组,所以如foo()[]是非法的 
  * 数组里面不能有函数,所以像foo[]()是非法的
  但是像下面这样则是合法的 ??????????
  * 函数的返回值允许是一个函数指针,如:int(*fun())();
  * 函数的返回值允许是一个指向数组的指针,如:int(*foo())[]
  * 数组里面允许有函数指针,如int(*foo[])()
  * 数组里面允许有其他数组,如常见到的int foo[][]
3.结构(struct)
  结构就是一种把一些数据项组合在一起的数据结构。其他编程语言把它称为记录(record).
  struct 结构标签(可选) -->加上结构标签的目的是将来可以用struc flag 作为 struct {内容}的简写
  {
      类型  标识符;
      ....
  }变量定义(可选);

  结构中也允许存在位段,无名字段以及字对齐所需填充字段。
  struct pid_tag --> 是否还记得ip的首部的定义
  {
      unsigned int inactive : 1;
      unsigned int : 1;   /* 1个位的填充 */
      unsigned int recount : 6;
      unsigned int : 0; /* 填充到下一个边界 */
      short pid_id;
      ...
  }
  注意,也可以把一个布尔标志以位而不是字符来表示。位段的类型必须是int,unsigned int
  或者signed int(或者加上限定符)
  建议结构体的声明和结构体变量的声明分开,这样更有可读性

  跟结构有关的参数传递问题
  * int型变量i跟只包含一个int型成员的结构体变量s在参数传递时的方式可能完全不同
  * 在结构中放置数组,可以把数组当作第一等级的类型,用赋值语句拷贝整个数组,以
    传值调用的方式把它传递到函数,或者把它作为函数的返回类型
    --> 这里不就可以返回数组了吗
4.联合(union)
  联合在许多其他语言中被陈祚变体记录(variant record).外表与结构类似,但在内存布局上有关键区别。
  在结构中,每个成员依次存储,在联合中,所有的成员都从偏移地址零开始存储。这样,每个
  成员的位置都重叠在一起:在某一个时刻,只有一个成员真正存储于该地址。
  联合的外观同结构一样,只是用关键字union代替了struct。所以,掌握了结构,基本上也掌握了联合。
  联合一般被用来节省空间。联合也可以把同一个数据解释成两种不同的东西。
  union 大专栏  《C专家编程》读书笔记(三) bit32_tag
  {
      int whole; /* 一个32位的值 */
      struct {char c0, c1, c2, c3;}byte; /* 4个8位的字节 */
  }
5.枚举(enum)
  枚举通过一种简单的途径,把一串名字与一串整型值联系在一起。
  很少有什么事情只能靠枚举来完成而用#define不能解决的,但枚举有一个优点:#define定义
  的名字一般在编译时被丢弃,而枚举名字则通常一直在调试器中可见,可以在调试代码时使用它们。

3.3优先级规则

理解C语言声明的优先级规则
A 声明从它的名字开始读取,然后按照优先级顺序依次读取
B 优先级从高到低依次是:
  B.1 声明中被括号括起来的那部分
  B.2 后缀操作符:
      括号()表示这是一个函数,而方括号[]表示这是一个数组
  B.3 前缀操作符:星号*表示 "指向...的指针"。
C 如果const和(或)volatile关键字的后面紧跟类型说明符,那么它作用于类型说明符。在其他
  情况下,const和(或)volatile关键字作用于它左边紧邻的指针星号。

eg:char * const *(*next)();
这个声明表示"next是一个指针,它指向一个函数,该函数返回另一个指针,该指针指向一个类型
为char的常量指针"

3.4通过图表分析C语言的声明

还是牢记上面的规则吧,关键是后缀比前缀优先级高!括号最高。

3.5typedef可以成为你的朋友

为一种类型引入新的名字,而不是为变量分配空间。
typedef类似于宏文本替换--它并没有引入新类型,而是为现有类型取个新名字。但它们之间存在关键区别。
由于typedef看上去跟变量声明完全一样,读起来也是一样,上一节分析的技巧同样适用typedef
一般情况下,typedef用于简洁地表示指向其他东西的指针。
eg:signal函数
原型:void (*signal(int sig, void(*func)))(int);
typedef void(*ptr_to_func)(int);
则可以表示成:ptr_to_func signal(int, ptr_to_func);
/* 它表示signal是一个函数,他接受两个参数
 * 其中一个是int, 另一个是ptr_to_func, 返回值为ptr_to_func
 */
tips:
     不要在一个typedef中放入几个声明器
     千万不要把typedef嵌入到声明的中间部分。

3.6typedef int x[10]和#define x int[10]的区别

在typedef和宏文本替换之间存在一个关键性的区别。正确思考这个问题的方法就是把typedef
看成是一种彻底"封装"的类型——在声明它之后不能再往里面增加别的东西。
它和宏的区别体现在两个方面:
1.可以用其他类型说明符对宏类型名进行扩展,但对typedef所定义的类型名却不能这样做。
  #define peach int
  unsigned peach i; /* 没有问题 */
  typedef int banana;
  unsigned banana i; /* 错误,非法 */
2.在连续几个变量的声明中,用typedef定义的类型能够保证声明中所有的变量均为同一种类型,
而用#define定义的类型则无法保证。
eg:
  #define int_ptr int *
  int_ptr chalk, cheese;
  扩展后变为:
  int * chalk, cheese;  --> 两种不同的类型

3.7typedef struct foo{…;}的含义

不要为了方便起见对结构使用typedef。这样做的惟一好处是不必写"struct",但这个关键字可以
提示一些信息,不应该被省掉。
typedef应该用在:
* 数组、结构、指针以及函数的组合类型
* 可移植类型

3.8理解所有分析过程的代码段

3.9轻松一下

原文地址:https://www.cnblogs.com/lijianming180/p/12389047.html