第一章 导言
1.1 入门
学习一门新的程序设计语言的唯一途径就是使用它编写程序。对于所有语言的初学者来说,编写的第一个程序几乎都是相同的,即;
打印出以下内容
hello, world
尽管这个练习很简单,但对于初学语言的人来说,它仍然可能成为一大障碍,因为要实现这个目的,我们首先编写程序文本,然后成功地进行编译,并加载、运行,最后输出到某个地方。掌握了这些操作细节后,其他事情就比较容易了。
#include <stdio.h> //包含标准库的信息
main() //定义名为main的函数,它不接受参数值
{ //main函数的语句都被括在花括号中
printf("hello, world\n"); //main函数调用库函数printf以显示字符序列
} // \n代表换行符
在Linux系统中,输入 gcc hello.c && ./a.out
编译、加载、运行
1.2 变量与算术表达式
来看下一个程序,使用公式 C=(5/9) (F-32) 打印下列华式温度与摄氏温度对照表:
0 -17
20 -6
40 4
60 15
80 26
100 37
120 48
140 60
160 71
180 82
200 93
220 104
240 115
260 126
此程序中仍然只包括一个名叫main的 函数定义。它比前面的hello.c 的程序长一些,但并并不复杂。这个程序中引入了一些新的概念,包括注释、声明、变量、算术表达式、循环以及格式化输出。
#include <stdio.h>
/* 当fahr= 0, 20, ..., 300时,分别打印华氏温度与摄氏温度对照表 */
main()
{
int fahr, celsius;
int lower, upper, step;
lower = 0; //温度表的下限
upper = 300; //温度表的上限
step = 20; //步长
fahr = lower;
while (fahr <= upper)
{
celsius = 5 * (fahr-32) / 9;
printf("%d\t%d\n", fahr, celsius);
fahr = fahr + step;
}
}
/* 与*/之间的字符序列将被编译器忽略。注释可以自由的运用在程序中,使得程序更易于理解。程序中允许出现空格、制表符或换行符之处,都可以使用注释
在C语言中所有变量都必须先声明后使用。声明通常放在函数起始处,在任何可执行语句之前。声明用于说明变量的属性,它由一个类型名和一个变量表组成,例如:
int fahr,celsius;
int lower,upper,step;
其中int 表示后所列变量为整数,与相对应的,float表示所列变量为浮点数。int 与float 类型的取值范围取决于具体的机器。对于int类型通常为16位,其范围在-32768~+32767之间,也有用32位表示的int类型。float通常为32位,其至少有6位有效数字。
C语言还提供了其他一些基本数据类型:
char 字符---一个字节
short 短整型
long 长整型
double 双精度浮点型
这些数据类型对象的大小也取决于具体的机器。还存在这些基本数据类型的数组、结构、联合,指向这些类型的指针以及返回这些类型值的函数。我们将在后续响应的章节中分别介绍。
在上面的温度转换程序中,最开始计算是下列4个赋值语句:
lower = 0;
upper = 300;
step = 20;
fahr = lower;
它们为变量设置初值。各条语句均以分号结束。
温度转换表中的各行计算方式相同,因此可以用循环语句重复输出各行。这是while循环语句的用途:
while (fahr <= upper){
...
}
while 循环语句是这样的:首先测试圆括号中的条件,如果条件为真(fahr <= upper)则执行循环体(括在花括号中的3条语句)
然后再重新测试圆括号中的条件,如果为真,则再次执行循环体,当圆括号中的条件测试结果为假(fahr > upper)时循环结束,并继续执行跟在while循环语句之后的下一条语句。
在该程序中,绝大部分工作都是在循环体中完成的。循环体中的赋值语句
celsius = 5 * (fahr - 32 ) / 9;
用于计算与指定华氏问对相对应的摄氏温度值,并将结果赋值给变量celsius
printf是一个通用输出格式化函数,其中的每个百分号表示其他的参数之一进行替换的位置,并指定打印格式例如%d指定一个整型参数
(\t)一个制表符的空间
格式说明可以省略宽度与精度。例如:%6f表示待打印的浮点数至少有6个字符宽 %.2f指定待打印的浮点数的小数点后有两位小数,但宽度没有限制 %f则仅仅要求按照浮点数打印该数。
%d 按照十进制整型数打印
%6d 按照十进制整数型打印,至少6个字符宽
%f 按照浮点数打印
%6f 按照浮点数打印,至少6个字符宽
%.2f 按照浮点数打印,小数点后有两位小数
%6.2f 按照浮点数打印,至少6个字符宽,小数点后有两位小数
1.3for 语句
对于某个特定任务我们可以采用多种方法来编写程序。下面这段代码也可以实现前面的温度转换程序的功能
#include <stdio.h>
main()
{
int fahr;
for (fahr = 0; fahr <= 300; fahr = fahr + 20)
printf ("%3d %6.1f\n", fahr, (5.0/9.0)*(fahr-32));
}
for 语句是一种循环语句,它是对while 语句的推广。如果将for语句与前面介绍的while语句比较,就会发现for语句的操作更直观一些。
fahr=0 是初始化部分,仅在进入循环前执行一次。
fahr <=300 是控制循环的测试或条件部分。循环控制将对该条件求值,如果结果为真(true)则执行循环体
fahr = fahr + 20 以将循环变量fahr 增加一个步长,并再次对条件求值。如果计算的到的条件值为假循环将终止执行。与while语句一样,for循环语句的循环体可以只有一条语句,也可以是用花括起来的一组语句。
1.4符号常量
#define指令可以把符号名定义为一个特定的字符串
#define 名字 替换文本
在该定义之后,程序中出现的所有在#define中定义的名字,都将用相应的替换文本替换。其中,名字与普通变量名的形式相同:他们都是以字母打头的字母和数字序列;替换文本可以是任何字符序列,而不仅限于数字。
符号常量通常是大写字母,这样可以很容易与用小写字母拼写的变量名相区别。注意#define指令行的末尾没有分号
1.5字符输入/输入
标准库提供的输入/输出模型非常简单。无论文本从何处输入,输出到何处,其输入/输出都是按照字符流的方式处理。文本流是由多行字符构成的字符序列,而每行字符则由0个或多个字符组成,行末是一个换行符。
标准库提供了一次读/写一个字符的函数,其中最简单的是getchar和putchar两个函数。每次调用时,getchar函数从文本流中读入下一个输入字符,并将其作为结果值返回。
1.5.1 文件复制
借助于getchar与putchar函数,可以在不了解其他输入/输出知识的情况下编写出数量惊人的有用的代码。最简单的例子就是把输入一次一个字符的复制到输出,其基本思想如下
#include <stdio.h>
main()
{
int c;
c = getchar();
while (c != EOF) {
putchar(c);
c = getchar();
}
}
其中,关系运算符 !=表示 “不等于”
字符在键盘、屏幕或其他的任何地方无论以什么形式表现,它在机器内部都是以位模式存储的。char类型专门用于存储这种字符型数据,当然任何整型(int)也可以用于存储字符型数据。因为某些潜在的重要原因,我们在此使用int类型
1.5.2 字符计数
下列程序对于字符进行计数,它与上面的复制程序类似。
#include <stdio.h>
main ()
{
long nc;
nc = 0;
while (getchar() != EOF)
++nc;
printf("%ld\n", nc);
}
其中,语句
++nc;
引入了一个新的运算符++,其功能是执行加1操作。可以用语句nc=nc+1代替它,但语句++nc更精炼一些,且通常效率更高。
与该运算符响应的是自减运算符--。++与--这两个运算符既可以作为前缀运算符也可以作为后缀运算符。
1.5.3 行计数
统计行计数等价于统计换行符的个数。
#include <stdio.h>
main()
{
int c, nl;
nl = 0;
while ((c = getchar() ) ! = EOF)
if(c == '\n' )
++nl;
printf("%d\n", nl);
}
在该程序中,while循环语句的循环体是一个if语句, 他控制自增语句++nl。if语句先测试圆括号中的条件,如果该条件为真,则执行其后的语句。这里再次用缩进方式表明语句之间的控制关系。
双等于号==是c语言中表示“等于”关系的运算符(类似于pascal中的单等于号=及fortran中的.EQ)由于C语言将单等于号作为复制运算符,因此使用双等于号==表示相等的逻辑关系,以示区分。
1.5.4 单词计数
用于统计行数、单词数与字符数。这里对单词的定义比较宽松,它是任何其他不包含空格、制表符或换行符的字符序列。
#include <stdio.h>
#define IN 1
#define OUT 0
main()
{
int c, nl, nw, nc, state;
state = OUT;
nl = nw = nc = 0;
while ((c = getchar()) !=EOF){
++nc;
if (c == '\n')
++nl;
if (c == '' || c =='\n' || c =='\t')
state = OUT;
else if (state == OUT) {
state = IN;
++nw;
}
}
printf("%d %d %d\n", nl, nw, nc);
}
程序执行时,每当遇到单词的第一个字符,它就作为一个新单词加以统计。state变量记录程序当前是否正位于一个单词之中,它的初值是“不在单词中”,即初值被赋为OUT。
1.6 数组
编写一个程序,以统计各个数字、空白符以及所有其他字符出现的次数。这个程序的实用意义并不大,但我们可以通过该程序讨论C语言多方面的问题。
#include <stdio.h>
main ()
{
int c, i, nwhite, nother;
int ndigit[10];
nwhite = nother =0;
for (i = 0; i < 10; ++i)
ndigit[i] = 0;
while((c = getchar()) != EOF)
if(c >= '0' && c <='9')
++ndigit[c-'0'];
else if (c == '' || c == '\n' || c == '\t')
++nwhite;
else
++nother;
printf("digits =")
for (i = 0; i < 10; ++i)
printf("%d", ndigit[i]);
printf(", white space = %d, other = %d\n",
nwhite, nother);
}
1.7 函数
到目前为止,我们所使用的函数(如printf、getchar和putchar)都是函数库中提供的函数,现在让我们
编写函数。现在编写一个求幂的函数power(m,n)来说明函数定义的方法。用于计算整数m的n次幂,其中n是正整数。对函数调用
power(2,5)来说,结果为32。
#include <stdio.h>
int power (int m, int n);
main()
{
int i;
for (i = 0; i < 10; ++i)
printf("%d %d %d\n", i, power(2,i), power(-3,i));
return 0;
}
int power(int base, int n)
{
int i, p;
p = 1;
for (i = 1; i <= n; ++i)
p = p * base;
return p;
}
函数定义可以以任意次序出现在一个源文件或多个源文件中,但同一函数不能分割存放在多个文件中。每次调用时,main函数返回一个格式化的整数并打印。在表达式中,power(2,i)同2和i一样都是整数。
1.8 参数——传值调用
传值调用的利大于弊。在被调用函数中,参数可以看作时便于初始化的局部变量,因此额外使用的变量更少,这样程序可以更紧凑简洁。
1.9 字符数组
用来存放字符的数组称为字符数组,例如:
- char a[10]; //一维字符数组
- char b[5][10]; //二维字符数组
- char c[20]={'c', ' ', 'p', 'r', 'o', 'g', 'r', 'a','m'}; // 给部分数组元素赋值
- char d[]={'c', ' ', 'p', 'r', 'o', 'g', 'r', 'a', 'm' }; //对全体元素赋值时可以省去长度
字符数组实际上是一系列字符的集合,也就是字符串(String)。在C语言中,没有专门的字符串变量,没有string类型,通常就用一个字符数组来存放一个字符串。
1.10 外部变量与作用域
- 局部变量(内部变量):在定义它的函数内有效,但是函数返回后失效;
- 全局变量(外部变量):在所有源文件内均有效。在同源文件的函数中使用使用全局变量,需要将全局变量提前声明;同时在不包含全局变量定义的不同源文件需要用extern关键字再次声明
如果在该函数内定义了一个与之前定义的全局变量同名的变量,那么该同名局部变量就会在函数内部屏蔽全局变量的影响。即当局部变量与全局变量同名时,在局部变量的作用范围之内,全局变量不起作用。局部变量优先原则;
全局变量是有默认值的(引用类型的变量默认值都是null,基本类型的变量默认值则不一样,int型变量默认值是0),但是局部变量没有默认值;
全局变量可以在函数外定义并初始化,但不能进行赋值操作。