码迷,mamicode.com
首页 > 其他好文 > 详细

《The C Programing Language》阅读笔记

时间:2015-05-24 00:05:59      阅读:95      评论:0      收藏:0      [点我收藏+]

标签:

《The C Programing Language》

要理解一种程序语言,而不仅仅只是会使用它。

                               -----我的心声

介绍部分:

作者 C语言设计者 Kernighan  Ritchie  标准C语言及其程序设计方法

应用级编程两个主流语言 C++  Java 都建立于C的语法和基本结构上

现在世界上许多软件都是C语言及其衍生的各种语言的基础上开发出来的。

C在传播中,肯定会有变化和进展,88年 ANSI(美国国家标准协会) 为C语言指定了一个精确的标准。即ANSI C。

自C诞生之后,C悄悄地演进,其发展早已超出了它仅仅作为UNIX操作系统的编程语言的初衷。虽然UNIX系统本身及其上运行的大部分程序都是用C语言编写的。

C语言并不受限于任何一种操作系统或机器,由于它很适合用来编写编译器和操作系统,因此被称为“系统编程语言”,但它同样适合于编写不同领域中的大多数程序。

C版本?C89 C99

操作系统、编译器用C来编写,那么用什么工具反过来编译这些文件?

 

第一章 导言

main是一个特殊的函数名—每个程序都从main函数的起点开始执行。这意味着每一个程序都必须拥有一个main函数。

stdio.h 标准输入/输出库

“···” 字符串或字符串常量

转义字符:为表示无法输入的字符或不可见字符提供了一种通用的可扩充机制。

int和float类型的取值范围取决于具体的机器。比如int在有些机器上是2字节,有的则是4字节。int short long char float double 数据类型对象的大小取决于机器。

正确的缩进以及保留适当空格的程序设计风格对程序的易读性非常重要。

每行只写一条语句,并在运算符两边各加上一个空格字符。

printf(“%d\t%d\n”, fahr, celsius);   制表符相当于是给定一个表格区域。所以是对齐的。

printf函数并不是C语言本身的一部分,C语言本身并没有定义输入/输出功能。它仅仅是标准库函数中一个有用的函数而已。但是ANSI标准定义了printf函数的行为,因此,对于符合该标准的编译器和库来说,该函数的属性都是相同的。

函数库并不能作为语言的一部分,它由编译器公司提供。但须遵从标准。

printf(“%3d %6d\n”);  指明打印宽度 

即使浮点常量取的是整型值,在书写时最好还是为它加上一个显示的小数点。6.  5.

printf的格式化输出可查看相关资料。

在实际编程中,选择while与for中任意一种循环语句,主要看使用哪一种更为清晰。

为了程序的可读性,应对程序中出现的一些常量进行宏定义。赋予它有意义的名字。

#define 名字 替换文本    #define UPPER 300 称做符号常量 宏定义进行的只是文本替换

符号常量通常用大写字母拼写,与小写字母拼写的变量名区别。#define指令行没有分号。

getchar() putchar() 一次读写一个字符的函数

 

 

 

 

#include<stdio.h>

main()

{int c;

 while((c=getchar())!=EOF)

       putchar(c);  }        

什么时候能让程序结束?

关于getchar()使用:

字符并不是输入一个显示一个的,调用getchar()相当于打开了一个字符流输入的开关,此时可输入一串字符(包括回车换行符)暂存在一个空间中,回车后,字符流输入开关关闭。每次调用getchar()便从中按顺序取一个字符,putchar()就只负责输出。当被getchar()取完后,此时再调用getchar()会继续要求打开输入开关。

 

long至少为4字节,在某些机器上int与long类型的长度相同,但在一些机器上,int类型的值可能只有2字节(最大值为32767)。printf(“%ld”,nc);  对于长整型 格式输出为%ld

float与double类型,printf都用%f进行说明。

 

如果源程序分散在多个文件中,那么,在编译和加载时,就需要做更多的工作,但这是操作系统的原因,并不是语言属性决定的。

函数的参数使用的名字只在函数内部有效,对其他函数都是不可见的,其它函数可以使用与之相同的参数名而不会起冲突。另外函数内部声明的变量名也只在函数内部有效。

关于各种类型的变量函待总结。

