读书笔记之:C程序设计-现代方法

《C程序设计-现代方法》这本C语言书是一本不错的C语言的书。其中的讲解挺有条理,并且每章后面的释疑部分真的解答了我的很多疑问。

第2章 C语言基本概念
1. 在某些C语言的书中,main函数的结尾使用的是exit(0),而不是return 0,二者是否一样?
    当出现在main函数中时,这两种语句是完全等价的:二者都终止程序执行,并且向操作系统返回0值.
2. float类型的名字是由何而来的?
   float是floating-point的缩写形式.有些语言中称为real类型

第3章 格式化输入/输出
1. 转换说明%i也可以用于读写整数,%i和%d之间的区别?
   在printf格式串中使用时,二者没有区别.但是,在scanf格式串中%d只能与十进制形式的整数相匹配,而%i则可以匹配八进制,十进制和十六进制.
2.printf中显示%的话可以使用%%
3. 如果v有副作用,那么v+=e和v=v+e是不等价的。
   如:
   a[i++]+=2与a[i++]=a[i++]+2
4. C语言从Ken Thompson早期的B语言中继承了++和--操作。Thompson是从BCPL语言进行改进创造了B语言,而他创建++操作符因为在B语言的编译器中++i比i=i+1可以产生更简洁的翻译。

第6章 循环
1. 哪个无限循环格式更可取,while(1)还是for(;;)?
   C程序员传统上喜欢for(;;)的高效性,因为早期的编译器经常强制程序在每次执行while循环体时测试条件1.但是对现代编译器来说,在性能上两种无限循环应该没有差别.
2. 听说程序员应该永不使用continue语句,这种说法对吗?
   continue语句确实很少使用.尽管如此,continue语句有时候还是非常方便的.
第7章 基本类型
1. 浮点常量为什么存储成double格式而不是float格式?
   由于历史的原因,C语言更倾向于使用double类型,float类型则被看成"二等公民".
2. 为什么使用%lf读取double类型的值,而用%f进行显示呢?
   这是一个十分难回答的问题.首先,注意,scanf函数和prinf函数都是不同寻常的函数,因为它们都没有将函数的参数限制为固定数量.scanf函数和printf函数有可变长度的参数列表.当调用带有可变长度参数列表的函数时,编译器会安排float参数自动转换成double类型,其结果是printf函数无法区分float类型和double类型的参数.这解释了在printf函数调用中为何可以用%f既表示float类型又表示double类型的参数.
   另一方面,scanf函数是通过指针指向变量的.%f告诉scanf函数在所传地址位置上存储一个float类型的值,而%lf告诉scanf函数在该地址上存储一个double类型值.这里float和double的区别是非常重要的.如果给出了错误的转换说明,那么scanf函数将可能存储错误的字节数量(没有提到的是,float类型的位模式可能不同于double类型的位模式)

第8章 数组
1. 下面的程序有可能导致无限循环.
  int a[10],i;
  for(i=0;i<=10;i++)
     a[i]=0;
2. 这个数组的初始化合法吗?
   int a[]={4,9,1,8,[0]=5,7};
  合法.工作原理:编译器在处理初始化式列表时,会记录下一个待初始化的数组元素的位置.正常情况下,下一个元素是刚刚被初始化的元素后面那个.但是当列表中出现初始化式时,下一个元素会被强制为指示符对应的元素,即使该元素已经被初始化了.
  该初始化的效果类似:int a[]={5,7,1,8};
  数组长度是4
3. 将一个数组复制到另一个数组中的方法?
  (1) for循环的方式
      for(i=0;i<N;i++)
         a[i]=b[i];
  (2) 利用<string.h>头的函数memcpy.该函数是一个底层函数,它把字节从一个地方简单的复制到另一个地方.为了把数组b复制到数组a中,使用函数memcpy的格式如下:
      memcpy(a,b,sizeof(a));
