一、C语言简介
1.1、C语言的主要特点
(1) C语言是结构化程序设计语言
(2) 具有丰富的数据类型
(3) 具有丰富的运算符
(4) 结构紧凑,使用方便、灵活
(5) 具有低级语言功能,可以直接访问物理地址,进行位操作
(6) 可移植性好
1.2、面向过程&面向对象的程序设计
1.2.1、面向对象的程序设计
基本思想:
把一个需要求解的复杂问题分为若干个模块来处理,每个模块处理一个子问题;设计时遵循自顶向下、逐步细化、模块化设计和结构化编码的原则。
“自顶向下”就是将整个待解决的问题按步骤、有次序地进行分层,明确先做什么,再做什么,各层包含什么内容。
“逐步细化”就是对分层后的每一层功能进行详细设计,并仔细检验其算法的正确性。只有当本层功能及其算法正确无误之后,才能向下一层细化。如果每一层的设计都没有问题,则整个程序功能及其算法就是正确的。
“模块化设计”就是将处理问题的整个程序分为多个模块,其中一个主模块和若干个子模块,由主模块控制各个处理子问题的子模块,最终实现整个程序的功能。模块化设计的思想是一种“分而治之”的思想,即把一个复杂的问题分为若干个子问题来处理就简单多了,也便于程序的检验和调试。所谓模块是指一个能完成某项特定功能、既可以组合又可以分解的程序单元。
“结构化编码”是指在进行结构化程序设计之后,用结构化语言编写程序的过程。
优点:编程简单、结构性强、可读性好,程序执行时序特征明显;遵循这种结构的程序只有一个入口和一个出口。
缺点:存在数据与程序模块的分离和程序的可重用性差等问题。
1.2.2、面向对象的程序设计
基本思想:
将一个需要求解的问题分解为一系列实体(对象),然后围绕这些对象建立数据和函数;函数的功能决定了该对象的行为。规定一个对象的函数可以访问另一对象的函数,但一个对象的数据不能应用于另一对象的函数中。(即只有属于该对象的成员函数才能访问自己的数据成员,从而达到了保护数据的目的。)
1.3、算法
程序设计的主要任务是描述数据和处理数据。通过定义数据结构类型来实现描述数据的目的;通过设计算法来实现处理数据的目的。
1.3.1、算法的概念:
解决某一问题所采取的方法和步骤,对于同一个问题可能有多个算法; 算法的正确性是通过算法分析去证明的,对于同一个问题可能有多个算法,不但要学会设计算法,而且要设计出好的方法,判断一个算法的优劣,也属于算法分析的范畴。
1.3.2、算法的特性
⑴有穷性 一个算法其操作步骤应当是有限的;
⑵确定性 算法中的每一个步骤应当有确定的意义,不能有二义性;
⑶有效性 算法中的每一个步骤应当正确、可行,并且能有效地执行;
⑷有零个或多个输入 执行算法时可能需要从外界获取的信息;
⑸有一个或多个输出 执行算法后应当得到输出的结果,但结果未必正确。
1.3.3、算法的表示方法
①自然语言表示算法
②伪代码表示算法
③流程图表示算法
④N-S流程图表示算法
1.4、C语言上机实验
1.4.1、Sources File(源文件)/Header Files(头文件)/Resource Files(资源文件)
用高级语言编写的程序是源程序,将源程序储存在外储存器上称为源文件(扩展名通常为.c/.cpp),源程序文件由字母、数字和一些符号组成,在计算机内以ASCLL码表示。计算机通过编译、链接后生成可执行文件才可以被执行。
1.4.2、编辑源程序/源文件
将写好的C语言键入计算机,以文本文件的形式储存在计算机的外储存器上
1.4.3、编译源文件
把源文件翻译成计算机能够识别的目标代码,并由此生成一个与源文件相对应的目标文件。在编译过程中,首先检查源程序中是否存在语法和词法错误,如果有错,会显示错误信息,错误修正后才能够继续编译。生成一个与源文件相匹配的以.obj为扩展名的目标文件(机器语言)。
严格意义上讲,应该修改程序代码直至既无致命错误,也无警告(警告可能在项目运行时出错,其他可能都不影响)。
1.4.4、链接目标文件
启动代码是程序与操作系统的接口;C语言需要调用系统标准函数库中的函数,而目标文件中不包含这些函数。链接就是将这三者(目标文件、库函数代码、启动代码)结合起来,,将其放在一个文件(可执行文件)中,扩展名为.exe。
1.4.5、运行可执行文件
直接双击以.sln为扩展名的项目解决方案文件
1.5、部分C语言名词解释
1.5.1、C语言中的关键字/保留字
①类型说明保留字——说明变量、函数或其他数据结构类型的保留字
int、long、short、float、double、char、unsigned、signed、const、void、olatile、enum 、struct、union
②语句定义保留字——说明一个句子功能的保留字
if 、else、 goto、 switch、 case、 do、 while、 for、 continue、 break、 return、 default、typedef
typeof,给已有的数据类型起别名,并没有创造新的类型,只是换了称呼
#include<stdio.h>
// 1. short int a;
//2.short int INT16;
//3.
typedef short int INT16;//给short int 起别名INT16
int main() {
short int b = 101;
INT16 c = 111;//可以用INT16来声明short int类型的变量
printf("b=%d\n", b); printf("c=%d\n", c);
return 0;}
③存储类别说明保留字——说明变量、函数或其他数据结构存储类型的保留字
auto、register、extern、static
④.长度运算符保留字——用于以字节为单位计算类型存储大小 sizeof
1.5.2、标识符
①标识符是用以标识变量、数组、函数、文件和各种对象名称的符号
②标识符的命名规则:首字符必须是字母或下划线;后续字符可以是字母、数字或下划线;长度限制;尽可能的便于理解和区分,见名知意
③标识符注意事项:
标识符区分大小写字母;不能和保留字同名,也不可以和已经定义的函数名或者系统标准库函数名同名;以下划线开头的标识符一般用于系统内部,作为内部函数和变量名;
1.5.3、变量&常量
程序中处理的主要对象是数据,数据在程序中有两种表示形式:常量和变量。要创建一个应用程序,首先要描述算法,算法中要说明的数据也是以常量和变量的形式来描述的。所以,常量和变量是程序员编程时使用最频繁的两种数据形式。
①常量:
⑴在程序运行期间,其值不能被改变的量,包括值常量/直接常量/字面量(直接以输入输出字面值表示)和符号常量/宏/全局变量(用指定的标识符代替一个常量,先定义,后使用)
【符号常量举例】#define PI = 3.141592
⑵ 标识符(宏名)被定义为符号常量,其值为后面的常量值。符号常量使用大写字母形式。
一般情形下多次使用同一常量时,可定义符号常量,便于符号常量修改(出于精度考虑之类)
#include<stdio.h>
#define PI 3.1415926
int main(void)
{ float randius,area,circum;
scanf("%f",&r);
area=PI*r*r;
circum=2*PI*r;
printf("%f %f",area,circum);
return 0;
}
②变量:
在程序运行过程中,其值可以改变的量;变量有三个属性:变量名、变量的数据类型和变量值;变量在使用之前要先定义(声明);声明变量和初始化变量。
声明/定义变量:【举例】int name; 初始化变量:【举例】int name = value;
float f1,f2,f3=24.5;一个初始化变量,两个声明变量
int a, b, c;//一个数据类型说明符后定义多个相同类型的变量
float d = 1.0, f = 1.0, g = 1.0;//可以为多个同类型变量赋同一初值,但要分别赋给各个变量
1.5.4、注释:
①多行注释(/* */)
②单行注释(//)
1.5.5、预处理命令
①调用输入输出函数时,要在源文件中使用以下命令包含头文件<stdio.h>
②调用数学函数时,要在源文件中使用以下命令包含头文件<math.h>
1.5.6、类、对象、属性、事件、方法
1.5.7、三类机制:封装、继承、多态
1.5.8、编辑器、编译器、链接器
1.5.9、运行和调试
二、C语言的运算符
2.1、C语言的字符集
字母26个、数字0-9的组合、空白符(空格符、制表符、回车换行符)、特殊字符
2.2、算术运算符
2.2.1、算术运算符类型
+ - * /(整除) % (取余) ++ or - -(变量自增/自减)
2.2.2、算术表达式
①算术表达式是由算术运算符和括号将运算对象(如常量、变量、函数等)连接起来的一个有值的式子;
②表达式的类型为该表达式值的类型;
③表达式的求值顺序:先按运算符的优先级执行,当一个运算对象两侧的运算符优先级别相同时,则按结合性(左结合或者右结合)处理;赋值运算符为右结合性。
2.2.3、运算符的分类:
①单目运算:参与运算的操作对象只有一个——变量的自增自减;变量的自增或者自减运算符只能用于变量,不能用于常量或者表达式。
②双目运算:参加运算的操作对象有两个——+ - * / %;整数相除,结果为整数,且只保留整数部分;取余数运算的两个操作数必须是整数,结果也是整数
③三目运算:参加运算的操作对象有三个——()?():()
【举例】max=a>b?a,b 将a、b中更大的数赋值给max
#include<stdio.h>
int main()
{
/* 变量自增和自减*/
int i = 6, a, b;
printf("%d\n", ++i);//7
printf("%d\n", i++);//7
a = --i; printf("%d\n", a);//7
b = i--; printf("%d\n", b);//7
printf("%d\n", -i++);//-6
printf("i=%d\n", i);//i=7
/*变量的自增使用:
i++是参与整体操作,再更新i的值;
++i是先更新变量i的值,再参与整体运算
注意:i参与运算不同于i的值发生改变 */
int m = 3, n = 4, x;
x = -m++;
x = x + 8 / ++n;
printf("%d\n", x);//-2
return 0;
}
2.3、逻辑运算符
2.3.1、逻辑运算符类型
&&逻辑与 ||逻辑或 !逻辑非
#include<stdio.h>
int main() {
/*逻辑运算符或、非、且*/
int a = 10; int b = 20; int c = 30;
if ((a < b) && (b > c))//逻辑与&&——and,见假则假
{
printf("条件为真\n");
}
if ((a > b) || (b > c))//逻辑或||——or,见真则真
{
printf("逻辑或的结果为真\n");
}
if (!(a > b))//逻辑非!——not,否命题
{
printf("逻辑非条件成立\n");
}
2.3.2、逻辑运算符注意事项
①C语言没有逻辑型数据。因此,在返回逻辑值时:用1表示“真”,用0表示“假”;在判断逻辑值时:用非0代表“真”,用0代表“假”。
②C语言中关系表达式只能对一个条件进行测试,而诸如20≤x≤100这种多条件判断则不能用关系表达式表达,但是可以用逻辑运算符表达。
【实例】0≤x≤10 → x>=0&&x<=10 ;a>5或x<15 → a>15‖x<15
注意:在C语言中,i<j<k的意思是:if i<j, 则true, 1<k?; if i>=j, 则flase, 0<k?
③在一个逻辑表达式中如果有多个逻辑运算符,则优先顺序为非 !、与&&、或 ||;逻辑运算符的优先级低于关系运算符。
【举例】!5 || 8==9 假 ‘A’+2 || 'D' 真
④逻辑运算中的短路原则:当能够得出表达式的结果时,就不再继续进行余下的计算。
2.4、关系运算符
== != < > <= >=
关系运算也称比较运算,通过对两个量进行比较,判断其结果是否符合给定的条件,若条件成立,则比较的结果为真,否则就为假。
关系运算符的优先级别分为两个等级,其中 < > <= >= 为同级运算符,且高于== != ;== != 为同级运算符关系;关系运算符的优先级低于算术运算符,高于赋值运算符;关系运算符左结合。
关系运算的结果应为逻辑值,即“真”或“假”,所以关系运算的结果是以整数1表示真,以整数0表示假,结果的类型为整型。而在逻辑判断的时候,将非0视为真,0视为假。
2.5、位运算符
<< >> & ^ |
2.6、赋值运算符
2.6.1、赋值运算符内容
赋值运算符(=);复合赋值运算符(+= /+= * = -= %= <<= >>= &= ^= |=)
2.6.2、赋值运算的转换规则:
①实型数据赋给整型变量时,向下取整。如:K=8.88 ——>int k=8
②整型数据赋给实型变量时,以浮点形式取值。如: f=15——>float f =15.0000
③字符型数据赋给整型变量时,整型变量的高八位补0,低八位取字符的ASCII码值。
【举例】 K=‘A’——>int K=65
④整型数据赋给字符型时,只把低8位赋给字符变量。
【举例】: C=578——>char C = 'B' D=65——>char D = ‘A’
注意:在赋值运算时,如果右边表达式的sizeof比左边的sizeof长,将丢失部分数据。
2.7、逗号运算符
2.7.1、逗号表达式:
用逗号运算符将各表达式连接起来的式子;运算结果是最后一个表达式的值;
#include<stdio.h>
int main()
{
int a = 5, b = 10;
int z = (a, b, (a * b));
printf("第一种方法:%d",z);
printf("第二种方法:%d", (a, b, (a + b)));
return 0;
}
2.7.2、逗号运算符注意事项:
逗号运算符是所有运算符中最低的;程序中并非所有出现逗号的地方都组成逗号表达式。
2.8、运算的优先级和结合性
2.8.1、运算的优先级:
①在表达式中按照优先级先后来进行计算,运算级高的先于优先级低的运算;优先级一样再按照结合性运算;
②建议当表达式比较复杂的时候,用()括起来,括号的运算优先级最高,优先算括号里面的,这样也可以防止写错表达式
三、数据类型
C语言具有丰富的数据类型和运算符。C语言处理的数据类型不仅有字符型、整型和实型等基本数据类型,还有有基本数据类型构成的数组、结构体和共用体等构造类型以及指针类型。
在C语言程序中,需要对所用到的数据指定其数据结构,即要说明数据的组织形式。C语言中,对数据结构的描述是通过说明数据类型来体现的。强调数据类型的意义在于确定不同数据类型的存储长度、取值范围和允许的操作。
3.1、基本数据类型
基本数据类型是C语言数据类型的基本型,主要特点是其值不可再分解为其他类型,C语言的基本数据类型包括整型、浮点型、字符型等。
3.1.1、整型/整数数据
⑴ 十进制整数:0-9的随机组合,例如 12,65,0,-456,65535
⑵八进制整数:以数字0开头,用0-7这8个数字表示,例如 0123,0101,0177777
⑶十六进制整数: 以ox开头,用0-9,a,b,c,d,e,f这几个数字表示,例如 0xC,0x41,0xFFFF
【注意】八进制不输出前导符0;十六进制不输出前导符0x;但键入时需要
⑷长整型整数(后缀加“L”或“l”):
十进制长整型数字 12L,65536L;
八进制长整型数字 014L,020L;
十六进制长整型数字 0XCL,0X10000L
⑸无符号整数(后缀加“U”或“u”): 15Lu,017u,0xFu
在定义整型变量时,要注意数据类型允许的数值范围;不同类型的整型变量的字节数与操作系统、编译系统、机器有关;超出各种整形类型变量的取值范围,会导致数据溢出,造成结果出错。在定义变量时,要注意不同数据类型的取值范围,当其值超出了最大取值范围时,会产生溢出错误。
#include<stdio.h>
int main()
{
char a;
short int b;
int c;
long int d;
float e = 3.8f;
double f = 5.6;
printf("sizeof(a) =%d\n",sizeof(a));
printf("sizeof(b) =%d\n", sizeof(b));
printf("sizeof(c) =%d\n", sizeof(c));
printf("sizeof(d) =%d\n", sizeof(d));
printf("sizeof(e) =%d\n", sizeof(e));
printf("sizeof(f) =%d\n", sizeof(f));
return 0;
}//确认所使用计算机不同类型数据所占内存的大小
//sizeof(name) 长度运算符,用来测量变量、数组的占用存储空间的大小(字节数衡量)
3.1.2、字符型数据
①字符常量:用单引号括起来的单个字符。e.g.: 'a' , 'A', '=', '+', '5': 'a' , 'A', '=', '+', '5'
⑴字符常量只能是一个字符,只能用单引号括起来,可以是字符集中的任意字符。
⑵字符常量之转义字符:
②字符串常量 :用双引号括起来的字符序列。e.g.:“C Programming”, “$12.5”, “CHINA”; ⑴ 区别于字符常量用单引号作为定界符,字符串常量用双引号作为定界符;
⑵ 字符常量只能是单个字符,字符串常量则可以含零个、一个或多个字符
⑶字符串常量中所含字符的个数,称为字符串的长度;需要注意的是,字符常量占一个字节,而每个字符串的结尾,编译器会自动添加一个结束标志位“\0”,因而字符串常量占的字节数等于字符个数加1。字符串的长度非负;
⑷可以把字符常量赋值给字符串常量,但是不可以将字符串常量赋值给字符串型变量(在C语言中,没有字符串型变量)
⑸双引号是字符串的定界符,不是字符串的一部分。如果字符串中需含有双引号
("),则要使用转义符
③字符型变量:——用来存放单个字符常量的变量。
在内存单元中,每个字符型变量分配一个字节用于存放一个字符。(实际上存放的是字符的ASCII码值。)所以,在C语言中字符型数据和整型数据之间可以通用(赋值、输出、运算)
当整型数据按字符型数据处理时,只有低8位字节参与操作。
C语言中的字符串大小写相关操作可以通过ASCLL编码完成。(+ or - 32)
int main()
{ /*整型数据和字符型数据的通用*/
char c1, c2;
c1 = 97;
c2 = 98;
printf("c1=%c,c2=%c\n", c1, c2);//c1=a,c2=b
printf("c1=%d,c2=%d\n", c1, c2);//c1=97,c2=98
return 0;}
3.1.3、实型数据
①实型常量:实型常量就是实数(浮点数),(包括单精度实型float、双精度实型double),以f结尾的是单精度实型,不是以f结尾的浮点数是双精度变量。
②浮点常量默认为double类型,把一个浮点常量赋给不同的精度的浮点变量时,系统将根据变量的类型截取浮点常量的有效数字,指定为float类型,需要加后缀f;指定为double long 类型,需要加后缀l。
③C语言中,输出double类型(双精度实型)以及float类型(单精度实型)时,默认输出6位小数(不足六位以 0 补齐,超过六位按四舍五入截断)。
④float 类型的储存单元只能保证前7位有效数字,double类型则是16位,超出部分不能保证正确,(可能在操作时会随机生成),另外,printf()函数标准的格式输出只能是f而不是lf
⑤浮点数的两种表示形式
⑴十进制小数形式 如:4.58; 0.025;……
⑵指数形式/科学记数法:阶码带正负号,由整数部分、小数、小数部分、小数点、e or E、整数阶码构成
【举例】1.2E+5 (等于1.2×10^5=120000); 3.4E-5 (等于3.4×10^(-5)=0.000034)
⑥实型变量——用来存放实型数据的变量。
3.1.4、不同数据类型间的混合运算
在C程序中,当不同类型的量进行运算时,要转换成同一种类型然后再进行运算。
⑴自动类型转换:将低级类型(占据内存字节数少)自动转换成高级类型,以保证数据的精度不下降,然后再按同类型量进行运算。(尽可能最小转化量)
当表达式中出现的了带小数点的实数,参加运算的成员全部变成double类型参与运算,结果也是double类型;当表达式中带有符号数,也有无符号数,参加运算的成员变成无符号数参加运算,结果也是无符号数(表达式中无实数);在赋值语句中等号右边的类型自动转化为等号左边的类型。
#include<stdio.h>
int main(){
printf("%d\n", 5 / 2);//2
printf("%f\n", 5.0 / 2);//2.500000
/*符号数中的类型转化*/
int a = -8; unsigned int b = 7;
if (a + b > 0)
{ printf("a+b >0\n"); }
else if (a + b < 0)
{ printf("a+b <0\n"); }
else
{ printf("a+b =0\n"); }//a+b >0
printf("a+b=%x\n", a + b);//a+b=ffffffff 如何理解这个结果??
/*赋值语句中的类型自动转化*/
int an; float f1 = 5.8f;//如果不带f,默认是double类型
an = f1;//在赋值语句中等号右边的类型自动转化为等号左边的类型。
printf("a=%d\n", a); printf("f=%f\n", f1);
return 0;}
⑵强制类型转换:将一个表达式的值转换成所需要的类型,是一种临时性转换,并未改变变量的原类型;通过类型转换运算来实现。
一般形式:(数据类型符)(表达式)or(数据类型符)name
【实例】(double)i (int)(f1+f2) (float)(14/4)
3.2、构造类型(由基本数据类型构成的)
构造类型是根据已定义的一种或多种数据类型用构造的方法定义的。也就是说,一个构造类型的值可以分解成若干个“成员”或“元素”。每个“成员”或“元素”都是一个基本数据类型或又是一个构造类型。C语言的构造类型包括:数组类型、结构体类型和共用体类型。
3.2.1、数组
用C语言中的一些基本数据类型,如整型、实型和字符型等定义的变量只能保存一项数据;构造类型数据是由基本类型数据按一定规则组成的。
在程序设计中,数组是一种普遍使用的数据结构,是数目固定、类型相同的数据的有序集合。数组中的每一个数据(变量)称为数组元素,数组中的所有元素都有同一种数据类型,数组在内存中占有一段连续的存储空间。利用数组可以方便地实现成批数据的存储和处理。
C语言中的数组有两个特点:一是数组元素的个数必须是确定的,二是数组元素的类型必须一致。
3.2.2、结构
3.2.3、共用体
3.3、指针类型
指针是一种特殊而又具重要作用的数据类型,其值表示某个量在内存中的地址。虽然指针变量的取值类似于整型量,但这是两种完全不同类型的量,一个是变量的数值,而指针变量的值是变量在内存中存放的地址。
3.4、空类型
通常情况下,在调用函数时被调用函数要向调用函数返回一个函数值。函数值的类型应该在定义函数时在函数的说明部分(函数头)加以说明。ex: int max(int a, int b ,intc)
但是,在实际应用中也有这样一类函数:该函数被调用后无需向调用函数返回函数值,即该函数只是作为调用函数执行中的一个“过程”。这类函数定义为“空类型”(也称为“无值型”),其类型说明符为void。
3.5、自定义
用新的类型名替代已有类型名使用。
用typedef定义
3.6、枚举类型
将一类数据所有可能的取值都一一列举出来,并给每一个值指定一个名称和一个整型的编码
四、语句
4.1、声明语句
类型说明符 变量名; 【实例】char c1,c2.c3='5';
类型说明符 函数名(参数列表) 【实例】int max(int a,int b);函数max返回值的类型为整型。
4.2、表达式语句
任何表达式都可以加上分号构成语句;计算结果保留在一个变量上,才具有实际意义。
【实例】 ++p; /*自增运算表达式加分号构成语句*/
N+=10; /*复合赋值表达式加分号构成语句*/
a+b; /* a+b表达式加分号构成语句*/
4.3、函数调用语句
【形式】函数名(实参列表) ;
【实例】printf(“Very Good!\n”); /*输出Very Good!*/
max(x,y,z); /*调用自定义函数max,求x,y,z中的最大数*/
4.3.1、函数调用之输入输出语句
输入:从外存或外设(如键盘、磁盘文件等)进入或者读入计算机内存,获取数据;输出:从计算机内存取出或写出到外存或外设(如显示器、磁盘文件等)的过程。
C语言本身没有提供输入输出的命令,所有的数据输入和输出操作都是通过调用C语言标准库函数来实现的,在使用C语言的库函数时,要在程序文件的开头部分用预编译命令“#include”将有关头文件包含到源文件中。
#include<stdio.h>
int main()
{
{
/*字符输出函数putchar(c)
向终端输出单个字符,c为形式参数,有且仅有一个参数
输出的字符可以是字符型数据、整型数据或转义字符。*/
char a = 56; putchar(a);//ASCLL编码056对应的是8
putchar('\101');//输出大写字母A
putchar('b');//输出小写字母b
putchar('\n');//输出换行符,输出位置移动到下一行开头
putchar('\015');//输出回车符,不换行,输出的当前位置移动到本行开头
/*\015,输出8进制三位数,对应ASCLL码13,即CR;
CR,换行符,用于将鼠标移动到行首,并不前进至下一行*/
}
{
/*字符输入函数getchar()
从终端输入单个字符,getchar()只能接收一个字符,
该字符可赋给字符变量、整型变量或作为表达式的一部分。
输入多余一个字符的时候,只接受第一个字符;
通常把输入的字符赋予一个字符变量,构成赋值语句;
在输入时,空格、换行符等都作为字符读入;只有在用户按回车键时,读入才开始执行。
getchar函数的返回值是用户输入的第一个字符的ASCLL码,且将用户输入的字符回显到屏幕, 如出错就返回-1;
后续的getchar调用不会等待用户按键,而是直接读取缓冲区的字符,直到缓冲区中的字符全部读完,才会等待用户按键。*/
printf("input a cha:\n");//提示输入
char c = getchar();//变量初始化
printf("%c\n", c); printf("%d", c);//分别以整型和字符型输出结果
printf("新的开始");
fflush(stdin);//清空输入缓冲区
//getchar 和putchar 的混合使用
char c1 = getchar();
char c2 = getchar();
putchar(c1); putchar('\n'); putchar(c2);
}
{
/*printf()输出函数按用户指定的格式,把指定的数据默认输出到显示器屏幕上
格式控制字符串:
%d 十进制; %x or %X 十六进制; %o or %O 八进制;
%f 单精度浮点数; %lf 双精度浮点数;
%c 单个字符输出; %s 字符串输出; %p 指针的值
%e or %E 科学记数法,以十为底指数形式表达小数
%g or %G 根据实数的大小自动选择%e or %f 输出*/
printf("%5.2f");
//左对齐,>=5的数据宽度,保留两位小数;printf()浮点数标准输出是f而不是lf
printf("%-05.3f");
//右对齐,不足5位前面补0,保留三位小数
int i1 = 56, i2 = 588; char c3 = '54';
printf("%c%c%d", i1, i2, c3);
//0-255范围内的整数数据,系统将该整数对应的ASCLL编码表对应的字符输出;
//如果超出该范围,则去周期,将数字调整到该范围内,再转化;
//字符型数据在%d、%o、%x输出时,则相反
double f1 = 618.123456789;
printf("f1=%e", f1);//f1=6.181235e+002
/* %e科学记数法输出:整数部分有一位非0数字,小数部分占6位,指数部分占5位,小数点占1位;
其中指数部分构成:e +/- 三位数字; 取小数位时遵循四舍五入原则*/
printf(" \' \\ %% \" ");
//转义字符输出 \n \t \b \r \' \" \\ %%
/*
①计算机的实数运算存在误差,整数运算应注意是否会产生溢出错误;
通过printf()函数格式化输出时,可能会涉及格式转化问题、有效位数限制
以及格式符使用不当问题等导致结果出现偏差;
②变量或者表达式的个数多于格式控制字符串的个数时,多余项不予输出;
当格式控制字符串的个数多于输出表列中变量或表达式的个数时,
无对应变量或表达式的格式控制字符串会输出随机值*/
}
{
/*scanf()函数
①格式控制字符串参考上述;&取变量地址运算符,获取在编译连接时系统分配给变量的地址;
②如果没有非格式字符作为输入数据的分隔符,在输入数据的时候,
可以使用一个或多个空格、制表符、换行符作为两个数值型数据之间的分隔符;
如果已经使用逗号、冒号,则需在键入数据时保持原格式不变,区别中英文输入;
③%*d意味着该数据不赋值,避免键入的用一份个数据的空格等作为输入数据;
④scanf函数中无精度控制,但可以控制数据输入宽度
⑤如果键入的数据类型为字符型数据,则所有输入的字符均为有效字符,包括空格、换行符等
⑥遇见空格、换行符、制表符、非法输入(ex: 123A)系统会认定该数据输入结束*/
double f2; scanf_s("f2=%d\n", &f2);
//在格式控制符中出现的普通字符,包括转义字符形式的字符,务必原样输入,例如f2=3.1415926\n;
double f3; scanf_s("%3d", &f3);
//输入数据只读取三行
char f4, f5;
printf("请输入连续两个字符");
scanf_s("%c %c", &f4, f5);//允许有若干个空格作为数据输入之间的分隔符
getchar();
/*scanf函数输入字符后需要输入回车键,回车键也将作为有效字符输入缓冲区,
为避免该回车键影响其他字符赋值,可以选择用getchar来吃掉该字符*/
}
}
4.4、流程控制语句
由C语言规定的语句保留字组成,用于控制程序的流程,来实现程序的各种结构
4.4.1、 条件判断语句
条件语句 if ();if()……else ;if () else if …… else
多分支选择语句 switch (){……}
4.4.2、转向语句
无条件转向语句:goto 结束本次循环语句:continue
终止执行switch 或循环语句:break 函数返回语句:return
4.4.3、循环执行语句
while语句:while()……
do while 语句:do……while();
for 语句:for()……
4.5、复合语句
【形式】 用{ }将多个语句和声明括起来组成的一个语句;复合语句可以出现在允许语句出现的任何位置
【举例】:if (a>b) { x=0;b+=1;x=a;}
4.6、空语句
仅由一个分号构成的语句,不产生任何操作。
空语句的使用一般有两种情况,一是在循环语句中使用空语句提供一个不执行操作的空循环体;二是为有关语句提供标号,用以说明程序执行的位置。在程序设计初期,有时需要在某个位置加一个空语句来表示存在一条语句,以待之后进一步完善。
五、结构化程序设计
有顺序结构、循环结构、分支结构这三种基本结构,通过堆叠和嵌套方式组成的算法结构可以解决任何复杂的问题,由基本结构所构成的算法属于结构化算法,它不存在无规律的转向,只在本级本结构内部才允许存在分支和向前或向后的跳转。
5.1、结构化程序
5.1.1、设计和编写基本思想
把一个需要求解的复杂问题分为若干个模块来处理,每个模块处理一个子问题;设计时遵循自顶向下、逐步细化、模块化设计和结构化编码的原则。
“自顶向下”就是将整个待解决的问题按步骤、有次序地进行分层,明确先做什么,再做什么,各层包含什么内容。
“逐步细化”就是对分层后的每一层功能进行详细设计,并仔细检验其算法的正确性。只有当本层功能及其算法正确无误之后,才能向下一层细化。如果每一层的设计都没有问题,则整个程序功能及其算法就是正确的。
“模块化设计”就是将处理问题的整个程序分为多个模块,其中一个主模块和若干个子模块,由主模块控制各个处理子问题的子模块,最终实现整个程序的功能。模块化设计的思想是一种“分而治之”的思想,即把一个复杂的问题分为若干个子问题来处理就简单多了,也便于程序的检验和调试。所谓模块是指一个能完成某项特定功能、既可以组合又可以分解的程序单元。
“结构化编码”是指在进行结构化程序设计之后,用结构化语言编写程序的过程。
5.1.2、结构化程序的特点
结构化程序的主要优点是编程简单,结构性强,可读性好,执行时除遇到特殊流程控制语句外,总是按事先设计的控制流程自顶而下顺序执行,时序特征明显。遵循这种结构的程序只有一个入口和一个出口。
程序化结构化程序也存在缺点,如数据和程序模块的分离、程序的可重用性差等。
5.2、顺序结构
顺序结构由若干个依次执行的处理模块组成,是构成算法的最简单,也是最基本的结构。
5.3、分支/选择结构
选择结构/分支结构,是根据条件从若干个分支中选择其中的一个分支去执行选择结构有单分支选择,双分支选择和多分支选择等三种形式。
5.3.1、if语句的三种形式
①单分支结构
if ( < 条件 > ) 语句A ;
语句执行过程:
先判断条件(表达式),若条件成立(True),就执行语句A;否则,直接执行if后面的语句。条件可以是逻辑表达式、关系表达式、任意的数值类型。
语句可以是单语句,也可以是复合语句,并且在复合语句 { } 外不需再加分号 。
【实例】if (grade > =60) printf(“Pass \n”);
if (3) printf("O. K.\n");//逻辑真
if (‘q’) printf("%d\n",‘a’);//逻辑真
if (3<x && x<=5) printf("3<x<=5\n" );
if (a>b) {t=a; a=b; b=t; } //交换
C语言处理真和假的规则为:真值是非0值,假值为0值,如果条件表达式的结果为0,则按假处理,若条件表达式的结果为非零,则按照“真”处理。
②双分支结构
if ( < 条件 > ) 语句A ; else 语句B ;
语句执行过程:
先判断条件(表达式),若条件成立,就执行语句A;否则,执行语句B。即一定会执行语句A和语句B中的一句,且只能执行其中的一句; else子句不能作为语句单独使用,必须与if配对使用;else与if必须成对出现,且else总是与最近的一个未配对的if配对。
【实例】if (grade > =60) printf(“Pass \n”); else printf(“Failure”);
③ 多分支结构
if (< 条件1 >) 语句1 ;
else if (< 条件2 >) 语句2 ;
···else if (< 条件n-1 >)语句n-1 ;
else 语句n ;语句执行过程:
先判断条件1(表达式1),若条件1成立,就执行语句1后,退出该if结构;否则,再判断条件2(表达式2),若条件2成立,则执行语句2后,退出该if结构;否则,再判断条件3(表达式3),若条件3成立,则执行语句3后,退出该if结构;……。
④if语句的嵌套
在 if 语句中又包含有一个或多个if语句称为 if 语句的嵌套
为避免错误可用{ }将内嵌结构括起来以确定if与else的配对关系属内嵌范围
5.3.2、用条件表达式实现选择结构
条件运算符( ? : )三目运算符,运算优先级仅高于赋值运算符和逗号运算符,右结合性。
条件表达式:由条件运算符构成的表达式。
形式: < 表达式1 >?< 表达式2 >∶< 表达式3 > 各表达式的类型可不同。
运算规则:如果表达式1的值为真,则以表达式2 的值作为整个条件表达式的值,否则以表达式3的值作为整个条件表达式的值。
注意:条件表达式可用来处理简单的选择结构,但条件表达式不能完全取代双分支 if 语句 ;此外,对于复杂的双分支结构,用条件表达式代替,会因为过于复杂的表达式而降低程序的易读性。
【实例】a = 1, b = 2, c = 3, d = 4;
a > b ? a : c > d ? c : d; // 该表达式等价于a>b?a:(c>d?c:d)(右结合性)
5.3.3、switch语句
①switch语句一般形式
switch(表达式)
{
case <常量表达式1> :语句1;[break;]
case <常量表达式2> :语句2;[break;]
……
case <常用表达式n-1> :语句n-1;[break;]
default :语句n ;[ break;]
}
说明:
1)、表达式:switch后面括号内的表达式应是整形或字符类型表达式;case后面的表达式只能是常量表达式,可以是整型常量表达式、字符常量表达式、枚举表达式;
2)、case常量表达式1~(n-1):case也是关键字。常量表达式应与switch后的表达式类型相同,且各常量表达式的值不允许相同;
3)、语句1~n:可省略,或为单语句,或为复合语句;
4)、default,关键字,可省略,也可出现在switch语句体内的任何位置; switch语句中若没有default分支,则当找不到与表达式相匹配的的常量表达式时,不执行任何操作。
②switch语句执行过程
计算表达式的值,将该值与case关键词后的常量表达式的值逐一进行比较,一旦找到相同的值,就执行该case及其后面的语句序列,直到遇到break语句或switch语句的结束处才退出switch语句,如未能找到相同的值就执行default语句,如果没有default分支,一旦测试出任何case分支都不匹配,则不执行任何操作,转向switch后面的语句执行。
注意:
switch语句用于多重选择,可解决从一系列值中通过比较找出匹配值这样的多分支问题;switch语句将一个表达式的值和一个整数或字符常量表中的元素逐一比较,一旦发生匹配,与匹配常量相关联的语句就会被执行。
遇到第一个相同的case常量分支之后,顺序向下执行,不再进行是否相同的判断,因此除非特别情况,一般情况下break语句必不可少;
多个case可以共同使用一个语句序列。
③用switch语句实现多分支选择结构
/*输入一个0-3之间的数字,判断奇偶数,并输出该数字*/
#include<stdio.h>
int main(void)
{
int x;
printf("请输入一个0-3的整数:");
scanf_s("%d", &x);
switch (x)
{
case 0: case 2:
printf("输入的数字是偶数,数字为%d。\n",x);break;
case 1: case 3:
printf("输入的数字是奇数,数字为%d。\n", x);break;
default: printf("输入错误!\n");
}
return 0;
}
5.3.4、break 语句&continue语句
①在分支结构中:
强迫终止程序的执行,即提前退出程序的执行。用法:只能用在switch语句和循环体中,且在嵌套语句中只能退出一层程序执行;
当循环结构中执行continue语句时,continue语句能够跳过该结构中剩余语句,执行下一个循环过程。
#include <stdio.h>
int main(void)
{
{ int x;
for (x = 1; x <= 10; x++)
{
if (x == 5)
break;//break语句退出本层次循环,但如果是嵌套循环,只退出一层
printf("%d ", x);
}
printf("\n当 x == % d 时跳出循环。\n", x); }
/*在while和do-while结构中, continue语句被执行之后,立即进行循环条件的测试*/
int y;
y = 1;
while (y <= 10)
{
if (y == 5)
continue;
printf("%d \n", y);
y++;
}
/*在for结构中,表达式3被执行之后,然后进行循环条件的测试*/
{
int z;
for (z = 1; z<= 10; z++)
{
if (z== 5)
continue;
printf("z=%d ", z);
}
}
return 0;
}
②在循环结构中
满足循环条件从而退出循环的方式都是根据事先指定的循环条件正常执行和终止循环,但有时也需要在循环中间设置退出点,甚至可能需要设置多个退出点,break语句可以实现;有时需要忽略循环体中部分剩余语句的执行,重新开始下一次循环,continue语句可以实现。
③break语句使用要点:
当break语句在switch语句中使用时,其作用是跳出该switch语句。当break语句在循环语句中使用时,其作用是跳出本层循环。可以用break语句从内循环跳转到外循环,但允许从外循环跳转到内循环
在多层嵌套结构中,break语句只能跳出一层循环或者一层switch语句
④continue语句使用要点:
continue语句用于循环结构中,其作用是结束本次循环,不再执行continue语句之后的循环体语句,强制开始下一次循环
对于for语句,当执行了其循环体中的continue语句后,紧跟着执行计算表达式3'然后转到计算表达式2,进行循环条件判断。对while语句和do-while语句,执行了其循环体中的continue语句后,直接转到循环条件判断;
continue语句的作用只是结束本次循环,而不是终止整个循环的执行。而hreak语句则是使程序从本层循环中退出
/*任意输入10个数找出其中的最大数和最小数*/
#include<stdio.h>
int main(void)
{
int n;
double max, min,x;
printf("请输入第一个数字:");
scanf_s("%lf", &x);
max = min = x;
for (n = 2; n <= 10; n++)
{
printf("\n请输入第%d个数字:",n);
scanf_s("%lf", &x);
if (x > max)
{
max = x;
continue;
}
if (x < min)
min = x;
}
printf("max=%lf,min=%lf", max, min);
return 0;
}
5.4、循环结构/重复结构
循环结构用来表示有规律的重复执行某一处模块的过程,被重复执行的处理模块称为循环体,循环体执行的次数由控制循环的条件来决定,根据检查循环条件的方式,循环可分为当型循环和直到型循环两种结构。
循环体:被反复执行的程序段循环变量,用来控制循环是否继续进行的变量;循环结构有两种类型即当型循环和直到型循环。
在循环开始前就已经知道循环的次数的循环称为计数控制的循环;在计数控制的循环中,通常采用一个称为计数器的循环控制变量来记录当前已循环的次数。在循环之前,给这个循环控制变量赋初值,每当循环体被重复执行一遍时,这个变量就改变一次(通常是循环变量的增量运算)。循环条件通常是测试循环变量是否超过设定的循环次数。当控制变量的值超过设定的循环次数时,循环结束。
5.4.1、while循环
①while语句的基本用法
while(表达式) 语句;
表达式是循环控制表达式,作用是进行循环条件判断;语句是循环体语句,是循环条件所满足时重复执行的代码部分;循环体语句可以为空语句,或一条语句,或复合语句。
当执行while语句时,先计算表达式的值,若表达式的值为真(非0),则进入循环,执行循环体语句;每次执行循环体语句后会再次计算表达式来对循环条件进行判断,如此循环,直到条件为假。表达式的值为零,循环结束,执行后面的第一条语句。
②while语句的特点是先判断条件(计算表达式),当条件成立时,执行循环体语句。属于“当型”循环。while的循环体语句如果没有能够使循环条件改变为假的语句,则循环不能终止,一般会造成死循环
③若条件表达式只是用来表示”等于零”或“不等于零"的关系时,表达式可以简化成如下形式: while(x ! =0)可写成while(x) while(x ==0)可写成while(!x)
/*用while循环计算I+2+3+···+100累加和*/
#include<stdio.h>
int main(void)
{
int i=1, sum = 0; // 初始化循环变量i
while (i <=100)
sum += i;
i++;
printf(" 1+2+3+···+100=%d\n", sum);
return 0;
}
5.4.2、do while 循环
do-while语句的特点是:先执行循环体语句一次,再判断循环条件(计算表达式),确定是否继续循环。从程序的执行过程看,do-while循环属于“直到型”
①do-while语句的基本语法
do { 语句 } while(表达式);
当循环体语句包含一个以上的语句时,应使用复合语句表示;用do-while语句编程时,应注意对循环变量进行修改
②当执行do-while语句时,先执行循环体语句,然后计算表达式(条件),当表达式的值为真(非0)时,再次执行循环体语句。每次执行循环体语句后都计算表达式对循环条件进行判断。如此循环,直到表达式的值为假(0)为止,循环结束。
/*用do while 循环计算1+2+3+……+100的累计和*/
#include<stdio.h>
int main(void)
{
int i = 1, sum = 0;
do {
sum += i;
i++;
} while (i <= 100);
printf(" 1+2+3+···+100=%d\n", sum);
return 0;
}
注意:do while循环与while循环的区别:
如果初始化变量已经不符合循环条件,do while 循环也至少执行一次循环体;但是对于while循环,次数为0次。因此,do while 循环代表的直到型循环至少会执行一次,而while代表的当型循环可能一次也不执行;当while语句中的表达式第一次执行的值为“真”时,两种语句得到的结果是一致的,否则结果不同。
5.4.3、for循环
for语句是C语言中使用最为灵活、功能特别强的循环语句
①for 循环一般形式:
for(表达式1;表达式2;表达式3) 语句;
或 for(循环变量赋初值;循环条件;循环变量增值)循环体语句
表达式1通常用于给循环变量赋初值,表达式2用于对循环条件进行判断,表达式3通常用于对循环变量进行修改,语句为循环体。因此,for语句也可以写成如下形式:
②for循环的执行流程
第1步:先计算表达式1的值;第2步:再计算表达式2(条件)的值,若表达式2的值为非0(“真”),则执行for语句的循环体语句,然后再执行第3步。若表达式2的值为0(“假",条件不成立),结束for循环,直接执行第5步;第3步:计算表达式3的值;第4步:转到第2步;第5步:结束for语句(循环),执行for语句后面的语句。
/*用for循环计算1+2+3+……+100的累计和*/
#include<stdio.h>
int main(void)
{
int i, sum = 0;
for (i = 1; i <= 100; i++)
sum += i;
printf("1+2+3+……+100=%d", sum);
return 0;
}
③for循环的注意事项
⑴for循环中表达式1、表达式2、表达式3都可以全部或部分省略;
若省略表达式1,则应在for语句之前给循环变量赋初值;
若表达式3省略,则应在循环体中变化循环变量;
若省略表达式2,则在循环体中要有结束循环的语句,如使用break语句或调用exit()函数;3
个表达式可以全部或部分省略,但需在循环体内修改循环变量、设置循环条件,防止程序进入死循环(对应位置分号必须保留)
⑵表达式1-3可以是任何类别的表达式
⑶如果循环条件一开始就为假,则循环体将不执行,转向执行for循环之后的语句
⑷循环变量可以用浮点数,但是使用时一定要慎重。由于计算机中的浮点数都是有限位数,唯一不同的是精度。因此程序中的浮点数一般都存在误差,这可能会导致程序出现逻辑错误或结果的差异,所以不应把相等判断作为结束循环的条件
5.4.4、嵌套循环结构
①在一个循环体内又包含有另一个或多个完整的循环结构,称为循环的嵌套。
②循环的嵌套要点如下:
⑴内循环必须完整地嵌套在外循环内,两者不允许相互交叉。3种循环语句可以相互嵌套,但不允许交叉
⑵当循环并列时,其循环变量可以同名,但嵌套时循环变量不允许同名。
⑶选择结构和循环结构彼此之间可以相互嵌套,但二者不允许交叉。
⑷嵌套循环结构常常用于输出某些二维图形。对于这样的问题,关键是寻找出图形生成的规律,然后将这些规律用循环语句实现。
/*用嵌套循环打印九九乘法表*/
#include<stdio.h>
int main(void)
{
int i, j;
for (i = 1; i <= 9; i++)
{
for (j = 1; j <= i; j++)
printf("%d*%d=%d\t", i, j, i * j);
printf("\n");
}
return 0;
}
5.4.5、goto语句(不推荐)
goto语句是一个无条件转向语句,它能使程序执行的顺序无条件地改变。
goto语句一般形式为:goto 语句标号;..... 语句标号:语句序列;
goto语句的作用是将程序的执行转向语句标号所在的位置;golo语句易使程序流程无规律,可读性差,结构化程序设计方法主张限制使用golo语句
#include<stdio.h>
#define PI =3.1415926;
int main(void)
{
/* goto语句与if语句一起构成循环结构,
例如,用goto语句和标号loop计算1+2 +···+100累加和*/
int i = 1,sum=0;
loop1:
sum += i;
i++;
if (i <= 100) goto loop1;
printf("1+2+3+……+100=%d", sum);
/*goto语句可以实现从内循环体中跳转到多层循环体外, 例如,
用goto语句计算半径r = 1到r= 10时圆的面积,直到面积area大于100为止。*/
int radius=1;//半径
double area;
loop:
if (radius <= 10) {
area = PI * r * r;
if (area > 100) goto leap;/*使程序跳出循环*/
printf("radius=%d,area=%.2lf", radius, area);
r++;
goto loop;/*使程序进入循环*/ }
leap:
return 0;
}
六、数组
用C语言中的一些基本数据类型,如整型、实型和字符型等定义的变量只能保存一项数据;构造类型数据是由基本类型数据按一定规则组成的。
在程序设计中,数组是一种普遍使用的数据结构,是数目固定、类型相同的数据的有序集合。数组中的每一个数据(变量)称为数组元素,数组中的所有元素都有同一种数据类型,数组在内存中占有一段连续的存储空间。利用数组可以方便地实现成批数据的存储和处理。
C语言中的数组有两个特点:一是数组元素的个数必须是确定的,二是数组元素的类型必须一致。
/*学生会换届选举,全体会员票选学生会主席,共有3名候选人,每个人的编号分别为1、2、3;
每名会员填写一张选票,在同意的候选人姓名后打勾。
编写程序由键盘输入每张选票上所投候选人的编号,统计每位候选人所得票数*/
#include<stdio.h>
int main(void)
{
/*方法一、用基本数据类型实现*/
int v1, v2, v3,num;
v1 = v2 = v3=0;
printf("请输入候选人编号(1-3),输入0时结束:");
scanf_s("%d", &num);
while (num != 0) {
if (num == 1) v1++;
else if (num == 2) v2++;
else if (num == 3) v3++;
else printf("输入数据不合法,请重新输入数据。\n");
printf("请输入候选人编号(1-3),输入0时结束:\n");
fflush(stdin);
scanf_s("%d", &num); }
printf("投票结束!\n");
/*方法二、用数组实现*/
int vote[3] = {0}, num, i;
printf("请输入候选人编号(1-3),输入0时结束:\n");
scanf_s("%d", &num);
while (num != 0)
{
if (num >= 1 && num <= 3)
{ vote[num]++; printf("请输入候选人编号(1-3),输入0时结束:\n"); }
else
{
printf("输入数据不合法,请重新输入数据。\n");
printf("请输入候选人编号(1-3),输入0时结束:\n");
fflush(stdin);
scanf_s("%d", &num);
}
}
printf("投票结束,!投票结果为:\n");
for (i = 1; i < 3; i++)
{
printf("%d号候选人的票数是:%d\n", i, vote[i]);
}
return 0;
}
6.2、一维数组
6.2.1、一维数组的定义
㈠、数组必须先定义后使用。在定义数组时,应该说明数组的类型、名称、维数和大小。一维数组是指带一个下标的数组;
㈡、定义一维数组的一般形式为:
类型说明符 数组名[常量表达式]
其中:
①类型说明符为C语言的关键字,它说明了数组中每个数组元素的数据类型,如:整型、实型或字符型等。
②数组名是数组的名称,是一个合法的标识符,其命名方式与变量名相同。
③[]是下标运算符,其个数反映了数组的维数,一维数组只有一个下标运算符,下标运算符的优先级别很高,为1级,可以保证其与数组名紧密结合在一起。
④常量表达式是由常量及符号常量组成的,其值必须是正整数,它指明了数组中数组元素的个数,即数组的长度
【举例】int array[ 100 ]; 定义了一个名为array的整数数组,其包含100个整型的数组元素;
double name[5];定义了一个名为name的整数数组,其包含5个浮点型的数组元素;
⑤数组在定义时应注意以下几点:⑴数组的类型实际上是指数组元素的取值类型。对于同一个数组,其所有元素的数据类型都是相同的;⑵数组名不能与程序中的其他变量名相同;⑶不能在方括号中用变量来表示元素的个数(因为对数组空间的分配是在编译时进行的,如果定义数组时数组的大小是变量,计算机无法确定应该为变量分配多少内存合适,所以定义数组时必须用常量指定大小)
6.2.2、一维数组的储存
数组定义以后,编译系统将在内存中自动地分配一块连续的存储空间用于存放所有数组元素。C语言中,数组名表示内存中的一个地址,是数组中所有元素(一片连续存储空间)的首地址,存储单元的多少由数组元素的类型和数组的大小决定。
【举例】int array[ 5 ]; 整数数组array有5个元素,由于一个int型变量在内存中占有4字节的储存单元,因此,整形数组array在内存中连续占用20个字节储存单元
注意,数组名代表的是数组在内存中存储单元的首地址,不是数组的值。
6.2.3、一维数组元素的引用
①C语言规定,数组是一种数据元素的序列,数组名代表的是数组在内存中的首地址,因此不能用数组名一次引用整个数组,只能逐个引用数组元素,一个数组元素实质上就是一个同类型的普通变量,其标识方法为数组名后跟一个下标,下标表示了元素在数组中的顺序号。
②数组元素的引用方式为:数组名[下标]
注意:
⑴对数组元素进行引用时应注意下标的取值范围。C语言规定,下标的范围为0<=下标<=数组长度-1;
⑵C语言编译器不检查引用数组元素时的下标是否超出范围,如果在程序执行时下标超出了范围,则会得到错误的数据,有时还会因为引用了禁止访问的内存区而导致程序被中断。
⑶在程序中,数组元素的引用常常会出现在赋值语句中
【举例】float b[4];b[0]=1.0;b[1]=7.6;b[2]=b[0+b[1];b[3]=b[1]-b[0]
⑷对数组连续元素的引用通常是使用循环结构,数组与循环结构的配合使用是处理大量数据的最常用方法
【举例】int i, array[4]; for (i=0;i<=3;i++) scanf("%d",&array[i]);
int i ,array[50]; for (i=6;i<=36;i++) printf("array[%d+12]=%d",i,array[i+12])
6.2.4、一维数组的初始化
①所谓数组的初始化就是在定义数组的同时给数组元素赋初值。数组初始化是在编译阶段进行的,这样可以减少运行时间,提高效率。对数组进行初始化,其一般形式如下:
类型说明符 数组名[常量表达式]={初值表}
②一维数组可以用以下几种方式对数组元素进行初始化
⑴对全部或部分数组元素赋初值,指定数据长度
⒈数组的长度与花括号中数据的个数相等,
【举例】int array[5]={1,2,3,4,5}
⒉ 数组的长度与花括号中数据的个数不等,对于未赋初值的数据元素,系统将系统将自动赋初值0
【举例】int array[5]={1,2,3}
⑵对全部数组元素赋初值时,不指定数组的长度,系统自动将根据初值数据个数确定数组长度。由于定义数组时省略了数组的长度,则依据花括号中数据的个数,系统自动定义数组的长度,并自动给全部元素赋初值。
【举例】double array[ ]={1,2,3,4,5,6}
⑶对全部数组元素初始化为0时
【举例】float array[ ] = {0,0,0,0,0} or float array[5]={0}
注意:如果不对数组元素赋初值,系统不保证数组元素具有特定的值,但即使仅给一个数组元素赋了初值,其余的数组元素也会得到特定的值”0",
【举例】 int array[5] = { 520 };只有第一个被赋值为520,其余依旧为0,所以本质上(3)仅仅是针对(2)中数组首项元素的处理。
6.2.5、一维数组的输入输出
最简单的一维数组元素取得值的方式是通过初始化或赋值语句来实现的。而最灵活、最常用的一维数组的输入输出则是通过使用C语言基本输入输出函数配合循环结构来进行的。
/*输入10个学生的成绩,计算10位学生总成绩,
并输出score数组中对应的数据元素*/
#include<stdio.h>
int main(void)
{
float score[10], sum = 0.0;
int i;
printf("请输入10学生的成绩:\n");
for (i = 0; i < 10; i++)
{
scanf("%f", &score[i]);
sum += score[i];
}
for (i = 0; i < 10; i++);
printf("score[%d]=%6.2f\n", i, score[i]);
printf("sum=%.2f\n", sum);
return 0;
}
6.2.6、一维数组的应用
/*用选择法对任意N个数按由小到大方式进行排序。
选择法是最简单、最直观的对数据进行排序的算法,其思路是:
通过比较和交换,将符合要求的最小的数,放在前面,每轮确定一个数;
每确定一个数以后,在剩下的数中,依次解决;
如果需对k个数进行排序,则要进行k-1轮的比较,每轮分别要经过
k-l、k-2、k-3、 、1次比较就可使数据完全排序*/
#include<stdio.h>
int main(void)
{
int array[6], i, j, k;
printf("请任意输入6个整数:\n");
for (i = 0; i <= 5; i++)
scanf_s("%d", &array[i]);
printf("\n");
for (i = 0; i < 5; i++)
{
for (j=i+1;j<6;j++)
if (array[j] < array[i])
{
k = array[j];
array[j] = array[i];
array[i] = k;
}
}
printf("按由小到大顺序输出6个整数是:\n");
for (i = 0; i < 6; i++)
printf("%d,", array[i]);
printf("\n");
return 0;
}
存在问题 :
/*把一个整数依序插入已排序的数组,设数组已按照从大到小顺序排列。
可以考虑和第一个程序合并*/
#include<stdio.h>
int main(void)
{
int s, t, x, array[11];
/*按由大到小顺序输入10个整数*/
printf("请按由大到小顺序输入10个整数:\n");
for (s = 0; s <= 9; s++)
scanf_s("%d", &array[s]);
/*输入要插入的整数*/
printf("请输入要插入的整数:");
scanf_s("%d", &x);
/*从大到小的十个数排列,找到插入整数的位置,并记录位置*/
for (s=0,t=10;s<=9;s++)
if (x > array[s])
{
t = s;
break;
}
/*该位置及其往后数字后移一位,数组长度+1;
同时将插入整数替代原有位置上数字;*/
for (s = 10; s > t; s--)
array[s] = array[s - 1];
array[t] = x;
printf("\n结果为\n");
/*输出新的数组元素排序*/
for (s = 0; s <= 10; s++)
printf("%d,",array[s]);
printf("\n");
return 0;
}
/*按分数段统计各段学生人数*/
#include<stdio.h>
int main(void)
{
float score[10] = { 0 };
int n[5] = {0};
int i;
for (i = 0; i < 10; i++)
{
printf("请输入第%d名学生的成绩:\n");
scanf_s("%f", &score[i]);
while (score[i] > 100 || score[i] < 0)
{
printf("输入成绩应在0-100之间,请重新输入:\n");
scanf_s("%f", &score[i]);
}
}
for (i = 0; i < 10; i++)
{
if (score[i] >= 90) n[0]++;
else if (score[i >= 80]) n[1]++;
else if (score[i >= 70]) n[2]++;
else if (score[i >= 60]) n[3]++;
else n[4]++;
}
printf("\n统计结果如下:");
for (i = 0; i < 4; i++)
printf("分数在%d-%d之间的学生人数为%d人", 100 - 10 * i, 90 - 10 * i, n[i]);
printf("不及格的人数有%d", n[4]);
return 0;
}
6.3、二维数组
6.3.1、二维数组的定义
二维数组是指带两个下标的数组.在逻辑上可以将二维数组看成是一张具有行和列的表格或一个矩阵,第1个下标表示行号,第2个下标表示列号·。定义二维数组的一般形式为:
类型说明符 数组名[常量表达式1][常量表达式2]
6.3.2、二维数组的储存
C语言规定,在计算机中二维数组的元素是按行的顺序依次存放的,即在内存中,先顺序存放二维数组第一行的元素,再顺序存放二维数组第二行的元素,依此类推
6.3.3、二维数组元素的引用
二维数组元素的引用与一维数组相似,也只能逐个被引用,其一般形式为:数组名[下标1][下标2]
数组在引用时下标的范围应满足如下条件:0 <=下标1<常量表达式1.、0 <=下标2<常量表达式2。
6.3.4、二维数组的初始化
由于二维数组的数据在内存中是按行依次存放的,因此二维数组的初始化也是按此顺序进行赋值的。其一般形式为:
类型说明符 数组名[常量表达式1][常量表达式2]={初值表}
二维数组可以用以下几种方式进行初始化
对二维数组的全部元素赋初值,在初始化格式的一对花括号内,初值表中每行数据另用一对花括号括住 【举例】int x[2][4]={{1,2,3,8},{4,5,8,6}};
也可以从数组首地址开始依次存放数据,通过赋值,在二维数组中,依次初始化各元素的值
【举例】int y[2][4]=(1,2,3,4,5,6,7,8);
对二维数组的部分元素赋初值
【举例】int array[3][5]={{5,8}{2}{6,5}}
给二维数组的全部元素赋初值,也可以不指定第一维的长度,但第二维的长度不能省略
【举例】 int x[ ][5]={{1,2,3,4,5},{6,7,8,9,10},{11,12,13,14,15}}
int y[ ][5] = { 1,2,3,4,5,6,7,8,9,10,11,12,13,14,15};
6.3.5、二维数组的输入输出
二维数组与一维数组一样,其数组元素的取值可以通过初始化方式得到。除此之外,使用赋值语句也可以赋予或改变数组元素的值。但最灵活、最常用的二维数组的输入输出还是通过使用C语言基本输入输出函数配合循环结构来进行的
/*计算一个5x5的整数矩阵两条对角线上的数值之和*/
#include<stdio.h>
#define N 5
int main(void)
{
int i, j, array[N][N], sum = 0, n = N;
for (i = 0; i < N; i++)
{
printf("linr %d:", i);
for (j = 0; j < N; j++)
{
scanf_s("%d", &array[i][j]);
if (i ==j) sum += array[i][j];
if (i + j == N - 1) sum += array[i][j];
}
}
for (i = 0; i < N; i++)
{
for (j = 0; j < N; j++)
{
printf("array[%d][%d]=%d\t", i, j, array[i][j]);
if (j == n - 1) printf("\n");
}
}
if (n % 2) {
sum -= array[(N - 1) / 2][(N - 1) / 2];
printf("sum=%d\n", sum); }
return 0;
}
6.3.6、二维数组的应用
将一个二维数组的行和列元素互换,存到另一个二维数组中。
分析:二维数组的行列互换,就是求它的转置矩阵。
两个数组的元素对应关系为:a[ i ][ j ]=b[ j ][ i ];
输入5名学生3门功课的考试成绩,计算并输出每个学生3门功课的平均成绩。
分析:需要定义一个二维数组score[ 5] [ 3]来存放学生的成绩,定义一个一维数组average[5]计算并存储每个学生3门功课的平均分。
#include<stdio.h>
int main(void)
{
int score[5][3] = {0}, i, j;
float average[5] = {0};
for (i = 0; i < 5; i++)
{
printf("请输入第%d个学生的三科成绩:\n", i + 1);
for (j = 0; j < 3; j++)
{
scanf_s(" %5d", &score[i][j]);
while (score[i][j] > 100 || score[i][j] < 0)
{
printf("成绩应在0~100之间,请重新输入:\n");
scanf_s(" %5d", &score[i][j]);
}
}
}
for (i = 0; i < 5; j++)
for (j = 0; j < 3; j++) {
average[i] += score[i][j];
average[i] = average[i] / 3; }
printf(" \n成绩单如下:\n");
for (i = 0; i < 5; i++)
{
printf("第%d个学生的考试成绩为:", i + 1);
for (j = 0; j < 3; j++)
printf(" %4d", score[i][j]);
printf("\t平均成绩:%.2f\n", average[i]);
}
return 0;
}
用折半查找法在N个整数中查找指定整数x。若找到,输出数组元素的下标;否则.输出提示信息“数组中无此数”。
分析:折半查找法可以在一组有序(递增或递减)的数据中快速查找指定数值。因此可
先采用排序算法对N个数进行由小到大的排序、然后用折半查找法在有序数据中查找指定数值x。
解决方案:本例采用冒泡排序算法,冒泡法的基本思想是将两两相邻的元素进行比较,如果前一个元素大于后一个元素,就交换这两个元素。在排序过程中数值较大的数逐渐从顶部移向底部,数值较小的数逐渐从底部移向顶部,就像水底的气泡一样逐渐向上冒。若要使 N个数按顺序排列必须进行N-1轮排序,且第l轮排序要进行N-1次比较。在一轮冒泡排序的过程中,如果所有参与比较的元素都没有进行交换,说明数组中所有元素已经是有序的,排序过程应该结束了。可以在程序段中,用标记变植标记在某一轮排序过程中是否存在交换。。
折半查找法的基本思想是将有序数组a的N个元素分成个数大致相同的两半,取中间位置元素a[N/2]与欲查找的x作比较,如果x等于a[N/2]则找到x,算法终止。如果x<a [N/2],则查找范围缩小一半,在数组a的左半部继续搜索r(数组元素呈升序排列)。如果 r>a[ N/2],则在数组a的右半部继续搜索x。折半查找是一种高效的查找方法,它可以明显减少比较次数,提高查找效率,但是折半查找法的前提条件是数据必须是有序排列的。
#include<stdio.h>
#define N 10
int main(void)
{
int array[N], temp, i, j, flag, x, low, high, mid;
printf("请输入%d个数字\n", N);
for (i = 0; i < N; i++)
scanf_s("%d", &array[i]);
for (flag = 1, i = 0; i < N - 1 && flag; i++)
{
flag = 0;/*用标记变量flag标记在某一轮排序过程中是否存在交换*/
for (j = 0; j < N-i-1; j++)
if (array[j] > array[i])
{
temp = array[j];
array[j] = array[j+1];
array[j + 1] = temp;
flag = 1;
}
}
printf("由小到大排序后的%d个数字:\n",N);
for (i = 0; i < N; i++)
printf("%d", array[i]);
printf("\n请输入待查整数:\n");
scanf_s("%d", &x);
low = 0; high = N - 1;
while (low <= high)
{
mid = (low + high) / 2;
if (array[mid] == x)
{
printf("找到整数%d,其下标为:%d。\n", x, mid);
break;
}
else if (array[mid] > x)high = mid - 1;
else low = mid + 1;
}
if (low > high) printf("数组中无此数");
return 0;
}
七、函数
使用C语言编程必须会使用函数。C语言允许用户使用编译系统自带的库函数,例如 printf()和scanf()。去简化程序的开发过程,也支持用户自定义函数。C语言正是通过函数提供支持模块化软件开发的功能。函数是C语言程序的基本构件,也是结构化程序设计思想的重要组成部分
7.1、程序的模块化
7.1.1、程序模块化的定义
将复杂的问题分解成较小的部分是一种常用的方法。在规划大型程序时,首先必须从整体上理解整个问题;然后,把它分成功能相对独立的几个部分(子问题)。如果某个子问题依然复杂,还可以继续将这个子问题分解成更小的子问题,直到问题能被简单、清晰地表述为止。这种从顶层开始,连续地逐层向下将问题分解成便于管理的部分的过程叫做自顶向下的设计。程序设计中这些分解的子问题称为模块。上层模块通过调用下层模块完成程序模块的组装。自顶向下和结构化程序设计原则规定了一个程序必须被分成一个主模块和与其相关的若干模块,当然每个模块还可以继续被分成子模块。这个过程被称为程序的模块化,也称为模块化程序设计方法。
7.1.2、模块化的优点
- 使复杂的问题简单化。
- 模块可重复使用,降低程序开发的工作量。
- 模块功能相对独立,便于维护。
7.1.3、模块间关系处理
自顶向下的模块化程序设计通常用模块的结构图来表示每个模块和它的子模块之间的关系。结构图是自顶向下,从左往右进行阅读的。图示中,主模块用来表示解决问题的代码集,包含3个子模块。模块1被进一步分成3个子模块,模块3被分成2个子模块。如果要编写模块l的代码,就要为它的3个子模块编写代码。
主模块是一个调用模块,每个子模块都是被调用模块。因为模块1和模块3包含子模块,所以它们既是被调用模块,也是调用模块。模块与它的子模块之间可以传递数据,而没有直接调用关系的模块之间要传递数据就必须通过它们共同的调用模块来实现。
7.2、C语言的函数
C语言通过使用函数来实现程序的模块化。一个C程序是由一个或者多个函数组成的,其中有且只能有一个叫做main,被称为主函数。程序的运行从主函数开始,并且结束于主函数。根据需要,主函数调用其他函数来完成特定的任务,其他函数也可以互相调用。图 示表示了一个C程序的结构图
C语言中的函数是个独立的模块,它会被调用去执行一个特定的任务。被调用的函数 (简称被调函数)从调用它的函数(简称主调函数)那里得到程序的控制权。被调函数完成了它的任务后,就把控制权返回给主调函数。被调函数可能返回给主调函数一个数据,也可能不返回。可以说,main函数是被操作系统所调用的,main函数依次调用其他的函数。当main函数执行完成后,就把控制权交还给操作系统。
通常,一个函数可以看成是一个黑盒子,它的任务是得到0个或多个数据,然后对它们进行处理,最后最多返回一个数据。函数也可以通过结构体返回多个数据。同时,一个函数可以产生副作用。函数的副作用是指函数内部的操作会引起程序状态发生变化等附加影响,例如,把数据发送给显示器或者写入文件.会改变主调函数的一个变量的值。
在C语言中使用函数有许多好处,主要有以下几点:
①可以单独编写和测试每个函数,简化了程序开发的过程。
②几个独立的小函数比一个大函数(如果程序只包含一个函数)更容易理解和实现。
③标准库是供人使用的函数集。这些函数是事先编写好的,经过测试,能正常工作,因此可以放心使用,而不必知道它们的代码。标准库可以加快开发程序的速度,是C语言的一个组成部分,也增强了C语言的能力
④人们可以编写自己的函数库,应用于自己感兴趣的程序类型。
⑤在开发大型程序时,可以把程序的开发团队分成小组,每个小组负责一组函数的实现,最后把全部函数组合成完整的程序。这样就可以提高程序开发的效率,也便于管理和维护程序。
/*先定义函数cube,用来计算任意一个整数的立方,然后定义函数main。
在main函数中,两次凋用cube函数分别计算输入的两个整数的立方。
函数cube在被调用时所需要的数据是通过参数传递得到的,
即函数main调用函数cube时把main函数中的变量x或y的值拷贝给cube函数的参数x。
函数cube通过return语句把计算结果传递给函数main。*/
#include<stdio.h>
/*定义整数立方函数,通过返回值来输出函数结果*/
int cube(int x)
{
return x * x * x;
}
/*主程序*/
int main(void)
{
int x, y, z;
printf("请输入两个整数:\n");
scanf_s("%d%d", &x, &y);
z = cube(x) + cube(y);//两次调用立方函数
printf("%d^3+%d^3=%d\n", x, y, z);
return 0;
}
从用户定义的角度,C语言函数可分为库函数和用户自定义函数。
库函数
C语言提供了丰富的库函数,这些函数包括常用的输入输出函数,如printf、scanf函数;常用的数学函数,如sin、cos、sqrt函数;有处理字符和字符串的函数,如strcmp、strcpy、 strlen函数;等等。由于库函数是C语言系统提供的,用户无需定义要使用的库函数,但是需要在程序源代码中包含该函数对应的头文件(如:#include<stdio. h>),然后就可以在程序中直接调用库函数。
用户自定义函数
C语言库函数不可能满足用户的所有需求,为了完成特定功能,用户必须自己编写函数。按C语言规则在程序中定义的用户自己编写的函数,称为用户自定义函数。程序中在调用用户自定义函数前,必须对被调用函数进行说明。
7.3、函数的定义
C语言函数定义的一般形式为:
函数类型 函数名(形参表){ 说明部分 语句部分 }
说明:
函数类型是指函数返回值的类型。如果函数返回值为int类型,则可以省略不写。即默认函数返回值的类型为int类型。这是C89标准的规定,但是建议不要省略,以免破坏代码的可读性。如果函数无返回值,则必须把函数定义为void类型。
函数名,即函数的名称,是用户根据函数所完成的功能给函数的命名,必须是一个合法的标识符。在同一程序中,函数名必须唯一。
形参表包括了0个或多个形式参数的说明,各参数说明之间用逗号隔开,
形参是形式参数的简称,是函数保存从主调函数传递进来的数据的变量。一个形参说明包括参数名称和参数类型说明符,名称在类型说明的后面。形参名必须是一个合法的标识符,只要在同一函数中唯一即可。每一个参数的类型必须单独说明,即使有几个同类型的参数,也不能共用一个类型说明。例如,void swap (int x, int y)不能写成void swap (int x, y)。如果定义的函数是无参函数,参数表留空或写上void,但是不能省略函数名后的一对圆括号。
一对大括号括住的区域是函数体,通常由说明部分和语句部分组成,它决定了函数要实现的功能和任务。函数体可以为空,但是不能省略一对大括号。函数体为空的函数表明它暂时什么也不做,可能在迟些时候再补上代码。
函数不能嵌套定义,即不能在函数内部再定义函数。
根据函数的参数个数和有无返回值,C语言函数可分为无参数无返回值函数、有参数无返回值函数、无参数有返回值函数和有参数有返回值函数。
#include<stdio.h>
/*无参数无返回值的函数,定义welcome函数显示欢迎信息*/
void welcome()
{
printf("Welcome to WuHan University!\n");
}
/*函数welcome不需要从main函数接收数据,也不返回数据,但是有一个副作用,即在控制台窗口中显示一个消息。
即使没有参数,在调用welcome函数时仍然需要括号,否则系统会把welcome当作变量来对待,而不是函数*/
/*有参数无返回值的函数,把接收的日期和时间分量以常用格式显示
定义PrintDateTime函数按常用格式显示日期和时间*/
void PrintDateTime(int year, int month, int day, int h, int m, int s)
{
printf("%04d-%02d-%02d %02d:%02d:%02d", year, month, day, h, m, s);
}
/*函数PrintDateTime从main函数接收一个时间数据的6个分量,但是不返回数据,
而且有一个副作用。副作用是在控制台窗口中按常用格式显示时间数据*/
/*定义一个无参数有返回值的函数,读取输入的一个整数,并把该数返回
定义GetInteger函数读取输入的一个整数*/
int GetInteger()
{
int num;
printf("请输入一个整数:");
scanf_s("%d", &num);
return num;
}
/*函数GetInteger不需要从main函数接收数据,而是直接读取输入的数据,并且用return语句返回一个数据。
这种函数的调用形式可以作为表达式的一部分,在这里是把返回值赋值给变量x和y。*/
/*定义一个有参数有返回值的函数,把一个华氏温度值换算成摄氏温度值。
华氏温度与摄氏温度换算公式为:C= (5/9) *(F-32)。
定义Fahrenheit2C函数把一个华氏温度值换算成摄氏温度值*/
double Fahrenheit2C(double f)
{
return (5.0 / 9.0) * (f - 32.0);
}
/*函数Fahrenheit2C从main函数接收一个数据,用return语句把表达式计算结果作为返回值返回。
在用printf函数输出转换结果时,把调用Fahrenheil2C的表达式直接作为输出项。*/
int main(void)
{
welcome();//第一个函数
printf("现在时间是");
PrintDateTime(2014, 9, 28, 7, 30, 0);
printf("\n");//第二个函数
int x, y;
x = GetInteger();
y = GetInteger();
printf("两个数的和是:%d\n", x + y);//第三个函数
printf("请按任意键继续\n");
getch();
double temperature;
printf("输入一个华氏温度值:");
scanf_s("%lf", &temperature);
printf("转换成摄氏温度值是%.1f\n", Fahrenheit2C(temperature));//第四个函数
return 0;
}
7.4、函数的调用
7.4.1、函数调用的一般形式
不同的函数实现各自的功能、完成各自的任务。要将它们组织起来,按一定顺序执行,是通过函数调用来实现的。主调函数通过函数调用向被调函数传送数据、转移控制权;被调函数在完成自己的任务后,又会将结果数据回传给主调函数并交回控制权。各函数之间就是这样在不同时间、不同情况下实行有序的调用,共同来完成程序规定的任务。
函数调用的一般形式为:函数名(实参表)
如果调用的是无参函数,实参表为空,但是不能省略一对圆括号,这与定义函数是一致的。另外,实参的个数、出现的顺序及类型应与函数定义中的形参表一致。实参将与形参一一对应进行数据传送。实参表包括多个实参时,各参数间也要用逗号隔开。
在程序中调用函数时,应当注意以下几点:
- C语言参数传递时,主调函数中实参向被调函数中形参的数据传送采用传值方式,即把各个实参的值按顺序分别赋值给形参。被调函数执行过程中修改形参的值不会影响主调函数中实参变量的值。但是数组名作为参数传送时不同,是“传址”,会对主调函数中的数组元素产生影响。实际上,传址方式本质上还是传值。
- 由于采用传值方式,实参表中的参数可以是常量和表达式。尤其值得注意的是,当实参表中有多个实参时,C89标准并没有规定对实参表达式求值的顺序。采用自右至左或自左至右求值顺序的系统均有。本书采用的VC2010的C语言编译器是采用自右至左的顺序求值。因此,在程序中调用函数时应尽量避免多个实参表达式求值与它们的计算顺序有关。
- 在调用函数前应该先对函数进行说明。可以通过函数原型或函数定义对函数进行说明。如果不作说明,编译时会报错,因为C语言无法进行实参类型的检查与转换。
- 函数调用也是一种表达式,其值就是函数的返回值。
-
/*定义一个整数幕运算函数,利用该函数计算任意输入的整数n的(n-1)次幂 与(n-l)的n次幕的值,并显示结果*/ #include<stdio.h> /*定义整数幂函数power*/ int main(void) { int n, j; long int l, m; printf("Input a integer:"); scanf_s("%d", &n); /*调用power函数,求n的n-1次幂*/ j = n - 1; l = power(n, j); printf("the %dth power of %d=%ld\n", j, n, l); /*调用power函数,求n的n-1次幂*/ m = power(j, n); printf("the %dth power of %d=%ld\n", n, j, m); return 0; } long int power(int base, int exponent) { long int p; if (exponent > 0) for (p = 1; exponent > 0; --exponent) p = p * base; else { p = 1; } return p; }
7.4.2、函数的参数
参数的传送涉及形参与实参。形参是形式参数的简称,形参是指函数定义一般形式中形式参数表中的参数。在函数定义时,形参表中的参数并没有具体的给定值,仅具有可以接收实际参数的意义。只有在函数被调用、启动后,才临时为其分配存储空间,并接收主调函数传送来的数据,实现函数定义所规定的功能。在函数调用结束后,形参所占存储空间也将会被释放。
实参是实际参数的简称,在主调函数调用被调函数时给出,可以是常量、变量和表达式。C语言中,参数数据传送是实参向形参单方向的"值传送",也称“传值”,即把实参的值拷贝给形参。实参变量与形参变量不共用存储空间,所以,形参接收数据后,不管在被调函数中被怎样处理,其结果均不可能反过来影响主调函数中实参变量的值。通常情况下,实参与形参的类型应该匹配。实参与形参的类型不匹配会导致错误。
/*求给定范围内的素数个数*/
#include<stdio.h>
#include<math.h>
/*定义CountPrimeNumbers函数,寻找给定范围内的素数*/
int CountPrimeNumbers(int rangeS, int rangeE)
{
int i, j, k,n = 0;
if (rangeS < rangeE)
k = rangeS; rangeS = rangeE; rangeE = k;
for (i = rangeS; i <= rangeE; i++)
{
k = sqrt(i);
for (j = 2; j <= k; j++)
if (i % j == 0)break;//不是素数,跳出for循环
if (j > k) n++;
}
return n;
}
int main(void)
{
int a, b,count;
printf("输入两个整数作为要统计素数个数的氛围:\n");
scanf_s("%d%d", &a, &b);
count = CountPrimeNumbers(a, b);
printf("该范围内有%d个素数\n",count);
return 0;
}
7.4.3、函数的返回值
被调函数在完成一定的功能和任务之后,可以将函数处理的结果返回主调函数,这种数据传送称为函数的返回值。函数的返回值通常采用在函数体中用return语句显式给出。return语句的一般形式为:
return [(]表达式[)];
其中的表达式也可以是常量、变量和有返回值的函数调用,其值作为函数值返回给主调函数。可以用一对圆括号把表达式括起来,也可以省略圆括号。函数中可以有多条return语句,但是程序执行至某一条return语句时就终止被调函数。被调函数最终返回给主调函数的值的类型取决于声明的函数类型。
另外,对于不需要提供返回值的函数应该用void作为函数类型定义,表明此函数返回值为“无类型”或“空类型”。作了这样的定义,如错误地在程序中将此类函数作带返回值函数使用,编译时系统会发现并给出错误提示,以便于定位错误。
7.4.4、函数的声明
由于C语言运行时的灵活性,C语言在函数运行时并不对传送的参数类型进行检查,但是实参与形参类型不符可能导致运行结果的错误。为避免此类错误,对被调用函数的说明,标准C语言引入了函数原型的概念。
与变量类似,函数也应该遵循“先声明,后使用"的原则。函数原型是对函数的声明,其作用是把函数名、函数类型以及形参的个数、顺序、类型等通知编译系统。这样当函数被调用时,可对实参、形参的类型、个数等匹配情况进行检查。
函数原型的一般形式为:
函数类型函数名(参数类型1,参数类型2,……,参数类型n) ;
或:
函数类型函数名(类型参数1,类型参数2,……,类型参数n) ;
函数的定义也具有声明函数的作用,因此,如果被调函数的定义出现在它的主调函数之前,也可以不写函数原型。例如,程序7-8把函数Fahrenheit2C的定义放在主函数的前面,起到了声明的作用,因此在主函数中可以直接调用函数Fahrenheit2C,而不需再写该函数的原型。
虽然C语言对函数定义放在何处没有严格规定,但是为了符合依照执行顺序阅读的习惯,通常将主调函数定义放在被调函数的前面,这就违背了函数的“先声明,后使用“原则,所以可以把所有的自定义函数的声明都放在主函数的前面,自定义函数的定义依照调用顺序放在主函数的后面,来规避这个问题。
7.4.5、数组作为函数参数
由于实参可以是变量、常量及表达式,因此,数组元素自然也可以作为函数的实参,在主调函数与被调函数间来传送数据。它们也遵从“传值",即单向从实参向形参传送数据的特性。
另外,由于引进了数组这种数据结构,如果仅仅容许数组元素作为实参来传送数据,在很多情况下,会感到使用起来不方便。C语言规定数组可以作为实参和形参,在主调函数与被调函数间进行整个数组的传送。在调用函数时,与形参数组对应的实参是同类型数组的名称,即数组名作实参。实际上,数组名表示的是数组的内存地址,形参数组是指针变量,在参数传递时依然是值传递方式,只是数值是地址,因此,也被称为“传址”。
用数组作函数参数时,应该注意以下几个方面:
①)数组作为函数参数,应该在主调函数与被调函数中分别定义数组,不能只在一方定义。
②实参数组和形参数组类型应该一致。
③实参数组和形参数组大小不要求一致,因为传送时只是将实参数组的地址传给形参数组。因此,一维形参数组也可以不指定大小,在定义数组时,在数组名后跟一个空的方括弧。被调函数涉及对数组元素的处理,可另设一个参数来指明数组元素的个数。
④因为采用“传址”方式,所以形参数组与实参数组共用一段内存空间(为实参数组分配的),那么,对形参数组的元素值进行修改实际上改变的是实参数组元素的值。
⑤使用多维数组作为函数参数时,同样应该在主调函数与被调函数中分别定义数组,数组类型也应该一致,才不致出错。另外,在被调函数中定义形参数组时,至多也只能省略第一维的大小说明,这是因为多维数组数组元素的存放是按连续地址存放的,不给出各维的长度说明,将无法判定数组元素的存储地址。
#include<stdio.h>
/*声明销售业绩分析函数
参数n是传入的商品种类数量
参数price是传入的各类商品的各月销售价格
参数volume是传入的各类商品的各月销量
参数amount是传出的各类商品的上半年销售总额*/
void SalesAnalysis(int n, double price[][6], int volume[][6], double amount[]);
int main()
{
/*定义二维数组保存3种商品每个月的价格*/
double salesPrice[3][6] = { {23.9, 23.9, 23.9, 22.9, 21.9, 21.9}, {89.9, 89.9, 79.9,79.9, 69.9, 69.9},{65.9, 65.9, 65.9, 65.9, 65.9, 65.9} };
/*定义二维数组保存3种商品每个月的销量*/
int salesVolume[3][6] = { {300, 290, 456, 358, 372, 366}, {155, 134, 98, 79, 82, 94},{233, 215, 242, 201, 239, 251} };
/*定义一维数组保存3种商品上半年的销售总额*/
double salesAmount[3] = {0.0};
int i;
/*调用销售业绩分析函数,计算各类商品上半年的销售总额*/
SalesAnalysis(3, salesPrice, salesVolume, salesAmount);
/*显示各类商品上半年的销售总额*/
printf("种类上半年销售总额(万元)\n");
for (i = 0; i < 3; i++)
{
printf("%-5d%-.4f\n", i+1, salesAmount[i]/10000);
}
return 0;
}
void SalesAnalysis(int n, double price[][6], int volume[][6], double amount[])
{
int t, m;
for (t = 0; t < n; t++)
{
double a = 0.0;
for (m = 0; m < 6; m++)
a += price[t][m] * volume[t][m];
amount[t] = a;
}
}
/*在上面的程序中,函数SalesAnalysis有3个形参是数组,在主函数中用大小和类型相同的数组的名称作为调用该函数的实参。
这样在函数SalesAnalysis被执行时,3个形参数组与对应的实参数组共用一段内存空间,
也就实现了把上半年各类商品的价格和销量数据从主函数传递给了SalesAnalysis函数。
同时,在SalesAnalysis函数中把计算得到的每类商品的销售总额写入 amount形参数组,
实际上就是写入主函数中的salesAmount数组。
可见,主函数调用SalesAnalysis函数产生的副作用就是保存在salesAmount数组中的3类商品的上半年销售总额
*/
7.4.6、函数的嵌套调用
在一个函数调用过程中又调用另一个函数称为函数的嵌套调用。在C语言中,由于函数的定义是独立的,各函数均处于平行的关系,理论上,任何一个函数都可调用其他的函数,甚至调用它本身。main函数为例外。原则上,C语言对函数的嵌套调用深度并未刻意加以限制。
/*编写一个计算圆环面积的程序。
定义函数计算圆的面积,再定义一个函数计算由两同心圆组成的圆环的面积。
编写程序由用户输入两同心圆的半径,利用前面的两个函数计算圆环的面积,并显示结果*/
#include<stdio.h>
#define PI 3.1415926
double CircleArea(double r);
double RingArea(double rl, double r2);
int main(void)
{
double randius1, randius2, area;
printf("输入两同心圆的值randius1,randius2:");
scanf_s("%lf%lf", &randius1, &randius2);
area = RingArea(randius1, randius2);
printf("面积为:%.2f\n", area);
return 0;
}
double CircleArea(double randius)
{
return PI * randius * randius;
}
double RingArea(double randius1, double randius2)
{
double area;
area = CircleArea(randius1) - CircleArea(randius2);
if (area < 0) area *= -1;
return area;
}
/*关于程序执行的几点说明:
(1) 程序总是从主函数开始执行,主函数通过调用库函数scanf,得到用户从键盘输入的两个半径值,
然后再通过调用自定义函数RingArea,得到圆环的面积,最后通过调用库函数 printf输出圆环的面积。
(2) 函数RingArea被调用执行时,通过形参接收主函数传来的两个半径值,
然后两次调用函数CircleArea分别计算两个圆的面积,再将得到的两个圆的面积相减,得到圆环的面积.返回给主函数。
(3) 函数CircleArea被调用执行时,通过形参接收RingArea函数传来的半径值,计算圆的面积,返回给CircleArea函数。
*/
7.4.7、函数的递归调用
数学函数中有一些是采用递推形式定义的,例如求一个数n的阶乘,以及斐波那契亚数列
以求一个数n的阶乘为例:在求解n的阶乘中使用了(n-1)的阶乘,也即要算出n!,必须先要算出(n-l)!,而要算出(n-1)!,又必须算出(几-2) !'依次类推,直至l ! = 1。一旦求得1! = 1,再以此为基础,依次计算2!, 3!,…,(n-1)!, n!。
解决此类递归函数的求值问题,所使用的函数必须要能调用自身。在执行过程中直接或间接调用自身的函数在C语言中是允许的,被称为递归函数,又称自调用函数。对递归函数进行调用,称之为递归调用,递归调用是一种特殊的嵌套调用。递归调用必须在满足一定条件时结束递归凋用,否则无限制地递归调用将导致程序无法结束(死递归)。
/*编写一个计算整数阶乘的程序。其中,定义递归函数求解任意整数n的阶乘*/
#include<stdio.h>
long int fact(unsigned int n);
int main(void)
{
int n;
long int sum;
printf("请输入一个整数:");
scanf_s("%d", &n);
sum = fact(n);
printf("%d!=%ld\n", n, sum);
return 0;
}
long int fact(unsigned int n)
{
long int f;
if (n == 1)
f = 1;
else
{
f = fact(n - 1)*n;
}
return f;
}
递归函数的结构十分简练,构造递归函数的关键是找到适当的递归算法和终结条件,因为递归的过程不能无限制地进行下去,必须要有一个结束此过程的条件。终结条件往往是问题最简单情形,可以通过简单的计算或处理就能解决,或者直接知道解答。
要实现函数的递归调用,首先要分析问题是否可以采用递归形式来定义,数学函数中有不少是采用递归形式定义的,有了清晰的定义,就可以很容易地编写递归函数。实际上,前面两个例子的计算问题都可以采取迭代的方法,通过使用while语旬构造显式的循环结构加以实现。还有一类问题只有使用递归方法才好解决,典型的例子是汉诺(Hanoi)塔问题。
汉诺塔问题是指在一个塔座(设为塔X)上有若干片圆盘,圆盘的大小各不相等,按大盘在下、小盘在上的顺序叠放,现要将其移放至另一个塔座(设为塔Z)上去。问:仅依靠一个附加的塔座(设为塔Y),在每次只允许搬动一片盘片,且在整个移动过程中始终保持每座塔座上的圆盘均为大盘在下、小盘在上的叠放方式,能做到么?如何移动?
对此问题,采用递归的方法:对于数量不等的盘子(设为n,片),只要能将除最下面最大的一片盘子外,其余的盘片(n-1片)移至塔Y座上,剩下一片就可直接移至塔Z上。其余的(n-1片)盘片既能从塔X移至塔Y,自然也可照理从塔Y移至塔Z,问题就解决了。每次使用同样的办法解决最下面最大一片盘子的移动问题,一次次搬下去,直至剩下最后一片盘子,直接搬到塔Z上去就可以了。图7-4(b)展示了用递归方法解决3片盘的汉诺塔问题的思路。这就是典型的递归方法,递归的结束条件是只剩下一片盘子时,可直接移至目的座(塔Z)上。
/*编写一个解决任意规模汉诺塔问题的程序,要求能显示盘片移动过程中
每一搬动步骤,其中塔座X、Y、Z分别用字符`X'、'Y'、`Z'表示。*/
#include<stdio.h>
#include<conio.h>
/*把编号为n的盘从s塔移到d塔*/
void move(int n, char s, char d);
/*把n个盘从x塔移到z塔,y塔作为辅助塔*/
void hanoi(int n, char x, char y, char z);
int main(void)
{
int n;
printf("输入汉诺塔问题的圆盘数量:");
scanf_s("%d", &n);
hanoi(n, 'X', 'Y', 'Z');
printf("请按任意键继续……");
getch();
return 0;
}
void hanoi(int n, char x, char y, char z)
{
if (n == 1)
move(n, x, z);
else
{
hanoi(n - 1, x, z, y);
move(n, x, z);
hanoi(n - 1, y, x, z);
}
}
void move(int n, char s, char d)
{
printf("%d\t%c-->%c\n", n, s,d);
}
7.5、变量的作用域
7.6、变量的储存类别
7.7、较大型C语言程序的组织
7.7.1、头文件
7.7.2、内部函数和外部函数
7.7.3、构建多文件的程序
7.8、程序设计案例
函数是C语言的基本单元
在C语言中,函数是程序的基本组成单位。C语言程序仅由一个主函数,即main函数构成;C语言程序中的函数由说明部分和函数体两部分组成,
函数一般形式为:函数类型 函数名称(参数表){说明语句;功能语句}
函数说明:说明函数的类型、函数名和函数参数
函数体:包括变量说明和语句执行部分
函数说明部分:说明函数是指说明函数的类型、名称、参数以及参数的类型;
函数参数列表
函数的返回值
标准函数:C 编译系统提供的库函数。(包括:输入输出函数、数学函数、字符和字符串函数、时间函数、动态存储分配函数等)
函数在使用之前必须说明,说明函数是指说明函数的类型、函数的名称、函数的参数、参数的类型;用户在调用库函数时,实现需要知道函数在哪个头文件里,在程序开头用include命令加以说明,这样程序在编译、链接时系统才知道提供的是库函数而不是自己编写的函数,不需要链接
标准库函数
根据应用功能对函数分类
字符处理函数
转换函数
目录路径函数
诊断函数
图形函数
输入输出函数
接口函数
字符串处理函数
内存管理函数
数学函数
日期和时间函数
进程控制函数
printf不要使用%lf
八、指针
九、文件处理
十、盲点注意
⑴一个C程序由一个或多个函数组成,但有且仅有一个主函数(main函数);main函数是程序执行的入口,也是程序结束运行的出口,可置于程序的任何位置;一个项目中即使有多个文件,所有文件中有且仅有一个主函数main()。
⑵程序中可以有预处理命令(如:include 命令)。预处理命令通常放在程序的最前面。
⑶每个语句以分号结尾;预处理命令、函数头和花括号“}”之后不能加分号。
⑷ 函数包括函数的说明部分和函数体;函数体包括变量说明部分和执行部分;函数体由一对花括号“{ }”括起来。
⑸标识符和保留字之间须加空格以示分隔。
⑹一行可以写几个语句,一个语句也可以写在多行上。
⑺程序区分大小写字母。一般变量、语句等用小写字母书写;符号常量、宏名等用大写字母书写;C语言保留字均使用小写字母。
(8)在程序中出现的操作对象(如变量、数组、函数等)在使用前需要进行说明或者定义