main()函数也一般需要个return语句,程序需向其执行环境返回状态。

传值引用  若非要修改调用函数中的变量,则可按地址引用,另需注意的是,数组名代表的是地址。函数中传数组就是传地址,函数内部操作会影响数组。

每一字符串的末尾隐含一位空字符,即\0。

函数中的每个局部变量只在函数被调用时存在,在函数执行完毕退出时消失,这也是其他语言通常把这类变量称为自动变量的原因。static变量(静态变量)虽也是局部变量,但这种类型的局部变量在多次调用之间保持值不变。

外部变量必须定义在所有函数(包括主函数)之外,且只能定义一次,定义后编译程序将为它分配存储单元。

每个需要访问外部变量的函数中,必须声明相应的外部变量,说明其类型。声明时可以用extern语句显示声明,也可以通过上下文隐式声明。

函数在使用外部变量之前,不需要知道它的名字,所以必须在函数内部声明一遍所用的外部变量。如果外部变量的定义出现在函数之前,则可省略声明。但觉得声明了,可读性会高一点。

如果程序包含在多个源文件中,而某个变量在1文件定义,在2、3文件中使用,那么在2、3文件中就需要使用extern声明来建立该变量与其定义之间的联系。

人们通常把变量和函数的extern声明放在一个单独的文件中,并在每个源文件开头使用#include语句把所要用的头文件包含进来。

extern声明?

在ANSI C中,如果要声明空参数表,则必须使用关键字void进行显式声明。

过分依赖外部变量会导致一定的风险,因为它会使程序中的数据关系模糊不清------外部变量的值可能会被意外地或不经意地修改,而程序的修改又变得十分困难。

应该多读代码,多实践,并多去思考、理解。

 

类型、运算符与表达式

变量名:字母和下划线开头,在传统C语言用法中,变量名使用小写字母,符号常量名全部使用大写字母。

short和long两个限定符的引入可以为我们提供满足实际需要的不同长度的整型数。

各编译器可以根据硬件特性自主选择合适的类型长度,但short和int至少2字节,long至少4字节,且short不能长于int,int不能长于long。

限定符signed、unsigned可用于限定char类型或任何整型。不带限定符的char类型对象是否带符号取决于具体的机器。long double类型表示高精度的浮点数。

有关这些类型长度定义的符号常量以及其他与机器和编译器有关的属性可在标准头文件<limits.h>与<float.h>中找到。

long double 是ANSI C中引入的。

常量的表示: 123(int) 123l(long) 123u(unsigned) 123ul(unsigned long) 123.4(double) 123.4f(float) 123.4l(long double)

076(八进制) 0x78(十六进制)

\a响铃符 \b回退符 \f换页符 \n换行符 \r回车符

\t横向制表符 \v纵向制表符 \\反斜杠 \?问号 \’单引号 \”双引号

\ooo八进制数 \xhh十六进制数

常量表达式是仅仅只包含常量的表达式,这种表达式在编译时求值,而不在运行时求值。

编译时可以将多个字符串常量连接起来,例如:“hello,” “ world”与 “hello, world”是一样的,字符串常量的连接为将较长的字符串分散在若干个源文件行中提供了支持。

标准库函数strlen(s)可以返回字符串参数s的长度,但长度不包括末尾的’\0’。

枚举常量是另外一种类型的常量。枚举是一个常量整型值的列表。

enum Boolean{NO,YES};  枚举类型的声明

在没有显式说明的情况下,enum类型中第一个枚举名的值为0,第二个为1,如果只指定了部分枚举名的值,那么未指定值的枚举名的值将依着最好一个指定值向后递增。

枚举为建立常量值与名字之间的关联提供了一种便利的方式,相对于#define语句来说,优势在于常量值可以自动生成。

如果变量不是自动变量,则只能进行一次初始化,该初始化会在程序开始之前进行。

默认情况下,外部变量与静态变量将被初始化为0,未经显示初始化的自动变量的值为未定义值。

任何变量的声明都可以使用const限定符限定。该限定符指定变量的值不能被修改。const限定符也可配合数组参数使用,表明函数不可修改数组元素的值。(而不是地址)

int strlen(const char[]);   不能修改的是元素而不是地址。

