函数
一个较大的程序可以分为若干个程序模块,每一个模块用来实现一个特点的功能。 在高级的程序中用子程序实现模块的功能。 子程序由函数来完成。 一个C程序可由一个主函数和多若干个其他函数构成。
#include <stdio.h>
Void main()
{
void printfstar(); //对printstar函数声明
void print_message(); //对print_message函数声明
printstar(); //调用printstar函数
print_message(); //调用print_message函数
printstar(); //调用printstar函数
}
说明: 1.一个C程序由一个或多个程序模块组成,每一个程序模块作为一个源程序文件。对较大的程序,一般不希望把所有内容全放在一个文件中,而是把他们分别放在若干个源文件中,再由若干个源程序文件组成一个C程序。这样便于分别编写,分别编译,提高调试效率。一个源程序文件可以为多个C程序公用。
2.一个源文件由一个或多个函数以及其他有关内容(如命令行,数据定义等)组成。一个源程序文件是一个编译单位,在程序编译时是以源程序文件为单位进行编译的,而不是以函数为单位进行编译。
3.C程序执行是从Main函数开始的,如是在main函数中调用其他函数,在调用后流程返回到main函数,在main函数中结束整个程序的运行。
4.所有函数都是平行的,即在定义函数时是分别进行的,是互相独立的。一个函数并不从属于另一函数,即函数不能嵌套定义。函数间可以互相调用,但不能调用main函数。Main函数是系统调用的。
5.从用于角度来看,函数有两种。 ①标准函数:库函数,这是由系统提供的,用户不必自己定义这些函数,可以直接使用它们。应该说明,不同的C系统提供的库函数的数量和功能会有一些不一样,当然许多基本的函数是共用的。 ②用户自己定义的函数。用以解决用户的专门需要。
6.从函数的形式看,函数分为两类: ①无参数函数,如 printstar和 print_message都是无参数函数。在调用无参数函数时,主调函数不向被调用函数传递数据,无参函数一般用来执行指定的一组操作,例如 Printstar函数。 ②有参数函数。在调用函数时,主调函数在调用被调用函数时,通过参数向被调用函数传递数据,一般情况下,执行被调用函数时会得到一个函数值,供主调函数使用。
定义无参数函数的一般形式: 类型标识符 函数名() { 声明部分; 语句部分; } 在定义函数的时要用“类型说明符”指定函数值的类型,即函数带回来的值的类型。 Printstar 和 print_message 函数为 void 类型,表示不需要带回函数值。
定义有参数函数的一般形式: 类型标识符 函数名(形式参数表列) { 声明部分; 语句部分; }
int max(int x,int y)
{
int z; /*函数中的声明部分*/
z= x>y ?x:y ;
return (z);
}
定义空函数的一般形式为: 类型标识符 函数名 () {} 例如: dummy () { } 调用此函数时,什么工作也不做,没有任何实际作用。在主调函数中写上 “dummy();” 表明这里要调用一个函数, 而现在这个函数没有起作用,等以后 扩充函数功能时补充上。
定义空函数的一般形式为:
类型标识符 函数名 ()
{}
例如:
dummy ()
{ }
实际参数和形式参数
在前面提到的有参函数中,在定义函数时函数名后面括号中的变量 称为 ”形式参数”(形参)。 在主调用函数中调用一个函数时,函数名后面括号中的参数(可以是一个表达式)称为实际参数(实参)
大多数情况下,主调函数和被调函数之间有数据传递的关系。 Return后面的括号中的值()作为函数待回的值(称为函数返回值) 在不同的函数之间传递数据,可以使用的方法: 参数: 通过形式参数和实际参数 返回值: 用return 语句返回计算结果 全局变量: 外部变量
int max(int x, int y) //定义有参函数 max
{
int z;
z = x > y ? x : y;//三目
return (z);
}
1.在定义函数中指的形参,在未出现函数调用时,它们并不占内存中的存储单元。只有在发生函数调用时,函数max 中的形参才被分配内存单元。在调用结束后,形参所占的内存单元也被释放。 2.实参可以是常量,变量或表达式,如max(3,a+b); 但要求它们有确定的值。在调用时将实参的值赋给形参。 3.在被定义的函数中,必须指定形参的类型(c = max (a , b); )
4.实参与形参的类型应相同或赋值兼容。如上例题实参和形参都是整型。如果实参为整型而形参X为实型,或者相反,则按照不同类型数值的赋值规则进行转换。 例如实参值 a 为3.5 ,而实参x 为整型,则将实数 3.5 转换成整数 3 然后送到形参 b,字符型与整型可以互相通用。
5.在C语言中,实参对形参的数据传递是 “值传递”(相当于copy) ,单向传递,只由实参传给形参,而不能由形参传回来给实参。在内存中,实参单元与形参单元是不同的单元。
在调用函数时, 给形参分配存储单元,并将实参对应的值传递给实参,调用结束之后,形参单元被释放,实参单元仍保留并维持原值。因此,在执行一个被调用函数时,形参的值如果发生改变,并不会改变主调函数的实参的值。 例如:在执行函数过程中,x和y的值变成10 和 15,而 a和b仍然为 2 和 3 。
通常,希望通过函数调用使主函数能得到一个确定的值,这就是函数的返回值。 例如: max(2,3) 的 值是3 , max(5,2)的值是5 。赋值语句将这个函数值赋给变量C。
函数的返回值
1.函数的返回值是通过函数中的 return 语句获得的。 Return 语句将被调用函数中的一个确定之带回主调用函数中去。 如果需要从被调用函数带回一个函数值供主调函数使用,被调用函数中必须包含return 语句。如果不需要从被调用函数带回函数值可以不要 return 语句。
一个函数可以由一个以上的return 语句,执行到哪一个 return 语句,哪一个return 语句起作用。 return 语句后面的括号也可以不要。 如: return z; 等价于 return (z); 2.Return后面的值可以是一个表达式 如
int max(int x, int y)
{
return (x>y ? x:y);
}
函数的返回值一些说明问题
2.函数的返回值应当属于某一个确定的类型,在定义函数时指定函数返回值的类型。 例如:下面是三个函数的首行。
int max(float x, float y) //函数值为整型
char letter(char c1, char c2) //函数值为字符型
double min(int x, int y) //函数值为双精度型
在C语言中,凡不加类型说明的函数,自动按整型处理。 例 max函数 首行的函数类型可以省些, 用Torbo c 2.0编译程序时能通过,但用Turbo c++ 2.0编译程序不能通过,因为 C++ 要求所有函数都必须指定函数类型。 在定义函数时指定的函数类型一般应该和 return 语句中的表达式类型一致。 如果函数值的类型和return 语句中表达式的值不一致,则以函数类型为准。 对数值型数据,可以自动进行类型转换,即函数类型决定返回值的类型。
对于不带回值的函数,应当用“void ”定义函数为 “无类型”(或称 “空类型”) 这样,系统就保证不使函数带回任何值,即禁止在调用函数中使用被调用函数的返回值。 此时在函数体中不得出现 return 语句。
include <stdio.h>
Void main()
{
int max(float x,float y);
float a,b;
float c;
scanf(“%f,%f”,&a,&b);
c = max(a,b);
printf(“Max is %f \n”,c);
}
int max(float x,float y)
{
float z; //z为实型变量
z = x > y ? x : y ;
return (z);
}
如果实参表列包括多个实参,对实参求值的顺序并不是确定的,有的系统按自左到右顺序求实参的值,有的系统从右到左的顺序。 许多C版本是按自右向左的顺序求值,如Tubro C.
#include <stdio.h>
Void main()
{
int f(int a,int b); /* 函数声明 */
int i = 2,p;
p = f(i,i++); /* 函数调用 */
printf(“%d \n”,p);
}
int f(int a, int b)
{
int c;
if(a>b) c = 1;
else if( a == b) c =0;
else c = -1;
return (c);
}
对于函数调用 Int I = 2 , p; p = f(i , i+1);
如果按自左向右顺序求实参的值,则函数调用相当于 f(2,3)
如果自右向左的顺序求实参的值,则函数调用相当于 f(3 , 3)
函数的嵌套调用
嵌套定义就是在一个定义函数时,其函数体内又包含另一个函数的完整定义。 然而,C语言不能嵌套定义函数,但可以嵌套调用函数,也就是说在调用一个函数的过程中,又调用另一个函数。
计算 s = 2*2 ! + 3*3 ! ( ! 在高等数学里是阶乘的意思, 4! = 4*3*2*1 )
Long square (int p); //平方
Long factorial (int q); //阶乘
Void main()
{
int I;
long s = 0;
for(I = 2;i<=3;i++)
{
s = s+ square(i);
}
printf(“%ld \n”,s);
}
Long square(int p)
{
int k;
long r;
long factorial(int);
k = p *p;
r =factorial(k);
return r;
}
Long factorial(int q)
{
long c = 1;
int I;
for(I = 1; i<=q; i++)
{
c*= I;
}
return c;
}
在调用一个函数的过程中又出现直接或间接地调用该函数本身,称为函数的递归。C语言的特点之一就在于允许函数的递归调用。 例如:
int f(int x)
{
int y,z;
z = f(y);
return (2 * z);//无退出
}
用递归的方法求 n! 即 5! = 4!*5, 4! = 3! *4… N = 1; (N = 0,1) N *( N-1) ! (N < 1)
#include <stdio.h>
#include <stdlib.h>
long recursion(int n);
int main()
{
int n;
long result;
printf("input a interger number: \n");
scanf("%d",&n);
result = recursion(n);
printf("%d \n",result);
return 0;
}
long recursion(int n)
{
long temp_result;
if(n <0)
{
printf("n<0,input error ! \n");
}
else if(n ==0 || n ==1)
{
temp_result = 1;
}
else
{
temp_result = recursion(n -1)*n;
}
return temp_result;
}
数组作为函数参数
数组可以作为函数的参数使用,进行数据传送。数组用作函数参数有两种形式。 1.一种是把数组元素(下标变量)作为实参使用。 2.另一种是把数组名作为函数的形参和实参使用。
数组元素就是下标变量,它与普通变量并无区别。因此它作为函数实参使用与普通变量是完全相同的,在发生函数调用时,把作为实参的数组元素的值传送给形参,实现单向的值传递。 判别一个整型数组 a[10] = {1,2,3,4,-1,-2,-3,-4,2,3}; 中各元素的值,若大于0 ,则输入该值,小于等于0 则输出0 值。
数组名作函数参数与用数组元素作实参有几点不同: 1.用数组元素作实参时,只要数组类型和函数的形参变量的类型一致,那么作为下标变量的数组元素的类型也和函数形参变量的类型是一致的。因此,并不要求函数的形参也是下标变量。换句话说,对数组元素的处理是按普通变量对待的。 然而,用数组名作函数参数时,则要求形参和相对于的实参都必须是类型相同的数组,都必须有明确 的数组说明。当形参和实参二者不一致时,即会发生错误。
2.在普通变量或下标变量作函数参数时,形参变量和实参变量是由编译系统分配的两个不同的内存单元。在函数调用时发生的值传递是把实参变量的值赋予形参变量。 在用数组名作函数参数时,不是进行值传递,即不是把实参数组的每一个元素的值都赋予形参变量的各个元素,是地址传递。 因为实际上形参数组并不存在,编译系统不为形参数组分配内存。
那么,数据的传送是如何实现的呢? 在此之前介绍过,数组名就是数组的首地址。因此在数组名作为函数参数时所进行的传送只是地址的传送,也就是说实参数组的首地址赋予形参数组。 形参数组名取得该首地址之后,也就等于有了实在的数组。实际上是型参数数组和实参数组为同一数组,共同拥有一段内存空间。
例题: 有一个一维数组 score,存放着10个学生的成绩,求平均成绩(写一个 average 函数求平均成绩)
ouble average(double array[10]);
Void main()
{
double score[10] = {82,100,87.5,89,78,85,67.5,92.5,93,94} , result ;
result = average(score);
printf(“average is %5.2 lf \n ,result”);
putchar(‘\n’);
}
形参数组不定义长度
double average(double array[10]);
{
double result = 0;
int I =0;
for(i=0;i<10;i++)
{
result +=array[i];
}
result /=10;
return result ;
}
局部变量和全局变量
局部变量 在一个函数内部定义的变量是内部变量,它只在本函数范围内有效,也就是说只有在本函数内才能使用它们,在此函数以外不能使用这些变量,这称为“局部变量”。
Float t1( int a) /*函数 f1 */
{
int b,c;
}
Char f2(int x.int y) /*函数 f2*/
{
int I,j;
}
Void main() /* 主函数*/
{
int m,n;
}
3.形式参数也是局部变量,例如上面f1函数中的形参 a,也只在 f1函数中有效,其他函数可以调用f1函数,但不能引用f1函数的形参 a; 4.在一个函数内部,可以在复合语句中定义变量,这些变量只在本复合语句中有效,这种复合语句也称为”分程序”或“程序块”。
1.主函数中定义的变量 m ,n 也只在主函数中有效,而不因为在主函数中定义而在整个文件或程序中有效。 主函数也不能使用其他函数中定义的变量。 2.不同函数中可以使用相同名字的变量,它们代表不同的对象,互不干扰。例如:上面再f1函数中定义了变量c 和变量b,倘若在 f2 函数中也定义变量 b,c,它们在内存中占不同的单元,互不混淆。
全局变量
在函数内定义的变量是局部变量,而在函数之外定义的变量称为外部变量,外部变量是全局变量。(也称全程变量) 全局变量可以为本文件中其他函数所共同使用,它的有效范围为从定义变量的位置开始到本源文件结束。
例题 : 有一个一维数组,存放十个学生成绩,写一个函数求平均分,最高分和最低分。
①全局变量在程序的全部执行过程中都占用存储单元,而不是仅仅需要时才开辟单元。 ②使用全局变量过多,会降低程序的清晰性,人们往往难以清楚的判断出每个瞬间各个外部变量的值。在各个函数执行时都可能改变外部变量的值,程序容易出错。因此,要限制使用全局变量。
③它使函数的通用性降低了,因为 函数在执行时要依赖于其所在的外部变量。 如果将一个函数转移到另一个文件中,还要将有关的外部变量及其值一起移过去。 若该外部变量与其他文件的变量同名时,就会出现问题,降低了程序的可靠性和通用性。 一般要求把C程序中的函数做成一个封闭体,除了可以通过“实参”-“形参”的渠道与外界发生联系外,没有其他渠道。
变量的存储方式
动态存储方式与静态存储方式 前面介绍 了从变量的作用域角度来分,可以分为全局变量和局部变量。那么从变量值存在的时间角度来分,又可以分为静态存储方式和动态存储方式。
所谓静态存储方式是指在程序运行开始由系统分配固定的存储空间的方式。 动态存储方式则是在程序运行期间根据需要进行动态分配存储空间的方式。 用户存储空间可以分为三部分: 1.程序区 2.静态存储区 3.动态存储区
在C语言中每一个变量和函数有两个属性,数据类型和数据的存储类别 对数据类型(如整型,字符型等)。存储类别指的是数据在内存中存储的方式。 存储分为两大类:静态存储和动态存储类 具体分为四种:自动的(auto),静态的(static),寄存器的(register),外部的(extern),根据变量的存储类别,可以知道变量的作用域与生存期。
auto 变量
函数中的局部变量,如不专门声明为static 存储类别,都是动态地分配存储空间的(栈),数据存储在动态存储区。 函数中的形参和在函数中定义的变量(包括在复合语句中定义的变量),都属此类,在调用该函数时系统会给他们分配存储空间,在函数调用结束时就自动释放这些存储空间。 因此这类局部变量称为自动变量,自动变量用关键字 auto作存储类别的声明。
用static声明局部变量
有时希望函数中的局部变量的值在函数调用结束后不消失而保留原样,即其占用的存储单元不释放,在下一次该函数调用时,该变量已有值,就是上一次函数调用结束时的值。 这时就应该指定该局部变量为“静态局部变量”,用关键字 static进行声明,通过例子来了解他的特点。
对静态局部变量的说明
1.静态局部变量属于静态存储类别,在静态存储区内分配存储单元,在程序整个运行期间都不释放。 而自动变量(即动态局部变量)属于动态存储类别,占动态存储空间而不占静态存储区空间,函数调用结束后即释放。
2.对静态局部变量是在编译时赋初值的,即只有赋初值一次,在程序运算时它已有初值。以后每次调用函数时不再重新赋初值而只是保留上次函数调用结束时的值。 而对自动变量赋初值,不是在编译时进行的,而是在函数调用时进行,没调用一次函数重新给一次初值,相当于执行一次赋值语句。
3.如在定义局部变量时不赋初值的话,则对静态局部变量来说,编译时自动赋初值0(对数值型变量)或空字符(对字符变量)。 而对自动变量来说,如果不赋初值则它的值是一个不确定的值。 这是由于每次函数调用结束后存储单元已释放,下次调用时又重新另分配存储单元,而所分配的单元中的值是不确定的。
4.虽然静态局部变量在函数调用结束后仍然存在,但其他函数是不能引用的。 例题: 输出1 到 5 的阶乘值。
#include <stdio.h>
Int fac(int n)
{
static int f = 1;
f = f * n;
return (f);
}
Void main()
{
int i;
for(i=1;i<=5;i++)
{ printf(“%d! = %d \n”,i,fac(i)) ; }
}
Regist 变量
一般情况下,变量(包括静态存储方式和动态存储方式)的值是存放在存储中的。 当程序中用到哪一个变量的值时,由控制器发出指令将内存中该变量的值送到运算器中。经过运算器进行运算,如果需要存数,再从运算器将数据送到内存存放。
如果有一些变量使用频繁(例如在一个函数中执行 10000次循环,每次循环中都要引用某局部变量),则为存取变量的值要花费的时间。 为提高执行效率,C语言允许将局部变量的值存放在CPU中,需要用时直接从寄存器取出参加运算,不必再到内存中存取。
include <stdio.h>
Int fac(int n)
{
register int i,f=1;
for(i=1;i<=5;i++)
{
f * = 1;
}
return (f);
}
Void main()
{
int i;
for(i=1;i<=5;i++)
{ printf(“%d! = %d \n”,i,fac(i)) ; }
}
由于对寄存器的存取速度远高于对内存的存取速度,因此这样作可以提高执行效率。这种变量叫做寄存器变量,用关键字 register 做声明。 例如上例题 1到5 的阶乘,改进
#include <stdio.h>
Int fac(int n)
{
register int i,f=1;
for(i=1;i<=5;i++)
{
f * = 1;
}
return (f);
}
Void main()
{
int i;
for(i=1;i<=5;i++)
{ printf(“%d! = %d \n”,i,fac(i)) ; }
}
用extern声明外部变量
外部变量即全局变量,它的作用域是从变量的定义处开始,到本程序文件的末尾。 在此作用域,全局变量可以为程序中各个函数所引用。编译时将外部变量分配在静态存储区。 有时需要用extern 来声明外部变量,以扩展外部变量的作用域。
用static声明外部变量
有时在程序设计中希望某些外部变量只限于被文本引用,而不能被其他文件引用。这时可以在定义外部变量时加一个 static 声明。