发新话题
打印

[C&C++] The C Programming Language

The C Programming Language

The C programming LanguageBy Brian W. Kernighan and Dennis M. Ritchie. Published by Prentice-Hall in 1988 ISBN 0-13-110362-8 (paperback)
ISBN 0-13-110370-9
附件: 您所在的用户组无法下载或查看附件
cliang

TOP

《The C Programming Language》读书笔记

作为笔记而言,完全是一种自写自看的行为,本来是没有必要写这篇东西的。但是作为一个生活在网络时代的学生来说,想学好一样东西最好的办法把自己理解的东西放出去,让人讨论,从而,错误得到及时的更正,正确的思想得到肯定,进一步激发深入学习的激情,另外,还可以避免因为成天面对机器而忘记人话是咋说的(还真的差点忘了),恩,好处多多,何乐不为?
为什么要学习c语言?对于今天这个惟利是图的世界来说,恐怕初学者第一要问的就这个问题,他们中的很多人都会说在拥有c++,java,c#这些高级语言的今天,c能做什么呢?在网络中,得到的回答往往是:c无所不能,然后是一些语重心长的说教,呵呵,对于一个对编程知道不多的人来说这样的回答没有任何意义,因此我对这样的问题的回答是:那些高级语言的出现并不能结束c三十年的长盛不衰,c语言没有被任何一种语言所代替,而和c同时代的那些东西。恐怕今天的人连名字都忘了,而在这个世界的每个角落都有无数的编程爱好者和从业人士对c有着无以伦比的狂热。这是事实,这是真理,它证明了一切。无须多言了。
毫无疑问没有人能比K&R对c更有发言权了,用他们亲笔所写的《The C Programming Language》来入门是再合适不过了,尽管现在市场上关于c的教材到处都是,但是大半都是以这本书为根基的,严格来说关于c的一切疑问都可以在这本书中得到解释,我的笔记也将以此为本,另外会引入另一本巨作《c专家编程》的观点(强烈向各位有一定c基础的人。推荐此书),为了灵活,这里不作任何约定,在每篇笔记开头会标明笔记内容。
-----------------------------------------------------------------------------------------
笔记范围:《The C Programming Language》第一章

应该说,算上这次,我应该是第四次读这本书了,每次重读都收获不少。都引起我新的思考,很难想象这本一本技术小书给人留下的感觉是如此深刻,如此令人回味,本章的内容是很容易理解的概念,对于编程学习者是最起码的知识,但是有些东西还是被初学者忽视。以至于在各个技术社区的初学者问题中层出不断。其实这些东西,在第一章就得到了比较完整的解释,下面只是把他们提出来强调一下,
第一.关于循环终止。在很多书籍中都存在这样的循环语句:
while ( getchar() != EOF ) {….}
很多人不明白这个EOF为何物?具体如何操作?以至于让上面的语句变成了无限循环,呵呵,其实EOF。是文件结束符(end of file),在第七章有说明。其为系统常量。值为-1,当然你在终端输入-1,循环并未结束,why?how to do?恩,你在《c专家编程》里会了解到,c的第一批使用者都是系统设计者和编译器设计者。在他们的理念里,信息往往以文件为单位的。这个标志只是文件结束的状态,一般不由用户提供。而键盘等输入端在os中是个极其特殊的文件。需要用户显式标志文件结束。说是系统常量当然有着系统依赖性,因此不同的系统标志方式就完全不同。Windows下是ctrl+z。linux下是ctrl+D.另外EOF其实不用显式说明。上面的代码与此完全等价:
while( getchar() ) {……}
(注:后来有读者提出
while( getchar() ) {}

while( getchar() != EOF ) {}
不是等价的。
作者也承认写错了!)

第二.声明和定义。尽管这个问题地球人都知道,但是真正说的清楚的人并不多。有人认为变量的声明和定义没有什么区别。有人认为这个与初始化有关系(我曾经就这样白痴,汗~~~),对于后者那就是根本就不明白这两个概念,声明只是给编译器一个提示,有这么个名字存在于程序中,和运行环境毫无关系。可以重复出现,定义是具体分配内存空间和指定了变量的位子(左值),在同一域中只能出现一次;对于前者的观点。在单文件程序中。几乎找不到错误。但如果你把这样的句子放人头文件,int a;一旦这个头文件被重复包涵。必然出现链接错误。其实这样:
extern int a;//声明
int a;//定义
第三.字符数组和字符串。有人认为这两个是同一概念,是这样吗?不,完全不是,前者为容器(数据结构),后者为数据。这样说也许太理论化,好,我们来改写下那个hello world
#i nclude <stdio.h>
#i nclude <stdlib.h>
int main()
{
printf( "\0hello world " );
system("pause");
return 0;
}
呵呵,什么也没有?是的。还记得字符串是怎么结束的吗?\0 "\0hello world "是个常量数组。但是字符串却是“”。字符数组和字符长度是不一样的,
这章尽管非常简单,但是每个例子都经典之作,你能从代码中学到文字中不能学到的东西。建议你每个都抄一遍.