取模运算符%不能应用于float或者double类型。在有负操作数的情况下,整数除法截取的方向以及取模运算结果的符号取决于具体机器的实现,这和处理上溢或下溢的情况是一样的。

逻辑运算符&&与||有一些较为特殊的属性。有&&与||连接的表达式按从左到右的顺序进行求值,并且,在知道结果值为真或假后立即停止计算。

通过优先级的概念,能够隐式地对表达式加括号,但运行是依然从左到右或者从右到左(赋值运算符)。

当一个运算符的几个操作数类型不同时,就需要通过一些规则把它们转换为某种共同的类型,一般来说,自动转换是指把比较窄的操作数转换为比较宽的操作数,并且不丢失信息的转换。

针对可能导致信息丢失的表达式,编译器可能会给出警告信息。

在ASCII字符集中,大写字母与对应的小写字母作为数字值来说具有固定的间隔。在其他字符集中,情况可能会改变,需注意。

标准头文件<ctype.h>定义了一组与字符集无关的测试和转换函数。

如tolower(c)函数将c转换为小写形式。isdigit(c)判断字符是否为数值符号。

C语言没有指定char类型的变量是无符号变量还是带符号变量,当把一个char类型的值转换为int类型的值时,对于不同的机器,其结果也不同,这反映了不同机器结果的不同。

(是否跟编译器有关?)

为了保证程序的可移植性,如果要在char类型的变量中存储非字符数据,最好指定signed或unsigned限定符。

某些函数(比如isdigit)在结果为真时可能返回任意的非0值,在if、while、for等语句的测试部分中,真就意味着非0,这两者之间没有区别。

if(isdigit)  而不要写成 if(isdigit==1)

在隐式算术类型转换中,一般来说,对于二元运算符,运算之前先要把较低的类型提升为较高的类型,结果为较高的类型。

表达式中float类型的操作数不会自动转换为double类型,这一点与最初的定义有所不同。

一般来说,数学函数(如math.h)使用双精度类型的变量,使用float主要是为了在使用较大的数组时节省存储空间,有时也为了节省机器执行时间(双精度算术运算特别费时)。

当表达式中包含unsigned类型的操作数时,转换规则要复杂一些。这也与机器相关。

见附录A.6的总结。

赋值时也要进行类型转换。

在编写程序的过程中,应该要注意到可能会发生变量转换的地方,并思考所带来的影响。

当把double类型转换为float类型时,进行四舍五入还是截取取决于具体的实现。

(类型名)表达式 表达式首先被赋值给类型名指定的某个变量,然后再用该变量替换上述整条语句。

表达式++n先将n的值递增1再使用n的值,n++则先使用n的值,再递增1。

C语言提供了6个位操作运算符。这些运算符只能作用于整型操作数,即只能作用于带符号或无符号的char、short、int与long类型。

& 按位与 | 按位或 ^按位异或 <<左移  >>右移 ~按位求反

按位与运算符经常用于屏蔽某些二进制位。按位或用来将某些二进制位置为1。

在对unsigned类型的无符号值进行右移位时,某些及其将对左边空出的部分用符号位填补(即“算术移位”),而另一些机器则对左边空出的部分用0填补(即逻辑移位)。

大多数二元运算符都有一个相应的赋值运算符op=,若expr1和expr2是表达式,那么:

expr1 op= expr2  等价于 expr1=(expr1)op(expr2) 区别在于,前一种形式expr1只计算一次。

这种表达方式简洁、还有助于编译器产生高效代码。

条件表达式: expr1?expr2:expr3  首先计算expr1,如果其值不为0,则计算expr2,并以该值作条件表达式的值,否则计算expr3,并以该值作为条件表达式的值,expr2和expr3中只能有一个表达式被计算。

如果expr2和expr3的类型不同,结果的类型将有转换规则决定。

如 (n>0)?f: n;  如果f为float类型,n为int类型,那么表达式是float类型,与n是否为正值无关。采用条件表达式可以编写出很简洁的代码。

结合性,具体指的是什么?

C语言没有指定同一运算符中多个操作数的计算顺序(&&、||、?:、,运算符除外)。

例如,k=f()+g(); f()可在g()之前计算,也可在后。因此若f过程中改变了g所使用的变量,结果就会依赖于f和g的计算顺序了。为了保证特定的计算顺序,可以把中间结果保持在临时变量中。

类似地,C语言也没有指定函数中各参数的求值顺序。这取决于编译器。

函数调用、嵌套赋值语句、自增自减运算符都有可能产生此种副作用。

在任何一种编程语言中,如果代码执行结果与求值顺序相关,则都是不好的程序设计风格。很自然,有必要了解哪些问题需要规避,但是如果不知道这些问题在各种机器上是如何解决的,就最好不要尝试运用某种特殊的实现方式。

在C语言中,分号是语句结束符,而Pascal等语言却把分号用做语句之间的分隔符。

用一对花括号{}把一组声明和语句括在一起就构成了一个复合语句,复合语句在语法上等价于单条语句。

if-else语句:注意嵌套,程序要有缩进,建议多使用花括号。

else-if语句:编写多路判定最常用的方法。

switch语句:一种多路判定语句,它测试表达式是否与一些常量整数值中的某一个值匹配,并执行相应的分支动作。

switch(表达式)

{

 case 常量表达式:语句序列

 case 常量表达式:语句序列

 default:语句序列

}

break语句将导致程序的执行立即从switch语句中退出。在switch语句中,case作用只是一个标号,因此,某个分支中的代码执行完后,程序将进入下一分支继续执行,除非在程序中显示地跳转。跳出switch语句最常用的方法是使用break语句与return语句。break语句还可强制控制从while、for与do循环语句中立即退出。

一段比较典型的switch代码:

     switch(c)

{

  case ‘0’:case ‘1’:case ’2’: case ’3’: case ’4’:

  case ’5’: case ’6’: case ’7’: case ’8’: case ’9’:

      ndigit[c-‘0’]++;

      break;

  case’ ‘:case ’\n’: case ’\t’:

      nwhite++;

      break;

  default:

      nother++;

      break;

}

即程序只是提供跳转到case处,如果没有break就会一直运行下去。

作为一种良好的程序设计风格,在switch语句最后一个分支(default)的后面也加上一个break语句。

退出循环手段:条件不符、break语句、return语句。在程序设计中到底选用while语句还是for循环语句,主要取决于程序设计人员的个人偏好。

标准库中提供了一个更完善的函数strtol,它将字符串转换为长整形数。

逗号运算符”,”也是C语言优先级最低的运算符,在for语句中经常使用。被逗号分隔的一对表达式将按照从左到右的顺序进行求值。for(i=0,j=strlen(s)-1;i<j; i++,j--) {}

某些情况下的逗号并不是逗号运算符,比如分隔函数参数的逗号,分隔声明中变量的逗号等,这些逗号并不保证各表达式按从左到右的顺序求值。

应该慎用逗号运算符,逗号运算符最适用于关系紧密的结构中。

do-while循环在循环体执行后测试终止条件,这样循环体至少被执行一次。

经验表明,do-while循环比while循环和for循环用得少得多,尽管如此,do-while循环语句有时还是很有用的。

continue语句用于使for、while或do-while语句开始下一次循环的执行。在while和do-while语句中,continue语句的执行意味着立即执行测试部分,在for循环中,意味着使控制转移到递增循环变量的部分,continue只用于循环语句。

C语言提供了可随意滥用的goto语句以及标记跳转位置的标号。理论上讲,goto语句是没有必要的,实践中不使用goto语句也可以很容易地写出代码。

但是在某些场合下goto语句还是用得着的,比如在某些深度嵌套的结构中,要一次跳出两层或多层循环。这种情况下使用break语句是不能达到目的的,它只能从内层循环退出到上一级的循环。

for() for(){if() goto error;}    error:

标号的命名方式与变量命名形式相同,标号的后面要紧跟一个冒号。标号可以位于对应的goto语句所在函数的任何语句的前面,标号的作用域是整个函数。

所有使用了goto语句的程序代码都能改写成不带goto语句的程序,但可能会增加一些额外的重复测试或者变量。除一些少数情况,建议尽可能少地使用goto语句。

一个程序可以保存在一个或者多个源文件中。各个文件可以单独编译,并可以与库中已编译的函数一起加载。

把一个功能分解为几个函数来实现,可以把不相关的细节隐藏在函数中,从而减少了不必要的相互影响的机会,并且,这些函数也可以在其他程序中使用。

