读《The C Programming Language》(8)

第六章讲的是结构体,包括structure, union和bit-fields。除了介绍基本知识,作者在举例子的时候牵扯了一些数据结构的知识,比如二叉树、哈希表等,学起来很有意思。这章的内容上大学学C语言时好像没有作为重点,学得稀里糊涂,出来之后才发觉很有用。

第一节 结构体基本知识

这节一个要注意的地方是,在结构体

struct point {
    int x;
    int y;
};

的声明中,point是可以省略的,作者管它叫structure tag。这个标签是用来命名这个结构体的,它可以代表后面括号中的整个声明。

结构体声明实际上定义了一种类型(type),这种类型和其他类型一样可以声明变量:

struct { ... } x, y, z;

第二节 结构体和函数

结构体作为参数被传给一个函数时仍然是按值传递。作者举了一个例子:

/* addpoints: add two points */
struct addpoint(struct point p1, struct point p2)
{
    p1.x += p2.x;
    p1.y += p2.y;
    return p1;
}

函数里改变p1的值是不会影响调用函数的原变量值的,因为传过来的参数只是原变量的一个拷贝。在结构体不大的时候这种拷贝不会有什么开销,但是如果结构体很大,通常会采用传递指针的方法以避免拷贝整个结构体。

另外,作者在讲到结构体操作符.和->时强调,这两个操作符在优先级层次中处于最高的位置,和它们同一层次的还有函数调用的()和数组下标用的[]。

第三节 结构体数组

在这一节作者介绍了sizeof运算符,它的操作对象有:变量、数组、结构体和类型(包括基本类型和自定义类型)。这里要注意的一点是,虽然数组名是指针,但把数组名和指向这个数组的指针传给sizeof得出的结果是不同的。传数组名得出的是数组的长度,而传指针只能得出地址的长度(32位机上是4个字节)。比如:

int a[10];
int *p = a;

那么sizeof(a)不等于sizeof(p)。

第四节 结构体指针

作者在这节举例子时提到了数组越界的问题,&tab[-1]和&tab[n](tab是一个长度为n的数组)看起来都越界了,但前者是非法的,而后者只要你不去取指针&tab[n]所指向的值就是合法的。这是由语言的定义所保证的。

另外值得注意的是,不要假设结构体的大小是各成员变量的大小之和。因为内存对齐的要求,结构体中可能会有未命名的“空洞”存在。比如

struct {
    char c;
    int i;
};

可能要求8个字节而不是5个。

第五节 自引用结构体

这一节和数据结构密切相关,作者举的例子也是一个二叉树的例子。除了我们常用的结构体里包含相同结构体的指针这种形式以外,作者还提到了另一个不常用的自引用形式:两个结构体相互引用。例如:

struct t {
    ...
    struct s *p; /* p points to an s */
};
struct s {
    ...
    struct t *q; /* q points to a t */
};

第六节 表查找

这节作者在例子里通过哈希查找算法把结构体映射到相应的数组项里。作者使用的哈希方法虽然简单,但说明了一般的哈希原理。

/* hash: form hash value for string s */
unsigned hash(char *s)
{
    unsigned hashval;
    for (hashval = 0; *s != '/0'; s++)
    hashval = *s + 31 * hashval;
    return hashval % HASHSIZE;
}

在这个函数里,一个字符串通过一些组合运算生成一个整数作为数组索引。看了这个函数你就明白,哈希不过是通过一些运算把其它类型的数据映射到一个整数上。

第七节 类型定义(typedef)

C语言提供了typedef这种机制,用来创建新的数据类型名。作者强调,这种机制不创建新的类型,只是给已有的类型加了一个名字。下面举三个例子:

  • typedef int Length;是最普通不过了的。定义之后int和Length就可以互换。
  • typedef char *String;就看出和#define的差别了。定义之后String就成了字符指针类型。
  • typedef int (*PFI)(char *, char *);这个很少用,居然定义了一个返回整型的函数指针PFI。这里的每个字母都有意义,P代表pointer,F代表Function,I代表interger。有了PFI之后,声明函数就和声明变量似的:PFI strcmp, numcmp;

作者最后还给出了使用typedef的两个主要原因:

  1. 适应跨平台的需要。如果数据类型和具体的机器相关,那么程序被移植时只需改变typedef。
  2. 使程序更易读。定义良好的typedef就是最好的程序注释。

第八节 联合体

作者给联合体的定义是:"A union is a variable that may hold (at different times) objects of different types and sizes, with the compiler keeping track of size and alignment requirements." 这个定义里包含了三层意思:

  1. 联合体是一个变量。它不像结构体那样是若干个变量的复合,它就是一个变量。
  2. 这个变量可以装载不同类型不同大小的对象。注意括号里的"at different times"意味着这个变量这次可以装载这种类型的,下一次又可以装载其它类型的。
  3. 编译器会处理诸如内存大小和对齐的问题。这就是联合体的好处,你只要用就好了,琐事由编译器搞定。

虽然编译器帮我们处理了很多问题,但程序员必须知道此刻联合体里面存的数据是什么类型的。如果存的是一种类型,取的时候当成另一种类型就会出错。

实际上,联合体就是一种特殊的结构体,联合体的操作在结构体上都可以用。联合体在初始化时只能被赋予一个与其第一个成员变量相同类型的值。

第九节 位域

位域可以代替一些用作标记的位操作。位域的一般形式很简单,但也有一些不常用到的特殊形式和性质:

  • 字段可以没有名字,只有冒号和宽度,这样的字段起填充作用。
  • 宽度为0的字段用来强制在下一个字边界上对齐。
  • 字段能否覆盖字边界由具体的实现决定。
  • 字段在一些机器上由左向右排列,而在另一些机器上由右向左排列。所以依赖于具体的排列次序的程序不具有可移植性。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值