目录
一、函数的定义和调用
- 在程序中用到的所有函数,必须“先定义,后使用”。所有函数都是平行的,即在定义函数是是分别进行的,是互相独立的。一个函数并不从属于另一个函数,即函数不能嵌套定义。函数间可以互相调用,但不能调用maiin函数。main函数是被操作系统调用的。
定义函数需包含以下几个内容:
①指定函数的名字,以便以后按名调用。
②指定函数的类型,即函数返回值的类型。
③指定函数的参数的名字和类型,以便在调用函数时向它们传递数据。对无参函数不需要这一项。
④指定函数应该完成什么操作,也就是函数时做什么的,即函数的功能。
库函数只提供了最基本、最通用的一些函数,而不可能包含人们在实际应用中所用到的所有函数。程序设计者需要在程序中自己定义想用的而库函数并没有提供的函数。 - 定义函数的方法 函数体包括声明部分和语句部分。
①定义无参函数
类型名 函数名()
{ 函数体 }
或 类型名 函数名(void)
{ 函数体 } 函数名后面括号内的void表示"空",即函数没有参数。
②定义有参函数
类型名 函数名 (形式参数表列)
{ 函数体 }
③定义空函数
类型名 函数名()
{ }
意义:可以在将来准备扩充功能的地方写上一个空函数,先占一个位置,等以后扩充程序功能时用一个编号的函数代替它。空函数是有用的。
构成C程序的基本单位是函数(√) - 函数调用
(一)函数调用可以作为一个函数的实参,但是不能作为一个函数的形参(函数返回值存在寄存器中,8位寄存器只能储存数据而不能储存地址,无地址,不能作为形参)。实参向形参的数据传递是“值传递”,单向传递,只能由实参传给形参,而不能由形参传给实参。实参和形参在内存中占有不同的存储单元,实参无法得到形参的值。函数形参的值也可以传回对应的实参(×)
函数参数传递是个不可逆的过程,实参将其值传递给形参,调用结束时形参并不将其值回传给实参
(二)调用函数并不一定要求包括分号,只有作为函数调用语句才需要有分号。如果作为函数表达式或函数参数,函数调用本身是不必有分号的。
(三)在调用函数过程中,系统会把实参的值传递给被调用函数的形参。该值在函数调用期间有效。只能实参传入形参。函数在使用时形参变量在未出现函数调用时,并不占用内存,只在调用时才占用,临时分配(给类型分配空间)。调用结束后,将释放内存。在函数定义时,可以没有变量名,但一定要有变量类型。
(四)实参可以是常量、变量或表达式,但要求他们有确定值。形参只能是变量,在被定义的函数中,必须指定形参的类型,且没有确定值。
(五)函数的返回值是通过函数中的return 语句获得的。return语句将被调用函数中的一个确定值带回到主调函数中去。如果需要从被调函数带回一个函数值(供主调函数使用),被调函数必须包含return语句。return语句后面的括号可以不要。在定义函数时制定的函数类型一般应该和return语句中的表达式类型一致。如果函数值类型和return 语句中表达式的值不一致,则以函数类型为准。对数值型数据,可以自动进行类型转换。即函数类型决定返回值的类型。若调用一个函数,且此函数中没有return语句,该函数返回一个不确定的值。
(六)如果用户自己定义函数,应该要进行声明。
不用声明函数的情况:①函数放主函数前。 ②都是整型或字符型(整型的一种)(系统自动按整型声明声明)。 - 数组作为函数参数
(一)数组元素可以用作函数实参,但不能用作形参。因为形参是在函数被调用时临时分配存储单元的,不可能为数组元素单独分配存储单元(数组是一个整体,在内存中占连续的一段存储单元)。 m=max(m,a[i]); 注意这里实参传过去是某一个值而不是数组。
(二)数组名也可作函数参数(包括实参和形参)。用数组元素作实参时,向形参变量传递的是数组元素的值,而用数组名作函数实参时,向形参(数组名或指针变量)传递的是数组首元素的地址。 aver=average(score) 这里的score为数组
(三)可声明形参数组大小(如 float average(float array[10])这里为数组 ),但实际上,指定其大小是不起任何作用的,因为C语言编译系统并不检查形参数组大小,只是将实参数组的首元素的地址传给形参数组名。所以形参数组可以不指定大小,在定义数组时,在数组名后面跟一个空的方括号(如float average(float array[]) 但是实参不能这么写 实参写法参考(二)中例子)。
(四)多维数组名作函数参数。可以用多维数组名作为函数的实参和形参,在被调用函数中对形参数组定义时可以指定每一维的大小,也可以省略第一维的大小说明。如int array[3][10]或int array[][10]都合法而且等价。但是不能吧第二维以及其他高维的大小说明省略。如int array[][]是不合法的。因为二维数组是由若干个一维数组组成的,在内存中,数组是按行存放的,因此,定义二维数组时,必须指定列数,不能只指定第一位行数而忽略第二维列数。如 int array[3][]是错误的。 -
(易错)有以下函数定义: void fun(int n, double x) {…} 若以下选项中的变量都已经正确定义并赋值,则对函数fun的正确调用语句是__C__。
A.fun(int y, double m) ;B.k=fun(10, 12.5);C.fun(x,n);D.void fun(n, x);若函数fun没有返回值,选项B错误;选项A中应将y和m前面的函数的类型说明符去掉,故选项A不正确;;选项D在调用函数时,不应该再声明其返回类型,故迭项D不正确.
-
c语言规定形参可以是常量、变量或表达式,只要与其对应的实参类型一致即可(×) 形参只能是变量
-
以下函数值的类型是__int__。
fun(float x)
{ float y;
y=3*x-4;
return y;}
-
(易错)
请读程序:
#include<stdio.h>
func( int a, int b)
{ int c; c=a+b; return c; }
main()
{ int x=6, y=7, r;
r=func( func(x--,y--),x+y);
printf("%d\n",r);
}
上面程序的输出结果是_26_。 传递过后,在函数中自减。
-
对以下程序,正确的说法是(程序中没有错误,可以正常运行 )
sub (char x,char y)
{
int z;
z=x%y;
return z;
}
main( )
{
int g=5,h=3,k;
k=sub(g,h);
printf("%d\n",k);
}
-
有一函数的定义如:void fun(char *s){……},则不正确的函数调用是( D )
[单选题]
A.main() {char a[20]=”abcdefgh”; fun(a); …… }
B.main() { char a[20]=”abcdefgh”; fun(&a[0]); …… }
C.main() { char a[20]=”abcdefgh”; char *p=a;fun(p); …… }
D.main() { char a[20]=”abcdefgh”; fun(a[]); …… }
-
设在主函数中有以下定义和函数调用语句,且fun函数为void类型;正确的fun函数的首部应为( void fun(double b[][22]))(要求形参名为b)
main()
{double s[10][22];
int n; fun(s); }
-
若有以下程序
#include <stdio.h>
int f(int a)
{ return a%2; }
main()
{ int s[8]={1,3,5,2,4,6},i,d=0;
for (i=0;f(s[i]);i++) d+=s[i];
printf("%d\n",d);
}
程序输出的结果为__9_。
二、嵌套调用和递归调用
- 函数的递归调用:在调用一个函数的过程中又出现或间接地调用该函数本身。
-
在函数调用过程中,如果函数funA调用了函数 funB,函数funB又调用了函数funA,则称为函数的直接递归调用。(×)
递归调用就是一种特殊的嵌套调用,是某个函数调用自己,或者是调用其他函数后再次调用自己。如果一个函数在其内部调用自己,我们称这样的调用为直接调用;若两个函数之间相互调用,则成为间接调用。题目中funA和funB函数之间互相调用,因此称为间接递归调用。
-
对以下程序,正确的说法是( )
long fib(int n)
{if(n>2) return(fib(n-1)+fib(n-2));
else return (2);}
main()
{ printf("%d\n",fib(3)); }
fib(3) =fib(2)+fib(1),而fib(2)的返回值为2,fib(1)的返回值也为2,故输出的fib(3)的值为4。
三、局部变量和全局变量(变量作用域的问题)
- 局部变量
(一)在函数的开头定义;
(二)在函数内的复合语句内定义;
(三)在函数的外部定义。
在一个函数内部定义的变量只在本函数范围内有效,只有在本函数内才能引用它们;在复合语句内定义的变量只有在本复合语句范围内有效,只有在本复合语句内才能引用它们。
①在主函数中定义的变量也只在主函数中有效,并不因为在主函数中定义而在整个文件或程序中有效。主函数也不能使用其他函数中定义的变量。
②不同函数中可以使用同名的变量,它们代表不同的对象,互不干扰。
③形式参数也是局部变量。
④ - 全局变量
①在函数内定义的变量是局部变量,而在函数之外定义的变量称为外部变量,外部变量是全局变量(也称全程变量)。全局变量可以为本文件中其它函数所共用。它的有效范围为从定义变量的位置开始到本源文件结束。
②设置全局变量的作用是增加了函数间数据联系的渠道。由于同一文件中的所有函数都能引用全局变量的值,因此如果在一个函数中改变了全局变量的值,就能影响到其它函数中全局变量的值。相当于各个函数间有直接的传递通道。由于函数的调用只能带回一个函数返回值,因此有时可以利用全局变量来增加函数间的联系渠道,通过函数调用能得到一个以上的值。 - 为便于区别全局变量和局部变量,在C程序设计人员中有一个习惯(但非规定),将全局变量名的第一个字母用大写表示。
- 如果在同一个源文件中,全局变量与局部变量同名:局部变量的作用范围内,局部变量有效,全局变量被”屏蔽“,即它不起作用,
-
在main函数中定义的变量称为全局变量(×) 函数体内定义的变量都是局部变量
-
下面程序的输出结果是_20 10_。
#include <stdio.h>
main( )
{
int x=10;
{
int x=20;
printf ("%d,", x);
}
printf("%d\n", x);
} 以大括号为分界,将程序分为体内和体外执行,互不干扰。(如变量定义等)
四、变量的存储方式和生存期
- 从变量的作用域(即从空间)的角度来观察,变量可分为全局变量和局部变量。
- 从变量值存在的时间(即生存期)来观察。变量的存储有两种不同方式:静态存储方式和动态存储方式。静态存储方式是指在程序运行期间由系统分配固定的存储空间的方式。而动态存储方式则是在程序运行期间根据需要进行动态的分配存储空间的方式。
- 内存中的供用户使用的存储空间的情况。这个存储空间可分为三部分。
(一)程序区;
(二)静态存储区;
(三)动态存储区。
数据分别存放在静态存储区和动态存储区中。 - 全局变量全部存放在静态存储区中,在程序开始执行时给全局变量分配存储区,程序执行完毕就释放。
- 在动态存储区中存放一下数据:
①函数形式参数。在调用时给形参分配存储空间。
②函数中定义的没有用关键字static(只局限于它所在文件,其他文件不能调用)声明的变量,即自动变量。
③函数调用是的现场保护和返回地址等。
对以上这些数据,在函数调用开始时分配动态存储空间,函数结束时释放这些空间。在程序执行过程中,这种分配和释放是动态的,如果在一个程序中两次调用同一函数,而在此函数中定义了局部变量,在两次调用时分配给这些局部变量的存储空间的地址可能是不相同的。 - 如果一个程序中包含若干个函数,每个函数中的局部变量的生存期并不等于整个程序的执行周期,它只是程序执行周期的一部分。在程序执行过程中,先后调用各个函数,此时,会动态地分配和释放存储空间。
- 在C语言中,每一个变量和函数都有两个属性:数据类型和数据的存储类别。
C的存储类别包括4种:自动的(auto)、静态的(static)、寄存器的(register)、外部的(extern)。在定义和声明变量和函数式,一般应同时指定其函数类型和存储类别,也可以采用默认方式指定(如果用户不指定,系统会隐含地指定为某一种存储类别)。 - 局部变量的存储类别
①自动变量(auto变量)
函数中的局部变量,如果不专门声明为static(静态)存储类别,都是动态地分配存储空间的,数据存储在动态存储区中。实际上,关键词auto可以省略,不写auto则隐含指定为“自动存储类别”。
②静态局部变量(static局部变量)
在函数调用完后不消失而继续保留原值,即其占用的存储单元不释放,在下一次再调用该函数时,该变量已有值(就是上一次函数调用结束时的值)。
(1)静态局部变量属于静态存储类别,在静态存储区内分配存储单元。在程序整个运行期间都不释放。而自动变量(即动态局部变量)属于动态存储类别,分配在动态存储区空间而不在静态存储区空间,函数调用结束后即释放。
(2)对静态局部变量是在编译时赋初值的,即只赋初值一次,在程序运行时它已有初值。以后每次调用函数时不再重新赋初值,而只是保留上次函数调用结束时的值。而对于自动变量赋初值,不是在编译时进行的,而是在函数调用时进行的,每调用一次函数重新给一次初值,相当于执行一次赋值语句。
(3)如果在定义局部变量是不赋初值的话,则对静态局部变量来说,编译时自动赋初值0(对数值型变量)或空字符'\0'(对字符型变量)。而对自动变量来说,它的值是一个不确定的值。这是由于每次函数调用结束后存储单元已释放,下次调用时又重新另分配存储单元,而所分配的单元中的内容是不可知的。
(4)虽然静态局部变量在函数调用结束后仍然存在,但其它函数是不能引用它的。因为它是局部变量,只能被本函数引用,而不能被其它函数引用。
③寄存器变量(register变量)
一般情况下,变量(包含静态存储方式和动态存储方式)的值是存放在内存中的。当程序中用到哪一个变量的值时,由控制器发出指令将内存中该变量的值送到运算器中。经过运算器进行运算,如果需要存数,再从运算器将数据送到内存存放。
如果有一些变量使用频繁,则为存取变量的值要花费不少时间。为提高执行效率,允许将局部变量的值放在CPU中的寄存器中,需要用时直接从寄存器取出参加运算,不必再到内存中去存取。由于对寄存器的存取速度远高于对内存的存取速度,因此这样做可以提高执行效率。
3种局部变量的存储位置是不同的:自动变量存储在动态存储区;静态局部变量存储在静态存储区;寄存器存储在CPU中的寄存器中。
- 全局变量的存储类别
全局变量都是存放在静态存储区中的。因此他们的生存期是固定的,存在于程序的整个运行过程。一般来说,外部变量是在函数的外部定义的全局变量,它的作用域是从变量的定义处开始,到本程序文件的末尾。在此作用域内,全局变量可以为程序中各个函数所引用。但有时程序设计人员希望能扩展外部变量的作用域。有以下几种情况。
①在一个文件内扩展外部变量的作用域。
如果外部变量不在文件的开头定义,其有效的作用范围只限于定义处到文件结束。在定义点之前的函数不能引用该外部变量。如果要在定义点之前的函数需要引用该外部变量,则应该在引用之前用关键字extern对该变量作“外部变量声明”,表明把该外部变量的作用域扩展到此位置。有了此声明,就可以从“声明”处起,合法地使用该外部变量。用extern声明外部变量时,类型名可以写也可以省写。因为它不是定义变量,可以不指定类型,只需写出外部变量名即可。提倡将外部变量的定义放在引用它的所有函数之前,这样可以避免在函数中多加一个extern声明。
②将外部变量的作用域扩展到其他文件
如果一个程序包含两个文件,在两个文件中都要用到外部变量Num,不能分别在两个文件中个自定义一个外部变量Num,否则在进行程序的连接时会出现“重复定义”的错误。正确的做法是:在任一个文件中定义外部变量Num,而在另一文件中用extern对Num作“外部变量声明”,即“extern Num;”。在编译和连接时,系统会由此知道Num有“外部链接”,可以从别处找已定义的外部变量Num,并将在另一文件中定义的外部变量Num的作用域扩展到本文件,在本文件中可以合法地应用外部变量Num。用这种方法扩展全局变量的作用域应十分慎重,因为再执行一个文件中的操作时,可能会改变该全局变量的值,会影响到另一文件中全局变量的值,从而影响该文件中函数的执行结果。
extern既可以用来扩展外部变量在本文件中的作用域,又可以使外部变量的作用域从一个文件扩展到程序中的其他文件,系统处理:在编译时遇到extern时,先从本文件中找外部变量的定义,如果找到,就在本文件中扩展作用域;如果找不到,就在连接时从其他文件中找外部变量的定义。如果从其他文件中找到了就将作用域扩展到本文件;如果再找不到,就按出错处理。
③将外部变量的作用域限制在本文件中
在定义外部变量时加一个static声明,这样外部变量只限于本文件引用,而不能被其他文件应用。这种加上static声明、只能用于本文件的外部变量称为静态外部变量。
注:不要误认为对外部变量加static声明后才采取静态存储方式(存放在静态存储区中),而不加static的是采取动态存储(存放在动态存储区)。声明局部变量的存储类型和声明全局变量的存储类型的含义是不同的。对于局部变量来说,声明存储类型的作用是指定变量存储的区域(静态存储区或动态存储区)以及由此产生的生存期的问题,而对于全局变量来说,由于都是编译时分配内存的,都存放在静态存储区,声明存储类型的作用是变量作用域的扩展问题。
用static声明一个变量的作用是:
(1)对局部变量用static声明,把它分配在静态存储区,该变量在整个程序执行期间不释放,其所分配的空间始终存在。
(2)对全局变量用static声明,则该变量的作用域只限于本文件模块(即被声明的文件中)。 -
如果要限制一个变量只能为本文件所使用,必须通过_静态外部变量_来实现。
-
下面程序的输出是_3600__。
int w=3;
main()
{
int w=10;
printf("%d\n",fun(5)*w);
}
fun(int k)
{
if(k==0) return w;
return(fun(k-1)*k);
}
-
以下程序的输出的结果是__2 6__。 静态局部变量是在编译时赋初值的,以后每次调用时不再重新赋值,而是保留上次函数调用结束时的值。
int x=3;
main()
{
int i;
for (i=1;i<x;i++) incre();
}
incre()
{
static int x=1;
x*=x+1;
printf(" %d",x);
}
-
以下只有在使用时才为该类型变量分配内存的存储类说明是auto和 register。
-
下面程序的输出是(6)
fun3(int x)
{ static int a=3;
a+=x;
return(a); }
main()
{
int k=2,m=1,n;
n=fun3(k);
n=fun3(m);
printf("%d\n",n);
}
-
在C语言中,形参的缺省存储类是auto.
五、内部函数和外部函数
- 根据函数能否被其他源文件调用,将函数区分为内部函数和外部函数。
- 内部函数:函数只能被本文件中其他函数所调用。在定义内部函数时,在函数名和函数类型的前面加static。即:
-
static 类型名 函数名(形参表);以下只有在使用时才为该类型变量分配内存的存储类说明是auto和 register。
内部函数又称为静态函数,因为它使用static声明的。使用内部函数,可以使函数的作用域只局限于所在文件。这样,在不同的文件中即使有同名的内部函数,也互不干扰,不必担心所用函数是否会与其它函数模块中的函数同名。
通常把只能由本文件使用的函数和外部变量放在文件的开头,前面都冠以static使之局部化,其他文件不能引用。 -
外部函数:在函数首部的最左端加关键词extern,则此函数是外部函数,可供其他文件调用。如函数首部可以: extern int fun (int a, int b)
C语言规定,如果在定义函数时忽略extern,则默认为外部函数。
在需要调用此函数的其他文件中,需要对此函数作声明(即使在本文件中调用一个函数,也要用函数原型进行声明)。在对此函数作声明时,要加关键词extern,表示该函数“是在其他文件中定义的外部函数”。