C语言-函数
C语言-函数
无参函数
- 无参函数的定义形式
类型标识符 函数名()
{声明部分
语句
}
其中,类型标识符合函数名称为函数头。类型标识符指明了本函数的类型,函数的类型实际上是函数返回值的类型。函数名是由用户定义的标识符,函数名后有一个括号()
,其中无参数,但是括号不可少。
{}
中的内容称为函数体。在函数体中声明部分,是对函数体内部所用到的变量的类型说明。在很多情况下都不要求无参函数有返回值,此时函数类型符可以写为void
。例如:
void Hello(){
printf("Hello, world\n);
}
这里只把main
改为Hello
作为函数名,其余不变。Hello
函数是一个无参函数,当被其他函数调用时,输出Hello, world
字符串。
有参函数
- 有参函数定义的形式
类型标识符 函数名(形式参数表列)
{
声明部分
语句
}
有参函数比无参函数多了形式参数表列。在形参表中给出的参数称为形式参数。他们可以是各种类型的变量,各参数之间用,
隔开。在进行函数调用时,
主调函数将赋予这些形式参数实际的值。形参既然是变量,必须在形参表中给出形参的类型说明。例如:
int max(int a, int b){
if (a>b) return a;
else return b;
}
第一行说明max
函数是一个整型函数,其返回的函数值是一个整数。形参为a,b
均为整型量。a,b
的具体值是由主调函数在调用时传送过来的。在{}
中的函数体内,除形参外,没有其他变量,因此只有语句没有声明部分。
在max
函数体中的return
语句是把a
(或者b
)的值作为函数的值返回给主调函数。有返回值函数中至少有一个return
语句。注意在C程序中,一个函数的定义可以放在任意位置,既可以放在主函数main之前,也可以放在main之后。
#include<stdio.h>
//自定义函数
int max(int a, int b){
if (a>b) return a;
else return b;
}
int main(){
int max(int a, int b); // 先声明后使用,符合c语言先定义后使用的规范
int x,y,z;
printf("input two numbers:\n");
scanf("%d%d",&x,&y);
z = max(x,y);
printf("max number = %d\n",z);
}
结果显示:
函数的参数和函数的值
- 形式参数和实际参数
函数的参数主要分为形参和实参。形参出现在函数定义中,在整个函数体内都可以使用,离开该函数则不能使用。实参出现在主调函数中,进入被调函数后,实参变量也不能使用。形参和实参的功能是做数据传送。
发生函数调用时,主调函数把实参的值传送给被调函数的形参从而实现主调函数向被调函数的数据传送。函数的形参和实参具有以下特点:- 形参变量只有在被调用时才分配内存单元,在调用结束时,即可释放所分配的内存单元。因此形参只有在函数内部有效。函数调用结束返回主调函数后则不能再使用该形参变量。
- 实参可以是常量、变量、表达式、函数等,无论实参是何种类型的量,在进行函数调用时,它们都必须具有确定的值,以便把这些值传送给形参。因此应预先用赋值,输入等办法使实参获得确定值。
- 实参和形参在数量上,类型上,顺序上应严格一致,否则会发生类型不匹配的错误
- 函数调用中发生的数据传送是单向的,即只能把实参的值传送给形参,而不能把形参的值反向地传送给实参。因此在函数调用过程中,形参的值发生改变,而实参中的值不会变化。
实例:实现计算\(\sum n_{i}\)
#include<stdio.h>
//函数调用
int main(){
int n;
printf("input number\n");
scanf("%d",&n);
s(n);
printf("n=%d\n",n);//打印输入值
}
int s(int n){
int i;
for(i=n-1;i>=1;i--)
n = n+i;
printf("n=%d\n",n);//打印结果
}
结果输出:
- 函数的返回值
函数的值是指函数被调用之后,执行函数体中的程序段所取得的并返回给主调函数的值。如调用正弦函数取得正弦值,对函数的返回值有几点说明- 函数的值只能通过return语句返回主调函数
return语句的一般形式:return 表达式;
或return (表达式);
- 函数值的类型和函数定义中函数的类型应保持一致。如果两者不一致,则以函数类型为准,自动进行类型转换。
- 如函数值为整型,在函数定义时可以省去类型说明
- 不返回函数值的函数,可以明确定义为“空类型”,类型说明符为
void
- 函数的值只能通过return语句返回主调函数
函数的调用
- 函数调用的一般形式
在C语言中,函数调用的一般形式为:函数名(实际参数表)
。对无参函数调用时则无实际参数表。实际参数表中的参数可以是常数,变量或其他构造类型数据及表达式。各实参之间用逗号分隔。 - 函数调用的方式
在C语言中,可以用以下几种方式调用函数:- 函数表达式:函数作为表达式中的一项出现在表达式中,以函数返回值参与表达式的运算。这种方式要求函数是有返回值的,例如
z=max(x,y)
是一个赋值表达式,把max
的返回值赋予变量z
。 - 函数语句:函数调用的一般形式加上分号即构成函数语句,例如
printf("%d",a);
,scanf("%d",&b);
都是以函数语句的方式调用函数。 - 函数实参:函数作为另一个函数调用的实际参数出现。这种情况是把该函数的返回值作为实参进行传送,因此要求该函数必须是有返回值的,例如
printf("%d",max(x,y));
即使把max
调用的返回值又作为printf
函数的实参来使用。
在函数调用中还应该注意的一个问题是求值顺序的问题。所谓求值顺序是指对实参表中各量是自左至右使用还是自右至左使用?对此,各系统的规定不一定相同。
结果显示:#include<stdio.h> int main() { int i = 8; printf("%d\n%d\n%d\n%d\n", ++i, --i, i++, i--); }
- 被调函数的声明和函数原型
在主调函数中调用某函数之前应对该被调函数进行说明(声明),这与使用变量之前要先进行变量说明是一样的。在主调函数中对被调函数做说明的目的是使编译系统知道被调函数返回值的类型,以便在主调函数中按此种类型对返回值做相应处理。
其一般形式:类型说明符 被调函数名(类型 形参,类型 形参...);
或者类型说明符 被调用函数名(类型,类型);
例如:
int max(int a,int b);
或者写成int max(int, int);
C语言中规定在以下几种情况下可以省去主调函数对被调函数的函数说明
- 如果被调函数的返回值是整型或字符型时,可以不对被调函数作说明,而直接调用。这时系统将自动对被调函数返回值按整型处理。
- 当被调函数的函数定义出现在主再作说明而直接调用。
- 如在所有函数定义之前,在函数外预先说明了各个函数的类型,则在以后的各主调函数中,可不再对被调函数作说明。
- 函数表达式:函数作为表达式中的一项出现在表达式中,以函数返回值参与表达式的运算。这种方式要求函数是有返回值的,例如
- 函数的嵌套使用
C语言中不允许作嵌套的函数定义。因此各函数之间是平行的,不存在上一级函数和下一级函数的问题。但是C语言允许在一个函数的定义中出现对另一个函数的调用。这样就出现了
函数的嵌套调用。即在被调函数中又调用其它函数。这与其它语言的子程序嵌套的情形是类似的。
实例1:计算\(s = 2^{2}!+3^{2}!\)
解析:可以编写两个函数,一个用来计算平方值的函数,另一个计算阶乘。主函数先调用平方值计算函数,再调用阶乘函数
#include<stdio.h>
//阶乘计算
long f2(int q){
long i,c=1;
for (i=1;i<q;i++)
c = c*i;
return c;
}
//计算平方
long f1(int p){
int k;
long r;
long f2(int);//先声明f2函数
k= p*p;
r = f2(k);
return r;
}
int main(){
int i;
long f1(int);
long s=0;
for (i=2;i<=3;i++);
s=s+f1(i);
printf("\ns=%ld\n",s);
}
结果输出:
- 函数的递归调用
一个函数在它的函数体内调用它自身称为递归调用。这种函数称为递归函数。C语言允许函数的递归调用。在递归调用中,主调函数又是被调函数。执行递归函数将反复调用其自身,没调用一次就进入新的一层。例如:
int f(int x){
int y;
z = f(y);
return z;
}
实例:用递归函数计算\(n!\)
#include<stdio.h>
//递归函数
long f(int n){
long s;
if (n<0) printf("n<0,input error");
else if (n==0||n==1) s=1;
else s = n*(f(n-1));
return s;
}
int main(){
int n;
long y;
long f(int n);
printf("input a integer number:\n");
scanf("%d",&n);
y = f(n);
printf("%d!=%ld",n,y);
}
结果输出:
实例:Hanoi塔问题
一块板上有三根针,A,B,C。A 针上套有 64 个大小不等的圆盘,大的在下,小的在上.要把这 64 个圆盘从 A 针移动 C 针上,
每次只能移动一个圆盘,移动可以借助 B 针进行。但在任何时候,任何针上的圆盘都必须保持大盘在下,小盘在上。求移动的步骤。
#include<stdio.h>
// Hanoi塔问题
move(int n,int x,int y,int z){
if (n==1)
printf("%c-->%c\n",x,z);
else{
move(n-1,x,z,y);
printf("%c-->%c\n",x,z);
move(n-1,y,x,z);
}
}
main(){
int h;
printf("\ninput number:\n");
scanf("%d",&h);
printf("the step to moveing %d disks:\n",h);
move(h,'a','b','c');
}
结果输出:
- 数组作为函数参数
数组可以作为函数的参数使用,进行数据传送。数组用作函数参数有两种形式,一种是把数组元素(下标变量)作 实参使 ;另一种是把数组名作为函数的形参和实参使用。- 数组元素作为函数实参
数组元素就是下标变量,它与普通变量并无区别。因此它作为函数实参使用与普通变量是完全相同的,在发送函数调用时,把作为实参的数组元素的值传送给形参,实现单向值传送。
实例:判别一个整数数组中各元素的值,若大于 0 则输出该值,若小于等于 0 则输出 0值
结果输出#include<stdio.h> //数组实参 void nzp(int v){ if (v>0) printf("%d",v); else printf("%d",0); } int main(){ int a[5],i; printf("input 5 numbers:\n"); for (i=0;i<5;i++){ scanf("%d\n",&a[i]); nzp(a[i]); return 0; } }
- 数组元素作为函数实参
局部变量和全局变量
在讨论函数的形参变量时曾经提到,形参变量只在被调用期间才分配内存单元,调用结束立即释放。这一点表明形参变量只有在函数内才是有效的,离开该函数就不能再使用了。这种变量有效性的范围称变量的作用域。不仅对于形参变量,C语言中所有的量都有自己的作用域。变量说明的方式不同,其作用域也不同。C语言中的变量,按作用域范围可分为局部变量和全局变量。
- 局部变量
局部变量也称为内部变量。局部变量是在函数内做定义说明的,其作用域仅限于函数内,离开该函数后再使用这种变量是非法的。
int f1(int a){
int b,c;//a,b,c在函数f1范围内有效,其作用域为f1函数。
}
关于局部变量的作用域说明几点:
- 主函数中定义的变量也只能在主函数中使用,不能在其它函数中使用。同时,主函数中也不能使用其它函数中定义的变量。因为主函数也是一个函数,它与其它函数是平行关系。这一点是与其它语言不同的,应予以注意。
- 形参变量是属于被调函数的局部变量,实参变量是属于主调函数的局部变量
- 允许在不同的函数中使用相同的变量名,它们代表不同的对象,分配不同的单元,互不干扰,也不会发生混淆。如在前例中,形参和实参的变量名都为
n
,是完全允许的。 - 在复合语句中也可定义变量,其作用域只在复合语句范围内
main(){
int s,a;
...
{
int b;
s=a+b;//b作用域
...
}
...//s,a作用域
}
- 全局变量
全局变量也称为外部变量,它是在函数外部定义的变量。它不属于哪一个函数,它属于一个源程序文件。其作用域是整个源程序。在函数中使用全局变量,一般应作全局变量说明。
只有在函数内经过说明的全局变量才能使用。全局变量的说明符为extern
。但在一个函数之前定义的全局变量,在该函数内使用可不再加以说明。
实例:输入正方体的长宽高l,w,h
。求体积及三个面x*y,x*z,y*z
的面积
#include<stdio.h>
//输入正方体的长宽高 l,w,h。求体积及三个面 x*y,x*z,y*z 的面积
int s1,s2,s3;//在函数外部定义的外部变量,都是全局变量
int vs(int a,int b,int c){
int v;
v = a*b*c;
s1=a*b;
s2=b*c;
s3=a*c;
return v;
}
int main(){
int v,l,w,h;
printf("input length,width,and height\n");
scanf("%d%d%d",&l,&w,&h);
v=vs(l,w,h);
printf("v=%d,s1=%d,s2=%d,s3=%d\n",v,s1,s2,s3);
}
结果输出:
变量的存储类型
- 动态存储方式和静态存储方式
从变量的作用域(即从空间)角度来分,可以分为全局变量和局部变量。从另一个角度,从变量值存在的作用时间(即生存期)角度分,可以分为静态存储方式和动态存储方式。- 静态存储方式:是指在程序运行期间分配固定的存储空间的方式
- 动态存储方式:是在程序运行期间根据需要进行动态的分配存储空间的方式
用户存储空间可以分为三个部分: - 程序区
- 静态存储区
- 动态存储区
全局变量全部存放在静态存储区,在程序开始执行时给全局变量分配存储区,程序运行完毕就
释放。在程序执行过程中它们占据固定的存储单元,而不动态地进行分配和释放;动态存储区存放以下数据: - 函数形式参数
- 自动变量(未加static声明的局部变量)
- 函数调用实的现场保护和返回地址。
对以上这些数据,在函数开始调用时分配动态存储空间,函数结束时释放这些空间。在C语言中,每个变量和函数有两个属性:数据类型和数据的存储类型。
- auto变量
函数中的局部变量,如不专门声明为 static 存储类别,都是动态地分配存储空间的,数
据存储在动态存储区中。函数中的形参和在函数中定义的变量(包括在复合语句中定义的变
量),都属此类,在调用该函数时系统会给它们分配存储空间,在函数调用结束时就自动释
放这 部变量称为自动变量。自动变量用关键字 auto 作存储类别的声明。例如:
int f(int a) /*定义f函数,a为参数*/
{auto int b,c=3;//定义b,c自动变量
...
}
a是形参,b,c是自动变量,对c赋初值3.执行完f函数后,自动释放a,b,c所占的存储单元。关键字auto可以省略,auto 不写则隐含定为“自动存储类别”,属于动态存储方式。
- 用static声明局部变量
有时候希望函数中的局部变量的值在函数调用结束后不消失而保留原值,这时就应该指定局部变量为“静态局部变量”,用关键字static进行声明。
#include<stdio.h>
//考察静态局部变量的值
f(int a){
auto b=0;
static c=3;
b=b+1;
c=c+1;
return (a+b+c);
}
main(){
int a=2,i;
for(i=0;i<3;i++){
printf("%d",f(a));
}
}
结果输出:
对静态局部变量的说明:
- 静态局部变量属于静态存储类别,在静态存储区内分配存储单元。在程序整个运行期间都不释放。而自动变量(即动态局部变量)属于动态存储类别,占动态存储空间,函数调用结束后即释放。
- 静态局部变量在编译时赋初值,即只赋初值一次,而对自动变量赋初值是在函数调用时进行,每调用一次函数重新给一次初值,相当于执行一次赋值语句。
- 如果在定义局部变量时不赋初值的话,则对静态局部变量来说,编译时自动赋初值0(对数值型变量)或空字符(对字符变量)。而对自动变量来说,如果不赋初值则它的值是一个不确定的值。
实例:打印1到5的阶乘值
```
#include<stdio.h>
//打印1-5的阶乘值
int fac(int n){
static int f=1;
f=f*n;
return f;
}
main(){
int i;
for(i=1;i<=5;i++)
printf("%d!=%d\n",i,fac(i));
}
```
- register变量
为了提高效率,C
语言允许将局部变量得值放在CPU
中的寄存器中,这种变量叫“寄存
器变量”,用关键字register
作声明。
实例:使用寄存器变量
结果显示:#include<stdio.h> //使用寄存器变量 int fac(int n){ register int i,f=1; for (i=1;i<=n;i++) f=f*i; return f; } main(){ int i; for(i=0;i<=5;i++) printf("%d!=%d\n",i,fac(i)); }
几点说明:- 只有局部变量自动变量和形式参数可以作为寄存器变量
- 一个计算机系统中的寄存器数目有限,不能定义任意多个寄存器变量
- 局部静态变量不能定义为寄存器变量
- 用extern声明外部变量
外部变量(即全局变量)是在函数的外部定义的,它的作用域为从变量定义处开始,到
本程序文件的末尾。如果外部变量不在文件的开头定义,其有效的作用范围只限于定义处到文件终了。如果在定义点之前的函数想引用该外部变量,则应该在引用之前用关键字extern对该变量做“外部变量声明”。表示该变量是一个已经定义的外部变量。有了
此声明,就可以从“声明”处起,合法地使用该外部变量。
实例:用extern声明外部变量,扩展程序文件中的作用域
结果输出:#include<stdio.h> //用extern声明外部变量,扩展程序文件中的作用域 int max(int x,int y){ int z; z=x>y?x:y; return z; } main(){ extern A,B; printf("%d\n",max(A,B)); } int A=13,B= 2;
预处理命令
以“#”
号开头的预处理命令。如包含命令#include
,宏定义命令#define
等。在源程序中这些命令都放在函数之外,而且一般都放在源文件的前面,它们称为预处理部分。
所谓预处理是指在进行编译的第一遍扫描(词法扫描和语法分析)之前所做的工作。预处理是C语言的一个重要功能,它由预处理程序负责完成。当对一个源文件进行编译时,系统将自动引用预处理程序对源程序中的预处理部分做处理,处理完毕自动进入对
源程序的编译。C语言提供了多种预处理功能,如宏定义、文件包含、条件编译等。合理地使用预处理功能编写的程序便于阅读、修改、移植和调试,也有利于模块化程序设计。
- 宏定义
在C语言源程序中允许用一个标识符来表示一个字符串,称为“宏”。被定义为“宏”的标识符称为“宏名”。在编译预处理时,对程序中所有出现的“宏名”,都用宏定义中的字符串去代换,这称为“宏代换”或“宏展开”。
宏定义是由源程序中的宏定义命令完成的。宏代换是由预处理程序自动完成的。在C语言中,“宏”分为有参数和无参数两种。- 无参宏定义
无参宏的宏名后不带参数。
其定义的一般形式为:#define 标识符 字符串
。其中#
表示这是一条预处理命令。凡是以#
开头的均为预处理命令。define
为宏定义命令。标识符
为所定义的宏名。字符串
可以是常数、表达式、格式串等。符号常量的定义就是一种无参宏定义。此外常对程序中反复使用的表达式进行宏定义。如:#define M (y*y+3*y)
。其作用是指定标识符M
来代替表达式(yy+3y)。
实例:宏定义
结果输出:#include<stdio.h> //宏定义 #define M (y*y+3*y) main(){ int s,y; printf("input a number:"); scanf("%d",&y); s=3*M+4*M+5*M; printf("s=%d\n",s); }
- 带参宏定义
C语言允许宏带有参数。在宏定义中的参数称为形式参数,在宏调用中的参数称为实际参数。带参宏定义的一般形式:#define 宏名(形参表) 字符串
。例如:
实例:带参宏定义#define M(y) y*y+3*y //宏定义 k=M(5) //宏调用
#include<stdio.h> // 带参宏定义 #define MAX(a,b) (a>b)?a:b main(){ int x,y, max; printf("input two numbers:"); scanf("%d%d",&x,&y); max=MAX(x,y); printf("max=%d\n",max); }
- 无参宏定义
- 文件包含
文件包含是C预处理程序的另一个重要功能。文件包含命令行一般形式:#include "文件名"
。例如#include<stdio.h>
或者#include "math.h"
。文件包含命令的功能是把指定的文件插入该命令行位置取代该命令行,从而把指定文件和当前的源程序文件练成一个源文件。
对文件包含命令有几点说明:- 包含命令中的文件名可以用双引号括起来,也可以用尖括号括起来。例如以下写法都是允许的。
使用尖号表示在包含文件目录去查找(包含目录是由用户在设置环境时设置的),而不在源文件目录去查找;使用双引号则表示首先在当前的源文件目录中查找,若未找到才到包含目录中去查找。用户编程时可根据自己文件所在的目录来选择某一种命令形#include "stdio.h" #include <math.h>
式。- 一个include命令只能指定一个被包含文件,若有多个文件要包含,则需要用多个include命令。
- 文件包含允许嵌套,即在一个被包含的文件中又可以包含另一个文件。
- 条件编译
预处理程序提供了条件编译的功能。可以按不同的条件去编译不同的程序部分,因而产生不同的目标代码文件。这对于程序的移植和调试是很有用的。条件编译有三种形式:- 第一种形式:
它的功能是,如果标识符已被#ifdef 标识符 程序段1 #else 程序段2 #endif
#define
命令定义过则对程序段 1 进行编译;否则对
程序段 2 进行编译。如果没有程序段 2(它为空),本格式中的#else
可以没有,即可以写
实例:条件编译#ifdef 标识符 程序段 #endif
结果输出:#include<stdio.h> //条件编译 #define NUM ok main(){ struct stu{ int num; char *name; char sex; float score; }*ps; ps=(struct stu*)malloc(sizeof(struct stu)); ps->num=102; ps->name="Zhang ping"; ps->sex='M'; ps->score=62.5; #ifdef NUM //由于在程序的第 16 行插入了条件编译预处理命令,因此要根据 NUM 是否被定义过来决定 //编译 句。而在程序的第一行已对 NUM 作过宏定义,因此应对第一个 `printf`语句作编译故运行结果是输出了学号和成绩 printf("Number=%d\nScore=%f\n,ps->num,ps->score"); #else printf("Name=%s\nSex=%c\n",ps->name,ps->sex); #endif free(ps); }
- 第二种形式
与第一种形式的区别是将“ifdef”改为“ifndef”。它的功能是,如果标识符未被#ifndef 标识符 程序段 1 #else 程序段 2 #endif
#define 命令定义过则对程序段 1 进行编译,否则对程序段 2 进行编译。这与第一种形式的功能正相反。- 第三种形式
它的功能是,如常量表达式的值为真(非 0),则对程序段 1 进行编译,否则对程序段2进行编译。因此可以使程序在不同条件下,完成不同的功能。#if 常量表达式 程序段1 #else 程序段2 #endif
实例:
本例中采用了第三种形式的条件编译。在程序第一行宏定义中,定义 R 为 1,因此在条件编译时,常量表达式的值为真,故计算并输出面积。#include<stdio.h> //条件编译 #define R 1 main(){ float c,r,s; printf("input a number:"); scanf("%f",&c); #if R r=3.14159*c*c; printf("area of round is:%f\n",r); #else s=c*c; printf("area of square is:%f\n",s); #endif }