




版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领
文档简介
第5章数组5.1一维数组5.2二维数组与多维数组5.3字符数组5.4数组的综合示例本章小结
C语言提供了多种数据类型,除了前面介绍的整型、实型和字符型等基本数据类型外,还有一些扩展的数据类型,如数组、指针、结构等。由于它们是由基本数据类型按一定规则组成的,所以被称为复合数据类型或构造数据类型。
5.1一维数组
一维数组是最简单的数组,数组元素只有一个下标。5.1.1一维数组的定义及引用1.一维数组的定义在C语言中,与变量的定义一样,数组也遵循“先定义后使用”的原则。一维数组的定义格式为类型说明符数组名[常量表达式];
例如:
shortscore[8];
表示定义一个数组,数组名为score,共有8个元素,每个元素的数据类型均为短整型。
当定义了一个数组之后,C编译程序会为所定义的数组在内存中开辟一串连续的存储单元,本例定义的score数组在内存中的排列如图5-1所示。
图5-1数组在内存中的排列
在定义一维数组时,应注意以下几点:
(1)类型说明符用来说明数组元素属于何种数据类型,如int、char、float或double等。
(2)数组名由用户自定义,与变量名的命名一样,遵循标识符命名规则。
(3)数组名后必须用“方括号”括起常量表达式,不能用其他括号。
(4)常量表达式定义数组的长度,表示数组的元素个数。
(5)常量表达式中一般包括整型常量、字符常量或符号常量,但不能包括实型(符号)常量或字符串(符号)常量。
(6)常量表达式中不能包括变量。
(7)数组元素的下标从0开始,上例定义的数组的8个元素分别是score[0]、score[1]、score[2]、score[3]、score[4]、score[5]、score[6]和score[7]。注意最大下标是7而不是8。
(8)相同类型的数组、变量可放在一起定义,中间用逗号隔开。
2.一维数组的引用
C语言规定数组不能以整体形式参与各种运算。参与各种运算的只能是数组元素,即
在程序中不能一次引用整个数组而只能逐个引用数组元素。一维数组元素的引用形式为
数组名[下标]
其中,下标可以是整型常量、整型变量或整型表达式。
数组元素与普通变量的表现形式不同,但实质是相同的,它也是一种变量。因此,一个数组元素可以像普通变量那样参与赋值、算术运算、输入和输出等操作。下面介绍一维数组的输入、输出操作,以便读者熟练掌握一维数组元素的引用。
1)一维数组的输入
我们可以在程序运行期间用赋值语句或从键盘输入语句scanf()为数组元素赋值。一般用一个循环语句来赋值。例如:
2)一维数组的输出
一维数组的输出是指用输出语句printf()将数组的元素逐个输出。例如:
从这些示例可以看出,数组元素是一种带下标的变量,它跟普通变量一样参与赋值、输入和输出等操作。但是,绝对不能把“数组名”当成变量一样使用。例如,定义
inta[100];
intb[10]={1,2,3,4,5,6,7,8,9,10};
后,采用如下语句进行数据输入输出操作:
scanf("%d",&a);
printf("%d",b);
则是错误的,无法输入或输出整个数组。
例5-1从键盘输入10个数,将这10个数逆序输出,然后求出这10个数的和并输出。
5.1.2一维数组的初始化
数组的初始化是指在定义数组的同时为数组元素赋初始值。一维数组在定义时进行初始化的格式为
类型说明符数组名[常量表达式]={值1,值2,…,值n};
其中,大括号中的各个值依次对应数组中的各个元素。各个值之间用逗号隔开。例如,数组定义及初始化语句为
intx[5]={1,2,3,4,5};
则有x[0]=1,x[1]=2,x[2]=3,x[3]=4,x[4]=5。
在初始化一维数组时,应注意以下几点:
(1)若“{}”中初值的个数小于数组元素个数,则只有数组的前部分元素对应获得初值,后部分没有获得初值的元素则置相应类型的默认值(如int型置整数0,char型置字符'\0',float型置实数0.000 000等)。例如,定义
intx[5]={1,2,3};
则有x[0]=1,x[1]=2,x[2]=3,x[3]=0,x[4]=0。
(2)若“{}”中初值的个数等于数组元素个数,则在数组定义时可省略元素个数,此时数组长度由{}中值的个数来决定。例如,定义
intx[]={1,2,3,4,5};
就相当于intx[5]={1,2,3,4,5}。
(3)若“{}”中初值的个数大于数组元素个数,则编译时会出现“toomanyinitializers”之类的错误,表示初值个数太多。例如,定义
intx[5]={1,2,3,4,5,6};
是不对的。因为初值个数6超过了定义的数组长度5。
5.1.3一维数组的应用
例5-2查找最大值。
解题思路第一个for语句逐个输入10个数到数组a中。然后把a[0]送到max中。在第二个for语句中,将a[1]~a[9]逐个与max中的数进行比较,若比max的值大,则把该数组元素送入max中,因此max的值在已比较过的数组元素中总是最大的。比较结束,输出max的值。
例5-3从键盘上任意输入10个整数,要求按从小到大的顺序显示出来。排序的方法有很多种,本例采用冒泡法。
解题思路冒泡法的基本思想:通过将相邻两个数进行比较和交换,使排序码(数值)较小的数逐渐从底部移向顶部,排序码较大的数逐渐从顶部移向底部。这就像水底的气泡一样逐渐向上冒,故而得名冒泡法。
由A[0]~A[n - 1]组成的n个数据,进行冒泡排序的过程描述如下:
首先将相邻的A[n - 1]与A[n - 2]进行比较,如果A[n - 1]的值小于A[n - 2]的值,则交换两者的位置,使较小的数上浮,较大的数下沉;接着比较A[n - 2]与A[n - 3],同样使小的数上浮,大的数下沉;依次类推,直到比较完A[1]和A[0]后,A[0]为具有最小排序码(数值)的元素,此时第一趟排序结束。
然后在A[1]~A[n - 1]区间内,进行第二趟排序,使剩余元素中排序码最小的元素上浮到A[1]。重复进行n-1趟排序后,整个排序过程结束。
5.2二维数组与多维数组
前面介绍的一维数组,它的数组元素只有一个下标,说明只用一个表示数组长度的常量表达式即可。如果一维数组的每个元素本身也是一个一维数组,则形成了一个二维数组。这时就要用两个下标来表示它的每个数组元素。多维数组是指二维及以上的数组,通常被表示为数组的数组,即一个数组的元素是其他数组。
5.2.1二维数组的定义及引用
1.二维数组的定义
二维数组的定义格式为
类型说明符数组名[常量表达式1][常量表达式2];
例如:
inta[3][4];
定义了一个二维数组,数组名为a,数组元素有3行4列,每个数组元素都是一个整型数据。
在定义二维数组时,应注意以下几点:
(1)类型说明符、数组名及常量表达式的要求与一维数组的相同。
(2)常量表达式1和常量表达式2各在一个方括号内,例如,定义不能写成
inta[3,4];
(3)二维数组可以看成一种特殊的一维数组,其特殊之处就在于它的元素又是一个一维数组。例如,二维数组a[3][4]可以理解为它有3个元素a[0]、a[1]、a[2],每一个元素却又是一个包含4个元素的一维数组,如图5-2所示。
图5-2二维数组a[3][4]的“组成”
(4)二维数组的元素在内存中的存放顺序为“按行存放”,即先顺序存放第一行的元素,再存放第二行的元素,依次类推,如图5-3所示。从图中可以看出,最右边的下标变化最快,最左边的下标变化最慢。这一特点也适用于二维以上的多维数组。图5-3二维数组在内存中的存放方式
2.二维数组的引用
二维数组元素的引用格式为
数组名[下标1][下标2]
5.2.2二维数组的初始化
二维数组的初始化跟一维数组初始化一样,也有3种方法。
(1)在定义数组的同时为数组元素赋初值。
①按行对二维数组赋初值。例如:
inta[3][4]={{88,78,98,85},{86,92,76,71},{82,96,84,77}};
其初始化结果可用一个二维表表示,如图5-4所示。这种赋初值方法比较直观,即把内层的第一个大括号内的数据赋给第一行的元素,第二个大括号内的数据赋给第二行的元素,依次类推即可。图5-4二维数组的初始化
也可以用这种方法只给数组中部分元素赋初值。例如:
inta[3][4]={{88}{0,0,76}};
其初始化结果可用一个二维表表示,如图5-5所示。图5-5二维数组的部分赋值
②把所有的数据写在一个花括号里,系统按数组的“按行存放”排列次序对各个元素赋初值。例如:
inta[3][4]={88,78,98,85,86,92,76,71,82,96,84,77};
其效果也如图5-4所示。
③如果对全部元素赋初值,则在定义中可省略第一维的长度,但第二维长度不可省,例如:定义
inta[3][4]={88,78,98,85,86,92,76,71,82,96,84,77};
与定义
inta[][4]={88,78,98,85,86,92,76,71,82,96,84,77};
是等价的。这是因为系统编译器可以根据数据总个数和列数来确定行数,故行数可以缺省。
(2)在程序运行时,用赋值语句为数组元素赋值。例如:
for(i=0;i<100;i++)
for(j=0;j<200;j++)
a[i][j]=0; //常用于对所有的数组元素赋一个初始状态值
(3)在程序运行时,用输入语句为数组元素赋值。例如:
for(i=0;i<100;i++)
for(j=0;j<200;j++)
scanf("%d",&a[i][j]);
例5-4用二维数组保存3个班的英语成绩(每个班20人),并求每个班的平均成绩。
5.2.3二维数组的应用
例5-5上三角矩阵是指主对角线以下的元素都为0的矩阵;主对角线为从矩阵的左上角至右下角的连线。试编写程序,判断一个给定的方阵是否为上三角矩阵。
例5-6应用数组构造n为6的杨辉三角形,并输出。杨辉三角形如图5-6所示。图5-6杨辉三角形
解题思路杨辉三角形是一种在数学中非常著名的三角形排列形式。它是一个用于表示二项式系数的数列。杨辉三角形的结构特点和生成方式如下:
(1)杨辉三角形是一个下三角矩阵。
(2)第一行和第二行只有元素1。
(3)从第三行开始,每行的第一个和最后一个元素都是1。
(4)对于中间的元素,它等于上一行中两个相邻元素之和。例如,第n行的第k个元素等于上一行的第k-1和第k个元素之和。
数学表达式是:C(n,k) = C(n - 1,k - 1) + C(n - 1,k)。
5.2.4多维数组
二维数组实际上是一种最简单的多维数组。C语言允许使用高于二维的多维数组,如三维数组、四维数组甚至更高维数的数组,允许使用的数组的最大维数由不同的C编译器
决定。在实际应用中,经常用到的是一维、二维和三维数组,四维以上的数组极少使用。
多维数组的定义格式为
类型说明符数组名[常量表达式1][常量表达式2]……[常量表达式n];
其中,维数由常量表达式的个数n来决定。若n为3,则是一个三维数组。例如:
floata[2][3][4];
定义了一个三维数组,它可以理解为,三维数组a包含2个二维数组(a[0]和a[1]),每个二维数组包含3个一维数组,而每个一维数组包含4个float型的数组元素(如a[0][0]包含a[0][0][0]、a[0][0][1]、a[0][0][2]和a[0][0][3]),如图5-7所示。图5-7三维数组a[2][3][4]的“组成”
a[2][3][4]在内存中的存放顺序,如图5-8所示。图5-8三维数组在内存的存放顺序
在多维数组中,引用数组元素时其下标个数要与维数相等。由图5-7可知,对于三维数组,引用数组元素时其下标个数应当为3,形如a[i][j][k]。若下标个数小于维数,则不能代表数组元素,而只相当于数组名,如a、a[i]和a[i][j]分别代表三维、二维和一维数组名。
引入多维数组可以使编程更为灵活,因为多维数组的每一维都可以根据实际情况的不同而赋予不同的含义,从而使多维数组能描述比较复杂的数据结构。
例5-7某年级共有4个班,每班各有30名学生,有6个科目的考试成绩。求各班每个学生的平均成绩并输出。
5.3字符数组
在字符数组中,每个元素为一个字符。例如,用一个一维的字符数组来存放字符串“Iamastudent”,字符串中的字符是逐个被存放到数组元素中的。由于字符是以ASCII码的形式存储,因此理解了数值数组后,字符数组也就很容易理解了。
5.3.1字符数组的定义及引用
1.字符数组的定义
用来存放字符型(char)数据的数组是字符数组。字符数组中的每一个元素都可以存放一个字符,因此一个字符数组不仅可以存放多个字符,也可存放字符串(字符串是含有结束标志的字符数组)。因为字符数组在存放字符串时,字符串末尾的结束符'\0'也一并存放,所以,一个字符串用一维数组来存放时,数组元素个数一定要比字符数多一个。
例如:
charc[13]="Howareyou!";
定义了一个字符数组c,存放的字符串中字符数为12,考虑还有1个字符串结束符 '\0',因此定义的数组长度不能少于13。
一维字符数组的定义格式如下:
char数组名[数组长度];
例如:
charc[7];
定义了一个名为c的长度为7的一维字符数组,c的每个元素可存储一个字符,整个字符数组可存储一个字符个数少于7的字符串。
二维字符数组的定义格式如下:
char数组名[行长度][列长度];
例如:
charstr[4][9];
定义了一个名为str的4行9列的二维字符数组,每行可存储一个字符个数少于9的字符串,一共可存储4个字符串。
二维字符数组元素存储的顺序与二维数值数组完全相同,在此不再赘述。
此外,也可用整型数组存储字符串,但会有一半的存储空间被浪费。
2.字符数组元素的引用
字符数组可以与数值数组一样,按元素引用和赋值,此时的下标形式、取值范围也与数值数组相同。
5.3.2字符数组的初始化
为字符数组元素指定初值,称为字符数组的初始化。字符数组的初始化也有两种方法:
(1)在定义的同时给数组赋初值。若提供的初值(字符)个数大于数组长度,则按语法错误处理(如编译时会出现诸如Error:Toomanyinitializersinfunctionmain之类的错误信息);若初值个数等于数组长度,则数组长度值可以省略不写;若初值个数小于数组长度,则只将这些初值字符赋给前面的数组元素,其余的元素自动置为空字符(即 '\0')。
例如,定义:
charc[8]={'G','O','O','D'};
后,数组在内存中的存放如图5-9所示(图中假设首地址为2000)。图5-9数组在内存中的存放
(2)用字符串常量对字符数组进行初始化。例如:
例5-8请编写一个程序,其功能是:将一个数字字符串转换为一个整数(不得调用C语言提供的将字符串转换为整数的函数)。例如,若输入字符串"-1688",则程序要能把它转换为整数值-1688并输出。
5.3.3字符串
在前面我们提到了字符串常量。所谓字符串常量就是用双引号括起来的一组字符。但实际上,字符串是一种字符型数组,并且这个数组的最后一个单元的值是 '\0' 。也就是说,字符串是一种以 '\0' 结尾的字符数组。这个结尾的字符 '\0' 唯一的作用就是标识字符串的结束。比如,字符串常量 "china" 地内存中的存放如图5-10所示。图5-10字符串在内存中的存放
5.3.4字符数组和字符串的输入与输出
1.字符数组的输入与输出
1)用%c格式输入/输出
用%c格式输入时,每个字符都有效。
例5-9从键盘输入一个学生姓名的全拼字母,并输出。
例5-10用%s格式改写例5-9的程序。
例5-11输入一个英文单词,若字母为小写,则将其转换为大写字母并输出。
2.字符串的输入与输出
1)字符串的输入
除了可以通过初始化使字符数组各元素得到初值外,也可以使用getchar()函数、scanf()
函数或gets()函数输入字符串(或字符数组)。
(1)逐个字符输入。
(2)整个字符串一次输入。
2)字符串的输出
字符串(或字符数组)的输出可以使用putchar()函数、printf()函数或puts()函数来实现。
(1)逐个字符输出。
(2)整个字符串一次输出。
5.3.5常用字符串处理函数
C语言提供了丰富的字符串操作函数,除了上面介绍的用于字符串输入输出的gets()和puts()函数外,还有很多其他专门的字符串操作函数,这些函数包含在头文件“string.h”中。因此,若需使用这些函数,应在程序之前加上语句:
#include<string.h>
下面介绍几种常用的字符串操作函数,这里写出这些函数实现的参考程序作为字符串操作的例子。
1.strlen(字符数组)
strlen(字符数组)的功能是测试字符串的长度。函数值为字符串的实际长度,不包括结束符 '\0'。
2.strcpy(字符数组1,字符数组2)
strcpy(字符数组1,字符数组2)的功能是把字符数组2中的字符串复制到字符数组1中去(即给一个字符数组赋值)。例如:
chars1[8],s2[]="GOOD";
strcpy(s1,s2);
执行后,s1数组的状态如图5-11所示。图5-11s1数组的状态
3.strcat(字符数组1,字符数组2)
strcat(字符数组1,字符数组2)的功能是把字符数组2中的字符串连接到字符数组1的字符串后面。
连接前后数组的状态如图5-12所示。
图5-12连接前后的数组状态
4.strcmp(字符数组1,字符数组2)
strcmp(字符数组1,字符数组2)的功能是对字符数组1中的字符串和字符数组2中的字符串进行比较。比较规则为:按字符ASCII的大小自左至右逐个比较两个字符串的字符,直到出现不同的字符或遇到 '\0' 为止。若全部字符相同,返回值为0;若字符串1>字符串2,则返回值为一个正数;若字符串1<字符串2,则返回值为一个负数。
5.3.6字符数组的应用
例5-12有3个字符串,要求找出其中“最大”者。
解题思路如果将两个字符进行比较,所谓“大”者是指字符的ASCII码较大的那个字符。例如,字符 'a'大于字符 'A'。如果是字符串,则从第一个字符开始一一进行比较,如果第一个字符相同,就比较下一个字符,直到出现不同为止。如果字符串中都是英文字母,有一个简单的判定方法:按英文字典的顺序,字典中位置在后的较大,例如 "girl">"boy","then"<"they"。
题目要求处理3个字符串,需要定义一个二维的字符数组(取名str),假定每个字符串不超过19个字符,则可定义二维字符数组的大小为3 × 20,即有3行20列,每一行可以容纳20个字符(包括最后的结束符 '0')。可以把str[0]、str[1]、str[2]看作3个一维字符数组,它们各有20个元素。现用gets()函数分别读取3个字符串。经过两次比较,就可得到最大者,把它放在一维字符数组string中。为叙述方便,把str[0]、str[1]、str[2]分别简称为串0、串1、串2。
例5-13输入一行字符,统计其中的单词总数,单词之间用空格分隔开
解题思路要统计输入行中的单词总数,可以定义单词为连续的非空格字符序列,并将连续的空格看作单词的分隔符。程序中,首先读取整行字符串,然后遍历每个字符。为了区分新单词的开始,可使用两个变量num和word。num(初始化为0)用来统计单词的个数,word(也初始化为0)作为标志位,指示当前是否在单词内。
遍历字符串时,若遇到空格,则将word置0,表示目前不在单词中。若当前字符不是空格并且word为0(表示上一个字符是空格或者是字符串的开始),则将word置1,并将num增加1,因为这意味着遇到一个新单词。如果当前字符不是空格且word已经为1,则继续遍历,因为这仍然是当前单词的一部分。遍历到字符串的结尾则遍历结束。最后,num变量就表示了输入字符串中的单词总数。
5.4数组的综合示例
例5-14求任意两个正整数(不超过8位)之间所有整数所包含的数字0~9出现的次数。解题思路因为输入的两个整数均不超过八位数,所以可以定义两个长整型变量来存储。通过循环将每次得到的整型数用sprintf()函数将其转换成8个字符的字符串并存放到字符数组str中,字符串不足8位时高位部分补空格字符,再对str中每个字符进行统计。数字出现的次数可以定义一个长整型数组count来表示,其大小为10,count[0]存放0出现的次数,count[1]存放1出现的次数,…,count[9]存放9出现的次数。
例5-15输入一行字符,统计其中各个大写字母出现的次数。
解题思路定义一个拥有26个int类型数组元素组成的一维数组num,用于存放26个大写字母出现的次数,num[0]存放字母 'A' 的次数,num[1]存放字母 'B' 的次数,…,num[25]存放字母 'Z' 的次数。调用库函数memset()将数组num清0。通过getchar()函数读入当前字符到ch变量中,如果是回车换行符 '\n',则循环结束,否则,判断ch是否为大写字母,是则数组num中相应数组元素num[ch-'A']加1。最后输出统计结果。
例5-16幻方是一种古老的数字游戏,n阶幻方就是把整数1 - n²排成n × n的方阵,使得每行中的各元素之和,每列中各元素之和,以及两条对角线上的元素之和都是同一个数S,S称为幻方的幻和。奇数阶幻方的构造方法很简单,图5-13所示为一个三阶幻方。图5-13三阶幻方
解题思路各数在方阵中的位置可以这样确定。首先把1放在最上一行正中间的方格中。然后把下一个整数放置到右上方,如果到达最上一行,下一个整数放在最后一行,就好像它在第一行的上面。如果到达最右端,则下一个整数放在最左端,就好像它在最右一列的右侧。当到达的方格中填上数值时,下一个整数就放在刚填写上数码的方格的正下方,照着三阶幻方,从1至9走一下即可。
例5-17最“牛”的一条微信。如果26个英文字母A~Z或a~z(不区分大小写)分别等于1~26,那么可以得到下面的计算结果。
例5-18用数字1,2,3,4,…,n × n这n²个数蛇形填充规模为n × n的方阵。蛇形填充方法为:对于每一条左下-右上的斜线,从左上到右下依次编号0,1,…,2n - 2;按编号从小到大的顺序,将数字从小到大填入各条斜线,其中编号为奇数的从左下向右上填写,编号为偶数的从右上到左下填写。
输入n,最后输出n × n的蛇形矩阵。比如n = 4时,方阵填充为如下形式:
解题思路通过遍历方阵的斜线,可以用一个统一的方法填充整个方阵。方阵被分为上半部和下半部。在上半部,从左上角开始,依次处理每一条从左下到右上的斜线。对于编号为偶数的斜线(从0开始编号),从右上向左下填充数字;而对于编号为奇数的斜线,从左下向右上填充。这一过程持续到达中线。当在处理下半部时,继续遵循相同的规则,但起始点变为每条斜线的最左侧或最下侧,直到填满整个方阵。通过这种方式,能保证每个数字正确地按照蛇形规则填入相应的位置。
本章小结
本章主要介绍了一维数组、二维数组与多维数组以及字符数组的概念和用法。一维数组是相同数据类型元素的有序集合,二维数组是一维数组的数组,而多维数组则是二维数组的数组,以此类推。字符数组是一种特殊的一维数组,用于存储字符串。这些数组类型在C语言中广泛应用于数据存储和处理。通过学习数组,学习者可以理解如何使用数组来有效地组织和操作数据,从而实现各种算法和数据结构。第6章函数6.1函数定义和声明6.2函数调用过程6.3函数的递归与嵌套调用6.4作用域与存储类型6.5函数与数组6.6函数的综合示例本章小结
6.1函数定义和声明
6.1.1函数定义的一般形式
函数定义的一般形式为函数类型说明函数名(形参说明表){说明部分;执行部分;}
6.1.2函数定义的要点
1.函数类型的说明
如上所述,函数类型指函数返回值的数据类型。函数返回时可能得到0个数据、1个数据或多个数据。
1)函数无返回值
如果函数没有返回值,则一般在定义函数时把函数类型写为void。例如:
voidPRINT()
{
printf("Test\n");
}
2)函数有1个返回值
如果函数有1个返回值,则在被调用函数的函数体中有return语句。此时函数类型的定义应根据return语句后的表达式的数据类型来定义。例如:
intmax(inta,intb)
{
returna>b?a:b;
}
3)函数有多个返回值
如果函数有多个返回值,则在被调函数的函数体中一般无return语句。多个值的返回是通过全局变量、数组或指针来实现的,这将在后面的章节中进行介绍。
2.函数的命名
函数名是函数的标识,函数的命名规则与变量命名规则相同。通过引用函数名,可调用该函数。
3.形参表及形参的说明
1)形式参数
在函数定义中写在圆括号中的参数称为形式参数(简称形参)。不带形参的函数其形参表有两种表现形式:一是形参表空着,二是在形参表中写上“void”以确定没有形参。例如:
voidPRINT(void)
{
printf("Thisisaexample\n");
}
两种表现形式的区别:若形参表空着,则在函数调用时,给一个或多个实在参数(简称实参),编译器不会提示有错误或警告;若形参表中写“void”,在函数调用时,给一个或多个实参,则编译器会给出警告“warningC4087:'PRINT':declaredwith'void'parameterlist”。
有形参函数的形参表由一个或多个形参组成,各参数间用逗号分隔,且必须给出每个参数的数据类型。
2)函数间利用参数传递数据
函数间通过参数传递数据,是指将调用函数中的实参向被调用函数中的形参按从右向左顺序依次传递。
实参向形参传递数据是指实参将值单向传递给形参,形参值的变化不传递给实参。其原因在于每次调用函数,都必须给形参分配新的空间,而不是复用原来实参空间,
因此修改形参不会影响实参。
例如:
即实参a、b两个变量的值没有得到交换。这里用图6-1来表示swap()函数调用开始时和结束后各参数的值。
图6-1swap函数调用过程中各参数的变化
5.主函数的定义
本书采用VisualC++2010Express作为集成开发环境。对于main()函数的定义特进行如下说明:
(1)同样的main()函数定义和源码,源文件的扩展名为“.c”时的编译结果与扩展名为“.cpp”时的编译结果可能不同,建议扩展名用“.c”,以确定采用C语言(而不是C++语言)的语法规则编译程序。
(2)对于C语言主函数的定义,在C99中规定了如下两种标准格式。
① intmain(void)
② intmain(intargc,char*argv[])
(3) VisualC++2010Express主要是用于C++程序开发的,对于C语言,兼容C89标准,因此,本书有些C程序中的main()函数仍然定义成main(){……}这种简单的形式。
(4)虽然VisualC++2010Express允许voidmain()的定义,但也要少用。因为这种用法不符合C99标准,很多编译器不支持。
(5) main()函数的返回值用于说明程序的退出状态。
综上所述,我们可以得出如下结论:
(1)如果C程序单纯只考虑能在VisualC++2010Express中编译通过,则主函数的定义形式几乎不受限制,可以定义成main(){……}这种简单的形式,也可以定义成voidmain(){……}。
(2)如果C程序要考虑可移植性,则要遵循主流标准。调试C/C++程序,符合C99标准及C++98标准的主函数定义形式为
6.1.3函数的声明
定义好的函数需要调用时,一般应在主调函数中对被调函数进行声明,即向编译系统声明将要调用此函数,并将有关信息(如被调用函数名、函数类型、形参的个数及类型等)通知编译系统。
函数声明的格式如下:
函数类型说明函数名(形参及其类型);
函数声明又称为函数接口(Interface)或接口说明,这种格式很像函数的定义,初学者很容易混淆。其实,它们的区别是很明显的:函数定义具有函数体,且在源程序中只能定义一次;函数声明没有函数体,且在不同的主调函数中可多次出现。
函数从来源上分,有系统库函数和用户自定义函数两种。
对于库函数的接口说明,系统按功能不同组成相应的头文件,如所有数学函数接口说明头文件为“math.h”,标准输入/输出库函数接口说明头文件为“stdio.h”。如果调用系统库函数,必须用预编译命令“#include”把相应的头文件包含在源程序的首部,之后就不必再进行接口说明了。例如,在某函数中调用数学函数sqrt(),则在源程序的开头加命令:
#include<math.h>
对于用户自定义函数,若被调函数定义在主调函数之前,可缺省函数接口声明;若被调函数定义在主调函数之后,则一定要进行函数接口声明。
6.2函数调用过程6.2.1函数调用的一般形式一个C程序是由一个或多个独立的函数组成的,在一个函数中引用另一个函数,称为函数的调用。其中引用另一个函数的函数,称为主调函数,被引用的函数,称为被调用函数。主调函数中的参数,称为实在参数,而被调用函数中的参数称为形式参数。一般地,主函数main()只能被操作系统调用,不能被其他函数调用;主函数main()可以调用库函数或其他函数;除主函数main()外,其他函数之间可以互相调用。
在一个程序中,通过函数调用将各函数联系在一起,程序总是从main()函数开始执行并调用所需要的函数,完成所调用函数的功能后,返回到main()函数继续执行,最后当main()函数执行完毕,整个程序运行结束。假设有main()函数和fun()函数,它们的调用过程如图6-2所示。图6-2函数调用执行过程示意图
一般函数的调用执行过程归纳如下:
(1)为被调用函数的所有形式参数分配内存,并将实在参数的值,按从右向左的方式一一对应地赋给相应的形式参数(对于无参函数,不做此工作)。
(2)程序控制由主调函数进入被调函数的函数体,之后依次执行被调用函数中变量定义部分,为局部变量分配存储空间,执行函数体中的可执行语句。
(3)当执行到“return”语句时,计算返回值(如果是无返回值的函数,不做这项工作),之后释放本函数中定义的局部变量和形式参数所占用的存储空间(对于static类型变量,其空间不释放),最后返回主调函数继续执行其他语句。
例6-4从键盘输入两个整数,求较大的整数。
程序执行的简单过程如下:①程序从main()函数开始执行,当执行到main()函数体第三行“c=max(a,b);”时,调用max()函数,把实在参数b、a的值传递给函数max()的形式参数y、x,并在主函数的该处设置函数断点,然后系统转去执行函数max()的函数体;②max()函数比较由主函数传递来的两个整数,然后执行return语句返回到主调函数的断点处,并将函数的返回值也传送给主调函数;③main()函数将max()函数的返回值赋给主调函数的内部变量c,程序继续往下执行,输出较大数,此时主函数执行完毕,整个程序运行结束。
6.2.2函数调用的参数传递
对带有参数的函数进行调用时,存在着如何将实参传递给形参的问题。根据实参传递给形参值的不同,参数传递通常有值传递方式和地址传递方式两种。
1.值传递方式
所谓值传递方式是指函数调用时,为形参分配内存单元,并将实参的值复制到形参中,当调用结束后,形参所占内存单元被释放,实参的内存单元仍保留并维持原值。
例6-5函数参数的值传递方式示例。
该程序首先在main()函数中定义了两个整型变量x和y,其初始值分别为7和11,然后调用swap()自定义函数试图交换x、y的值,可结果是,调用函数swap()以后,x和y的值并没有交换,x仍然是7,y仍然是11。为什么会出现这种情况呢?
这是因为实参x和y对应的内存单元与形参a和b所对应的内存单元是各不相同的,在函数调用时,只是将x的值7传递给形参a,y的值11传递给形参b,在swap()函数体内只是将a和b的值交换了,即a的值变为11,b的值变为7,当函数返回时,a和b的内存单元就释放了,x和y所对应的内存单元并没有进行任何的改变。这就好像是我复印一份文件给你,在你的文件上修改都不会改变我的文件一样。该程序函数参数调用的过程如图6-3所示。图6-3函数参数值传递方式示意图
2.地址传递方式
所谓地址传递方式是指,函数调用时将实参数据的存储地址作为参数传递给形参。其特点是形参与实参占用同样的内存单元,函数中对形参值的改变也会改变实参的值。因此函数参数的地址传递方式可实现调用函数与被调函数之间的双向数据传递。注意,实参和形参必须是地址常量或变量。
6.3函数的递归与嵌套调用
6.3.1函数的递归调用C语言中除允许一个函数调用其他函数外,还允许函数自己调用自己,这种函数称为递归函数。
1.递归概念
函数的递归调用是指一个函数在它的函数体内直接或间接地调用它自身。因此,递归调用有两种方式:直接递归和间接递归。直接递归指的是函数直接调用自身的过程,如图6-4(a)所示;间接递归指的是一个函数通过其他函数调用自身的过程,如图6-4(b)所示,函数1调用函数2,函数2又调用函数1。
图6-4两种递归调用示意图
例如:
fact(intn) /*fact函数定义*/
{
t=n*fact(n-1);
}
上述函数fact()中,函数fact()又要调用函数本身,它是直接递归。而递归函数怎么定义呢?以fact(n)=n!为例。因为
由①、②两式可得fact(n) = n × fact(n - 1)。假设想求fact(5)的值,则必须先求得fact(4)的值,同理想求fact(4)的值,则必须求fact(3)……,因而会无限地往下递归,程序无法得到终止。在调用过程中假设fact(1)=1,将值回代,则可得fact(2)的值,同理可得fact(3)、fact(4)的值,最后将其值回代得到fact(5)的值。因此,函数递归调用一定要有递归终止的条件,即当传递过去某个值时,函数值不再调用函数本身,而是一个确定值或过程。这也说明,递归函数的定义应包括两部分:函数的递归关系和函数递归终止条件。
2.递归举例
例6-6用递归法求fact(n) = n!。
计算阶乘可用如下形式描述:
它的递归调用和值的回代过程(设n = 5)如图6-5所示。主函数对fact(5)进行0级调用时,n=5,不满足n==1的条件,此时必须进行1级调用,得到fact(4)的值后,才能得出“fact(5)=5*fact(4)”。同样,计算fact(4)时需进行2级调用,即调用fact(3)。计算fact(3)要进行3级调用,即调用fact(2)。计算fact(2)需进行4级调用,即调用fact(1),此时n=1,满足条件n==1,得到p=1。返回p=1,即fact(1)返回至上层调用函数fact(2)处,得到“p=2*1=2”。
返回p=2,即fact(2)返回至上层调用函数fact(3)处,得到“p=3*2=6”。返回p=6,即fact(3)返回至上层调用函数fact(4)处,得到“p=4*6=24”。返回p=24,即fact(4)返回至上层调用函数fact(5)处,得到“p=5*24=120”。返回p=120,即fact(5)返回至上层调用函数main()处,输出运算结果。
图6-5递归调用和值的回代
例6-8汉诺塔问题。
有3个塔,每个都可以堆放若干个盘子。开始时,所有盘子均在塔A上,并且,盘从上到下,按直径增大的次序放置(如图6-6所示)。设计一个移动盘子的程序,使得塔A上的所有盘子借助于塔B移到塔C上。这里有两个限制条件:一是一次只能搬动一个盘子,二是任何时候不能把大盘子放在比它小的盘子的上面。图6-6汉诺塔问题示意图
解题思路要移动这n个盘子,可以定义一个函数:
move(n,a,b,c)
其中,字符型变量a、b、c分别表示A、B、C3个塔,函数move(n,a,b,c)表示将n个盘子从塔A(借助于塔B)移到塔C上。这个问题可以使用递归调用方法解决,在n > 0的前提下,函数move(n,a,b,c)通过下列3步实现移动。
(1)调用move(n-1,a,c,b),即将n-1个盘子从塔A(借助于塔C)移到塔B上。目的是让塔A上的第n个盘子(最下面的盘子)上无其他盘子。
(2)将底下的第n个盘子从A塔移到C塔。
(3)调用move(n-1,b,a,c),即将n - 1个叠放在塔B上的盘子(借助于塔A)移到C塔。
6.3.2函数的嵌套调用
将孤立的函数堆砌在一起并不能建立关系,实现程序的功能。程序中的函数需要通过相互调用建立关系。C语言规定,在一个函数内部不允许再定义函数,即函数不可嵌套定义,但是允许一个函数调用另外一个函数,这个被调用的函数还可以调用其他的函数,以形成任意深度的调用层次。这就是函数的嵌套调用。图6-7给出了函数嵌套调用的示意图。
图6-7函数嵌套调用的示意图
例6-9计算s = 2²! + 3²!。
解题思路本题可编写两个函数,一个是用来计算平方值的函数f1,另一个是用来计算阶乘值的函数f2。主函数先调f1计算出平方值,再在f1中以平方值为实参,调用f2计算其阶乘值,之后返回f1,最后返回主函数,在循环程序中计算累加和。
6.4作用域与存储类型
6.4.1作用域与生存期1.作用域所谓变量的作用域,就是指某个变量在其生存期内可被使用的范围。C语言中有两种变量:一种变量只能在所定义的模块内部有效,称为局部变量或内部变量;另一种变量从定义点开始至整个源程序结束都有效,称为全局变量或外部变量。
1)局部变量
局部变量指在函数或复合语句中定义的变量,只在本函数或复合语句范围内有效(从定义点开始到函数或复合语句结束),即只有在本函数或复合语句内才能使用它们,在此函数或复合语句以外是不能使用它们。
注意:
(1)主函数中定义的变量也只在主函数中有效,而不因为是在主函数中定义的而在整个文件或程序中有效。主函数也不能使用其他函数中定义的变量。
(2)不同函数中可以使用相同名称的变量,它们代表不同的对象,互不干扰。例如,在f1函数中定义了变量b和c,倘若在f2函数中也定义变量b和c,它们在内存中占不同的单元。
(3)形式参数也是局部变量。在函数中可以使用本函数声明的形参,在函数外则不能引用。
(4)在一个函数内部,可以在复合语句中定义变量,这些变量只在本复合语句中有效。
2)全局变量
局变量指在函数之外定义的变量。全局变量的有效范围为从定义点开始到本源程序结束。在此范围内它可以被本程序中所有函数所使用。在一个函数中既可以使用本函数中的局部变量,又可以使用有效的全局变量。
2.生存期
所谓变量的生存期,就是变量存活的周期。程序中的变量都要占用一定的内存空间,但并不是所有的变量在程序开始执行时就占用内存。为了节省内存,避免变量互相干扰,不可能让所有的变量始终存在,因此只有在必要时才为变量分配内存。当变量占用内存时,变量就生成了。当变量不再有用时,程序将释放变量所占用的内存,变量就撤销了。变量从被生成到被撤销的这段时间称为变量的生存期。它实际上就是变量占用内存的时间。
6.4.2变量的存储类型
由变量的生存期可知,有的变量在程序运行的整个过程都是存在的,而有的变量则是在调用其所在的函数时才临时分配内存单元,而在函数调用结束后就马上释放了,变量也不再存在了。因此变量的存储有两种情况:一是变量从定义点开始就被分配内存单元(即产生),直到程序运行结束才释放内存,这种方式称为静态存储方式;二是变量带有临时性,随调用的模块(如文件、函数或复合语句)分配内存单元,模块调用结束,释放内存,这种方式称为动态存储方式。
全局变量采用静态存储方式,程序开始执行时给全局变量分配内存空间,程序执行完毕释放。在程序执行过程中它们占据固定的存储单元,而不是动态地进行内存分配和释放。在函数中定义的变量,在函数调用开始时分配动态存储空间,函数结束时释放这些空间。在程序执行过程中,这种分配和释放是动态的。
C语言中变量的定义有两个属性:一是定义变量的数据类型;另一个是定义变量的存储类型。变量的数据类型规定了变量的存储空间大小和取值范围,变量的存储类型规定了变量的生存期和作用域。因此,定义一个变量的一般形式为
[存储类型]数据类型变量标识符表;
其中[]内为可选项。
不同存储类型的变量在计算机内部的存放位置是不同的。不同的存放位置决定了变量的存储类型,这也就决定了变量的生存期和作用域。变量可以存放在内存中的动态数据存储区或静态数据存储区中,也可以存储在CPU的寄存器中。
变量的存储类型有4种,分别是自动型、寄存器型、外部型和静态型,其说明符分别是auto、register、extern和static。下面结合程序示例,分别说明它们各自的生存期和作用域。
1.自动型(auto)
在各个函数或复合语句内定义的变量,也称为自动变量。自动变量用关键字“auto”进行标识,可缺省。
例如,在例6-4中的程序中,主函数main()的函数体中定义了3个自动变量a、b、c;在max()函中定义了1个自动变量z(形参也为自动变量)。当程序运行main()函数时,系统为a、b、c变量分别分配内存单元;在调用max()函数时,系统为max()函数的形参x、y和自动变量z分配内存单元,即生命开始,调用结束释放x、y、z的空间,即生命结束,其寿命为函数调用开始到结束期间,称为局部寿命。x、y、z的使用也只能在max()函数内部,这称为局部可用。
2.寄存器型(register)
如果有一些变量使用频繁(例如,在一个函数中执行1000000次循环,每次循环中都要引用某局部变量),则要为存取该变量的值花不少时间。为提高执行效率,C语言允许将局部变量的值放在CPU中的寄存器中,需要用时直接对寄存器读写数据。存储类型为寄存器型的变量称为寄存器变量。由于CPU的通用寄存器数量有限,在程序中允许定义的寄存器变量一般以少于8个为宜。如果定义为寄存器变量的数目超过系统所提供的寄存器数目,编译系统自动将超出的变量设为自动型存储类型。由于在计算机中对寄存器的读写速度远远高于对内存的读写速度,因此这样可以提高执行效率。寄存器变量用关键字register声明。
3.外部型(extern)
定义在所有函数(包括主函数)外部的变量称为外部变量。外部变量是一种全局变量。
1)外部变量的特性
对于外部变量,系统在编译时是将外部变量的内存单元分配在数据存储区,在整个程序文件运行结束后系统才收回其存储单元。因此外部变量的作用域是从外部变量定义点开始到源程序运行结束,即外部变量的寿命是全局的。在定义点之后的所有函数,都可以使用外部变量,也就是说其作用域也是全局的。
例6-12求ax2 + bx + c = 0(a ≠ 0)的根。
解题思路在a≠0的情况下。根据b2 - 4ac的不同值,方程分别有两个不等的实根、两个相等的实根和两个共轭的虚根。如果传递方程的3个系数a、b、c给函数,分别求出方程的根,则上述3种情况都需要返回两个数据,由于这里通过return语句无法实现,因此可借用外部变量实现。
程序开头的语句“floatx1,x2;”定义的是两个外部变量x1、x2,编译时分配x1、x2的存储空间,程序运行期间其空间一直存在。在其后定义的f1()函数、f2()函数、f3()函数和main()函数都可以引用变量x1、x2,也就是说在f1()函数、f2()函数、f3()函数中改写x1、x2值,就可以在main()函数中输出对应x1、x2值。这实现了不同函数之间的数据共享或通信。
2)外部变量的引用
外部变量的作用域是定义点开始到源程序的结束。若在定义点之前或别的源程序中要引用外部变量,则在引用该变量之前,需进行外部变量的引用说明。
外部变量的引用说明的一般形式为
extern外部变量数据类型外部变量名表;
一个C程序可以由很多个程序文件组成,每个文件称为一个编译单位。外部变量主要用于在多个编译单位间传递数据。若两个编译单位A和B需要交换信息,可在编译单位A中定义一个外部变量A,并把信息存放在其中,编译单位B要引用该变量需把该变量说明为外部型,告诉系统该变量在其他编译单位中已经定义了。
4.静态型(static)
静态变量是分配在内存中的。它们在程序开始运行时就分配了固定的存储单元,在程序运行过程中不释放,直到程序运行结束才释放它所占的存储空间。如果用static说明外部变量为静态型,则该外部变量只能在本程序中使用。
1)静态局部变量
静态局部变量是定义在函数体的复合语句中,用关键字“static”进行标识的变量。静态局部变量定义的一般形式为
static变量数据类型变量名表;
对于静态局部变量,系统在编译时将其内存单元分配在静态数据存储区,直到程序运行结束,对应的内存单元才释放。静态局部变量只有在编译时可以赋初值,以后每次调用时不再分配内存单元和初始化,只是引用上一次函数调用结束时的值。其作用域只限于其所定义的函数或复合语句中。因此静态局部变量是一种具有全局寿命、局部可用的变量。
2)静态全局变量
定义在所有函数(包括主函数)之外,用关键字“static”标识的变量,称为静态全局变量。静态全局变量和外部变量有共同点:都具有全局寿命,即在整个程序运行期间都存在。但二者在使用上有区别:静态全局变量只能在所定义的文件中使用,具有对文件的局部可见性;而一般的外部变量可以在所有文件中使用。
6.5函数与数组
6.5.1函数和一维数组1.一维数组元素作为函数的实参由于数组元素与相同类型的简单变量地位完全一样,因此,数组元素作为函数参数也和简单变量一样,采用值的单向传递方式。
例6-17
温馨提示
- 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
- 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
- 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
- 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
- 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
- 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
- 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。
最新文档
- 安全证a试题及答案
- 安全考试试题及答案
- 指南语言领域培训课件
- 英语考试培训课件
- 家政顾问培训课件模板
- 2025届陕西省咸阳市秦都区咸阳市实验中学英语七下期末调研模拟试题含答案
- 胃镜并发症的护理
- 山东省临沂市沂水县2025年英语八年级第二学期期末达标检测模拟试题含答案
- 中学生环保教育
- 《肺结节规范化诊治专家共识(2024)》解读 课件
- NPI流程管理制度
- 2025 年湖北省中考生物地理试卷
- 荆州中学2024-2025学年高二下学期6月月考语文答案(定)
- 公司年中会议策划方案
- 计算物理面试题及答案
- JG/T 455-2014建筑门窗幕墙用钢化玻璃
- 酒吧员工劳务合同范本
- 法人变更免责协议书
- 美洲文化课件教学
- 2025届重庆市巴川中学生物七下期末统考试题含解析
- 期末总动员暨诚信教育主题班会
评论
0/150
提交评论