第八章 函数
8.1 函数概述
8.2 函数的定义与调用
8.2.1 无参数无返回值的函数
- 函数的定义
void 函数名(void)
{
变量声明部分
执行部分
}
- 函数的用途
- 函数的原型声明
void 函数名(void); 或 void 函数名();
4. 函数的调用
函数名();
( )中不能有void
5. 函数的返回
可以利用return
return(表达式) //有返回值; return 表达式 //有返回值; return //无返回值;
8.2.2 无参数有返回值的函数
- 函数的定义
返回值类型符 函数名()
{
变量声明部分
执行部分
}
函数的返回值通常是函数计算的结果或执行的状态信息. 返回值通常是int(不写的话)
函数的返回值将由 return(表达式)或renturn 表达式 ; 语句返回给调用者(即调用这个函数的另一个函数).表达式的值即是函数的返回值. 一般情况下,表达式的值的类型应与函数返回值类型一致.
-
函数的用途
-
函数的原型声明
返回值类型符 函数名(void); 或 返回值类型符 函数名();
-
函数的调用
函数名(); 或 变量 = 函数名();
-
函数的返回
当return(表达式)语句中的表达式的类型与函数的返回值类型不一致时,将表达式的值强制转换成函数返回值的类型.
8.2.3 带参数无返回值的函数
- 函数定义
void 函数名(类型符1 形参名1,类型符2 形参名2 .... )
{
变量声明部分
执行部分 //这两个部分就是函数体
}
形参类似于函数变量声明部分中的变量,当函数定义时,变量可以有初值,但是形参却没有初值. 当函数被调用时,形参才会有值.
-
函数的用途
该类函数根据形参的值来进行某种事物的处理. 有了形参之后,调用函数可以把不同处理的值通过形参传递给被调用函数, 被调用函数可以根据形参的值来进行对应的处理. -
函数的原型声明
void 函数名(类型符1 形参名1, 类型符2 形参名2 ....);
void 函数名(类型符1 , 类型符2 ....)
-
函数的调用
函数名(实参列表)
实参列表时用逗号分隔的表达式.
对于调用带实参的函数时要注意两点:
实参列表中的实参必须与函数定义时的形参数量相同、类型相同
实参表求值顺序(即实参赋值给形参的顺序), 因系统而定. VC 是从右到左
void compare(int a, int b); //also (int ,int)
void main(){
int i=2;
compare(i,i++); //函数调用(i为实参)
printf("i = %d\n",i);
}
void compare(int a , int b) //函数定义,有形参无返回值(a,b为形参)
{
printf("a = %d b= %d\n",a,b);
if(a>b)
printf("a>b\n");
else
if(a == b)
printf("a=b\n");
else
printf("a<b\n");
}
- 函数返回
因为该类型的函数无返回值,所以如果要在函数体内中途返回给条用函数,只有通过 return; 语句返回即可
8.2.4 带参数有返回值的函数
- 函数的定义
返回值类型 函数名 (类型符1 形参名1, 类型符2 形参名2 ....)
{
变量声明部分
执行部分
}
如果省略返回值类型符,默认int
函数的返回值将由return(表达式); 或 return 表达式; 语句返回给调用者(调用这个函数的另一个函数)
-
函数的用途
-
函数的原型声明
返回值类型符 函数名(类型符1 形参名1, 类型符2 形参名2 ....);
返回值类型符 函数名(类型符1 , 类型符2 ....)
-
函数的调用
函数名(实参列表);
变量名 = 函数名(实参列表)
int max(int a ,int b);
void main()
{
int a,b,c;
scanf("%d%d",&a,&b);
c = max(a,b);
printf("the biggest number is %d",c);
//also write:
printf("the biggest number is %d",max(a,b));
}
int max(int a ,int b)
{
return (a>b?a:b);
}
- 函数的返回
函数题内一般会有return(表达式) 或者 return 表达式
8.3 函数参数的传递方式
通常有 值传递方式 和 地址传递方式
- 运用值传递方式
函数调用时,为形参分配内存单元,并将实参的值复制到形参中.
特点:形参与实参占用不同的内存单元,函数中对形参值的改变不会对实参进行改变. 这就是函数参数的值单向传递规则.
include <stdio.h>
void swap (int a, int b) ;
void main ( )
{
int x=7, y=11;
printf ("before swapped: ");
printf ("x=%d, y=%d\n", x, y);
swap (x, y);
printf("after swapped: ") ;
printf ("x=%d, y=%d\n", x, y) ;
}
void swap (int a, int b)
{
int temp;
temp = a;
a = b;
b = temp;
}
为什么会出现这种情况,因为在内存单元中没有作任何的改变, 这只是形参在发生改变
- 运用地址传递方式
函数调用时,将实参数据的存储地址作为参数传递给实参.
特点:形参与实参占用同样的内存单元, 函数中对形参值的改变也会改变实参的值。所以该地址传递的方式可以实现调用函数与被调用函数之间的双向数据传递。
注意:形参和实参必须时地址变量或变量。
比较典型的地址传递方式就是用数组名作为函数的参数,在用数组名作函数参数时,不是进行值的传送,即不是把实参数组的每一个元素的值都赋予形参数组的各个元素. 因为数组名就是数组的首地址,因此在数组名作函数参数时所进行的传送只是地址的传送,也就是说把实参数组的首地址赋予形参数组名
注意:
形参数组和实参数组的类型必须一致
形参数组和实参数组的长度可以不相同,因为在调用时,只传送首地址而不检查形参数组的长度.
多为数组也可以作为函数的参数,在函数定义时,对形参数组可以指定每一纬的长度,也可以省去第一纬的长度.
8.4 变量的作用域和生存期
- 作用域和生存期概念
- 变量的作用域:变量的作用范围(有效范围)
作用域分: 1. 局部变量 2.全局变量 - 变量的生存期
变量在程序开始执行开始占用内存,在程序执行的过程中,只有必要时才为变量分配内存. 当变量占用内存时,变量就生成了. 当变量不使用的时候,程序将释放变量所占用的内存,变量就撤销了. 从变量生成到消失的这段时间就是变量的生存期.
- 局部变量的作用域与生存期
(1) 局部变量
局部变量是在函数内作定义说明的, 作用域仅限于函数内.
注意:
主函数 main中定义的变量也是局部变量,只能在主函数中使用,
形参变量属于被调用函数的局部变量;
实参变量属于调用函数的局部变量
在符合语句中定义的变量也是局部变量,其作用域只在符合语句范围内.
(2)全局变量
全局变量也为 外部变量.
作用域是从变量定义处到程序文件的末尾
全局变量可加强函数模块之间的数据关系,使得这些函数依赖于全局变量,让函数的独立性降低.
在同一文件中,全局和局部变量可以一样,如果使用全局变量,则必须在变量名前加上两个冒号 ‘::’
引用全局变量:extern 类型说明符 全局变量名1 , ..... 全局变量名n ;
8.5 变量的存储类型
静态存储类型的变量:通常是在变量定义时就分配内存单元并一直保持不变,直至整个程序结束. (全局变量就是这种)
动态存储类型变量:使用时才分配,使用完立即释放 (形式参数)
auto(自动型)
register(寄存器型)
extern(外部型)
static(静态型)
-
自动变量(auto)
说明符为auto, 属于动态存储类型
[auto] 数据类型说明符 变量名1, 变量名2,....变量名n
auto可以省略, 自动变量只能在函数内或复合语句中定义 —>属于局部变量
注意: 在函数外部定义的没有带存储类型说明符的全局变量,如 int k; 并不代表是自动变量,而是外部变量,属于静态存储类型. -
外部变量(extern)
属于静态存储类型 -
静态变量(static)
属于静态存储类型的变量不一定就是静态变量.
(1)静态局部变量
在局部变量的说明前加上static 就构成静态局部变量
static int a, b;
static float array[5] = {1,2,3,4,5};
- 静态局部变量属于静态存储方式,它与自动变量相比有以下特点:
静态局部变量与自动变量均属于局部变量,都是在函数内或复合语句中定义.但自由变量是在调用函数或指定复合语句时才生成,推出函数或者符合语句时就消失. 而静态局部变量时在调用函数或执行复合语句之前就生成,退出函数或复合语句后仍然存在, 变量将保持现有的值, 直到程序终止时才消失. 它的生存周期为整个源程序.
静态局部变量作用域与自动变量相同.
对静态局部变量若在定义时为赋初值, 则系统自动赋初值0。
虽然离开定义它的函数后不能使用,但是在此调用定义它的函数时候,它还可以继续使用,并不会被消除,而且保存了前次被调用后留下的值.
(2)静态全局变量
全局变量(外部变量)的说明之前再冠以static就构成了静态全局变量. 全局变量本身就是静态存储方式.
主要的区别:
- 非静态全局变量的作用域是整个源程序,当一个源程序由多个源文件组成时,非静态的全局变量在各个源文件中通过外部变量说明都是有效的
- 静态全局变量则限制了其作用域,即只在定义该变量的源文件内有效,在同一源程序的其他源文件中不能通过外部变量说明来使用它.
- 寄存器变量(register)
这个放在CPU的寄存器中,使用时,不需要访问内存,直接从寄存器中读写,这样可提高效率.
说明:
只有局部自动变量和形式参数才可以定义为寄存器变量.
寄存器变量属于动态存储类型
8.6 函数的嵌套和递归调用
- 运用函数的嵌套调用
示意图
//计算三个数中最大数与最小数的差
#include<stdio.h>
int dif(int x, int y, int z);
int max(int x, int y, int z);
int min(int x, int y, int z);
void main()
{
int a, b, c, d;
scanf("%d%d%d",&a,&b,&c);
d = dif(a,b,c);
printf("Max - Min = %d\n",d);
}
int dif(int x, int y, int z) //求三数中的最大值与最小值的差
{
return (max(x,y,z)-min(x,y,z));
}
int max(int x, int y, int z) // find max number
{
int r;
r = x > y ? x : y;
return (r > z ? r : z);
}
int min(int x, int y, int z)
{
int r;
r = x < y ? x : y;
return(r < z ? r : z);
}
- 运用函数的递归调用
直接递归调用和间接递归调用
//打印数字三角形
#include<stdio.h>
void printf(int w);
void main()
{
int i;
if(w!=0) //递归结束的条件
{
print(w-1);
for(i = 1; i <= w; ++i){
prtinf("%d",w);
}
printf("\n");
}
}
//求N的阶乘 N!
/*
1. 可以用for 做出一个非递归函数 factn1
long factn1(int n)
{
long L =1;
int i;
for(i=1;i<=n;i++){
L *=i;
}
return(L);
}
*/
//2. 用递归函数来写
long fact(int n) //递归函数求n!
{
long L;
if(n==1)
return(1);
L = n* fact(n-1);
retrun(L);
}
//主函数
void main()
{
int n;
long L;
scanf("%d",&n);
L = fact(n);
printf("%d! = %ld\n",n,L);
}
[递归调用及堆栈变化示意图(factn1)]
[递归调用示意图( fact( ) )]编写递归函数有两个要点:
- 确定递归公式, 是实现递归函数的模版
- 根据公式确定递归函数的出口,即结束递归调用的条件
8.7 函数的作用域
- 内部函数
内部函数: 如果在一个源文件中定义的函数只能被本文件中的函数调用,而不能被同一源程序其他文件中的函数调用.
static 类型说明符 函数名(形参表)
例如:
static int func(int a, int b)
{
return (a > b ? a : b);
}
内部函数也称静态函数,但此处静态static的含义不是指存储类型,而是指对函数的作用域只局限于本文件.
- 外部函数
外部函数在整个源程序中都有效
extern 类型说明符 函数名(形参表)
如果函数定义中没有说明extern或static 则隐含为 extern.