英文原版:P9
这一章介绍了编写最简单程序所需的基本概念,包括预处理指令preprocessing directives
、函数functions
、语句statements
等。后续章节会深入讲解这些主题的。
2.1节展示了一个小的C程序,描述了如何编译和链接该程序。
2.2节讨论了如何理解该程序。
2.3节展示了如何增加注释。
2.4节介绍了变量,变量用于保存数据:在程序执行过程中,变量保存的数据可能会改变。
2.5节展示了如何使用scanf
函数读取数据到变量.
2.6节介绍了常数,常数用于保存数据:常数保存的数据在程序执行期间永不会改变。
2.7节解释了C语言命名(标识符identifiers
)的规则。
2.8节给出了一个程序布局的相关规则。
2.1 写个简单的程序
跟使用其他语言编写的程序相比,C程序几乎不需要模板:一个完整的程序可以简短到只有几行代码。
程序:printing a pun
源代码pun.c
#include <stdio.h>
int main(void)
{
printf("To C, or not to C: that is the questions\n");
return 0;
}
简单的观察:
- 预处理指令
是必需的,因为要包含有关C标准I/O库的相关信息。#include <stdio.h>
- 函数
在int main(void) { printf("To C, or not to C: that is the questions\n"); }
main
函数里的唯一代码行是一个显示期望信息的指令;
printf
是一个来自标准I/O库的函数,该函数可产生优雅的格式化输出;
\n
告诉printf
函数在打印这条消息后前进到下一行; - 返回语句
表明当该程序终止时,该程序返回一个0值给操作系统。return 0;
编译和链接
虽然这个程序很简单,但是运行pun.c
要比你期望的要复杂。
首先,需要创建名字是pun.c
的文件,该文件包含了程序。文件名字是啥不重要,后缀.c
是对于编译器来说是必需的。
接着,需要把程序转换成一种可在机器上执行的格式。对于C程序来说,通常有3步:
- 预处理
程序先被交给预处理器preprocessor,预处理器处理以#
开头的指令;预处理器有点像编辑器,可向程序中添加信息,进行修改; - 编译
把修改后的程序交给编译器:编辑器将修改后的程序转换成机器指令(目标码);即使是这样的程序也还不能运行; - 链接
链接器将由编译器输出的目标码和任何需要的额外代码整合后产生完全的可执行程序;这里的额外代码包括库函数(比如printf
)等;
幸运的是,这个过程通常都是自动的,并不需要一个接一个执行。实际上,预处理器是整合在编译器里的,用户几乎感觉不到。
不用编译器和操作系统中用于编译和链接的指令是不一样的。在UNIX系统上,C编译器通常是cc
。
编译和链接pun.c
:
% cc pun.c
其中,%是UNIX系统的提示符;使用cc编译器时链接过程是自动的,不要额外的链接指令;
编译和链接该程序后,cc会默认保存对应的可执行程序为a.out
。
cc有许多可选项:-o
支持选择包含可执行程序的文件名。比如如果想把pun.c
的可执行版本命名为pun
,则执行如下命令:
cc -o pun pun.c
如果使用gcc编译器,则执行如下指令:
gcc -o pun pun.c
2.2 一个简单程序的一般形式
让我们再细看下pun.c
,看能总结点什么不。简单的C程序具有如下形式:
directives
int main(void)
{
statements
}
注意:
C语言严重依赖缩写和特殊符号,这是C语言很简洁的原因之一。
大括号对{}
表明main
函数的开始位置和结束位置。
C语言里使用{
和}
的方式就跟其他语言里使用begin
和end
一样。
即使是最简单的C程序也依赖3个关键的特征:
- 指令
可编辑指令,用于在编译程序前修改程序; - 函数
有名字的可执行代码块,比如main
函数; - 语句
当程序运行时要执行的命令;
指令directives
- 指令通常都是以
#
开头,使其跟C程序里的其他项区分开; - 指令默认只有一行;
- 指令的结尾没有分号
;
或者其他特殊标记;
在C程序编译前,C程序要先被预处理器编辑。
被预处理器处理的命令称为指令。
第14和15章会详细讨论指令,现在只关心#included
指令。
程序pun.c
的开头:
#include <stdio.h>
这个指令描述的是编译程序pun.c
之前,<stdio.h>
里的信息将被包含进程序pun.c
。
<stdio.h>
包含有关C语言标准I/O库的信息。
C语言有许多类似<stdio.h>
的头文件:每个头文件都包含标准库某部分的信息。
为什么要在程序pun.c
中包含<stdio.h>
?
因为C语言跟其他语言不一样,没有内置读写命令,由标准库里的函数来提供输入和输出功能。
函数
函数是构建程序的基本块。实际上,一个C程序就是多个函数的集合。
函数分为两类:
- 程序员自己编写的函数;
- 作为C语言实现的一部分来提供的函数,即库函数;
在C语言里,函数很简单,就是一组有名字的语句而已。
有的函数计算一个值,有的不计算值。
计算值的函数使用return
语句来说明其返回什么值。比如,给其参数加1的函数有如下返回值语句:
return x+1;
计算参数的平方差的函数有如些返回值语句:
return y * y - z*z;
虽然一个C程序可由许多函数组成,但main
函数是必须有的。
main
函数是特殊的,是程序执行时被自动调用的。在第9章里,我们将学习如何编写其他函数,在此之前,main
函数是我们程序里唯一的函数。
如果main
是一个函数,则它有返回值吗?
答:main
函数有返回值:当程序终止时,将某个状态码返回给操作系统。
源代码pun.c
#include <stdio.h>
int main(void)
{
printf("To C, or not to C: that is the questions\n");
return 0;
}
几个观察:
-
int
在main之前,表明main
函数返回一个整数值。 - 在括号里的
void
表明main
函数没有参数。 - 返回值语句
有两个效果:return 0;
表明main
函数的返回值是0,表明程序正常终止;
导致main
函数结束执行; - 如果在
main
函数里没有返回值语句,则程序依旧会终止,但许多编译器将产生一个警告消息。
语句
- 一条语句就是程序运行时执行的一条命令;
- 每条语句都以分号
;
结束,例外是复合语句;
程序pun.c
里的语句只有两类:
- 返回值语句;
- 函数调用语句;
打印字符串
printf
函数是一个功能强大的函数。
使用printf
来展示用双引号包裹的字符串常量。
当printf
函数展示一个字符串常量时,该函数不展示双引号。
当printf
完成打印时,printf
不会自动前进到下一输出行。
为了让printf
前进到下一个输出行,必须在待打印的字符串结尾包含\n
(换行符)。
写一个换行符\n
将会终止当前行的输出,接下来的输出将进入下一行。
换行符\n
在字符串常量中可能会出现多次。
printf("Brevity is the soul of wit.\n Shakespeare\n")
2.3 注释
程序pun.c
仍然缺少重要的部分:文档。
每个程序应该包含一些身份信息:程序名,编写日期,作者,该程序的目的等。
在C程序中,这类信息被放在注释里。
符号/*
表示注释的开始,符号*/
表示注释的结束,参看:
/* This is a comment */
注释可出现在程序中的任何位置,要么单独成一行,要么跟其他程序文本在同一行上。
/*
* Name: pun.c
* Purpose: Prints a bad pun.
* Author: K. N. King
*/
// Name: pun.c
// Purpose: Prints a bad pun.
// Author: K. N. King
#include <stdio.h>
int main(void)
{
printf("To C, or not to C: that is the questions\n");
return 0;
}
注释有单行注释,也有多行注释。
2.4 变量和赋值
源文件dweight.c
/* Computes the dimensional weight of a 12"x 10"x 8" box */
#include <stdio.h>
int main(void)
{
int height, length, width, volume, weight;
height = 8;
length = 12;
width = 10;
volume = height * length * width;
weight = (volume + 165) / 166;
printf("Dimensions: %dx%dx%d\n", length, width, height);
printf("Volume (cubic inches): %d\n", volume);
printf("Dimensional weight (pounds): %d\n", weight);
return 0;
}
几乎没有程序像2.1节里的示例程序那么简单。
大部分程序在产生输出前需要执行一系列的计算,因此大部分程序在程序执行过程中需要一种方式来临时存储数据。
在C语言中,变量就是这些临时数据的存储位置。
类型
每个变量必须有个类型,描述了该变量持有的数据的类型。
C语言有许多类型。当前仅使用两种类型:int
和float
。
由于类型会影响变量是如何存储,可对该变量执行哪些操作,所以为变量选择合适的类型非常关键。
一个数值变量的类型可以确定两件事:
- 该变量可以存储的最小数和最大数;
- 小数点后是否允许跟数字;
声明
变量在使用前必须要声明,即描述变量的类型和名字。
赋值
打印变量的值
- %d
- %f
- %.2f
初始化
2.5 读取输入
源文件dweight2.c
/* Computes the dimensional weight of a
box from input provided by the user */
#include <stdio.h>
int main(void)
{
int height, length, width, volume, weight;
printf("Enter height of box: ");
scanf("%d", &height);
printf("Enter length of box: ");
scanf("%d", &length);
printf("Enter width of box: ");
scanf("%d", &width);
volume = height * length * width;
weight = (volume + 165) / 166;
printf("Volume (cubic inches): %d\n", volume);
printf("Dimensional weight (pounds): %d\n", weight);
return 0;
}
2.6 给常量定义名字
使用宏定义macro define来定义常量,比如
#define INCHES_PER_POUND 166
C程序员都遵守的约定:在宏名字里只使用大写字母。
源文件celsius.c
#include <stdio.h>
#define FREEZING_PT 32.0f
#define SCALE_FACTOR (5.0f/9.0f)
int main(void)
{
float fahrenheit, celsius;
printf("Enter Fahrenheit temperature: ");
scanf("%f", &fahrenheit);
celsius = (fahrenheit - FREEZING_PT) * SCALE_FACTOR;
printf("Celsius equivalent: %.1f\n", celsius);
return 0;
}
思考:
- 是否可将
SCALE_FACTOR
定义为(5/9)
?为什么?
2.7 标识符
在C语言中,
- 一个标识符可以包含字符、数字、下划线等,但必须以字母或者下划线开头;
- 标识符区分大小写;
- 关键字不能作为标识符;
C程序员都遵守的约定:除了宏以外,仅使用小写字母作为标识符,比如
symbol_table current_page name_and_address;
symbolTable currentPage nameAndAddress
2.8 C程序的布局
什么是tokens?
groups of characters that can not be split up without changing their meaning.
tokens就是一组不改变其含义就不能将其分隔的字符。
将一个C程序看做是一系列的tokens
:
- 标识符和关键字;
- 运算符,比如
+
、-
等 - 标点符号,比如
,
和;
等 - 字符串常量;
示例1 tokens识别
printf("Height: %d\n", height);
该语句共有七个tokens,分别为
- 标识符
printf
、height
,共两个; - 字符串常量
“Height: %d\n”
,共一个; - 标点符号
(
、,
、)
、;
,共4个;
规则
C语言允许在两个tokens之间插入任何数量的空白,比如空格、tabs、新行字符等。
这条规则对程序的布局有重要的影响:
- 一条语句可分成任意数量的行;
比如,下面的这条语句太长了以致于放在一行内很难掌握:printf("Dimensional weight (pounds): %d\n", (volume + INCHES_PER_POUND- 1) / INCHES_PER_POUND);
- 在tokens之间的空格使得眼睛更容易区分tokens;
建议在每个运算符的前后都添加一个空格,在每个逗号后面添加一个空格等,比如volume = height * length * width;
- 缩进使得嵌套更容易被发现,比如应该将声明和语句缩进使得“它们是嵌套在main函数内部的”这一事实很清楚;
- 空白行将一个程序分成多个逻辑单元;
注:
不可能在一个token内添加空格,除非修改程序的含义或者出现报错。
虽然修改了字符串的含义,但是在字符串内部放入一个空格是允许的。
在字符串内部放入一个换行符是非法的,比如
printf("To C, or not to C:
that is the questions\n");
将一个字符串从一行继续到另一行需要特殊的处理。
网友评论