第一章 C语言概述
C语言是20世纪70年代初期,在贝尔实验室开发出来的一种用途广泛的编程语言。
C语言的历史
C语言是Unix系统开发过程中的一个副产品。它被用来重写Unix系统。
到20世纪80年代,许多计算机开始使用C语言开发程序,为了保证其程序的可移植性,建立标准成为了共识。
1989年,通过C89标准。
1999年,通过C99标准,但这个标准还没有被普遍使用。
C++ 语言
虽然采纳了 ANSI/ISO 标准以后C语言自身不再发生变化。但是,从某种意义上说,随着基于C语言的新式语言的产生,C语言的演变还在继续。新式语言包括C++。它在许多方面对C语言进行了扩展,尤其是增加了面向对象编程的特性。
随着C++语言的迅速普及,在不久的将来你很可能会用C++语言编写程序。果真如此,为何还要费心学习C语言呢?首先,C++语言比C语言更加难学,因此在掌握C++语言前,最好先精通C语言;其次,我们身边存在着大量的C语言代码,需要去维护和阅读;最后,不是每个人都喜欢改用C++编程,例如对于编写小规模的程序,使用C++反而不会获得多少好处。
C语言的优缺点
C语言的优缺点都源于它最初的用途,以及其基础理论体系。
- C语言是一种底层语言。它提供了对内存访问的功能。C程序的许多服务都依赖于操作系统提供的接口。
- C语言是一种小型语言。C语言的特性不多,应用程序的绝大部分功能依赖于标准库。
- C语言是一种包容性语言。C语言假设用户知道自己在做什么,因此有编写自由度。C语言不强制进行错误检查。
C语言的优点
-
高效。发明C语言的目的是为了替代汇编语言。
-
可移植。有标准库的存在。
-
功能强大、灵活。C语言的数据类型和运算符集合有足够强大的表达能力。
-
与Unix集成。
C语言的缺点
-
C程序更容易隐藏错误。由于其灵活性,导致编写的代码令编译器很难检查错误。
-
C程序可能会难以理解。
-
C程序可能会难以修改。因为它设计时没考虑到维护的问题。C语言没有提供类,包等模块化概念。
高效地使用C语言
- 学习规避C语言的缺陷。比如越界问题。
- 使用软件工具。
- 利用现有的代码库。
- 采用切合实际的编码规范。
- 避免“投机取巧”和极度复杂的代码。
- 使用标准C,少用经典C。标准C即是 ANSI C ,本书采用的是标准C。
- 避免不可以移植性。
第二章 C语言基本概念
程序:显示双关语
这是经典C的一个示例:
// pun.c
#include <stdio.h>
main()
{
printf("To C, or not to C: that is the question.\n");
}
编译和链接
首先,需要一个.c文件保存程序代码,接下来需要把程序转换为机器可以执行的形式。通常包含下列三个步骤:
-
预处理。首先会把程序送交给预处理器( preprocessor )。预处理器执行以#开头的命令。
-
编译。修改后的程序现在可以进入**编译器( compiler )**了。编译器会把程序翻译成机器指令(即目标代码, object code )。
-
链接。**链接器( linker )**把由编译器产生的目标代码和任何其他附加代码整合在一起,产生完全可执行的程序。
这个过程可以一步完成,即:
gcc pun.c
在编译和链接好程序后,编译器 gcc 会把可执行程序放到默认名为 a.out 的文件中。编译器 gcc 有许多选项,其中 -o 允许给可执行程序选择一个名字:
gcc -o pun pun.c
简单程序的一般形式
形式如:
指令
int main()
{
语句
}
指令
在编译C程序之前,预处理器会首先对C程序进行编辑。我们把预处理器执行的命令称为指令。这里只关注 #include 指令。
#include <stdio.h>
这条指令说明,在编译前把 stdio.h 中的信息“包含”到程序中。这段程序中包含 stdio,h 的原因是:C语言没有内置的“读”和“写”命令。因此,进行输入/输出操作就需要用标准库中的函数来实现。
这里是指预所有指令都是以#开头。一条指令必须占据一行,且不留分号结尾。
函数
函数是用来构建程序的一个构建块。C程序就是函数的集合。函数分为两大类:一类是程序员编写的函数,另一类则是由C语言的实现所提供的函数。后者可以称为库函数( library function )。
在C语言中,函数仅仅是一系列组合在一起并且赋予了名字的语句。某些函数计算一个值,而某些函数不是。计算出一个值的函数可以用 return 语句来指定所“返回”的值。
每个程序必须有一个 main 函数。 main 函数是非常特殊的:在执行程序时系统会自动调用 main 函数。
main 函数在程序终止时向操作系统返回一个状态码。 pun 程序始终让 main 函数返回0,0表明程序正常终止。
建议加入 return 语句,如果不这样做,某些编译器可能会产生一条警告信息:
// pun.c
#include <stdio.h>
main()
{
printf("To C, or not to C: that is the question.\n");
return 0;
}
语句
语句是程序运行时执行的命令。每条语句都要以分号结尾。
一条语句可以占据多行。
程序 pun.c 只用到了两种语句。一种是返回语句,一种则是函数调用( function call )语句。为了在屏幕上显示一条字符串就调用了 printf 函数。
显示字符串
我们用 printf 函数显示了一条字符串字面量( string literal )。字符串字面量是用一对双引号包围的一系列字符。
当打印结束时, printf 函数不会自动跳转到下一输出行。为了让 printf 跳转到下一行,必须要在打印的字符串中包含一个 \n (换行符)。写换行符就意味着终止当前行,然后把后续的输出转到下一行进行。
换行符可以在一个字符串字面量中出现多次。比如:
printf("Brevity is the soul of wit.\n -- Shakespeare\n");
注释
注释就是代码的说明。在预编译后,注释会移除出代码。
例如:
/* This is a comment */
为 pun.c 增加注释:
/* Name: pun.c
Purpose: Prints a bad pun.
Author: K.N.King
Data written: 5/21/95
*/
变量和赋值
变量( variable )就是用来存储数据的存储单元。
类型
一个变量必须有一个类型。类型决定了存储单元的大小和对变量的操作方式。
int 型变量可以存储整数,例如0、1、392或者-2553,但是,整数的取值范围是受限制的。某些计算机上,int型数值的最大取值仅仅是32767。
float 型变量可以存储更大的数值,而且,float型变量可以存储带小数位的数据,例如379.125。但是,float 型变量有一些缺陷,即这类变量需要的存储空间要大于 int 型变量。而且,进行算术运算时 float 型变量通常比 int 型变量慢。另外, float 型变量所存储的数值往往只是实际数值的一个近似值。
声明
在使用变量前,必须对其进行声明,这也是为了便于编译器工作。例如,声明变量 height 的方式如:
int height;
如果几个变量具有相同的类型,就可以把它们的声明合并:
int height, length, width;
当 main 函数包含声明时,必须把声明放置在语句之前:
main()
{
声明
语句
}
赋值
变量可以通过赋值( assignment )的方式获得一个值。例如:
height = 8;
volume = height * length * width;
赋值运算符的右侧可以是一个含有常量、变量和运算符的公式(表达式, expression )。
显示变量的值
用 printf 可以显示当前变量的值。
printf("Height: %d\n", height);
占位符 %d 用来指明在打印过程中变量 height 的值的显示位置。 %d 仅用于 int 型变量,如果要打印 float 型变量,需要用 %f 来代替。默认情况下, %f 会显示小数点后6位数字,若需要显示小数点后n位数字,则可以把 .n 放置在%和f之间。
printf("Profit: $%.2f\n", profit);
初始化
当程序开始执行时,某些变量会被自动设置为0,而大多数变量则不会。没有默认值并且尚未在程序中被赋值的变量是未初始化的( uninitialized )。
使用初始化式对其变量进行初始化,如:
int a = 0;
读入输入
为了获取输入,就要用到 scanf 函数。 scanf 中的字母f和 printf 中的f含义相同,都是表示“格式化”的意思。 scanf 和 printf 函数都需要使用格式串( format string )来说明输入或输出的样式。
为了读取一个 int 型数值,可以使用如下的 scanf 函数调用。
scanf("%d", &i);
字符串“%d”说明 scanf 读入的是一个整数,i是一个 int 型变量,用来存储读入的输入。
读入一个 float 型数值时,需要这样的 scanf 调用:
scanf("%f", &x);
%f只适用于 float 型变量。
定义常量
常量( constant )是在程序执行过程中固定不变的量。当程序含有常量时,建议给这些常量命名。方式是使用宏定义( macro defination )。
#define N 4
这里的 #define 是预处理指令。当程序进行编译时,预处理器会把每一个宏用其表示的值替换回来。
此外,还可以利用宏来定义表达式:
#define SCALE_FACTOR (5.0 / 9.0)
当宏包含运算时,必须用括号把表达式括起来。
宏的名字一般用大写字母,这是大多数程序员遵守的规范。
标识符
标识符就是函数、变量、宏等实体的名字。
标识符由字母、数字和下划线组成,且区分大小写。必须以字母或下划线开头。
为了使名字清晰,可以使用两种命名风格:
symbol_table
SymbolTable
关键字
关键字( keyword )对编译器而言都有特殊的含义,因此标识符不能和关键字一样。
关键字类型
auto break case char const continue default do oouble else enum extern float for goto if int long register short signed sizeof static return struct switch typedef union unsigned void volatile while
这32个关键字之间可以通过相互的组合,来实现相应的功能
其中可以把这32个关键字可以根据作用分成数据类型关键字和流程控制关键字
数据类型关键字
基本数据类型关键字
类型 作用
void 声明函数无返回值或无参数,声明无类型指针,显式丢弃运算结果
char 字符型类型数据,C语言中属于整型数据的一种
int 整型数据,通常为编译器指定的机器字长
float 单精度浮点型数据
double 双精度浮点型数据
类型修饰关键字
类型 作用
short 修饰int,短整型,可以省略被修饰的int
long 修饰int,长整型,可以省略被修饰的int
signed 修饰整型数据,有符号类型,即为正
unsigned 修饰整形数据,无符号类型,即正负都可以
复杂类型关键字
类型 作用
struct 结构体类型数据
union 联合体数据类型
enum 枚举数据类型
typedef 声明类型别名,类似ssize_t
sizeof 得到特定类型或者特定类型变量的大小
存储类别的关键字
类型 作用
auto 指定为自动变量,由编译器自动分配及释放。通常在栈上分配(默认类型)
static 指定为静态变量,分配在静态变量区,修饰函数时,指定函数作用域为文件内部
register 指定为寄存器变量,建议编译器将变量存储到寄存器中使用,也可以修饰函数形参,建议编译器通过寄存器而不是堆栈传递参数
extern 指定对应变量为外部变量,即在另外的目标文件中定义
const 与volatile合称“cv特性”,指定变量不可被当前线程/进程改变(但有可能被系统或其他线程/进程改变
volatile 与const合称“cv特性”,指定变量的值有可能会被系统或其他进程/线程改变,强制编译器每次从内存中取得该变量的值
流程控制关键字
跳转结构关键字
类型 作用
return 用在函数体重,返回特定的数值
continue 结束当前的循环,开始下一次循环
break 结束当前循环
goto 无条件跳转的结构(想去哪去哪)
分支结构
类型 作用
if 条件判断
else 条件判断结构,一般与if连用
switch 选择分支
case 选择分支,一般与switch连用
default 选择分支的默认分支,一般与switch连用
循环结构
类型 作用
while 当型循环,当条件满足的时候执行
for for循环
do—while 直到型循环,直到型循环先执行,再进行判断
C语言程序的布局
C程序可以被看成一连串的记号( token )。记号就是无法分割的字符组。
标识符、关键字、运算符、字符串等都是记号。
记号之间可以有空格,换行等字符。
有记号的概念后,C程序就可以这样书写:
-
语句可以放到多行内。对于很长的语句这样很合适。
-
记号间的空格可以更容易区分记号。比如运算符两边加空格方便阅读。
-
缩进有助于识别程序嵌套。
-
空行可以把程序划分为逻辑单元。