返回值类型 函数名(参数声明表){ 声明和语句}

dummy(){} 这种不执行任何操作的函数有时很有用,可以在程序开发期间用以保留位置(留待以后填充代码)。如果函数定义中省略了返回值类型,则默认为int类型。

函数之间的通信可以通过参数、函数返回值以及外部变量进行。函数在源文件中出现的次序可以是任意的,只要保证每一个函数不被分离到多个文件中,源程序就可以分成多个文件。

编译多源文件程序?

函数的声明与定义必须一致。如果函数与调用它的函数放在同一源文件中,声明与定义的类型不一致,编译器就会检测到该错误。但如果函数的单独编译的(放在其他文件中),这种不匹配的错误就无法检测出来。结果导致程序出现错误。

如果函数带有参数,则要声明它们,如果没有参数,则使用void进行声明。

使用函数前,定要先写上函数原型。省得会出现许多未报错的毛病。

C语言不允许在一个函数中定义其他函数,C程序可以看成是由一系列外部对象构成,这些外部对象可能是变量或函数。因为外部变量可以在全局范围内访问,这就为函数之间的数据交换提供了一种可以代替函数参数与返回值的方式。

外部变量相比内部变量具有更大的作用域和更长的生存期。外部变量在程序执行过程中是永久存在的,它们的值在一次函数调用到下一次调用之间保持不变。因此如果两个函数必须共享某些数据,而这两个函数互不调用对方,这种情况下最方便的方式便是把这些共享数据定义为外部变量,而不是作为函数参数传递。

作用域规则:对于在函数开头声明的自动变量来说,其作用域是声明该变量名的函数,函数的参数也是如此,实际上可将它看做局部变量。不同函数的局部变量可以同名。

通常,外部变量或函数的作用域从定义它的地方开始,到其所在的文件的末尾结束。

但,如果要在外部变量的定义之前使用该变量,或者外部变量的定义与变量的使用不在同一个源文件中,则必须在相应的变量声明中强制性地使用关键字extern。

将外部变量的声明与定义严格区分开来很重要,变量声明用于说明变量的属性,而变量定义除此以外还将引起存储器的分配。

extern int sp; extern double val[]; (数组的长度在定义处)  这两个声明并没有建立变量并分配存储单元。

在一个源程序的所有源文件中,一个外部变量只能在某个文件中定义一次,而其它文件可以通过extern声明来访问它(定义外部变量的源文件中也可以包含对该外部变量的extern声明)。外部变量的定义中必须指定数组的长度,但extern声明则不一定要指定数组的长度。

extern声明若在函数外部,则从开始到文件末尾都有效,若extern声明在函数内部,则只在函数内部有效。

将外部变量和函数同一看做外部对象。

如何将一个程序分存在几个源文件中,也是个需要考虑的问题。

之所以分割成多个文件,主要是考虑在实际的程序中,它们分别来自于单独编译的库。只对其中一个文件修改,则重新编译时,只对该文件重新编译,其他文件则不重新编译。

另外,还必须考虑定义和声明在这些文件之间的共享问题,应尽可能地把共享的部分集中在一起,这样就只需要一个副本,改进程序时也容易保证程序正确性。把这些公共部分放在头文件中,在需要使用该头文件再包含进来。

一方面,我们希望每个文件只能访问它完成任务所需要的信息,另一方面现实中维护较多头文件比较困难,做了各折衷,在中等程序,最好只用一个头文件,而在大型程序,需要使用更多头文件,也需要更精心地组织它们。

在包含自己编写的.h文件时,应使用#include”··.h”,此命令会先在当前目录查找而后到编译器所指定的库文件目录寻找。#include<>命令则只到库文件目录中寻找。

可以用static声明限定外部变量与函数,可以将其后声明的对象的作用域限定为被编译源文件的剩余部分,通过static限定外部对象,可以达到隐藏外部对象的目的。这样在其它文件中的就可以设置同名的函数或变量了。

被static声明了的外部对象,在其他文件中无法访问。

static也可用于声明内部变量,同自动变量一样,是某个函数的局部变量,只能在该函数中使用,但与自动变量不同的是,不管其所在函数是否被调用,它一直存在,而不像自动变量那样,随着所在函数的被调用和退出而存在和消失。

