跳转至

第二章 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 )把由编译器产生的目标代码和任何其他附加代码整合在一起,产生完全可执行的程序。

这个过程可以一步完成,即:

cc pun.c

在编译和链接好程序后,编译器 cc 会把可执行程序放到默认名为 a.out 的文件中。编译器 cc 有许多选项,其中 -o 允许给可执行程序选择一个名字:

cc -o pun pun.c

如果使用 gcc 进行编译,那么建议在编译时采用 -Wall 选项:

gcc -Wall -o pun pun.c

也可以手动分布完成:

cc -o main.i -E main.c # 预编译
cc -o main.o -c main.i # 编译
cc -o main main.o      # 链接

简单程序的一般形式

形式如:

指令

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 )对编译器而言都有特殊的含义,因此标识符不能和关键字一样。

所有的关键字见书本p19

C语言程序的布局

C程序可以被看成一连串的记号( token )。记号就是无法分割的字符组。

标识符、关键字、运算符、字符串等都是记号。

记号之间可以有空格,换行等字符。

有记号的概念后,C程序就可以这样书写:

  • 语句可以放到多行内。对于很长的语句这样很合适。

  • 记号间的空格可以更容易区分记号。比如运算符两边加空格方便阅读。

  • 缩进有助于识别程序嵌套。

  • 空行可以把程序划分为逻辑单元。