7.1为什么要用函数
函数就是功能,每个函数用来实现一个特定的功能。
在程序设计中要善于利用函数,来减少重复编写程序段的工作量实现模块化设计。
一个C程序可以由一个主函数和若干个其他函数构成
例7.1
# include "stdio.h"
int main()
{
//声明print_star函数 告知编译器函数的相关信息 参数类型和数量 返回值类型
void print_star();
//声明print_message函数
void print_message();
//调用print_star函数
print_star();
//调用print_message函数
print_message();
//调用print_star函数
print_star();
return '\0';
}
//定义print_star函数
void print_star()
{
printf("**********************\n");
}
//定义一个print_message函数
void print_message()
{
printf("Hello World!!\n");
}
说明:
- 一个C程序由一个或多个程序模块组成,每个程序模块作为一个源程序文件。
- 一个源程序文件由一个或多个函数以及其他有关内容组成。一个源程序文件是一个编译单位。
- C程序执行是从main函数开始的。在main函数中结束整个程序的运行。
- 所有函数都是平行的,定义函数时是分别进行,相互独立的。
- 从用户使用的角度看,函数有两种。
- 库函数,由编译系统提供,不用自己定义,可以直接使用。
- 用户自定义函数。用于解决用户撰文需要的函数。
- 从函数形式上看,函数分两类。
- 无参函数。
- 有参函数。
7.2 怎样定义函数
C语言要求,在程序中用到的所有函数,必须“先定义,后使用”。
定义函数应包括以下几个内容:
- 函数名
- 函数返回值类型
- 函数参数名和类型,无参函数不需要这项
- 指定函数应当完成什么操作,函数的功能
7.2.2 定义函数的方法
定义无参函数
一般形式:
类型名 函数名()
{
函数体
}
或
类型名 函数名(void)
{
函数体
}
注意:
函数体包括声明部分和语句部分
声明部分,如:int x;
语句部分,如:printf(“%d”,x);
定义有参函数
类型名 函数名()
{
函数体
}
定义空函数
类型名 函数名()
{}
空函数用于扩充新功能
7.3 调用函数
7.3.1 函数调用形式
按函数调用在程序中出现的形式和位置来分,可以有一下3种调用方式。
1.函数调用语句
把函数调用作为单独的语句
2.函数表达式
函数调用出现在另一个表达式中
3.函数参数
函数调用作为另一个函数的参数
7.3.2 函数调用时数据传递
形式参数
定义函数时的变量名
实际参数
调用时传递的常量,变量或表达式。
实参和形参间的数据传递
形参从实参那获取值。
7.3.3 函数调用的过程
- 发生函数调用时,函数的形参被临时分配内存单元
- 将实参对应的值传递给形参
- 执行被调用函数的语句
- 通过return语句将函数值带回主调函数(如果函数不需要返回值,则不需要return语句,函数定义类型应定义为void类型)
- 调用结束,形参单元被释放。实参没有改变
7.3.4 函数的返回值
函数的返回值:函数调用使主调函数能得到一个确定的值。
说明(对于函数返回值):
- 函数的返回值是通过函数中的return语句获得的。
- 定义函数时指定函数返回值类型。
- 在定义函数时指定函数类型一般和return语句中的表达式类型一致。函数类型决定返回值的类型。数值类型会自动进行类型转换。
- 没有返回值的函数,定义为void类型
7.4 对被调用函数的声明和函数原型
函数调用所需条件
- 已经被定义好的函数
- 使用库函数,使用预编译指令(#include)将有关库函数时所需用到的信息“包含”到本文件中来。
- 使用自定义函数,在主调函数中对被调用函数进行声明。(声明是为了正确识别和检查被调用的函数[包括信息有:函数名,返回值类型,参数数量,顺序,类型]是否合法)
函数原型(函数声明)有两种:
- 函数类型 函数名(参数类型1 参数名1,参数类型2 参数2);
- 函数类型 函数名(参数类型1,参数类型2);
7.5 函数的嵌套调用
一张图就解释清楚了
7.6 函数的递归调用
在调用一个函数的过程有出现直接或间接的调用该函数本身,称为函数的递归调用。
例如:
# include "stdio.h"
int main()
{
int num=5;
//声明函数
int age(int num);
printf("%d",age(num));//在输出函数里调用函数
return 0;
}
//定义函数
int age(int num)
{
int c;
if(num==1)
c=10;
else
c=age(num-1)+2;
return c;
}
如果要求递归过程不是无限制进行下去,必须具有一个结束递归过程的条件。
# include "stdio.h"
/*
使用递归求阶乘
*/
int main()
{
int num=10;
//声明函数
int factorial(int num);
printf("%d",factorial(num));//在输出函数里调用函数
return 0;
}
//定义函数
int factorial(int num)
{
int c;
if(num==1||num==0)
c=1;
else if(num<0)
printf("参数出错");
else
c=factorial(num-1)*num;
return c;
}
7.7 数组作为函数参数
凡是变量可以出现的地方,都可以用数组元素代替。用法与变量相同。
传递数组名,传递的是第一个元素地址。
7.7.1 数组元素作函数实参
数组元素作函数实参时,把实参的值传给形参,是“值传递”方式。
# include "stdio.h"
/*
练习用数组元素作为参数
*/
int main()
{
int a[10],i,n,maxNum;
int max(int x,int y);//声明函数
printf("请依次输入10个数\n");
for(i=0;i<10;i++)//分别对元素进行赋值
{
scanf("%d",&a[i]);
}
for(i=1,maxNum=a[0];i<10;i++)//调用函数
{
if(max(a[i],maxNum)>maxNum)
{
maxNum=max(a[i],maxNum);
n=i+1;
}
}
printf("%d\t%d\n",maxNum,n);
}
//定义函数
int max(int x,int y)
{
return x>y?x:y;
}
7.7.2 数组名作函数参数
用数组元素作实参时,向形参变量传递的是数组元素的值,而用数组名作函数实参时,向形参传递的是数组元素的地址。
# include "stdio.h"
/*
练习用数组名作为参数
*/
int main()
{
int i;
float a[10],av;
float average(float array[10]);//声明函数
printf("请依次输入10个数\n");
for(i=0;i<10;i++)//分别对元素进行赋值
{
scanf("%f",&a[i]);
}
av=average(a);
printf("%f",av);
}
//定义函数
float average(float array[10])
{
int i;
float sum,aver;
for(i=1,sum=array[0];i<10;i++)
{
sum+=array[i];
}
aver=sum/10;
return aver;
}
/*
实参与形参类型不一致,会出错
形参数组可以不指定容量 可以这样定义:a[]
*/
# include "stdio.h"
/*
练习用选择排序
*/
int main()
{
int i;
float a[10],av;
void average(float array[],int n);//声明函数
printf("请依次输入10个数\n");
for(i=0;i<10;i++)//分别对元素进行赋值
{
scanf("%f",&a[i]);
}
average(a,10);
for(i=0;i<10;i++)
printf("%f\n",a[i]);
}
//定义函数
void average(float array[],int n)
{
int i,j,temp;
for(i=0;i<n-1;i++)
for(j=i+1;j<n;j++)
{
if(array[i]>array[j])
{
temp=array[i];
array[i]=array[j];
array[j]=temp;
}
}
}
7.7.3 多维数组作函数名
可以作为函数的实参和形参,可以省略第一维的大小,第二维不能省略,C语言编译系统不检查第一维的大小。
7.8 局部变量和全局变量
7.8.1 局部变量
定义变量的位置
- 函数的开头定义
- 函数内的复合语句内定义
- 函数外都定义
在复合语句内定义的变量旨在本复合语句范围内有效(花括号范围内),在本复合语句内才能引用他们。复合语句内不能使用,这被成为局部变量。
形式参数也是局部变量。
7.8.2 全局变量
全局变量:函数外部定义的变量成为外部变量,外部变量也称全程变量
作用范围:定义行到主函数结束,程序是从主函数开始到主函数结束
全局变量采用大驼峰命名
全局变量与局部变量重名,局部变量优先
如:
# include "stdio.h"
int a=3,b=5;
int main()
{
int max(int a,int b);
int a=8;
printf("the max num is %d \n",max(a,b));
}
int max(int a,int b)
{
return a>b?a:b;
}
结果:
the max num is 8
7.9 变量的存储方式和生存期
7.9.1 动态存储方式和静态存储方式
从空间(作用域)的角度来观察,变量可以分为全局变量和局部变量
从变量存在的时间(生存期)来看,变量存储存储方式有两种:
- 静态存储:在程序运行期间由系统分配固定的存储空间的方式。
- 动态存储:在程序运行期间根据需要进行动态的分配存储空间。
用户可以使用的内存存储空间结构:
- 程序区
- 静态存储区
- 动态存储区
全局按量全部存放在静态存储区中,在程序开始执行时给全局变量分配存储区,程序执行完毕后就释放。
动态存储区存放:
- 函数形式参数。在调用函数时给形参分配存储空间。
- 函数中定义的没有用关键字static声明的变量,自动变量
- 函数调用时的现场保护和返回地址。
在函数调用开始时分配动态存储空间,函数结束时释放这些空间。
在程序执行过程中,同一个程序调用两次相同函数,两次分配的给这些局部变量的存储空间地址可能是不相同的。
每个函数中的局部变量生存期只是程序执行期的一部分。
在程序执行过程中,先后调用函数,动态分配和释放空间。
C语言中,数据类型和数据存储类别是变量和函数的属性。
7.9.2 局部变量的存储类别
1.自动变量(auto变量)
函数中的形参和函数中局部变量,都是动态分配的,调用该函数是,系统会给这些变量分配存储空间,在函数调用结束后,就会自动释放这些存储空间。这类局部变量称为自动变量。
1.以auto修饰的变量 2.没有关键字修饰的。默认为自动变量
2.静态局部变量(static局部变量)
局部变量的值在函数调用后结束不小时而继续保留原值,即不释放存储空间,在下次调用函数时,该变量已有值。这是该指定该局部变量为“静态局部变量”
# include "stdio.h"
int main()
{
void test();
test();
test();
return 0;
}
void test()
{
static int a =1;
printf("%d\n",++a);
}
2
3
--------------------------------
Process exited after 0.01536 seconds with return value 0
请按任意键继续. . .
静态存储要多占内存,不能合理利用资源(和动态存储对比),且降低了程序的可读性,因此,若非必要,不要多用静态局部变量。
3.寄存器变量(register变量)
用于提高效率,寄存器的存取速度远大于内存的存取速度。
关键字是: register int f
寄存器变量存储在CPU中的寄存器中。
7.9.3 全局变量的存储类别
全局变量都是存放于静态存放区中的,因此它们的生存期(生命周期)是固定的。
作用域:定义行开始到本程序文件末尾
1.一个文件内扩展外部变量的作用域
在定义点之前的函数需要引用该外部变量,在引用之前用关键字externa对该变量作“外部变量声明”。
# include "stdio.h"
int main()
{
int max();
extern int A,B,C;//将外部变量的作用扩展到由此处开始。
scanf("%d%d%d",&A,&B,&C);
printf("%d",max());
return 0;
}
int A,B,C;//定义外部变量
int max()
{
int m;
m=A>B?A:B;
return m>C?m:C;
}
注意:
- 建议吧外部函数定义写在所有函数之前,可以避免不必要的extern的声明。
- 使用extern时,类型名可以省略
2.将外部变量作用域扩展到其他文件
如果一个程序包含两个文件,在两个文件中都要用到同一个外部变量Num,不能分别在两个文件找那个各自定义一个外部变量,连接时会出现“重复定义”的错误。
正确做法是:
- 在任意一个文件中定义外部变量
- 另一个文件中用extern对Num作“外部变量声明”。
3.将外部变量的作用域限制在本文件中
只限在本文件使用的外部变量,加关键字:static
static声明一个变量的作用:
- 对局部变量用static声明,把分配静态存储区,该变量在整个程序执行期间不释放,其分配的空间始终存在。
- 对全局变量用static声明,该变量的作用域只限本文件模块。
7.9.4 存储类别小结
从作用域角度分析,有局部变量和全局变量
从生存期来区分,有静态存储和动态存储
从变量值存放的位置来区分,有内存中的动态存储区,内存中的静态存储区,CPU中的寄存器
作用域和生存期的概念:作用域是空间上的描述;生存期是时间的的描述
static对局部变量来说:使变量从动态存储转为静态存储;static对于全局变量来说:规定了其作用域
7.10 关于定义和声明
定义是声明的一种体现,如int a;
建立存储空间的声明称为定义(定义一个变量,分配内存空间)
不需要简历存储存储空间的声明称为声明。(如:int max(int);)
外部变量只能定义一次,可多次声明。系统根据外部变量的定义分配存储单元,同时进行初始化。
7.11 内部函数和外部函数
7.11.1 内部函数
在定义内部函数时,在函数名和函数类型的前面加static
即:
static 类型名 函数名(形参列表)
{
函数体
}
不用担心函数是否会与其他文件模块函数同名
7.11.2 外部函数
在定义函数时,在函数首部的最左端加关键字extern,这函数是外部函数。关键字省略,默认为外部函数。
如:
extern int fun(int a,int b)
{
函数体
}
在需要调用此函数的其他文件中,需要对此函数做声明,声明时需要加extern关键字。表示这是其他文件中定义的外部函数。
由于函数在本质上时外部的,在程序中经常要调用其他文件的外部函数,为了方便编程,C语言允许声明函数时可以省略extern
在调用方中声明一个函数,这个函数就是被调用函数的函数原型。
函数原型的作用:
扩展函数的作用域。
常见的例子就是 # include指令的使用,通过引入其他库函数,然后进行使用。
- 本文链接:https://www.nscblog.top/posts/e1d8c1a1/
- 版权声明:本博客所有文章除特别声明外,均默认采用 许可协议。
若没有本文 Issue,您可以使用 Comment 模版新建。
GitHub Issues