4.在函数调用f(a,b)中,编译器如何知道逗号是标点符号还是运算符号呢?
   函数调用中的实际参数不能是任意的表达式,而必须是"赋值表达式".在赋值表达式中,不能用逗号作为运算符,除非逗号是在圆括号中.换句话说,在函数调用f(a,
   b)中,逗号是标点符号;而在f((a,b))中,逗号是运算符.

5. 如果几个函数具有相同的返回类型,能否把他们的声明合并?例如void print_pun(void),print_count(int n);合法吗?
   合法的.事实上,C语言甚至允许把函数声明和变量声明合并在一起:
   double x,y,average(double a,double b);
6. 为什么可以留着数组中第一维的参数不进行说明,但是其他维数必须说明呢?
   首先,需要知道C语言是如何传递数组的.在把数组传递给函数时,是把指向数组第一个元素的指针给了函数.
   其次,需要知道取下标运算符是如何工作的.C语言是按照行主序存储数组的,如果要计算下一个元素的指针,必须知道一个元素的大小,所以必须给出其他维的大小,这样才能知道一行的大小.
7. 间接递归:f1调用f2,f2调用f1,这其中涉及到一个地方:提前进行函数的声明.

第10章 程序结构
1. 局部变量
  特性:(1)自动存储期限:局部变量的存储单元是在包含该变量的函数被调用时自动分配的,函数返回时收回分配,所以称这种变量具有自动的存储期限.
       (2) 块作用域
  静态局部变量:
   静态存储期限的变量拥有永久的存储单元,在整个程序执行期间都会保留变量的值.即在程序执行期间它所占据的内存单元是不变的.
   但是它是块作用域,即它相对于其他函数是隐藏的,但是它会为将来同一个函数的再调用保留这些数据.
  形式参数:
  形式参数拥有和局部变量一样的性质,即自动存储期限和块作用域.事实上,形式参数和局部变量的唯一真正的区别是,在每次函数调用时对形式参数自动进行初始化.
2. 外部变量(全局变量)
  特性:(1)静态存储期限:
       (2)文件作用域:从变量被声明的点开始一直到所在的文件的末尾 
3. 具有静态存储期限的局部变量会对递归函数产生什么影响?
  当函数是递归函数时,每次调用它都会产生其自动变量的新副本.静态变量就不会发生这种情况,相反,所有的递归函数都共享一个静态变量.

第11章 指针
1. const int *p;
   int const *p;
   int* const p;
   前两个是一样的,都是不允许修改p所指向的值.第3个是指针p是不允许改变的

第13章 字符串
1. 用printf函数和puts函数写字符串
   printf函数会逐个写字符串中的字符,直到遇到空字符才停止.(如果空字符丢失,printf函数会越过字符串的末尾继续写,知道最终在内存的某个地方找到空字符为止).
   ======这儿可以进行一下实验,手动改内存中的数据,然后....
2. 用scanf函数和gets函数读字符串
  (1)gets函数不会在开始读字符串之前跳过空白字符(scanf函数会跳过,scanf读入的字符串永远不会包含空白字符)
  (2)gets函数会持续读入直到找到换行符才停止(scanf函数会在任意空白字符处停止).此外,gets函数会忽略掉换行符,不会把它存储到数组中,用空字符代替换行符.
3. 字符串函数库
  strcpy,strncpy,strlen,strcat,strcmp
4. 为什么不把字符串字面量称为"字符串常量"?
  因为他们并不一定是常量.由于字符串字面量是通过指针访问的,所以没有办法避免程序修改字符串字面量中的字符.
5.一些编译器试图通过只为相同的字符串字面量存储一份副本来节约内存.
   char *p="abc",*q="abc";
   编译器可能只存储"abc"一次,修改会使程序出现问题
第14章 预处理器

 

1. #运算符将宏的一个参数转换为字符串字面量
   ##运算符将两个记号"粘合"在一起