static类型的内部变量是一种只能在某个特定函数中使用但一直占据存储空间的变量。

若有两个文件,文件一定义了static变量,文件二定义了一个同名全局变量,则文件二无法引用文件一中的变量(static隐藏效果),同时文件一也访问不了文件二种的全局变量。

但是文件二可以通过全局函数来使用文件一中的静态全局变量,而文件一也可以通过一个函数来访问或引用文件二的同名变量。

函数中的静态局部变量,只在第一次调用函数时被初始化一次。

register声明告诉编译器,它所声明的变量在程序中使用频率较高,将变量存在机器的寄存器中,这样可以使程序更小、执行速度更快。register声明只适用于自动变量以及函数的形式参数。 如f(register unsigned m, register long n) { register int i;}

过量的寄存器声明并无声明害处,因为编译器可以忽略过量的或不支持的寄存器变量声明。另外不论寄存器变量实际上是不是存放在寄存器中,它的地址都是不能访问的。(特性)在不同机器中,对寄存器的数目和类型的具体限制也是不同的。

在C语言中,函数中不能定义函数。函数是平行的,它们之间可以有嵌套的关系。

程序块:程序块中可视为对变量的一种管理。在程序块中声明的变量可以隐藏程序块外与之同名的变量,它们之间没有任何关系,并在程序块结束前一直存在。

即在{}中,可定义新的自动变量,在括号结束后消失,然后若与外面的变量同名,则隐藏外面的变量。

自动变量(包括形式参数)也可以隐藏同名的外部变量与函数。

int x; int y;

f(double x) {double y;}  f函数中的形参x,自动变量y,与外部变量同名,但并不影响外面的变量。

在一个好的程序设计风格中,应该避免出现变量名隐藏外部作用域相同名字的情况,否则,很可能引起混乱和错误。

 

初始化的规则:在不进行显式初始化的情况下,外部变量和静态变量都将被初始化为0,而自动变量和寄存器变量的初值则没有定义。

对于外部变量与静态变量来说,初始化表达式必须是常量表达式,且只初始化一次(从概念上讲是在程序开始执行前进行初始化)。对于自动变量与寄存器变量,则在每次进入函数或程序块时都将被初始化。

对于自动变量和寄存器变量来说,初始化表达式可以不是常量表达式,表达式中可以包含任意在此表达式之前已经定义的值,包括函数调用。

 

递归:C语言中的函数可以递归调用,即函数可以直接或间接调用自身。函数递归调用自身时,每次调用都会得到一个与以前的自动变量集合不同的新的自动变量集合。

递归并不节省存储器的开销,因为递归调用过程中必须在某个地方维护一个存储处理值的栈,递归的执行速度并不快,但递归代码比较紧凑,并且比相应的非递归代码更易于编写与理解。在描述树等递归定义的数据结构时使用递归尤其方便。

 

C预处理器:预处理器是编译过程中单独执行的第一个步骤,#include<>指令用于在编译期间把指定文件的内容包含进当前文件中,#define指令则用任意字符序列替代一个标记。

两个要点,即替换和包含。

头文件用以包含常见的#define语句和extern声明,或库函数函数原型声明。

#define 名字 替换文本  这是种最简单的宏替换

  • 若#define指令要分行写,需要在每行的某位加上一个反斜杠\。指令定义的名字的作用域从其定义点开始,到被编译的源文件的末尾处结束。
  • 宏定义中,也可使用前面出现过的宏定义。谁放前面都无所谓,预编译器会逐层替代。(只要你有些,就会替换掉)
  • 替换只对记号进行,对括在引号中的字符串不起作用。

替换某些定义,结构语句:#define forever for(;;)  #define SHENGMING int i

