版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领
文档简介
第5章
函数函数
6C语言程序的开发环境5.1模块化程序设计与函数5.2函数定义5.3函数调用5.4函数递归调用5.5变量的作用域与储存期5.6编译预处理5.1模块化程序设计与函数
在设计较复杂的程序时,一般采用自顶向下的方法,将问题划分为几个部分,各个部分再进行细化,直到分解为很容易求解的小问题为止。求解小问题的程序算法叫做“功能模块”,把复杂的问题分解为单独的模块后,称为模块化设计。因此C语言程序设计是模块化程序设计。5.1.1模块与函数C语言中程序是由基本语句和函数组成,每个函数负责完成独立的小任务。c语言程序设计是模块化程序设计。任务、模块和函数的关系是:解决整个特定问题的大任务被分解成若干个小问题即功能模块,功能模块由一个或者多个函数来具体实现。解决整个特定问题的程序就通过这些函数的调用来完成。总结来说,模块化程序设计就是由设计函数和调用函数来实现。5.1.1模块与函数编程输出以下图形。
*********团结就是力量!*********方法一:(不用函数实现的程序源代码)#include<bits/stdc++.h>intmain(){ printf("*********\n"); printf("团结就是力量!\n"); printf("*********\n"); }【例5.1】5.1.1模块与函数方法二:(用函数实现的程序源代码)#include<bits/stdc++.h>intmain(){ voidprintstar();/*对printstar函数进行声明*/ voidprintunity();/*对printhelloworld函数进行声明*/ printstar();/*调用printstar函数*/ printunity();/*调用printhelloworld函数*/ printstar();/*调用printstar函数*/}voidprintstar(){/*定义printstar函数*/ printf("*********\n");}voidprintunity(){/*定义printhelloworld函数*/ printf("团结就是力量!\n");}5.1.2模块化设计的基本原则
1.模块独立模块完成独立的功能,和其他模块间的关系简单,各模块可以单独调试。修改某一模块,不会造成整个程序的混乱。
模块的独立性原则是指模块完成独立的功能,可单独进行调试。各个模块之间的联系简单,修改一个模块不会对整个程序产生大的影响。这要求我们在模块设计中注意几个问题:首先,因为模块具有相对独立性,所以在分解任务时要注意对问题的综合。其次,各个模块之间的联系简单,尽量只包含简单的数据传递这种联系,不要涉及到控制联系。最后要注意模块的私有数据的使用。5.1.2模块设计原则
2.模块规模适当
模块的规模不能太大或者太小。功能太强的模块,一般可读性就会很差,功能太小的模块,会出现很多接口。初学者只需记住此原则,在以后的实践中需经常总结经验。
3.分解模块要注意层次
在对任务进行多层次分解时,要尤其注意对问题进行抽象化。开始只需考虑大的模块,不要过分注意细节。在中期,大模块的设计中,再逐步细化求精,分解成较小的模块进行设计。5.2函数定义
在C语言中,函数(Function)是一个处理过程,把一段程序的工作放在函数中进行,函数结束时可以携带或不带处理结果。C语言中的程序处理过程全部都是以函数形式出现,最简单的程序至少也包含一个main()函数。函数必须先定义和声明后才能调用。5.2函数定义用户使用的函数通常有两种:标准库函数和自定义函数。标准库函数:是系统提供给用户,c语言的强大功能依赖于它丰富的库函数。需要注意的是,如果用户想调用这些函数,则必须先用编译预处理命令将相应的头文件包含到程序中。在前面各章的例题中反复用到printf、scanf、getchar、putchar、gets、puts、strcat等函数均属此类。自定义函数:用户自己编写。不仅要在程序中定义函数本身,而且在主调函数模块中还必须对该被调函数进行类型说明,然后才能使用。5.2.1函数定义的一般形式函数的定义就是把子任务的程序写到一个函数里。函数包含函数的说明部分和函数体两大部分。一般形式:类型名函数名(参数类型说明及列表)
{局部变量说明语句序列
}例如:输入两个整数,以下函数minpear输出两个数中较小者:intminpear(inta,intb)/*函数定义和形式参数类型说明*/{intt;/*局部变量说明*/if(a<b)t=a;/*执行语句*/elset=b;returnt;/*返回语句*/}
1.函数的说明部分函数的说明部分包括函数的类型、函数名、参数表和参数类型的说明。例如上例中第一行为函数的说明部分。(1)函数的类型。即函数的返回值类型。表示给调用者提供什么类型的返回值。函数可以有或者没有返回值。
如果函数没有返回值,则定义函数类型为空,用标识符void表示空类型。例如:voiddata(inta)
如果函数有返回值,例如上例中min函数的类型为int,即函数的返回值类型是int。(2)函数名。也叫函数标识符。它遵循C语句标识符的命名规范。(3)参数表。函数名后面的括号“()”里的内容是参数表,由变量标识符和类型标识符构成。参数表中的变量也叫作形式参数,即形参。5.2.1函数定义的一般形式
1.函数的说明部分
根据参数表中是否有参数可将函数分为无参函数和有参函数。无参函数:函数可以没有形参,叫作无参函数,注意无参函数的括号“()”不能省略。有参函数:如果有形参,在定义形参时,必须指定形参的类型。例如:intmin(inta,intb)5.2.1函数定义的一般形式
2.函数体
在函数定义中,函数体是用花括号括起来的部分,函数的具体功能在函数体中完成。函数体内的开头部分是定义和说明部分,后面是语句部分。函数声明和函数体构成了函数定义。
函数的返回值,是指函数被调用之后,执行函数体中的程序段后得到的结果,并返回给主调函数。关于函数的返回值有下面几点说明:
(1)有返回值的函数体:需要有return返回语句。格式如下:return(表达式);该语句的执行顺序是先计算表达式的值,然后把结果返回给主调函数。
(2)return语句表达式的类型要和函数定义中函数的类型保持一致。如果不一致,则以函数的类型为准,自动进行类型转换。
(3)若函数类型为整型,则函数定义时可以省略函数类型。
例如,上面提到的自定义函数min(a,b),根据传入的a和b两个值的大小返回一个t值,也就是两个数中的较小者。5.2.1函数定义的一般形式
函数调用时,应该遵循先定义后使用的原则。如果函数调用出现在函数定义之前,则要对函数先进行声明,也就是让系统先知道要用哪个函数以及函数类型等信息。5.3函数调用
函数声明与函数定义时的第一行是基本一致的,包括函数类型、函数名、形参个数、类型、次序。不同的是,函数声明是函数定义时的第一行在结尾时加上“;”,并且参数列表中可以省略参数名。
格式如下:
类型名函数名(参数类型说明列表);5.3.1函数的声明5.3.1函数的声明编程实现孔融让梨,输出较小的梨。步骤分析:输入两个整数,表示梨的重量;调用函数minpear()进行较小值的比较运算;输出运算结果;Intmain(){ intminpear(inta,intb);/*对minpear函数的声明*/ intx,y,z; printf("inputtwonumbers:\n"); scanf("%d%d",&x,&y); z=minpear(x,y);/*调用minpear函数*/ printf("minmum=%d",z);#include<bits/stdc++.h>intminpear(inta,intb){/*函数定义和形式参数类型说明*/ intt;/*局部变量说明*/ if(a<b)t=a;/*执行语句*/ elset=b; returnt;/*返回语句*/}【例5.2】5.3.1函数的声明
本例中,把x,y中的值传送给minpear的形参a,b。minpear函数执行的结果(a或b)将返回给变量z。最后由主函数输出z的值。
注意:孔融让梨程序运行结果如图:
根据函数有参数或者无参数两种情况,函数调用可分为:有参函数调用和无参函数调用。1、有参函数调用的形式:函数名(实参表达式)
例如:min(inta,intb);2、无参函数调用的形式:函数名()
例如:func();5.3.2函数的调用
根据调用方式不同,函数调用可分为:语句调用和表达式调用1、语句调用的形式:
例如:scanf(“%d”,&x);2、表达式调用的形式:
例如:a+min(x,y);5.3.3函数的嵌套调用
在C语言中不允许“嵌套定义函数”,即不能在一个函数的定义中作另一个函数的定义。例如下面的例子,在main函数的定义中作printdata函数的定义,这种写法是错误的:intmain(intargc,char*argv[]){//定义func函数voidprintdata(inta){printf("inprintdata,a=%d\n",a);}printdata(8);return0;}
这样的代码,在VC编译器,或者VisualStudio编译器中,属于非法定义的代码。虽然在func函数的调用之前,定义了func函数。但是,不能够在main函数中定义func函数,就是不能够嵌套定义函数。
注意:5.3.3函数的嵌套调用
但是C语言允许在一个函数的定义中出现对另一个函数的调用。这叫作函数的嵌套调用。也就是在被调函数中又调用其它函数。这与其它语言的子程序嵌套的情形是类似的。例如:intb()/*定义函数b*/{…….}inta()/*定义函数a*/{…….b();/*a中调用函数b*/}voidmain(){……a();/*主函数中调用函数a*/}调用a()f2()调用b()main()b()5.3.3函数的嵌套调用
图中表示了两层嵌套的情况。其执行过程是:在执行main函数时遇到了调用a函数的语句,即转到a函数去执行,在执行a函数时遇到了调用b函数的语句,则转到b函数去执行,当b函数执行完毕后返回a函数的断点继续执行,a函数执行完毕后返回main函数的断点继续执行。5.3.3函数的嵌套调用
学校为鼓励学生和辅导老师以赛促教、以赛促学、以赛促改,进一步推进学校创新创业教育迈上新台阶,要求统计计算机专业两个班的大学生计算机应用能力竞赛获奖人数,学校把任务分配给了辅导员,辅导员把任务分配给了班长。试编程实现。代码设计如下:#include<bits/stdc++.h>intmain(){ intcounselor(); printf("%d",counselor());}intcounselor(){ intmonitor(); return(monitor());}intmonitor(){ inta,b; scanf("%d,%d",&a,&b); returna+b;}【例5.3】5.3.3函数的嵌套调用大学生计算机应用能力竞赛获奖人数程序运行结果如图:main函数相当于学校,在输出函数中调用counselor函数。counselor函数中,在return语句中调用了monitor函数,monitor函数来求两个班的人数,并且通过return语句返回人数之和,层层返回。5.3.3函数的嵌套调用【例5.4】计算s=12!+22!+32!#include<bits/stdc++.h>longf1(intd){ /*定义求立方值的函数f1*/ intcube; longr; longf2(int); /*嵌套调用阶乘函数f2*/ cube=d*d; r=f2(cube); returnr;}
解题思路:本题可通过编写两个函数来实现,第一个函数f1用来实现计算平方值,第二个函数f2用来实现计算阶乘值。代码设计如下:5.3.3函数的嵌套调用longf2(intq){ /*定义求阶乘值的函数f2*/ longc=1; inti; for(i=1;i<=q;i++) c=c*i; returnc;}main(){ inti; longs=0; for(i=1;i<=3;i++) s=s+f1(i); /*调用求立方值的函数f1*/ printf("\ns=%ld\n",s);}5.3.4参数传递
形参出现在函数定义中,在整个函数体内都可以使用,离开该函数则不能使用。实参出现在主调函数中,进入被调函数后,实参变量也不能使用。形参和实参的功能是作数据传送。发生函数调用时,主调函数把实参的值传送给被调函数的形参从而实现主调函数向被调函数的数据传送。
5.3.4参数传递
函数的形参和实参具有以下特点:1、形参变量只有在被调用时才分配内存单元,在调用结束时,即刻释放所分配的内存单元。因此,形参只有在函数内部有效。函数调用结束返回主调函数后则不能再使用该形参变量。2、实参可以是常量、变量、表达式、函数等,无论实参是何种类型的量,在进行函数调用时,它们都必须具有确定的值,以便把这些值传送给形参。因此应预先用赋值,输入等办法使实参获得确定值。3、实参和形参在数量上,类型上,顺序上应严格一致,否则会发生类型不匹配”的错误。4、函数调用中发生的数据传送是单向的。即只能把实参的值传送给形参,而不能把形参的值反向地传送给实参。因此在函数调用过程中,形参的值发生改变,而实参中的值不会变化。5.3.4参数传递【例5.5】参数传递算法实例。#include<bits/stdc++.h>intmain(){ intn; printf("inputnumber\n"); scanf("%d",&n); ints(intn); s(n); printf("n=%d\n",n);}ints(intn){ inti; for(i=n-1;i>=1;i--) n=n+i; printf("n=%d\n",n);}5.3.4参数传递
本程序中定义了一个函数s,该函数的功能是求n+(n-1)+….+1的值。在主函数中输入n值,并作为实参,在调用时传送给s函数的形参量n(注意,本例的形参变量和实参变量的标识符都为n,但这是两个不同的量,各自的作用域不同)。
在主函数中用printf语句输出一次n值,这个n值是实参n的值。在函数s中也用printf语句输出了一次n值,这个n值是形参最后取得的n值0。从运行情况看,输入n值为10。即实参n的值为10。把此值传给函数s时,形参n的初值也为10,在执行函数过程中,形参n的值变为55。返回主函数之后,输出实参n的值仍为10。可见实参的值不随形参的变化而变化。5.4函数递归调用直接递归形式如下:voidfunc(){......func();/*函数func中调用函数func,直接递归*/......}
在函数内部调用自己,叫作函数的递归调用。递归调用分为直接递归和间接递归。
某一函数在函数体内部直接调用自身函数叫作函数的直接递归。即函数的嵌套调用的是函数本身。嵌套调用自身函数在我们的学习生活中就好像是榜样的示范!修身立德,才能言传身教,彰显榜样力量!
某一函数在函数体内部调用其他函数,其他函数再调用本函数,叫作函数的间接递归。5.4函数递归调用(2)间接递归形式如下:voidfunc1(){......func2();/*函数func1中调用函数func2*/......}voidfunc2(){......func1();/*函数func2中调用函数func1,间接递归*/
递归思想是一个非常有用的解决问题的思路。它不仅可以解决本身是递归定义的问题,还可以把看起来很复杂不容易描述出来的过程变得简单明了。下面针对这两方面分别举例说明递归思想。5.4函数递归调用【例5.6】用递归算法计算n!由于n!=n*(n-1)!是递归定义,所以求n!
(n-1)!
(n-1)!
(n-2)!
(n–2)!
(n-3)!
……
0!的问题,
根据公式有0!=1,
再反过来依次求出1!,2!……直到最后求出n!。5.4函数递归调用#include<stdio.h>longfac(intn){longf;if(n<0)printf("n<0,inputerror");elseif(n==0||n==1)f=1;elsef=fac(n-1)*n;return(f);}intmain(){intn;longy;printf("\ninputainteagernumber:\n");scanf("%d",&n);y=fac(n);printf("%d!=%ld",n,y);}直到最后的fac(n)都做完了,f的值被返回到了它的调用点:主函数中,这样就是一个递归运算。
程序中给出的函数fac是一个递归函数。主函数调用fac后即执行函数fac,如果n<0,n==0或n=1时都将结束函数的执行,否则就递归调用fac函数自身。注意每次递归调用的实参为n-1,即把n-1的值赋予形参n,最后当n-1的值为1时再作递归调用,形参n的值也为1,将使递归终止。然后可逐层退回。5.4函数递归调用
运行上述程序,若输入为5,则求5!。在主函数中调用语句y=fac(5),进入fac函数执行,判断n=5,不满足n等于0或1,故应该执行f=fac(n-1)*n,即f=fac(5-1)*5。此语句为递归调用。转为求fac(4)。进行四次递归调用后,fac函数的形参变为1,满足elseif(n==0||n==1),将结束函数的执行,故不再进行递归调用,而开始逐层返回主调函数。fac(1)的函数返回值为1,fac(2)的返回值为1*2=2,fac(3)的返回值为2*3=6,fac(4)的返回值为6*4=24,最后返回值fac(5)为24*5=120。递归算法程序运行结果如图所示:5.4函数递归调用
【例5.7】汉诺塔游戏是理论指导实践,锻炼学生们动手操作的经典游戏,请编程实现。汉诺塔是一个很繁杂的游戏,但可以用递归的方法异常简单的完成。规则:(1)一次只能移动一个金片。
(2)大的不能放在小的上面。
(3)只能在三个位置中移动。总体思想:1.将i-1个盘子先放到B座位上。2.将A座上地剩下的一个盘移动到C盘上。3.将i-1个盘从B座移动到C座上。5.4函数的递归调用递归法解汉诺塔#include<bits/stdc++.h>voidhanoi(intn,inta,intb,intc)
{
if(n==1)
printf(“%d->%d”,a,c);
else
{hanoi(n-1,a,c,b);
printf(“%d->%d”,a,c);
hanoi(n-1,b,a,c);
}
}voidmain()
{intn;
printf(“inputn:”);
scanf(“%d”,&n);
hanoi(n,1,2,3);
}再将n-1个盘子从b经过a移动到cn=1时,直接将金片从a移动到cn-1个金片从a经过c移动到b5.4函数的递归调用voidhanoi(intn,inta,intb,intc)
{
if(n==1)
printf(“%d->%d”,a,c);
else
{hanoi(n-1,a,c,b);
printf(“%d->%d”,a,c);
hanoi(n-1,b,a,c);
}
}voidmain()
{intn;
printf(“inputn:”);
scanf(“%d”,&n);
hanoi(n,1,2,3);
}递归解汉诺塔步骤输入3,则n=35.4函数的递归调用voidhanoi(intn,inta,intb,intc)
{
if(n==1)
printf(“%d->%d”,a,c);
else
{hanoi(n-1,a,c,b);
printf(“%d->%d”,a,c);
hanoi(n-1,b,a,c);
}
}voidmain()
{intn;
printf(“inputn:”);
scanf(“%d”,&n);
hanoi(n,1,2,3);
}递归解汉诺塔步骤n=3
主函数调用hanoi(n,1,2,3);第一次调用。第一次调用hanoi(n,a,b,c)(第一层)即要把三个金片移到c5.4函数的递归调用递归解汉诺塔步骤voidhanoi(intn,inta,intb,intc)
{
if(n==1)
printf(“%d->%d”,a,c);
else
{hanoi(n-1,a,c,b);
printf(“%d->%d”,a,c);
hanoi(n-1,b,a,c);
}
}voidmain()
{intn;
printf(“inputn:”);
scanf(“%d”,&n);
hanoi(n,1,2,3);
}由于n>1则执行hanoi(n-1,a,c,b)
(第二次调用)n=35.4函数的递归调用递归解汉诺塔步骤voidhanoi(intn,inta,intb,intc)
{
if(n==1)
printf(“%d->%d”,a,c);
else
{hanoi(n-1,a,c,b);
printf(“%d->%d”,a,c);
hanoi(n-1,b,a,c);
}
}voidmain()
{intn;
printf(“inputn:”);
scanf(“%d”,&n);
hanoi(n,1,2,3);
}由于仍然n>1则执行hanoi(n-1,a,c,b)
(第三次调用)5.4函数的递归调用递归解汉诺塔步骤voidhanoi(intn,inta,intb,intc)
{
if(n==1)
printf(“%d->%d”,a,c);
else
{hanoi(n-1,a,c,b);
printf(“%d->%d”,a,c);
hanoi(n-1,b,a,c);
}
}voidmain()
{intn;
printf(“inputn:”);
scanf(“%d”,&n);
hanoi(n,1,2,3);
}n=2第三层执行完毕,返回到第二层,即下去执行printf(“%d->%d”,a,c);把第二个金片摆到第二根针上5.4函数的递归调用递归解汉诺塔步骤voidhanoi(intn,inta,intb,intc)
{
if(n==1)
printf(“%d->%d”,a,c);
else
{hanoi(n-1,a,c,b);
printf(“%d->%d”,a,c);
hanoi(n-1,b,a,c);
}
}voidmain()
{intn;
printf(“inputn:”);
scanf(“%d”,&n);
hanoi(n,1,2,3);
}n=2执行下一条语句,又调用第三层hanoi(1,3,1,2)5.4函数的递归调用递归解汉诺塔步骤voidhanoi(intn,inta,intb,intc)
{
if(n==1)
printf(“%d->%d”,a,c);
else
{hanoi(n-1,a,c,b);
printf(“%d->%d”,a,c);
hanoi(n-1,b,a,c);
}
}voidmain()
{intn;
printf(“inputn:”);
scanf(“%d”,&n);
hanoi(n,1,2,3);
}n=1n=1,执行结果为3
25.4函数的递归调用递归解汉诺塔步骤voidhanoi(intn,inta,intb,intc)
{
if(n==1)
printf(“%d->%d”,a,c);
else
{hanoi(n-1,a,c,b);
printf(“%d->%d”,a,c);
hanoi(n-1,b,a,c);
}
}voidmain()
{intn;
printf(“inputn:”);
scanf(“%d”,&n);
hanoi(n,1,2,3);
}n=3第二层也执行完了,返回第一层,执行接下来的语句,结果为1
3。5.4函数的递归调用递归解汉诺塔步骤voidhanoi(intn,inta,intb,intc)
{
if(n==1)
printf(“%d->%d”,a,c);
else
{hanoi(n-1,a,c,b);
printf(“%d->%d”,a,c);
hanoi(n-1,b,a,c);
}
}voidmain()
{intn;
printf(“inputn:”);
scanf(“%d”,&n);
hanoi(n,1,2,3);
}n=3执行接下来的语句,再次调用
第二层hanoi(2,2,1,3)5.4函数的递归调用递归解汉诺塔步骤voidhanoi(intn,inta,intb,intc)
{
if(n==1)
printf(“%d->%d”,a,c);
else
{hanoi(n-1,a,c,b);
printf(“%d->%d”,a,c);
hanoi(n-1,b,a,c);
}
}voidmain()
{intn;
printf(“inputn:”);
scanf(“%d”,&n);
hanoi(n,1,2,3);
}5.5变量的作用域与储存期
变量的作用域,即变量有效性的范围。例如函数中的形参变量,只在函数调用时才给形参分配内存单元,函数调用结束后该内存单元就会被释放。这说明形参有效性的范围即作用域是函数内。形参在函数以外的范围是不起作用的。C语言中的变量,根据作用域可划分为两类,即局部变量和全局变量。5.5.1变量根据作用域进行划分变量
局部变量也叫内部变量。局部变量的作用域是函数内,即它是在函数内作定义说明的。函数以外的地方使用这种变量是不合法的。例如:voidfunc(){inta=3;c1+=32 c2+=32; c3+=32; c4+=32;}
在函数func中使用main函数中定义的局部变量c1,c2,c3和c4是非法的。c1,c2,c3和c4在main函数中定义说明,只能在main函数中使用,即c1,c2,c3和c4的作用域仅限main函数。故在func函数中使用c1,c2,c3和c4是非法的。
1.局部变量voidmain(){charc1,c2,c3,c4; printf("Enterc1,c2,c3,c4:"); scanf("%c%c%c%c",&c1,&c2,&c3,&c4); func(); printf(“%c%c%c%c\n”,c1,c2,c3,c4);}5.5.1变量根据作用域进行划分变量正确的使用如下:【例5.8】#include<bits/stdc++.h>voidfunc(){ charc1,c2,c3,c4; /*定义函数func中的局部变量c1,c2,c3和c4*/ c1='A',c2='B',c3='C',c4='D’; c1+=32; c2+=32; c3+=32; c4+=32; printf("%c%c%c%c\n",c1,c2,c3,c4);/*输出函数func中的c1,c2,c3和c4*/}intmain(){ charc1,c2,c3,c4; printf("Enterc1,c2,c3,c4:"); scanf("%c%c%c%c",&c1,&c2,&c3,&c4); func();/*调用函数func()*/ printf("%c%c%c%c\n",c1,c2,c3,c4);/*输出main函数中的c1,c2,c3和c4*/}5.5.1变量根据作用域进行划分变量
全局变量也叫外部变量,它是在函数外部定义,其作用域是从定义开始,到文件结束为止。对任何函数来说,它都不是局部的变量。全局变量具有全局作用域,即定义后,可以用在文件的所有函数中。如果要在函数中使用全局变量,应先对全局变量进行说明。遵循先说明后使用的原则。全局变量的说明符为extern,可以省略。但是,如果全局变量的定义在前,函数内使用全局变量在后,这种情况可不再加以说明。
2.全局变量5.5.1变量根据作用域进行划分变量
2.全局变量【例5.9】全局变量在main函数中赋值并改变的实例。#include<bits/stdc++.h>charc1,c2,c3,c4; /*定义全局变量c1,c2,c3和c4*/voidfunc(){ c1+=32; /*使用全局变量在main函数中赋的值,并改变m和n的值*/ c2+=32; c3+=32; c4+=32; printf("%c%c%c%c\n",c1,c2,c3,c4);/*输出已改变的c1,c2,c3和c4的值*/}intmain(){ c1='T',c2='E',c3='A',c4='M’; /*对全局变量c1,c2,c3和c4赋值*/ func(); /*调用函数func()*/ printf("%c%c%c%c\n",c1,c2,c3,c4);/*输出c1,c2,c3和c4*/}5.5.2变量根据存储方式进行划分
静态存储类型的变量的生存期为程序执行的整个过程,在该过程中占有固定的存储空间,通常称它们为永久存储。变量按存储方式划分:—静态存储—动态存储说明
动态存储类型变量只生存在某一段时间内。例如,函数的形参和函数体或分程序中定义的变量,只是在程序进入该函数或分程序时才分配存储空间,当该函数或分程序执行完后,变量对应的存储空间又被撤销。5.5.2变量根据存储方式进行划分1、静态存储类别(1)全局变量
全局变量全部存放在静态存储区,在程序开始执行时给全局变量分配存储区,程序执行完毕就释放内存。全局变量占据固定的存储单元,而不动态地进行分配和释放;(2)静态局部变量
静态局部变量是用static声明的局部变量,它属于静态存储方式。局部变量的值在函数调用结束后释放。有时希望保留函数中局部变量的原值,这种情况需要指定局部变量为“静态局部变量”,用关键字static进行声明。例如:Staticintm,n;
注意:静态局部变量在函数结束后仍保留原值,不随函数的结束而消失,生存期为整个程序。静态局部变量在编译时赋初值,即只赋初值一次,若未赋初值,系统自动赋值0.5.5.2变量根据存储方式进行划分【例5.10】静态局部变量实例。
#include<bits/stdc++.h>intfunc(){ staticintn=3; n*=2; return(n);}intmain(){ inti; for(i=0;i<3;i++) printf("%d\n",func());}5.5.2变量根据存储方式进行划分2、动态存储类别动态存储方式:是在程序运行期间根据需要给变量动态的分配存储空间。动态存储区存放函数的形式参数和auto自动变量。auto自动变量,是未加static声明的局部变量。函数中的局部变量如果不加static存储类别,都属于动态地分配存储空间,数据存储在动态存储区中。
函数中的形参和在函数中定义的变量都属于自动变量,在调用该函数时系统会给它们分配存储空间,在函数调用结束时就自动释放这些存储空间。自动变量用关键字auto作存储类别的声明。形式如下:intf(inta)/*定义f函数,a为参数*/{autointb,c=3;/*定义b,c自动变量*/……}a是形参,b,c是自动变量,对c赋初值3。执行完f函数后,自动释放a,b,c所占的存储单元。关键字auto可以省略,auto不写则隐含定为“自动存储类别”,属于动态存储方式。5.6编译预处理
“编译预处理”是C语言编译系统的一个组成部分。是在编译前由编译系统中的预处理程序对源程序的预处理命令进行加工。
与源程序中的语句不同,源程序中的预处理命令以“#”开头,结束没有分号,它们可以放在程序中的任何位置,作用域是自出现点到源程序的末尾。
预处理命令包括执行宏定义(宏替换)、文件包含和条件编译。5.6.1宏定义无参宏的宏名后不带参数。无参宏定义的一般形式为:#define标识符字符串:其中,“#”表示该行为预处理命令,“define”为宏定义命令,“标识符”为定义的宏名,这里为“M”,“字符串”可以是常数、表达式、格式串等。例如#defineM(x*x+3*x+1)定义后,可以用M来代替(x*x+3*x+1)。在编写源程序时,所有的(x*x+3*x+1)都可由M代替,而对源程序作编译时,将先由预处理程序进行宏代换,即用(x*x+3*x+1)表达式去置换所有的宏名M,然后再进行编译。
1.无参宏定义5.6.1宏定义【例5.11】无参宏定义算法实例。#include<bits/stdc++.h>#defineSENTENCE("IloveChina!")intmain(){ printf("%s",SENTENCE);}
1.无参宏定义5.6.1宏定义(1)宏定义是用宏名来表示一个字符串,在宏展开时又以该字符串取代宏名,这只是一种简单的代换,字符串中可以含任何字符,可以是常数,也可以是表达式,预处理程序对它不作任何检查。如有错误,只能在编译已被宏展开后的源程序时发现。(2)宏定义不是说明或语句,在行末不必加分号,如加上分号则连分号也一起置换。(3)宏定义必须写在函数之外,其作用域为宏定义命令起到源程序结束。如要终止其作用域可使用#undef命令。说明5.6.1宏定义一般形式:#define宏名(形参表)字符串
如:#defineS(a,b)a*b带参宏调用的一般形式为:
宏名(实参表);例如:#defineM(x)x*x+3*x+1/*宏定义*/……k=M(8);/*宏调用*/……在宏调用时,用实参8去代替形参x,经预处理宏展开后的语句为:k=8*8+3*8+1
2.带参宏定义5.6.1宏定义【例5.12】商朝末年西周初年的数学家商高在公元前1000年发现勾股定理的一个特例:勾三,股四,弦五。早于意大利的数学家毕达哥拉斯发现此定理证明五百到六百年。试用带参宏定义算法实现。
代码如下:#include<bits/stdc++.h>#defineM(x,y)sqrt(x*x+y*y)intmain(){ ints; s=M(3,4); printf("s=%d\n",s);}
2.带参宏定义5.6.2文件包含
文件包含是指一个源文件可以将另一个源文件的全部内容包含进来。文件包含允许嵌套。
#include命令有两种格式。
—#include<文件名>—#include“文件名”
两种格式区别使用尖括号表示在包含文件目录中查找(包含目录是由用户在设置环境时设置的),而不在源文件目录查找;使用双引号则表示首先在当前的源文件目录中查找,若未找到才到包含目录中去查找。5.6.3条件编译
按不同的条件去编译不同的程序部分,即按照条件选择源程序中的不同的语句参加编译,因而产生不同的目标代码文件。这对于程序的移植和调试是很有用的。(1)#ifdef
标识符程序段1
#else
程序段2
#endif
5.6.3条件编译(2)#ifndef
标识符
程序段1
#else
程序段2
#endif(3)#if
常量表达式程序段1
#else
程序段2
#endif
本章小结本章介绍了函数的模块化程序设计思想和原则,函数的定义、函数的调用、函数的递归调用。详述了变量的作用域和存储方式,全局变量、局部变量、自动变量和静态变量的特点及应用。还介绍了函数的编译预处理功能,使程序更易于理解和移植。如果把程序比作大一生活,则可以把大一生活分成几部分,每一部分都有独立的事情要做,但这些融合到一起,才是多彩的大学生活。目录1234指针与指针变量指针与数组指针与字符串指针与函数第6章指针5指针数组指针与指针变量6.16.1.1指针的概念地址:内存单元的编号地址基本概念数据在内存中的存储定义变量-->分配内存单元(变量名对应其地址)-->赋值例如:inta=5;charch=‘s;…20005
a200120022003…4000sch内存地址
数据的读写根据变量的地址读写内存单元的内容。6.1.1指针的概念…20003变量i20046变量j20089变量k…3010……内存例如:inti=3,j=6,k;printf(“%d%d”,i,j);scanf(“%d”,&k);
变量的访问直接访问:根据变量地址存取变量值间接访问:根据指针存取变量值6.1.1指针的概念变量地址内存单元指针变量地址内存单元…20003变量i20046变量j20089变量k…30102000p…内存例如:inti=3,j=6,k;printf(“%d”,i);int*p=&i;printf(“%d”,*p);
指针
(pointer)变量的地址6.1.1指针的概念例j=3;——直接访问指针变量…...…...4000400440064005整型变量j变量j_pointer40014002400340003例*j_pointer=10;——间接访问10指针和指针变量指向基本概念6.1.1指针的概念变量的指针:是一个变量的地址指针变量:用来存储变量地址(指针)的变量变量的地址(指针)变量的内存单元例如,若有inti=3;int*p=&i;则可以说p指向变量i6.1.2
指针变量的定义与使用
1.指针变量的定义一般形式:
类型名
*指针变量名;例:int*p;float*q;char*r;(1)指针变量名是p,q,r,而不是*p,*q,*r;(2)类型名是指针所指向的变量的数据类型,指针变量只能指向定义时所规定类型的变量。(3)指针变量存放的是所指向的某个变量的地址值,而普通变量保存的是该变量本身的值。注意:
例如:inti=5;floatj=2.5;int*point;point=&i;(语法正确)point=&j;(语法错误)6.1.2
指针变量的定义与使用
2.指针变量的使用两个运算符:&和*&:取地址运算符,取变量的地址
一般形式为&变量名
例:int*p;//此处*表示p为指针类型
p=&a;*:指针运算符,取指针所指向的内容
一般形式为*指针变量名
例:*p=a;6.1.2
指针变量的定义与使用注意:*和&具有相同的优先级,结合方向是从右到左。
&*p即对*p取地址,等价于&a;
p等价于&a;*p等价于a。
3.指针变量的赋值(1)通过地址运算符&进行赋值(2)通过指针变量进行赋值
例inti;int*p=&a;int*q=p;(3)给指针变量赋空值
例int*p=NULL;
例inta=5;int*p=&a;6.1.2
指针变量的定义与使用内存5……2010……2010a201020302050pq
1.指针变量的算术运算加减运算:一个指针可以加、减一个整数n,其结果与指针所指对象的数据类型有
关。指针变量的值(地址)增加或减少“n×sizeof(指针类型)”
6.1.3
指针变量的运算
例如,有下列定义:
int*p,a=2,b=3;假设a、b两个变量被分配在一个连续的内存区,a的起始地址为1000。2
3…
2
3
……10002
3
…1004a(1000)b(1004)a(1000)b(1004)a(1000)b(1004)p=&ap+16.1.3
指针变量的运算(1)指针的加减运算常用于数组的处理。例如:inta[10],*p=a,*x;x=p+2;/*实际上是p加上2*4个字节赋给x,x指向数组的第三个分量*/(2)对于不同类型的指针,指针变量“加上”或“减去”一个整数n所移动的字节数是不同的。例如:floata[10],*p=a,*x;p=p+2;/*实际上是p加上2*4个字节赋给x,x依然指向数组的第三个分量*/(3)*(p++)与(*p)++含义不同,前者表示地址自增,后者表示当前所指向的变量自增。
2.指针变量的关系运算
和基本类型变量一样,指针能进行关系运算。例如:p>q,p<q,p==q,p!=q,p>=q等。6.1.3
指针变量的运算
指针的关系运算在指向数组的指针中广泛的运用,假设p、q是指向同一数组的两个指针,执行p>q的运算,其含义为,若表达式结果为真(非0值),则说明p所指元素在q所指元素之后。或者说q所指元素离数组第一个元素更近些。
指针进行关系运算之前,指针必须初始化,另外,只有同类型的指针才能进行比较。6.1.4
多级指针变量指针不但可以指向基本类型变量,亦可以指向指针变量,这种指向指针型数据的指针变量称为指向指针的指针,或称多级指针。二级指针的定义形式形式:
数据类型
**指针变量例如:
inta,*p,**pp;a=10;p=&a;pp=&p;2000pp(3000)1000p(2000)10a(1000)二级指针示意图指针与数组6.26.2.1
指针与一维数组数组名是一个常量指针,它的值为该数组的首地址。指向数组的指针的定义方法与指向基本类型变量的指针的定义方法相同,例如:inta[5]={1,2,3,4,5};int*p;p=a;(把数组的首地址赋给指针变量p)6.2.1
指针与一维数组由于数组名代表数组首地址,是一个地址常量。因此,下面两个语句等价:
p=&a[0];p=a;在定义指针变量的同时可赋初值:
inta[10],*p=&a[0];(或int*p=a;)
等价于:
int*p;p=&a[0];12345pa[0]a[1]a[2]a[3]a[4]a数组指针示意图6.2.1
指针与一维数组*(p+i)a数组a[0]a[1]a[2]a[i]a[9]pp+1,a+1p+i,a+ip+9,a+9综上所述,引用一个数组元素有:(1)下标法:如a[i]形式;(2)指针法:如*(a+i)或*(p+i)。其中a是数组名,p是指向数组的指针变量,其初值p=a。
可以通过指针变量的运算来访问数组的其他元素,如果p的初值为&a[0],则:p+i和a+i就是a[i]的地址,*(p+i)或*(a+i)是p+i或a+i所指向的数组元素,即a[i]。指向数组的指针变量也可以带下标,如p[i]与*(p+i)、a[i]等价。6.2.1
指针与一维数组例6.4:用三种方法输出数组全部元素。(1)下标法voidmain(){ inta[10]; inti; for(i=0;i<10;i++) scanf("%d",&a[i]); printf("\n"); for(i=0;i<10;i++) printf("%d",a[i]);}6.2.1
指针与一维数组(2)通过数组名计算数组元素地址,输出元素的值voidmain(){ inta[10]; inti; for(i=0;i<10;i++) scanf("%d",&a[i]); printf("\n"); for(i=0;i<10;i++) printf("%d",*(a+i));}6.2.1
指针与一维数组(3)用指针变量指向数组元素voidmain(){ inta[10]; int*p,i; for(i=0;i<10;i++) scanf("%d",&a[i]); printf("\n"); for(p=a;p<(a+10);p++) printf("%d",*p);}三种方法的比较:用下标法比较直观,能直接知道是第几个元素;而使用指针法则执行效率更高6.2.1
指针与一维数组使用指针变量时,应注意:
(1)指针变量可实现使本身的值改变。
p++合法;但a++不合法(a是数组名,代表数组首地址,在程序运行中是固定不变的。)(2)要注意指针变量的当前值。voidmain(){inta[10];int*p,i;p=a;for(;p<a+10;p++)scanf("%d",p);printf("\n");for(;p<(a+10);p++)printf("%d",*p);}不能&p增加:p=a;6.2.1
指针与一维数组(3)*p++相当于*(p++),因为*与++优先级相同,且结合方向从右向左,其作用是先获得p指向变量的值,然后执行p=p+1;。(4)*(p++)与*(++p)意义不同,后者是先p=p+1,再获得p指向的变量值。若p=a,则*(p++)是先输出a[0],再让p指向a[1];
*(++p)是先使p指向a[1],再输出p所指的a[1](5)(*p)++表示的是将p指向的变量值+1。
1.二维数组的地址6.2.2
指针与二维数组a[0]a[1]aa+1inta[2][3];*a*(a+1)a、a+1都是指针,它们是长度为3的整数数组a是一个长度为2的数组数组元素是长度为3的数组6.2.2
指针与二维数组对二维数组inta[2][3],有a——二维数组的首地址,即第0行的首地址a+i——第i行的首地址a[i]*(a+i)
——第i行第0列的元素地址a[i]+j*(a+i)+j
——第i行第j列的元素地址*(a[i]+j)*(*(a+i)+j)a[i][j]a+i=&a[i]=a[i]=*(a+i)=&a[i][0],值相等,含义不同a+i
&a[i],表示第i行首地址,指向行a[i]
*(a+i)
&a[i][0],表示第i行第0列元素地址,指向列inta[2][3];a[0]a[1]100010121000100410121016a[0][0]a[0][1]a[1][1]a[0][2]a[1][2]aa+1a[1][0]6.2.2
指针与二维数组inta[2][3];二维数组元素表示形式:(1)a[1][2](2)*(a[1]+2)(3)*(*(a+1)+2)地址表示:(1)a+1
(2)&a[1][0](3)a[1](4)*(a+1)行指针a[0][0]a[0][1]a[1][0]a[1][1]a[0][2]a[1][2]地址表示:(1)&a[1][2](2)a[1]+2(3)*(a+1)+2列指针
2.指向二维数组的指针变量6.2.2
指针与二维数组(1)指向数组元素的指针变量。这种变量的定义与普通指针变量定义相同,其类型与元素数值类型相同。例如:int*p,a[2][3];
p=&a[1][2];(2)指向一维数组(二维数组的一行)的指针,也称行指针。定义形式为:
类型标识符(*指针变量名)[元素个数]例如:int(*p)[3];6.2.2
指针与二维数组(1)int(*p)[3];定义一个指针变量p,p指向包含3个元素的一维数组。(2)p+i与*(p+i)的区别:
p+i是指向第i行的指针(第i行的首地址);
*(p+i)是指向第i行第1个元素的地址;
两者数值相等,但含义不同:p+i的增值将以行长为单位,而*(p+i)增值将以元素长度为单位。
注意:指针与字符串6.3
指向字符串的指针称字符指针 定义形式:char*指针名6.3.1字符串指针利用指针来对字符串进行操作:通过在定义时初始化指针变量使指针指向一个字符串。用指针变量实现对字符串的访问。charstr[]=”student”;
字符数组名同样是字符数组的首地址,是常量指针,表示形式同一维数组。str[0]sstrstr[1]tstr+1str[2]ustr+2str[3]dstr+3str[4]estr+4str[5]nstr+5str[6]tstr+6str[7]\0str+7
(1)通过在定义时初始化指针变量使指针指向一个字符串。字符指针示例。例6.7#include<stdio.h>voidmain(){char*ch="student";printf("%s",ch);}6.3.1字符串指针chstudent\0(2)用指针变量来实现对字符串的访问。输出字符串中n个字符后的所有字符。例6.8#include<stdio.h>voidmain(){char*ch="IloveChina!"; intn=7; ch=ch+n; printf("%s\n",ch);}6.3.1字符串指针运行结果如下:
(1)字符指针变量本身是一个变量,定义后编译系统为其分配一个用于存放地址的内存单元,具体指向的内存单元需要通过给指针变量赋值来确定。字符数组定义后,编译系统会为其分配一段连续的内存单元,首地址由数组名表示。
(2)字符指针可以整体赋值,字符数组只能一个一个元素单独赋值。6.3.2
字符型指针与字符数组的区别例如;char*ch=”IloveChina!”;可以写成char*ch;ch=”IloveChina!”;//正确,ch是指针变量,字符串整体赋值可是,chara[20]=”IloveChina!”;不能写成chara[20];a=”IloveChina!”;//错误,a代表首地址,只能存放一个字符,不能整体赋值指针与函数6.46.4.1
指针作为函数参数
函数形参为指针变量,实参为指针变量#include<stdio.h>voidswap(int*p,int*q){ inttemp; temp=*p; *p=*q; *q=temp;}
输入两个整数,按从大到小的顺序输出。例6.96.4.1
指针作为函数参数运行结果如下:输入:35↙输出:5,3voidmain(){inta,b; int*p1,*q1;printf("请输入两个整数:"); scanf("%d%d",&a,&b); p1=&a; q1=&b; if(a<b) swap(p1,q1);//调用swap函数printf("按大小顺序输出:"); printf("%d
温馨提示
- 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
- 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
- 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
- 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
- 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
- 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
- 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。
最新文档
- 二零二五年度水路货运运输承包服务合同2篇
- 二零二五版水电安装工程安全评估与施工合同2篇
- 二零二五版农业贷款定金合同规范文本3篇
- 二零二五版幼儿园教师劳动权益保护及劳动合同解除程序协议3篇
- 二零二五版房产托管居间服务合同协议3篇
- 二零二五年房地产物业管理合作开发合同3篇
- 二零二五年度重点单位保安勤务合同5篇
- 二零二五版微电影导演定制化拍摄合同3篇
- 二零二五版KTV员工心理健康关爱计划合同2篇
- 二零二五年度高端酒店场地租赁合同范本2篇
- DB34∕T 4010-2021 水利工程外观质量评定规程
- 纳米复合材料的增韧增能机制
- 图书馆前台接待工作总结
- 卫生院药品管理制度
- 神经外科进修汇报课件
- 2024老年人静脉血栓栓塞症防治中国专家共识(完整版)
- 腾讯营销师认证考试题库(附答案)
- 邻近铁路营业线施工安全监测技术规程 (TB 10314-2021)
- 四年级上册脱式计算100题及答案
- 资本市场与财务管理
- 河南近10年中考真题数学含答案(2023-2014)
评论
0/150
提交评论