第二章:
本章的内容是学习编程中最基础东西,任何一门语言都会告诉你他支持那些数据类型、那些运算、有那些特点、以及有那些不完善的东西。学习这些东西相对来说是单调了点,麻烦了点,但是只有通过了这座迷宫,你才能进入C这个神奇的世界。因此初学者的成功至少有一半来自“耐心”。呵呵,准备好了吗?
本章的内容还是非常简单的,但是作者的字里行间隐藏了很多重要的信息,不加注意就会从我们的眼皮低下溜了去,下面将一一列出以示强调。
第一.变量和常量。很多人对于他们的区别很模糊,个人认为他们的主要区别在于是否分配内存空间,换句话说,就是是否存在左值。左值是什么?在第二章的从头到尾好像没找到这个名词,呵呵,你可以在附录中关于变量的条目中找到他,其实就是变量的地址,变量一旦被定义,左值就被确定了,一直到他的生存期结束。我们通常说的变量的值是指变量的右值。这才是我们能操作的对象。根据这个理论,那么就不难知道其实被const修饰的对象不是常量,他有左值,但是这里有个小麻烦,在本章的开头写明了被const修饰的是常量(本章第二段有个()说明),我查看了原版,并没有这个补充说明,看来应该是译者的理解,在《 C专家编程 》中的一个例子证明了我的想法是正确的,例子如下:
#i nclude “stdio.h”
#i nclude “stdlib.h”
#define one 1
const int two = 2;
int main()
{
     int ix = 1;
     switch( ix )
     {
         case one: printf( "this is 1" );/*ok*/
                   break;
         case two: printf( "this is 2" );/*error*/
     }
     system( "pause" );
     return 0;
}


大家都知道,case后面只能跟常量表达式,因此被const修饰的变量不是常量,只是变量的右值一般不能改变罢了。另外你也可以从上面感觉出#define和const的区别。
第二.关于换码序列。这个更多地方叫转义字符,他们大多数是有一些特殊的功能的字符,在上篇笔记中你已经看到了他的一点威力,下面我们再看一段代码:
#i nclude "stdio.h"
#i nclude "string.h"
#i nclude "stdlib.h"
int main()
{
     int ix;
     ix = strlen( "\0abc" );
     printf( "this is %d\n", ix );
     ix = strlen( "\007abc" );
     printf( "this is %d\n", ix );
     system( "pause" );
     return 0;
}

你会发现,两个差不多的字符串长度完全不一样,什么回事呢?第一个我们可以理解:\0是字符串结束符,因此其后的任何东西都不能算字符串的内容,因此长度为0。但是第二个呢?我们查了换码序列表就知道‘\007’这个为一个字符,因此长度为4。这个时候问题来了,编译器为什么没把‘\007’理解为‘\0’‘0’‘7’呢?如果这样的话长度也将为0,我们又没人为的加分割符号,呵呵,显然这个和编译器的具体实现相关,凭我们现有知识无法弄明白这点,姑且留着,等待“悟“的一天吧,相信我,这绝对是一种享受。
第三,关于++运算符,在很多教材上都有个看起来很经典的题目,其代码如下:
#i nclude "stdio.h"
#i nclude "stdlib.h"
int main()
{
     int ix, iy;
     iy = 1;
     ix = ( iy++ ) + ( iy++ ) + ( iy++ );
     printf( "this is %d\n", ix );
     iy = 1;
     ix = ( ++iy ) + ( iy++ ) + ( iy++ );
     printf( "this is %d\n", ix );
     iy = 1;
     ix = ( ++iy ) + ( ++iy ) + ( iy++ ) ;
     printf( "this is %d\n", ix );
     iy = 1;
     ix = ( ++iy ) + ( ++iy ) + ( ++iy ) ;
     printf( "this is %d\n", ix );
     system( "pause" );
     return 0;
}

呵呵,是不是很晕?这个本来无非为了说明先加后加的问题,这个地球人都知道,这里不加说明了,但是这样的程序本身就有很大的问题,编译器的运算并非一定是从左到右的(有些是按树的遍历来算的),因此你会发现不同的编译器结果会不一样,关于这个本章的结尾有很完整的解释,我就不再多说了,总之,这个测试本身就违背了语言的特性。
本帖最近评分记录
  • filalu 书签 +1 2007-10-21 10:10

TOP

发新话题