C程序设计语言学习笔记
邱志军 于 2008年 3月 11日起
赛尔软件有限公司(筹)
第一章 基本概念
第一节 入门
学习新的程序设计语言的最佳方式就是编写程序。每个C程序,不论大小如何,都由函数和变量组成。函数中包含若干用于指定所要作的计算操作的语句,而变量则用于在计算过程中存储有关值。每个C程序都从名为main的函数的起点开始执行。
第二节 变量和算术表达式
在C语言中,所有的变量必须先声明再使用,声明通常放在函数开始处的可执行语句之前。Printf函数不是C语言本身的一部分,C语言本身没有定义输入输出功能。
第三节 for语句
for语句是一种循环语句,是while语句的一种推广,for语句比较适合描述这样的循环:初值和增量都是单个语句,并且是逻辑相关的,因为for语句把循环控制逻辑放在一起,比while语句更紧凑。C语言中的一个通用规则:任何可以使用某个类型变量值的地方,都可以使用该类型的更复杂的表达式。
第四节 常量
#difine指令用于把符号名字(符号常量)定义为一特定的字符串:#difine 名字 替换文本,替换文本可以是任意字符序列,而不仅限于数。符号常量通常用大写字母书写,这样容易同变量名区分。
第五节 字符输入输出
由标准库提供的输入输出模型相当简单,文本的输入输出都是做为字符流处理的,不管它从何处输入,输出到何处。标准库中有几个函数控制一次读写一个字符,其中最简单的是,getchar和putchar两个函数。
文件复制
借助getchar和putchar函数,可以在不掌握其他输入输出知识的情况下编写出许多有用的代码。向其他许多东西一样,一个字符不论在键盘上以什么形式出现,在机器内部都是以位模形式存储的。Getchar函数通过一个特定的常量EOF来区分文件中的有效数据和文件结束标记,因此通常将getchar函数的返回值赋值给一个整数以承载这种区分(不能用char)。
字数计数
行技术
单词计数
第六节 数组
在C语言中,数组下标从0开始,下标可以是任意的整数表达式,包括整数变量和整数常量。
第七节 函数
函数为计算的封装提供了一种简便的方法,在其他地方使用函数时不需要考虑其实现。C语言使用了简单、方便、有效的函数,我们会经常看到只定义和调用了一次的短函数,这样使用函数使某些代码更容易理解。
不同函数的定义可以放在不同的文件中,但同一函数的定义必须放在同一个文件中。虽然将函数置于不同的文件在编译和装载时有所不同,但是那只是操作系统的事情,并非语言本身的规范。
一般而言,把在函数的定义中用括号括起来的命名的变量叫做参数,而把函数调用中与参数对应的值称为变元。函数原型和函数说明中的参数名可以不一致,甚至函数原型中的参数名可以忽略。
第八节 变元——按值调用
在C语言中,所有函数变元都是按值传递的,被调用函数所得到的变元值放在临时变量中而非原来的变量中。在C语言中,被调函数不能直接更改调用函数中变量的值,它只能更改其私有临时变量的值。在必要时,也可以对函数改写,使之可以修改调用函数中的变量,此时调用者要向被调用函数传递要改变值的变量的地址(也就是指向变量的指针)。
第九节 字符数组
C语言中最常用的数组类型就是字符数组,对字符数组的操作通常涉及到大量的指针操作,这也是C语言在处理字符串方面的灵活性和易于出错的首要原因。
第十节 外部变量和作用域
函数中的局部变量仅在函数调用时存在,通常我们称之为自动变量,通过引入静态存储类的局部变量我们可以给函数附加一个额外的变量以保存函数调用间的中间结果(对某些问题的求解这点很重要)。此外,C语言中还允许我们定义外部变量,所用的函数都可以对外部变量进行访问,因此外部变量也成为全局变量,外部变量用于在不同函数间交换数据。外部变量的说明可以通过external关键字显示声明,也可依据上下文(变量声明放在函数前)。
如果程序包含几个源文件,那么要引用另一个文件中定义的全局变量就必须得用external关键字加以显示声明。人们通常把变量的external声明和函数声明放在一个单独的文件中(头文件)。
第二章 类型、运算符与表达式
第一节 变量名
在选择变量名时一个比较明智的方法是使所选名字的含义能表达变量的用途。我们倾向于局部变量使用比较短的名字,外部变量使用比较长的名字。
第二节 数据类型与大小
在C语言中只有char、int、float、double几个基本数据类型,此外还有一些可以用于限定这些类型的修饰符,这些修饰符用来提供满足实际要求的大小的数据类型。
第三节 常量
对于整形、浮点型常量通常要用相应的字母后缀来具体声明其类型,对于字符常量我们用单引号括起来加以区分。
第四节 说明
除了某些可以通过上下文做的隐式说明外,所有变量都必须先说明后使用。如果变量不是自动变量,那么仅初始化一次,且一般在函数开始执行之前,初始化符为常量表达式。在变量中可以用const关键字限定符,用于指定该变量不可变,对于数组const限定符表明数组所有元素的值不可变。
第五节 算术运算符
整数除法要截取掉结果的小数部分,取模运算不能用于float、double对象。负数的整除截取方向依具体的机器而不同。
第六节 关系运算符和逻辑运算符
按照定义,如果关系运算符和逻辑运算符的结果为真则,那么它的值为1,否则值为0。一元求反运算将非零的值转换为0,为零的值转换为1。
第七节 类型转换
一般而言,只能把比较窄的运算分量自动转换为比较宽的运算分量,这样才不至于丢失信息。由于char类型就是小整数类型,算术表达式中可以自由的使用char类型的变量和常量。
第八节 自增和自减一元运算符
++运算符和- -运算符可以置于操作数的前后两处,但是其效果有点区别,这点要尤其小心。
第九节 按位运算符
C语言中提供了六个用于位操作的运算符,这几个运算符仅能用于整数分量。
第十节 赋值运算符和赋值表达式
大多数的二元运算符都对应有一个相应的赋值运算符,以简化编码,此外赋值语句有一个值,可以用于表达式中。
第十一节 条件表达式
条件表达式是一个表达式,可以用在任何可以应用表达式的地方,条件表达式可以用于编写简介的代码。
第十二节 运算符优先级和表达式求值顺序(略)
第三章 控制流
第一节 语句和分程序
在C语言中,分号是语句终结符,此外可以通过花括号将一组说明和语句括起来构成一个复合语句——语法上等价于单个语句。
第二节 if-else语句
由于if语句只是测试表达式的值,故表达式可以采用比较简介的形式,但是注意if语句后的语句无论多么简单都不能忘了书写分号。
第三节 else-if语句
当条件判断逻辑比较复杂时,就应该应用else-if语句,通过嵌套的if-else语句的结构来完成条件判断。
第四节 switch语句
Switch语句是一种多路判断语句,它根据表达式当否与若干常量整数中的某一个匹配来相应的执行相关的分支动作。此外,break语句用于从switch语句退出。
第五节 while和for循环语句
从语法上看,for循环语句中的三个组成部分都是表达式,其中表达式2进行相应的条件判断。C语言中有一个运算符,叫做逗号运算符,在for循环中经常要用到。
第六节 do-while循环
Do-While循环是先执行循环语句,再测试条件,循环至少要被执行一次,一般来说,使用该循环的机会要少得多。
第七节 break语句和continue语句
break语句用于从循环语句中提前退出来,而continue语句用于先行结束循环的执行,进入到下一次的循环中。
第八节 goto语句(略)
第四章 函数与程序结构
C语言在设计函数时,考虑到了效率和易于使用两个方面,一个C程序通常都是有一些较小的函数组成,而不是几个比较大的函数。
第一节 函数的基本知识
如果一个函数存在返回值,那么调用函数可以选择是否应用该返回值。此外,return语句可以选择是否返回值,即时函数声明了返回值。如果一个C程序中包含多个源文件,那么其编译和转载机制因平台而异。
第二节 返回非整数值的函数
函数的返回值可以为任意的类型,但是通常在调用前对函数进行相应的声明(记住声明和定义要一致)。
第三节 外部变量
由于C语言不允许在函数中嵌套定义函数,因此函数肯定是外部的。通常情况下,外部变量和函数具有如下性质:所有通过名字对外部变量和函数的引用(即使这种引用来自独立编译的函数),都是引用的同一对象(标准中把这一性质叫做外部连接)。
由于外部变量是可以全局访问的,这位函数之间交换数据提供了另一种可能(传统的方式为依靠变元和返回值)。此外,外部变量比自动变量就有更大的作用域和更长的生命周期。
第四节 作用域规则
用以构成C程序的函数和外部变量完全没有必要同时编译,一个程序可以放在几个文件中,可以从库中调用已编译的函数。C语言在结构上值得关注的几个问题,如下:
1. 怎样编写说明才能使所说明的变量在编译时被认为是正确的?
2. 怎样安排说明才能保证在程序载入时各部分能相连?
3. 怎样组织说明才能使得只需一份拷贝?
4. 怎样初始化外部变量?
一个名字的作用域指的是程序中可以使用该名字的部分。函数参数也可以看作是局部变量,具有局部作用域,仅在函数内部可见。
如果一个外部变量在定义前就要用到,或者这个变量要用在另一个源文件中,则必须要用extern关键字显示声明。变量的说明用于通报变量的性质(主要是类型),而变量定义除此之外还引起存储分配。
第五节 头文件
当存在多个源文件时,则我们必须考虑文件之间的定义以及说明的共享问题。通常,我们尽可能使所有的共享集中到一处,以使得仅需一份拷贝。在编辑头文件时,我们得进行在头文件的维护(数量)和信息的隔离方面进行权衡。
第六节 静态变量
可以在通常的说明之前加上static关键字,以指明静态存储,存在内部和外部静态变量之分。对于外部静态变量或函数仅同一文件中的函数可以访问。对于内部静态变量,其不同于普通自动变量的地方在于它一直存在于内存中,换言之,内部静态变量是一种仅能在一个函数内使用但一直占据内存的变量。
第七节 寄存器变量
Register说明用于提醒编译器变量在程序中使用频率较高,编译器可以忽略该选项。
第八节 分程序结构
通过在用花括号括起来的语句块中定义变量,可以获得变量的快作用域,起到类似分程序结构的效果。
第九节 初始化
在没有显示初始化时,外部变量和静态变量都被初始化为0,而局部变量和寄存器变量则没有定义(其初值为垃圾)。
第十节 递归
C函数可以递归调用,即一个函数可以直接或间接调用自己。
第十一节 C预处理程序
预处理从理论上讲是编译器进行的一个独立的步骤,C预处理程序提供了一些有用的语言功能。
文件包含
文件包含指令,即#include指令,在源程序中所有的include指令会被相应文件中的内容所替代。
宏替换
宏替换,即#define指令,任何字符序列都可以通过定义宏进行相应的替换,包括常量,表达式,函数等。
条件包含
C语言中通过#if指令为预处理添加条件逻辑,增强预处理指令的灵活性。
第五章 指针与数组
指针是一种用于存放另一个变量的地址的变量,指针通常能够产生更为高效、简洁的代码,但同时它也是C语言难以应用的根源之一。ANSI C中用void *做为通用指针的类型。
第一节 指针与地址
机器的存储器通常由连续编号的存储单元序列组成,它们可以单个或相连成组的方式进行操纵。指针由能存放一个地址的一组存储单元(通常是两个或四个字节)组成。
一元运算符&用于取一个对象的地址,取址运算符&仅能用于内存中的对象(如变量、数组甚至函数),不能用于表达式、寄存器变量等。
一元运算符*是间接寻址或间接引用运算符,当它应用于指针时,返回指针所指向的对象。
应该注意的是,一个指针只能指向一个特定类型的对象;每一个指针对象也有一个确定的数据类型(void指针除外)。
一元运算符&和*的优先级比算术运算符高,因此使用时通常不用加上括号。诸如*和++等一元运算符在表达式求值时,按从又到左的顺序和运算分量结合。
第二节 指针和函数变元
由于C语言以按值调用的方式将变元传递给函数,因此被调函数不能直接更改调用函数中变量的值。指针类型的变元,使得被调函数函数能够间接改变调用函数中的变量的值。
在标准C库函数中的scanf函数就是利用指针达到这种类似回调或钩子效果的例子。
第三节 指针和数组
数组下标所能完成的任务都能够通过数组来实现,此外指针运算要比数组下标更有效率但是更为难以理解。
按照数组的定义,一个类型为数组的变量或表达式的值就是该数组第一个元素的地址,因此通常将数组变量赋值给数组以进行相应的指针运算(数组变量的地址为固定的)。此外,对数组元素a[i]的访问可以通过*(a+i)这样的形式,其实本质上对a[i]的访问要先转换成*(a+i)这样的形式再进行运算的。有时,通过指针传递一个子数组的方式会比较贴切。
第四节 地址算术运算
一般,定义指针时可以对其进行初始化,但指针的值只能为0或者是某个特定类型变量的地址。指针不能与整数互相转化,但是指针同0进行的任何比较运算、赋值都是有意义的。
第五节 字符指针和函数
字符串常量是一个字符数组,当字符串常量做为函数的变元时,其实传递的是指向这个字符数组的一个指针。
第六节 指针数组和指向指针的指针
由于指针也是变量,所以也可以将指针置于数组中形成指针数组,同时也可以进一步提取其地址形成指针的指针的概念。
第七节 多维数组
C语言中还提供了多维数组的概念,这也可以做为指针数组的一种替代,尽管其用途相对更少。
第八节 指针数组的初始化
指针数组的初始化同一般数组的初始化一致,尤其是对字符串数组进行初始化时,往往直接使用字符串常量对指针进行赋值。
第九节 函数指针
在C语言中,函数本身不是变量但是可以定义指向函数的指针,函数指针可以存放在数组中,传递给函数以及做为函数的返回值等。
第十节 复杂说明
在C语言中,由于指针的灵活应用往往导致难以理解的说明,这是就要仔细区分说明到底是指一个函数还是一个指针变量。
第六章 结构
第一节 结构的基本知识
C语言中的结构,在声明和使用上类似于Java中的值对象,其他的要注意的是:C中的结构包含的字段都是公共的,并且C中结构类型的变量为值而非对象引用(C中仅有指针进行相对引用)。
第二节 结构与函数
对结构的合法操作只有拷贝、做为一个单元进行赋值、通过取址运算符&取其地址,还有就是访问其成员字段。此外,通过结构指针访问结构成员还有一种简写形式:p->结构成员。
第三节 结构数组及结构指针(略)
第四节 自引用结构
第七章 输入与输出