除引号外的部分随便替代!!(可以将代码写得没人能看懂,或者用#define编写语言转换模块)

#define指令很好玩。

  • 宏定义也可以带参数,这样可以对不同的宏调用使用不同的替换文本。

#define 函数名(参数列表)  表达式  比如 #define 比较哪个大(A,B) ((A)>(B)?(A):(B))

使用宏看起来很像是函数调用,但宏调用是直接将替换文本插入到代码中的。

适合于:如果对各种类型的参数的处理都是一致的,可将同一个宏定义应用于任何数据类型,而无需针对不同的数据类型需要定义不同的函数。(定义函数往往需要提供参数类型)

  • 使用宏调用需注意的两点:作为参数的表达式要重复计算两遍,如果表达式存在副作用(如含有自增运算符或输入/输出),则会出现不正确情况。 max(i++,j++)  替换完成后,进行一次自加,运行时后再进行一次自加。
  • 要适当使用圆括号来保证计算次序的正确性。最好将每个参数都用圆括号括起来。

宏可避免调用函数所需的运行开销。有些常用的函数,是通过宏来实现的。

可以通过#undef指令取消名字的宏定义,这样做可以保证后续的调用是函数调用而不是宏调用。

形式参数不能用带引号的字符串替换。如果在替换文本中,参数名以#作为前缀则结果将被扩展为 ”参数”。  如 #define 打印(A)  printf(#A"\n");

如果实际参数是带引号字符串  #前缀会导致替换时 “变为\”  \变为\\

##  连接参数 成为一个新的记号。

如果替换文本中的参数与##相邻,则该参数将被实际参数替换,##与前后的空白符将被删除,并对替换后的结果重新扫描。

#define paste(front,back) front ## back

因此,宏调用paste(name,1) 的结果将建立记号name1。

条件包含:#if语句对其中的常量整型表达式(不含sizeof、类型转换运算符或enum常量)求值,然后进入不同的分支,#else表示另一分支,#elif类似于else if语句,#ifend表示判断语句的结束。

表达式defined(名字)  当名字已经被定义了,则返回1,否则返回0。

C语言专门定义了两个预处理语句#ifdef与#ifndef,用来测试某个名字是否已经定义。

 

指针与数组

使用指针通常可以生成更高效、更紧凑的代码。谨慎使用指针,可以写出简单、清晰的程序。ANSIC使用类型void*代替作为通用指针的类型。

指针是能够存放一个地址的一组存储单元(通常是两个或4个字节)。取址运算符&只能应用于内存中的对象,即变量与数组元素,它不能作用于表达式、常量或register类型的变量。

一元运算符*:间接寻址或叫间接引用运算符。

int *p; 该声明语句表明表达式*p的结果是int类型。每个指针都必须指向某种特定的数据类型,一个例外的情况是指向void类型的指针,此类指针可以存放指向任何类型的指针,但它不能间接引用其自身。

指针参数使得被调用函数能够访问和修改主调函数中对象的值。

EOF(文件结束标记)是什么? 怎么用?

通过数组下标所能完成的任何操作都可以通过指针来实现,一般来说,用指针编写的程序比用数组下标编写的程序执行速度快,但另一方面,用指针实现的程序理解起来比较不易。

pa=&a[0];等价于 pa=a  数组名所代表的就是该数组最开始的一个元素的地址。

a[i] 也可写为 *(a+i)  pa[i] *(pa+i)  在计算数组元素a[i]时,C语言实际上先将其转换为*(a+i)的形式,然后再进行求值,在程序中这两种形式是等价的。

一个通过数组和下标实现的表达式可等价地通过指针和偏移量实现。但必须记住数组名和指针之间有一个不同之处,指针是变量,而数组名不是。

在函数定义中,形式参数 char s[]; 和 char *s; 是等价的。通常更习惯于使用后一种形式,因为它比前者更直观地表明了该参数是一个指针。

也可以把数组中间元素的地址赋给函数,而通过p[-n]来引用之前的元素,当然,引用数组边界之外的对象是非法的。

地址算术运算:C语言中的地址算术运算方法是一致且有规律的,将指针、数组和地址的算术运算集成在一起是该语言的一大优点。

通常,对指针有意义的初始化值只能是0或者是表示地址的表达式,对后者来说,表达式所代表的地址必须是在此前已定义的具有适当类型的数据的地址。

指针与整数之间不能相互转换,但0是惟一的例外:常量0可以赋值给指针,指针也可以和常量0进行比较。程序中经常用符号常量NULL代替常量0,这样便于更清晰地说明常量0是指针的一个特殊值。符号常量NULL定义在标准头文件<stddef.h>中。

指针与整数之间不能相互转换:两者虽都代表一个数,但上下文不一致,一个表示的是地址且说明了类型,一个则只是表示什么类型的多大的数。但强制转换是可行的。

size_t类型 即无符号整型。

所有的指针运算都会自动考虑它所指向的对象的长度。

有效的指针运算:相同类型指针间的赋值运算;指针同整数之间的加法或减法运算;指向相同数组中元素的两个指针间的减法或比较运算;将指针赋值为0或指针与0之间的比较运算。

非法操作:两个指针间的加法、乘法、除法、移位或屏蔽运算;指针同float或double类型之间的加法运算;不经强制类型转换而对两种不同类型的指针赋值(除非两个指针之一为void*类型)

字符串常量是一个字符数组,在字符串的内部表示中,字符数组以空字符’\0’结尾,printf(“hello,world\n”);实际上,printf接受的是一个指向字符数组第一个字符的指针。

void strcpy(char *s, char *t) {while(*s++=*t++);}

*p++=val;  //将val压入栈

val=*--p;   //将栈顶元素弹出到val中  此两式是进栈和出栈的标准用法。

指针数组以及指向指针的指针

C语言提供了类似于矩阵的多维数组,但实际上它们并不像指针数组使用得那样广泛。

C语言中的二维数组的使用方式和其他语言一样,数组元素按行存储,因此当按存储顺序访问数组时,最右边的数组下标(即列)变化得最快。

二维数组a[i][j]  数组名代表指向由行向量构成的一维数组,a[i]也是指针,指向第i行元素构成的一维数组。

如果将二维数组作为参数传递给函数,那么在函数的参数声明中必须指明数组的列数。数组的行数么有太大关系。

如:f(int daytab[2][13]) {}  f(int daytab[][13]) {} f(int (*daytab)[13]) {}  都是等价的

int (*daytab)[13] 声明了一个指针,该指针指向拥有13个元素的数组,也即指针的指针。

int *daytab[13] 则声明了一个具有13个元素的指针数组。

一般来说,除数组的第一维可以不指定大小外,其余各维都必须明确指定大小。

要能区分二维数组和指针数组之间的区别。指针数组的优点在于,数组的每一行长度可以不同。指针数组最频繁的用处是存放具有不同长度的字符串。

命令行参数:在支持C语言的环境中,可在程序开始执行时将命令行参数传递给程序。

main(int argc  char **argv)  argc代表参数的个数,argv为参数向量,是一个指向字符串数组的指针。在输入命令的时候,以空格隔开各字符串,每个字符串对应一个参数。(每个字符串以\0结尾,\0即为0)

按照C语言的约定,argv[0]的值是启动该程序的程序名,因此argc至少为1。如果argc值为1,则说明程序名后面没有命令行参数了。

第一个可选参数为argv[1],而最后一个可选参数为argv[argc-1],另外ANSI标准要求argv[argc]的值必须为一个空指针。

于<string.h>中定义的标准库函数strstr(s,t)返回一个指针,该指针指向字符串t在字符串s中第一次出现的位置,如果字符串t没有在字符串s中出现,函数返回NULL(空指针)。

Unix系统中的C语言程序有一个公共的约定:以负号开头的参数表示一个可选标志或参数。

指向函数的指针:

通用指针类型void *,由于任何类型的指针都可以转换为void *类型,并且在将它转换会原来的类型时不会丢失信息,所以在一些需要处理多种类型数据的函数中,可把参数设定为void *类型。

在C语言中,函数本身不是变量,但可以定义窒息那个函数的指针,这种类型的指针可以被赋值,存放在数组宏、传递给函数以及作为函数的返回值等等。比如在编写一个函数,可通过不同参数选择调用不同的函数,那么就可用函数的指针来实现这一功能。

int (*comp)(void *,void *)  表明comp是一个指向函数的指针,该函数具有两个void *类型参数,其返回值类型为int。comp是一个指向函数的指针,*comp则代表一个函数。

(*comp)(v[i],v[left])  则是对函数进行调用。

复杂声明:尽管实际中很少用到过复杂的声明,但是,懂得如何理解甚至如何使用这些复杂的声明是很重要的。本节提出了两个解析声明式的函数。主要是须从里面看。

《The C Programing Language》阅读笔记

标签:

原文地址:http://www.cnblogs.com/boowin/p/4525141.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!