b[i]=sqrt(b[i])*10;
那么实参数组a中的数据是否改变?
7.2.3函数的声明
函数定义的位置可以1)在调用它的函数之前,也可以2)在调用它的函数之后,甚至位于3)其它的源程序模块中。
●函数定义位置在前,函数调用在后,不必声明,编译程序产生正确的调用格式。
●函数定义在调用它的函数之后或者函数在其它源程序模块中,且函数类型不是整型,这时,为了使编译程序产生正确的调用格式,可以在函数使用前对函数进行声明。
这样不管函数在什么位置,编译程序都能产生正确的调用格式。
函数声明的格式:
函数类型函数名([参数类型][,…,[参数类型]]);
C语言的库函数就是位于其它模块的函数,为了正确调用,C编译系统提供了相应的.h文件。
.h文件内许多都是函数声明,当源程序要使用库函数时,就应当包含相应的头文件。
7、3函数的信息传递方式
7.3.1形式参数与实际参数
1、形式参数(形参):
函数定义时设定的参数。
2、实际参数(实参):
调用函数时所使用的实际的参数。
例中,主函数中调用max函数的语句是:
nmax=max(n1,n2,n3);其中n1,n2,n3就是实参,它们的类型都是整型。
3、参数的传递
在调用函数时,主调函数和被调函数之间有数据的传递-实参传递给形参。
具体的传递方式有两种:
(1)值传递方式(传值):
将实参单向传递给形参的一种方式。
(2)地址传递方式(传值):
将实参地址单向传递给形参的一种方式。
注意:
(1)单向传递:
不管“传值”、还是“传址”,C语言都是单向传递数据的,一定是实参传递给形参,反过来不行。
也就是说C语言中函数参数传递的两种方式本质相同-“单向传递”。
(2)“传值”、“传址”只是传递的数据类型不同(传值-一般的数值,传址-地址)。
传址实际是传值方式的一个特例,本质还是传值,只是此时传递的是一个地址数据值。
(3)系统分配给实参、形参的内存单元是不同的,也就是说即使在函数中修改了形参的值,也不会影响实参的值。
●对于传值,即使函数中修改了形参的值,也不会影响实参的值。
●对于传址,即使函数中修改了形参的值,也不会影响实参的值。
但是,注意:
不会影响实参的值,不等于不影响实参指向的数据。
传址与传值一样不能通过参数返回数据,但因为传递的是地址,那么就可能通过实参参数所指向的空间间接返回数值。
数据单向传递
ax
主调函数被调函数
不同的存储空间
传值(传递一般的数值)
a,x是不同的空间
数据单向传递
但是a
是地址
指向:
一块
内存
空间
ax
主调函数被调函数
不同的存储空间
传址(传递地址数值)
a,x也是不同的空间
(4)两种参数传递方式中,实参可以是变量、常量、表达式;形参一般是变量,要求两者类型相同或赋值兼容。
7.3.2函数的返回值
C语言可以从函数(被调用函数)返回值给调用函数(这与数学函数相当类似)。
在函数内是通过return语句返回值的。
使用return语句能够返回一个值或不返回值(此时函数类型是void)。
`
Return语句的格式:
Return[表达式];或return(表达式);
说明:
(1)函数的类型就是返回值的类型,return语句中表达式的类型应该与函数类型一致。
如果不一致,以函数类型为准(赋值转化)。
(2)函数类型省略,默认为int。
(3)如果函数没有返回值,函数类型应当说明为void(无类型)。
7、4函数与数组
数组作为参数时的情况:
–数组元素作实参,与单个变量一样
–数组名作函数参数,形、实参数都应是数组名或指针
–实参数组与形参数组的大小可以不同,但实参>形参;
7.4.1数组元素作实参
7.4.2一维数组名作实参
7、5递归函数递归调用
函数允许嵌套调用和递归调用。
递归调用是嵌套调用的特例。
7.5.1函数的嵌套调用
函数嵌套调用:
函数调用中又存在调用。
如函数1调用函数2,函数又调用函数3。
函数之间没有从属关系,一个函数可以被其它函数调用,同时该函数也可以调用其它函数。
例验证歌德巴赫猜想。
按照歌德巴赫猜想,任何一个偶数均可以表示为两个质数之和。
如16=13+338=7+31……
程序一
#include
main()
{
intprimeok(unsignedintn);
intproveok(unsignedintn);
unsignedn,m1,m2;
for(n=6;n<=20;n+=2)
{
m1=proveok(n);
m2=n-m1;
printf("%u=%u+%u\n",n,m1,m2);
}
}
intproveok(unsignedintn)
{unsignedinti;
intn1,n2;
for(i=3;i<=n;i++)
{n1=i*primeok(i);
n2=(n-i)*primeok(n-i);
if(n==n1+n2)break;
}
return(i);
}
intprimeok(unsignedintn)
{inti,m;
m=sqrt(n);
for(i=2;i<=m;i++)
if(n%i==0)break;
if(i>=m+1)
return
(1);
else
return(0);
}
程序二
#include
main()
{
intprimeok(unsignedintn);
intproveok(unsignedintn);
unsignedn,m1,m2;
for(n=6;n<=20;n+=2)
{
m1=proveok(n);
m2=n-m1;
printf("%u=%u+%u\n",n,m1,m2);
}
}
intproveok(unsignedintn)
{unsignedinti;
intn1,n2;
for(i=3;i<=n;i++)
{n1=i;
n2=n-i;
if((n==n1+n2)&&(primeok(n1)==1)&&(primeok(n2)==1))break;
}
return(i);
}
intprimeok(unsignedintn)
{inti,m;
m=sqrt(n);
for(i=2;i<=m;i++)
if(n%i==0)break;
if(i>=m+1)
return
(1);
else
return(0);
}
7.5.2函数的递归调用
1、函数的递归调用:
是指函数直接调用或间接调用自己,或调用一个函数的过程中出现直接或间接调用该函数自身。
前者称为直接递归调用,后者称为间接递归调用。
例如:
->f1()->f1()直接递归调用;->f1()->f2()->f1()间接递归调用。
2、使用递归调用解决问题的方法:
(有限递归)
(1)原有的问题能够分解为一个新问题,而新问题又用到了原有的解法,这就出现了递归。
(2)按照这个原则分解下去,每次出现的新问题是原有问题的简化的子问题
(3)最终分解出来的新问题是一个已知解的问题。
3、递归调用过程(两个阶段)
(1)递推阶段:
将原问题不断地分解为新的子问题,逐渐从未知的向已知的方向推测,最终达到已知的条件,即递归结束条件,这时递推阶段结束。
(2)回归阶段:
从已知条件出发,按照“递推”的逆过程,逐一求值回归,最终到达“递推”的开始处,结束回归阶段,完成递归调用。
例:
用递归法求n!
(P.114)
解:
n!
=n*(n-1)*(n-2)*……*1=n(n-1)!
。
递归公式:
10,1
n!
=
n*(n-1)!
其它
main()
{
floatfac(intn);
inti;
for(i=1;i<=20;i++)
printf("%d!
=%25.0f\n",i,fac(i));
}
floatfac(intn)
{
floatf;
if(n<0)printf("n<0,dataerror!
");
elseif(n==0||n==1)f=1;
elsef=fac(n-1)*n;
return(f);
}
7.6变量的存储类型及作用域
变量从空间上分为局部变量、全局变量。
从变量存在的时间的长短(即变量生存期)来划分,变量还可以分为:
动态存储变量、静态存储变量。
变量的存储方式决定了变量的生存期。
C语言变量的存储方式可以分为:
动态存储方式、静态存储方式。
自动(局部变量)(auto)
动态存储方式
寄存器(局部变量)(register)
存储方式
静态(局部变量)(static)
静态存储方式
静态全局变量(全局变量全部是静态的,不必用static修饰)
动态存储方式
动态存储方式:
在程序运行期间根据需要为相关的变量动态分配存储空间的方式。
C语言中,变量的动态存储方式主要有自动型存储方式和寄存器型存储方式。
1、自动型存储方式(auto)
auto型存储方式是C语言默认的局部变量的存储方式,也是局部变量最常使用的存储方式。
(1)自动变量属于局部变量的范畴,作用域限于定义它的函数或复合语句内。
(2)自动变量所在的函数或复合语句执行时,系统动态为相应的自动变量分配存储单元,当自动变量所在的函数或复合语句执行结束后,自动变量失效,它所在的存储单元被系统释放,所以原来的自动变量的值不能保留下来。
若对同一函数再次调用时,系统会对相应的自动变量重新分配存储单元。
[auto]类型说明变量名;
[register]类型说明变量名;
自动(局部)变量的定义格式:
其中:
auto为自动存储类别关键词,可以省略,缺省时系统默认auto。
例如:
前面各章中的函数中的局部变量,尽管没有明确定义为auto型,但它们都属于auto型变量。
在函数中定义变量,下面两种写法是等效的。
intx,y,z;或autointx,y,z;它们都定义了3个整型auto型变量x,y,z。
2、寄存器型存储方式(register)
register型存储方式是C语言使用较少的一种局部变量的存储方式。
该方式将局部变量存储在CPU的寄存器中,寄存器比内存操作要快很多,所以可以将一些需要反复操作的局部变量存放在寄存器中。
寄存器(局部变量)的定义格式:
其中:
register为寄存器存储类别关键词,不能省略。
值得注意的是:
CPU的寄存器数量有限,如果定义了过多的register变量,系统会自动将其中的部分改为auto型变量。
静态存储方式
静态存储方式:
在程序编译时就给相关的变量分配固定的存储空间(在程序运行的整个期间内都不变)的存储方式。
C语言中,使用静态存储方式的主要有静态存储的局部变量和全局变量。
[static]类型说明变量名[=初始化值];
1、静态存储的局部变量
静态局部变量的定义格式:
其中:
static是静态存储方式关键词,不能省略。
例如:
在函数内定义:
staticinta=10,b;
说明:
(1)静态局部变量的存储空间是在程序编译时由系统分配的,且在程序运行的整个期间都固定不变。
该类变量在其函数调用结束后仍然可以保留变量值。
下次调用该函数,静态局部变量中仍保留上次调用结束时的值。
(2)静态局部变量的初值是在程序编译时一次性赋予的,在程序运行期间不再赋初值,以后若改变了值,保留最后一次改变后的值,直到程序运行结束。
2、全局变量全部是静态存储的。
C语言中,全局变量的存储都是采用静态存储方式,即在编译时就为相应全局变量分配了固定的存储单元,且在程序执行的全过程始终保持不变。
全局变量赋初值也是在便宜时完成的。
因为全局变量全部是静态存储,所以没有必要为说明全局变量是静态存储而使用关键词static。
如果使用了static说明全局变量,不是说“此全局变量要用静态方式存储”(我们知道全局变量天生是静态存储的),那是有另外的含义-令人困惑的全局变量的static说明。
3、全局变量的extern声明及令人困惑的全局变量的static定义。
全局变量的static定义,不是说明“此全局变量要用静态方式存储”(全局变量天生全部是静态存储),而是说,这个全局变量只在本源程序模块有效(文件作用域)。
如果没有static说明的全局变量就是整个源程序范围有效(真正意义上的全局)。
也就是说,变量的作用域有:
分程序(复合语句)作用域,函数作用域,文件(模块)作用域,整个程序作用域。
在引用全局变量时如果使用“extern”声明全局变量,可以扩大全局变量的作用域。
例如,扩大到整个源文件(模块),对于多源文件(模块)可以扩大到其它源文件(模块)。
变量从数据类型的角度,可以分为整型、实型、字符型等。
变量的作用域:
变量的有效范围或者变量的可见性。
变量定义的位置决定了变量的作用域。
分程序作用域(复合语句作用域)
局部变量
函数作用域
变量的作用域
文件作用域(模块作用域)
全局变量
程序作用域
变量从作用域(变量的有效范围,可见性)的角度可以分为:
局部变量,全局变量。
1局部变量
1、局部变量:
是指在一定范围内有效的变量。
C语言中,在以下各位置定义的变量均属于局部变量。
(1)在函数体内定义的变量,在本函数范围内有效,作用域局限于函数体内。
(2)在复合语句内定义的变量,在本复合语句范围内有效,作用域局限于复合语句内。
(3)有参函数的形式参数也是局部变量,只在其所在的函数范围内有效。
例如:
doublefun1(intx,inty)/*x,y,m,n局部变量,在fun1函数内有效(作用域fun1函数)*/
{
intm,n;
......
}
intfun2(charch)/*ch,a,b局部变量,在fun2函数内有效(作用域fun2函数)*/
{
inta,b;
......
}
main()/*a,b局部变量,在main函数内有效(作用域main函数)*/
{
inta,b;
......
{
intx,y;/*x,y局部变量,在复合语句中有效(作用域复合语句)*/
......
}
}
同名,不同作用域,是不同的变量
说明:
(1)不同函数中和不同的复合语句中可以定义(使用)同名变量。
因为它们作用域不同,程序运行时在内存中占据不同的存储单元,各自代表不同的对象,所以它们互不干预。
即:
同名,不同作用域的变量是不同的变量。
(2)局部变量所在的函数被调用或执行时,系统临时给相应的局部变量分配存储单元,一旦函数执行结束,则系统立即释放这些存储单元。
所以在各个函数中的局部变量起作用的时刻是不同的。
2、全局变量
全局变量:
在函数之外定义的变量。
(所有函数前,各个函数之间,所有函数后)
全局变量作用域:
从定义全局变量的位置起到本源程序结束为止。
●在引用全局变量时如果使用“extern”声明全局变量,可以扩大全局变量的作用域。
例如,扩大到整个源文件(模块),对于多源文件(模块)可以扩大到其它源文件(模块)。
●在定义全局变量时如果使用修饰关键词static,表示此全局变量作用域仅限于本源文件(模块)。
例如:
x,y,z
ch1,ch2
t,p
全部是全局变量
intx,y,z;
floatf1(floata,floatb)
{
......
}
charch1,ch2;
intf2(intm)
{
......
}
doublet,p;
main()
{
......
}
x,y,z的作用域
ch1,ch2的作用域
t,p的作用域
externdoublet,p;
intx,y,z;
floatf1(floata,floatb)
{
t=a+b;
......
}
charch1,ch2;
intf2(intm)
{
externdoublet,p;
......
}
doublet,p;
main()
{
......
}
扩大了全局变量t,p的作用域
为整个文件(模块)
扩大了全局变量t,p的作用域
file2.c
externintx;
func(inta)
{
......
x=x+a;
}
作用域从file1扩大到file2
file1.c
intx;
main()
{
......
}
作用域扩大到file2.c
file3.c
intx;
func2()
{
......
}
file2.c
externintx;
func1(inta)
{
......
x=x+a;
}
file1.c
staticintx;
main()
{
......
}
说明: