1.展示优秀的C程序员所使用的编码技巧;
2.对C的历史、语言特性、声明、数组、指针、链接、运行时、内存以及如何进一步学习C++等问题进行了细致的讲解和深入的分析。
3.帮助有一定经验的C程序员成为C编程方面的专家;帮助C语言功底深厚的程序员站在C的高度了解和学习C++。
编程理念:任何人都可以享受编程。
编程应该是一项精妙绝伦、充满生机、富有挑战的活动,而讲述编程的图书也应能令读者时时迸射出激情的火花。
应用于PC和UNIX系统上的ANSI标准C语言,对C语言中与UNIX平台复杂的硬件结构(如虚拟内存等)相关的特性做了详细描述,也对PC的内存模型和Intel8086系列对C语言产生的影响做了全面介绍。
许多令C程序员困惑的主题:
*typedef struct bar{ int bar; }bar的真正意思是什么?
解析:
第一个bar是结构标签,第二个bar是结构中的成员变量名,第三个bar是结构类型。
#include
typedef struct bar {
int bar;
int z;
}bar;
/*第一个bar是一个结构标签名
*第二个bar是结构中的一个类型为int的bar变量名
*第三个bar是一个结构类型名
*struct bar 结构类型
*struct bar bar 第一个bar是一个结构标签,第二个bar是一个对象名
*bar.bar 第一个bar是一个对象名,第二个bar对象名的成员变量
*/
int main() {
//error
/*typedef struct bar bac; //not allowed!
bac.bar = 5;*/
bar bar;
bar.bar = 3;
struct bar baz;
baz.bar = 4;
printf("sizeof(bar) = %d,sizeof(struct bar) = %d\nsizeof(bar.bar) = %d,sizeof(baz.bar) = %d\n",
sizeof(bar), sizeof(struct bar), sizeof(bar.bar), sizeof(baz.bar));
printf("bar.bar = %d, baz.bar = %d\n", bar.bar, baz.bar);
return 0;
}
/* 输出:

*/
*我怎样把一些大小不同的多维数组传递到同一个函数中?
解析:
方法1
my_function(int my_array[10][20]);
多维数组最主要的一维的长度(最左边一维)不必显示写明。所有的函数都必须知道数组其他维的
确切长度和数组的基地址。有了这些信息,它就可以一次“跳过”一个完整的行,到达下一行。
方法2
我们可以合法地省略第一维的长度,像下面这样声明多维数组:
my_function(int my_array[][20]);
这样做不够充分,因为每一行都必须正好是20个整数的长度。函数也可以类似地声明为:
my_function(int (*my_array)[20]);
参数列表中(*my_array)周围的括号是绝对需要的,这样可以确保它被翻译成一个指向20个元素的
int数组的指针,而不是一个由20个int指针元素构成的数组。同样,我们对“最右边一维的长度必须为20”感到不快。
方法3
放弃二维数组,把它的结构改为一个Iliffe向量。也就是说,创建一个一维数组,数组中的元素是指向其他东西的指针。回想一下main()函数的两个参数,我们已经习惯了看到char *argv[];
的形式,有时也能看到char **argv;这样的形式,它能提醒我们怎样分析这个声明。可以简单地
传递一个指向数组参数的第一个元素的指针,如下所示(用于二维数组):
my_function(char **my_array);
注意:只有把二维数组改为一个指向向量的指针的前提下才可以这么做!
Iliffe向量这种数据结构的美感在于:它允许任意的字符串指针数组传递给函数,但必须是指针数组,而且必须是指向字符串的指针数组。这是因为字符串和指针都有一个显示的越界值(分别
为NUL和NULL),可以作为结束标记。至于其他类型,并没有一种类似且可靠的值,所以并没有
一种内置的方法知道何时到达数组某一维的结束标志。即使是指向字符串的指针数组,通常也需要
一个计数参数argc来记录字符串的数量。
方法4
放弃多维数组的形式,提供自己的下标方式。
char_array[row_size * i + j] = ...
这很容易误入歧途,而且会让你困惑,如果可以手工做这些事情,那么为什么还需要使用编译器呢?
总之,如果多维数各维的长度都是一个完全相同的固定值,那么把它传递给一个函数毫无问题。如果情况更普遍一些,也更常见一些,就是作为函数的参数的数组的长度是任意的,我们用下面的方法进一步的分析
*一维数组---没有问题,但需要包括一个计数值或者是一个能够表示越界位置的结束符。被调用的函数无法检测数组参数的边界。正因为如此,gets()函数存在安全漏洞,从而导致了Internet蠕虫的产生。
*二维数组---不能直接传递给数组,但可以把矩阵改写为一个一维的Iliffe向量,并使用相同的下标表示方法。对于字符串来说,这样做是可以的。对于其他类型,需要增加一个计数值或者能够标识越界位置的结束符。同样,它依赖于调用函数和被调用函数之间的约定。
*三维或更多维的数组---都无法使用,必须把它分解为几个维数更少的数组。
不支持多维数组作为参数传递是C语言存在的一个内在限制,这使得用C语言编写某些特定类型的程序是非常困难(如数值分析算法)。
*为什么extern char *p;同另一个文件的char p[100];不能够匹配?
解析:
为什么人们会认为指针和数组始终应该是可以互换的呢?答案是对数组的引用总是可以写成对指针的引用,而且确实存在一种指针和数组的定义完全相同的上下文环境。不幸的是,这只是数组的一种极为普通的用法,并非所有情况下都是如此。当用p[i]这种形式提取这个声明的内容时,实际上得到的是一个字符。但按照指针的方法,编译器却把它当做是一个指针,把ASCII字符解释为地址显然是牛头不对马嘴。
*什么是总线错误(bus error)?什么是段违规(segmentation violation)?
解析:
总线错误(bus error)是因为未对齐的内存访问请求时,被堵塞的组件就是地址总线。对齐(alignment)的意思就是数据项只能存储在地址是数据项大小的整数倍的内存位置上。事实上,总线错误几乎都是由未对齐的读或写引起的。段违规(segmentation violation)一般是由内存管理单元(负责支持虚拟内存的硬件)的异常所致,而该异常则通常是由解除一个未初始化或非法值的指针引起的。
*char *foo[]和char (*foo)[]有何不同?
解析:
//foo is a array, elements in array is a pointer points to the char
char *foo[];
//foo is a pointer, pointer points to a array of char
char (*foo)[];