- 1.1 软件,程序与计算机语言
软件是为完成某些特定功能而编制的一个到多个程序文件的集合。
程序是计算机指令的序列,编制程序的工作就是为计算机编制指令序列。
人和计算机交流也要用人和计算机都容易接受和理解的语言,这就是计算机语言。
- 1.2 程序语言的发展
1.2.1 机器语言
计算机的机器语言都是由0和1组成,计算机的所有数据都是由0和1组成。
1.2.2 汇编语言
汇编语言是用符号来表示这些固定的二进制指令的语言。
1.2.3 高级语言
高级语言总是尽量接近"高级动物”的自然语言和思维方式。
- 1.3 软件开发的基本方法与步骤
1.分析问题,建立数据模型。
2.确定数据结构和算法。
3.编织程序。
4.调试程序。
1.3.1 算法
通常计算机算法分为两大类:数值运算算法和非数值运算算法。
数值运算是对问题求数值解,非数值运算包括非常广泛的领域。数值运算有确定的数学模型,一般都有比较成熟的算法。非数值运算的种类繁多,要求不一。
- 算法的基本特征(有穷性,确定性,有效性,有零个或多个输入,有一个或多个输出。
- 算法的表示—流程图(对给定算法的一种图形解法,流程图又称为框图,它用规定的一系列图形,流程线及文字说明来表示算法中的基本操作和控制流程,其优点是形象直观,简单易懂,便于修改和交流。)
流程图包括起止框,输入/输出框,处理框,判断框,注释框,流程框,连接点。
1.3.2 编码实现
创建一个C语言程序的步骤:
1.编写源代码
2.将源代码编译成目标代码。
3.链接目标代码成为可执行程序
- 源代码
将源程序的代码指令存储,计算机采用两种不同格式存储文件,分别是文本格式和二进制格式。
文本文件包括ASCII码字符集的符号。ASCII字符集包括了字母表的大小写字母。还包括了从0-9的数和一些标点符号。二进制文件是由二进制数组成。
当编写程序的时候,我们将源代码存储到文本文件中,程序员通常会将多个源代码的文本文件创建一个程序。通过很短时间的编译和链接过程,文本文件中的源代码被转化成二进制指令存储到二进制文件中。
- 编译源代码
计算机不能执行源代码,我们要编写编译器或翻译器,这样,就可以将程序从高级语言程序转化成二进制代码,也就是机器语言。
- 链接
当链接起将程序链接成可执行的形式时,它用多个库来链接目标程序。库是一些预先编译好的函数的集合。这些函数可能完成一项或多项任务。
1.3.3 调试程序
程序中最易出现的几种不同类型错误是
- 语法错误
- 逻辑错误
- 开发错误
- 运行时错误
2.1编程语言概述
学习一门新语言的惟一途径就是使用它来编程。对于初学者来说, 编写的第一个程序几乎都是相同的,即在屏幕上显示“Hello, world”,专业说法为打印“Hello, world”。
【例1-1】打印 Hello, world。
- /* 第一个C语言程序
- 打印 Hello, world */
- #include<stdio.h> //包含头文件stdio.h
- int main(){ // 主函数
- printf("Hello, word "); // 打印字符串
- return 0; // 返回0,表示程序正确运行
- }
尽管这个程序很简单,但对初学者来说,它仍然可能成为一大障碍,因为要实现这个目的,首先要编写代码,然后编译、链接并运行,最后看到输出结果。掌握了这些操作细节以后,其它事情就比较容易了。
关于编译和链接将在下一节说明,这里先对程序进行解释:
- 第1行包含标准库文件,include称为文件包含命令,扩展名为.h的文件称为头文件。
- 第2行定义名为main的函数,它不接受参数值;main函数的语句都被括在花括号中;int为main函数返回值类型。
- 第3行打印“Hello, world”,main函数调用库函数printf以显示字符序列。
- 第4行表示main函数的返回值为0,return让函数返回一个值。
- 第5行结束main函数,花括弧必须成对出现。
位于“/* */”中和“//”后面的内容为注释,用来对程序进行说明;注释在编译时会被自动忽略。
一个C语言程序,无论其大小,都是由函数和变量组成的。
函数具有某些特定功能,能执行特定操作;函数中包含一些语句,以说明操作的过程。变量则用于存储计算过程中使用的值。
在本例中,函数的名字为main。通常情况下,函数的命名没有限制,但main是一 个特殊的函数名,每个程序都从main函数的起点开始执行,这意味着每个程序都必须在某 个位置包含一个main函数。
main函数通常会调用其它函数来帮助完成某些工作,被调用的函数可以是我们自己编写的,也可以来自于函数库。上述程序段中的第一行语句#include <stdio.h>用于告诉编译器在本程序中包含标准输入/输出库。许多C语言源程序的开始处都包含这一行语句。我们将在后续章节对标准库进行详细介绍。
函数之间进行数据交换的一种方法是调用函数向被调用函数提供一个值(称为参数)列表。函数名后面的一对圆括号将参数列表括起来。在本例中,main函数不需要任何参数,因此用空参数表( )表示。
函数中的语句用一对花括号{}括起来。本例中的main函数包含下面两条语句:
- printf("Hello, word ");
- return 0;
调用函数时,只需要使用函数名加上用圆括号括起来的参数表即可。上面这条语句将"hello, world
"作为参数调用printf函数。printf是一个用于打印输出的库函数,在此处, 它打印双引号中间的字符串。
用双引号括起来的字符序列称为字符串或字符串常量,如"hello, world
"就是一个字符串。目前我们仅使用字符串作为printf及其它函数的参数。
在C语言中,字符序列
表示换行符,在打印中遇到它时,输出打印将换行,从下一行的左端行首开始。如果去掉字符串中的
(这是个值得一做的练习),即使输出打印完成后也不会换行。在printf函数的参数中,只能用
表示换行符。如果用程序的换行代替,例如:
printf("Hello, word
");
C编译器将会产生一条错误信息。
printf函数永远不会自动换行,这样我们可以多次调用该函数以分阶段得到一个长的输出行。上面给出的第一个程序也可以改写成下列形式:
-
#include<stdio.h> int main(){ printf("Hello, "); printf("word"); printf(" "); return 0; }
这段程序与前面的程序的输出相同。
2.2 基本数据类型
所谓数据类型是按被定义变量的性质,表示形式,占据存储空间的多少,构造特点来划分的。在C语言中,数据类型可分为:基本数据类型,构造数据类型,指针类型,空类型四大类。
数据类型说明:
数据类型 | 说明 |
---|---|
基本数据类型 | 基本数据类型最主要的特点是,其值不可以再分解为其它类型。也就是说,基本数据类型是自我说明的。 |
构造数据类型 | 构造数据类型是根据已定义的一个或多个数据类型用构造的方法来定义的。也就是说,一个构造类型的值可以分解成若干个“成员”或“元素”。每个“成员”都是一个基本数据类型或又是一个构造类型。在C语言中,构造类型有以下几种:数组类型、结构体类型、共用体(联合)类型。 |
指针类型 | 指针是一种特殊的,同时又是具有重要作用的数据类型。其值用来表示某个变量在内存储器中的地址。虽然指针变量的取值类似于整型量,但这是两个类型完全不同的量,因此不能混为一谈。 |
空类型 | 在调用函数值时,通常应向调用者返回一个函数值。这个返回的函数值是具有一定的数据类型的,应在函数定义及函数说明中给以说明,例如在例题中给出的max函数定义中,函数头为:
int max(int a,int b);
其中“int ”类型说明符即表示该函数的返回值为整型量。又如在例题中,使用了库函数sin,由于系统规定其函数返回值为双精度浮点型,因此在赋值语句s=sin (x);中,s也必须是双精度浮点型,以便与sin函数的返回值一致。所以在说明部分,把s说明为双精度浮点型。但是,也有一类函数,调用后并不需要向调用者返回函数值,这种函数可以定义为“空类型”。其类型说明符为void。在后面函数中还要详细介绍。 |
2.3 数据类型转换
自动转换
自动转换发生在不同数据类型的量混合运算时,由编译系统自动完成。自动转换遵循以下规则:
- 若参与运算量的类型不同,则先转换成同一类型,然后进行运算。
- 转换按数据长度增加的方向进行,以保证精度不降低。如int型和long型运算时,先把int量转成long型后再进行运算。
- 所有的浮点运算都是以双精度进行的,即使仅含float单精度量运算的表达式,也要先转换成double型,再作运算。
- char型和short型参与运算时,必须先转换成int型。
- 在赋值运算中,赋值号两边量的数据类型不同时,赋值号右边量的类型将转换为左边量的类型。如果右边量的数据类型长度比左边长时,将丢失一部分数据,这样会降低精度,丢失的部分按四舍五入向前舍入。
下图表示了类型自动转换的规则。
【例2-1】自动数据类型转换
-
#include<stdio.h> int main(){ float PI=3.14159; int s,r=5; s=r*r*PI; printf("s=%d ",s); return 0; }
本例程序中,PI为实型;s,r为整型。在执行s=r*r*PI语句时,r和PI都转换成double型计算,结果也为double型。但由于s为整型,故赋值结果仍为整型,舍去了小数部分。
强制类型转换
强制类型转换是通过类型转换运算来实现的。其一般形式为:
(类型说明符) (表达式)
其功能是把表达式的运算结果强制转换成类型说明符所表示的类型。
例如:
- (float) a; /* 把a转换为实型 */
- (int)(x+y); /* 把x+y的结果转换为整型 */
在使用强制转换时应注意以下问题:
- 类型说明符和表达式都必须加括号(单个变量可以不加括号),如把(int)(x+y)写成(int)x+y则成了把x转换成int型之后再与y相加了。
- 无论是强制转换或是自动转换,都只是为了本次运算的需要而对变量的数据长度进行的临时性转换,而不改变数据说明时对该变量定义的类型。
【例2-2】强制数据类型转换
-
#include<stdio.h> int main(void){ float f=5.75; printf("(int)f=%d,f=%f ",(int)f,f); return 0; }
本例表明,f虽强制转为int型,但只在运算中起作用,是临时的,而f本身的类型并不改变。因此,(int)f的值为 5(删去了小数)而f的值仍为5.75。
2.4 运算符的分类
运算符不仅具有不同的优先级,还有不同的结合性。在表达式中,各运算量参与运算的先后顺序不仅要遵守运算符优先级别的规定,还要受运算符结合性的制约,以便确定是自左向右进行运算还是自右向左进行运算。
C语言的运算符可分为以下几类:
运算符 | 说明 |
---|---|
算术运算符 | 用于各类数值运算。包括加(+)、减(-)、乘(*)、除(/)、求余(或称模运算,%)、自增(++)、自减(--)共七种。 |
关系运算符 | 用于比较运算。包括大于(>)、小于(<)、等于(==)、 大于等于(>=)、小于等于(<=)和不等于(!=)六种。 |
逻辑运算符 | 用于逻辑运算。包括与(&&)、或(||)、非(!)三种。 |
位操作运算符 | 参与运算的量,按二进制位进行运算。包括位与(&)、位或(|)、位非(~)、位异或(^)、左移(<<)、右移(>>)六种。 |
赋值运算符 | 用于赋值运算,分为简单赋值(=)、复合算术赋值(+=, -=, *=, /=, %=)和复合位运算赋值(&=, |=, ^=, >>=, <<=)三类共十一种。 |
条件运算符 | 这是一个三目运算符,用于条件求值(?:)。 |
逗号运算符 | 用于把若干表达式组合成一个表达式(,)。 |
指针运算符 | 用于取内容(*)和取地址(&)二种运算。 |
求字节数运算符 | 用于计算数据类型所占的字节数(sizeof)。 |
特殊运算符 | 有括号(),下标[],成员(->,.)等几种。 |
表达式是由常量、变量、函数和运算符组合起来的式子。一个表达式有一个值及其类型, 它们等于计算表达式所得结果的值和类型。表达式求值按运算符的优先级和结合性规定的顺序进行。单个的常量、变量、函数可以看作是表达式的特例。
3.1 顺序结构的设计实例
例【3-1】输入三角形的三边长,求三角形面积。
已知三角形的三边长a、b、c,则该三角形的面积公式为:
area=( s(s-a)(s-b)(s-c) )1/2
其中s = (a+b+c)/2。
源程序如下:
-
#include <stdio.h> #include <math.h> int main(void){ float a,b,c,s,area; scanf("%f,%f,%f",&a,&b,&c); s=1.0/2*(a+b+c); area=sqrt(s*(s-a)*(s-b)*(s-c)); printf("a=%7.2f,b=%7.2f,c=%7.2f,s=%7.2f ",a,b,c,s); printf("area=%7.2f ",area); return 0; }
【例4.15】求ax2+bx+c=0方程的根,a、b、c由键盘输入,设b2-4ac>0。
源程序如下:
-
#include <stdio.h> #include <math.h> int main(void){ float a,b,c,disc,x1,x2,p,q; scanf("a=%f,b=%f,c=%f",&a,&b,&c); disc=b*b-4*a*c; p=-b/(2*a); q=sqrt(disc)/(2*a); x1=p+q;x2=p-q; printf(" x1=%5.2f x2=%5.2f ",x1,x2); return 0; }
3.2 if语句的使用
用if语句可以构成分支结构。它根据给定的条件进行判断,以决定执行某个分支程序段。C语言的if语句有三种基本形式。
语句的三种形式
1) 第一种形式为基本形式:if
if(表达式) 语句
其语义是:如果表达式的值为真,则执行其后的语句,否则不执行该语句。
【例3-3】
-
#include <stdio.h> int main(void){ int a,b,max; printf(" input two numbers: "); scanf("%d%d",&a,&b); max=a; if (max<b) max=b; printf("max=%d",max); return 0; }
本例程序中,输入两个数a、b。把a先赋予变量max,再用if语句判别max和b的大小,如max小于b,则把b赋予max。因此max中总是大数,最后输出max的值。
2) 第二种形式为: if-else
if(表达式)
语句1;
else
语句2;
其语义是:如果表达式的值为真,则执行语句1,否则执行语句2 。
【例3-4】
-
#include <stdio.h> int main(void){ int a, b; printf("input two numbers: "); scanf("%d%d",&a,&b); if(a>b) printf("max=%d ",a); else printf("max=%d ",b); return 0; }
输入两个整数,输出其中的大数。改用if-else语句判别a,b的大小,若a大,则输出a,否则输出b。
3) 第三种形式为if-else-if形式
前二种形式的if语句一般都用于两个分支的情况。当有多个分支选择时,可采用if-else-if语句,其一般形式为:
if(表达式1)
语句1;
else if(表达式2)
语句2;
else if(表达式3)
语句3;
…
else if(表达式m)
语句m;
else
语句n;
其语义是:依次判断表达式的值,当出现某个值为真时,则执行其对应的语句。然后跳到整个if语句之外继续执行程序。 如果所有的表达式均为假,则执行语句n。然后继续执行后续程序。 if-else-if语句的执行过程如下图所示。
【例3-5】
-
#include <stdio.h> int main(void){ char c; printf("input a character: "); c=getchar(); if(c<32) printf("This is a control character "); else if(c>='0'&&c<='9') printf("This is a digit "); else if(c>='A'&&c<='Z') printf("This is a capital letter "); else if(c>='a'&&c<='z') printf("This is a small letter "); else printf("This is an other character "); return 0; }
本例要求判别键盘输入字符的类别。可以根据输入字符的ASCII码来判别类型。由ASCII码表可知ASCII值小于32的为控制字符。在“0”和“9”之间的为数字,在“A”和“Z”之间为大写字母, 在“a”和“z”之间为小写字母,其余则为其它字符。这是一个多分支选择的问题,用if-else-if语句编程,判断输入字符ASCII码所在的范围,分别给出不同的输出。例如输入为“g”,输出显示它为小写字符。
if语句的嵌套
当if语句中的执行语句又是if语句时,则构成了if 语句嵌套的情形。其一般形式可表示如下:
if(表达式)
if语句;
或者为:
if(表达式)
if语句;
else
if语句;
在嵌套内的if语句可能又是if-else型的,这将会出现多个if和多个else重叠的情况,这时要特别注意if和else的配对问题。例如:
if(表达式1)
if(表达式2)
语句1;
else
语句2;
其中的else究竟是与哪一个if配对呢?应该理解为:
if(表达式1)
if(表达式2)
语句1;
else
语句2;
还是应理解为:
if(表达式1)
if(表达式2)
语句1;
else
语句2;
为了避免这种二义性,C语言规定,else 总是与它前面最近的if配对,因此对上述例子应按前一种情况理解。
【例3-6】
-
#include <stdio.h> int main(void){ int a,b; printf("please input A,B: "); scanf("%d%d",&a,&b); if(a!=b) if(a>b) printf("A>B "); else printf("A<B "); else printf("A=B "); return 0; }
比较两个数的大小关系。本例中用了if语句的嵌套结构。采用嵌套结构实质上是为了进行多分支选择,实际上有三种选择即A>B、A<B或A=B。这种问题用if-else-if语句也可以完成。而且程序更加清晰。因此,在一般情况下较少使用if语句的嵌套结构。以使程序更便于阅读理解。
3.3 switch语句的使用
C语言还提供了另一种用于多分支选择的switch语句, 其一般形式为:
switch(表达式){
case 常量表达式1: 语句1;
case 常量表达式2: 语句2;
…
case 常量表达式n: 语句n;
default: 语句n+1;
}
其语义是:计算表达式的值。 并逐个与其后的常量表达式值相比较,当表达式的值与某个常量表达式的值相等时, 即执行其后的语句,然后不再进行判断,继续执行后面所有case后的语句。如表达式的值与所有case后的常量表达式均不相同时,则执行default后的语句。
【例3-7】
-
#include <stdio.h> int main(void){ int a; printf("input integer number: "); scanf("%d",&a); switch (a){ case 1:printf("Monday "); case 2:printf("Tuesday "); case 3:printf("Wednesday "); case 4:printf("Thursday "); case 5:printf("Friday "); case 6:printf("Saturday "); case 7:printf("Sunday "); default:printf("error "); } return 0; }
本程序是要求输入一个数字,输出一个英文单词。但是当输入3之后,却执行了case3以及以后的所有语句,输出了Wednesday 及以后的所有单词。这当然是不希望的。为什么会出现这种情况呢?这恰恰反应了switch语句的一个特点。在switch语句中,“case 常量表达式”只相当于一个语句标号, 表达式的值和某标号相等则转向该标号执行,但不能在执行完该标号的语句后自动跳出整个switch 语句,所以出现了继续执行所有后面case语句的情况。 这是与前面介绍的if语句完全不同的,应特别注意。
为了避免上述情况,C语言还提供了一种break语句,专用于跳出switch语句,break 语句只有关键字break,没有参数。修改例题的程序,在每一case语句之后增加break 语句, 使每一次执行之后均可跳出switch语句,从而避免输出不应有的结果。
【例3-8】
-
#include <stdio.h> int main(void){ int a; printf("input integer number: "); scanf("%d",&a); switch (a){ case 1:printf("Monday "); break; case 2:printf("Tuesday "); break; case 3:printf("Wednesday "); break; case 4:printf("Thursday "); break; case 5:printf("Friday "); break; case 6:printf("Saturday "); break; case 7:printf("Sunday "); break; default:printf("error "); } return 0; }
在使用switch语句时还应注意以下几点:
- 在case后的各常量表达式的值不能相同,否则会出现错误。
- 在case后,允许有多个语句,可以不用{}括起来。
- 各case和default子句的先后顺序可以变动,而不会影响程序执行结果。
- default子句可以省略不用。
3.4 while语句的用法
while语句的一般形式为:
while(表达式) 语句
其中表达式是循环条件,语句为循环体。
while语句的语义是:计算表达式的值,当值为真(非0)时, 执行循环体语句。
【例3-9】用while语句计算从1加到100的值。
-
#include <stdio.h> int main(void){ int i,sum=0; i=1; while(i<=100){ sum=sum+i; i++; } printf("%d ",sum); return 0; }
使用while语句应注意以下两点。
1) while语句中的表达式一般是关系表达或逻辑表达式,只要表达式的值为真(非0)即可继续循环。
2) 循环体如包括有一个以上的语句,则必须用{}括起来,组成复合语句。
3.5 do-while语句的用法
do-while语句的一般形式为:
do
语句
while(表达式);
这个循环与while循环的不同在于:它先执行循环中的语句,然后再判断表达式是否为真,如果为真则继续循环;如果为假,则终止循环。因此,do-while循环至少要执行一次循环语句。
【例3-10】用do-while语句计算从1加到100的值
-
#include <stdio.h> int main(void){ int i,sum=0; i=1; do{ sum=sum+i; i++; } while(i<=100); printf("%d ",sum); return 0; }
3.6 for语句的用法
在C语言中,for语句使用最为灵活,它完全可以取代 while 语句。它的一般形式为:
for(表达式1; 表达式2; 表达式3) 语句
它的执行过程如下:
- 先求解表达式1。
- 求解表达式2,若其值为真(非0),则执行for语句中指定的内嵌语句,然后执行下面第3)步;若其值为假(0),则结束循环,转到第5)步。
- 求解表达式3。
- 转回上面第2)步继续执行。
- 循环结束,执行for语句下面的一个语句。
for语句最简单的应用形式也是最容易理解的形式如下:
for(循环变量赋初值; 循环条件; 循环变量增量) 语句
循环变量赋初值总是一个赋值语句,它用来给循环控制变量赋初值;循环条件是一个关系表达式,它决定什么时候退出循环;循环变量增量,定义循环控制变量每循环一次后按什么方式变化。这三个部分之间用分号(;)分开。例如:
-
for( i=1; i<=100; i++ ) sum=sum+i;
先给i赋初值1,判断i是否小于等于100,若是则执行语句,之后值增加1。再重新判断,直到条件为假,即i>100时,结束循环。相当于:
-
i=1; while(i<=100){ sum=sum+i; i++; }
对于for循环中语句的一般形式,就是如下的while循环形式:
表达式1;
while(表达式2){
语句
表达式3;
}
使用for语句应该注意:
1) for循环中的“表达式1(循环变量赋初值)”、“表达式2(循环条件)”和“表达式3(循环变量增量)”都是选择项,即可以缺省,但分号(;)不能缺省。
2) 省略了“表达式1(循环变量赋初值)”,表示不对循环控制变量赋初值。
3) 省略了“表达式2(循环条件)”,则不做其它处理时便成为死循环。例如:
-
for( i=1; ; i++ ) sum=sum+i;
相当于:
-
i=1; while(1){ sum=sum+i; i++; }
4) 省略了“表达式3(循环变量增量)”,则不对循环控制变量进行操作,这时可在语句体中加入修改循环控制变量的语句。例如:
-
for( i=1; i<=100 ; ){ sum=sum+i; i++; }
5) 省略了“表达式1(循环变量赋初值)”和“表达式3(循环变量增量)”。例如:
for( ; i<=100 ; ){ sum=sum+i; i++; }
6) 3个表达式都可以省略。例如:
for( ; ; ) 语句
相当于:
while(1) 语句
7) 表达式1可以是设置循环变量的初值的赋值表达式,也可以是其他表达式。例如:
-
for( sum=0; i<=100; i++ ) sum=sum+i;
8) 表达式1和表达式3可以是一个简单表达式也可以是逗号表达式。
-
for( sum=0,i=1; i<=100; i++ ) sum=sum+i;
9) 表达式2一般是关系表达式或逻辑表达式,但也可是数值表达式或字符表达式,只要其值非零,就执行循环体。例如:
-
for( i=0; (c=getchar())!=’ ’; i+=c );
循环的嵌套
【例3-11】循环嵌套的应用。
-
#include <stdio.h> int main(void){ int i, j, k; printf("i j k "); for (i=0; i<2; i++) for(j=0; j<2; j++) for(k=0; k<2; k++) printf("%d %d %d ", i, j, k); return 0; }