2. 预定义宏
   C语言中的预定义宏:
   __LINE__  被编译的文件中的行号
   __FILE__  被编译的文件名
   __DATE__  编译的日期
   __TIME__  编译的时间
   __STDC__  如果编译器符合C标准(C89或C99),那么值为1

#if defined(WIN32)
#elif defined(MAC_OS)
#elif defined(LINUX)
#endif

3. 托管式实现与独立式实现
   大多数程序都需要托管式实现,这些程序需要底层的操作系统来提供输入/输出和其他基本服务.C的独立式实现用于不需要操作系统(或只需要很小的操作系统)的程序.例如,编写操作系统内核时需要用到独立式实现(这时不需要传统的输入/输出,因而不需要<stdio.h>),独立式实现还可以用于为嵌入式系统编写软件

   第15章 编写大型程序
   1. 在程序外定义宏
   在编译程序时,C语言编译器通常会提供会提供一种指定宏的方法,这种能力是我们很容易对宏值进行修改,而不需要编辑程序的任何文件,当利用makefile自动构建程序时尤为有价值。大多数编译器支持-D选项来指定宏的值。

第16章 结构联合和枚举
1. 结构的开始处是否可能有"空洞"?
  不会.C标准指名只允许在成员之间或者最后一个成员的后边有空洞.因此可以用指向结构第一个成员的指针就是指向整个结构的指针(但是,注意这两个指针的类型不同)

第17章 指针的高级应用
1. 内存分配函数
   malloc--分配内存块,但是不对内存块进行初始化
   calloc--分配内存块的同时对内存块进行清零
   realloc--调整先前分配的内存块大小
  malloc函数不需要对分配的内存进行初始化,所以比calloc更高效

 

第18章 声明
1. 变量的性质
  C程序中的每个变量都有3个性质.
  (1)存储期限:自动存储期限,静态存储期限
  (2)作用域:块作用域,文件作用域
  (3)链接.具有外部链接的变量可以被程序中的几个(或全部)文件共享.
          具有内部链接的变量只能属于单独一个文件,文件中的函数可以共享该变量
          无连接的变量属于一个函数,不会被共享.
  变量的默认存储期限,作用域和链接都依赖于变量声明的位置.
  (1)在块内部声明的变量具有自动存储期限,块作用域,并且无链接
  (2)在程序的最外层(任意块外部)声明的变量具有静态存储期限,文件作用域和外部链接.
  可以通过(auto,static,extern和register)来改变变量的性质
  (1)static作用于块内部声明的变量时,变量具有静态存储期限(块作用域,无连接)
     static作用域块外部变量时,变量是内部链接(静态存储期限,文件作用域)
     注意:虽然函数不应该返回指向auto变量的指针,但是函数返回指向static变量的指针是没有错误的.
  (2)extern声明中的变量始终具有静态存储期限.不改变变量的作用域
     确定其链接具有一定的难度.一般具有外部链接,如果变量在较早的位置声明为static,那么具有内部链接
     extern的重要用处是对变量或函数的声明(不进行定义)
2. 声明符
   []数组
   ()函数
   *指针
   []和()的优先级高于*
   使用类型定义来简化声明(typdef)
3. 初始化式
   (1)具有静态存储期限的变量的初始化式必须是常量.
   (2)具有自动存储期限的变量没有默认的初始值.具有静态存储期限的变量默认情况下的值是零,并且是基于类型进行初始化的.
4. 作用域和链接之间的区别到底是什么?
   作用域是为编译器服务的,而链接是为链接器服务的.编译器用标识符的作用域来确定在文件的给定位置访问标识符是否合法.当编译器把源文件翻译成目标代码时,它会注意到有外部链接的名字,并最终把这些名字存储到目标文件内的一个表中.因此,链接器可以访问到具有外部链接的名字,而内部链接的名字或无连接的名字对链接器而言是不可见的.
5. 一个名字具有块作用域却有外部链接?
  比如在第一个文件中定义:
  int i;
  并且i是放在了任意函数外,所以默认情况下具有外部链接。在另一个文件中,函数f需要访问变量i,所以可以把i声明在f内:
  void f(void)
  {
      extern int i;
      ....
  }
  在第一个文件中i具有外部作用域,在第二个文件中i具有块作用域。
6. C语言中的const
 在C语言中const表示只读,而不是常量。首先,const对象只在它的生命期内为常量,而不是在程序的整个执行期内。
 比如:
 void f(int n)
 {
     const int m=n/2;
     ..
 }
 m的值在函数f返回之前都保持不变,但是再次调用f时,m可能取不同的值。假设m出现switch语句,就出问题了:

第19章 程序设计
1. C不是为开发大型程序而设计的
  在C语言被设计出来的时候并不是为开发大型程序而设计的,在1978年的一篇论文中,Ken Tompson估计UNIX内核大概是10000行C代码(加上一小部分汇编代码)。UNIX的其他部分的大小也是类似。在1978年的另一篇论文中,Dennis Ritchie和他的同时将PDP-11的C编译器的大小设定为9660行。按现在的标准,这绝对是小型程序。
  C语言的原型ALGOL 60语言。(也称为A语言)
  1963年,剑桥大学将ALGOL 60语言发展成为CPL(Combined Programming Language)语言。
  1967年,剑桥大学的Matin Richards 对CPL语言进行了简化,于是产生了BCPL语言。
  1970年,美国贝尔实验室的Ken Thompson将BCPL进行了修改,并为它起了一个有趣的名字“B语言”。意思是将CPL语言煮干,提炼出它的精华。并且他用B语言写了第一个UNIX操作系统。
  而在1973年,B语言也给人“煮”了一下,美国贝尔实验室的D.M.RITCHIE在B语言的基础上最终设计出了一种新的语言,他取了BCPL的第二个字母作为这种语言的名字,这就是C语言

第22章 输入输出
1. 文本文件与二进制文件
   文本文件字节表示字符,分为若干行且包含文件末尾标记
   二进制文件字节不一定表示字符,字节组可以表示其他类型的数据,不存在行标记和文件末尾标记
2. 文件操作
   (1)打开文件:fopen
      模式:"r"读,"w"写,"a"追加 -----文件不需要存在
           "r+","w+","a+"
           包含b时表示二进制文件的读写
   (2)关闭文件:fclose
   (3)临时文件:
      FILE*tmpfile(void)创建一个临时文件(用"wb+"模式打开)
      char*tmpnam(char*s);为临时文件产生名字
   (4)int remove(const char*filename);删除文件
      int rename(const char*old,const char*new);文件重命名
      int access(const char*filename,int amode);
          amode取值:00 文件是否存在
                    01 检查执行权限
                    02 检查写权限
                    04 检查读权限
                    06 检查读写权限
3. 格式化输入输出
  (1) ...printf函数
     printf
     fprintf
     sprintf
  (2)...scanf函数
     scanf
     fscanf
     sscanf

     %[]与%[^]
4. 行的输入输出
  (1) fputs,puts
  (2) fgets,gets
5. 块的输入输出
  fread,fwrite主要用于二进制流的读与写
  第一个参数数组地址
  第二个参数每个数组元素大小(以字节为单位)
  第三个参数要写的元素个数
  第四个参数文件指针,该指针说明了要写的数据位置
6. 文件定位
  int fseek(FILE* stream,long in offset,int where)改变相关文件的位置
  where的取值:SEEK_SET,SEEK_CUR,SEEK_END
  long int ftell(FILE*stream)返回当前位置
  void rewind(FILE*stream)把文件位置设置在起始处


posted @ 2012-02-26 20:56  Mr.Rico  阅读(705)  评论(0编辑  收藏  举报