第四章函 数第四章 函数
§ 4.1 函数概述
§ 4.2 函数的定义和声明
§ 4.3 函数的调用
§ 4.4 参数传递
§ 4.5 标识符的作用域
§ 4.6 变量的生存期
§ 4.7 递归函数
§ 4.8 C++的库函数
§ 4.1 函数概述
c++函数特点:
独立完成某个功能的语句块
封装了程序代码和数据,实现了更高级的抽象
减少代码重复,提高程序的可重用性
§ 4.1 函数概述
函数的几个例子例 1:
int bigger(int a,int b)
{
return (a>b)? a,b;
} //找出最大数获取参数并返回值
§ 4.1 函数概述例 2:
void delay(long a)
{
for(int i=1; i<=a; i++);
} //延迟一个小的时间片获取参数但不返回值
§ 4.1 函数概述例 3:
int geti( )
{
int x;
cout<<“please input a integer:\n”;
cin>>x;
return x;
} //从键盘上获取一个整数不获取参数但返回值
§ 4.1 函数概述例 4:
void message( )
{
cout<<“This is a message.\n”;
} //在屏幕上显示一条消息不获取参数也不返回值
§ 4.2 函数的定义和声明函数结构
函数头 +函数体返回值 函数名 (参数表 )
{
函数体
}
§ 4.2 函数的定义和声明函数结构 答疑函数:询问老师参数:询问的题目返回:答案动作:解答题目订餐函数:订餐参数:订的菜单返回:是或否动作:做菜并送到家
§ 4.2 函数的定义和声明
函数的定义一般形式:
类型 函数名 (形参表 )
{
语句组
}
注,1.默认的函数类型是 int
2.不允许函数定义嵌套
§ 4.2 函数的定义和声明
不允许函数定义嵌套如:
void main( )
{
int func( )
{

}
……
}
函数独立完成某个功能,
函数与函数之间通过参数和返回值联系参 数 ---函数的输入返回值 ---函数的输出
§ 4.2 函数的定义和声明
函数的声明(即函数原型)
返回类型 函数名 (参数表 );
函数原型的作用:
声明了函数的参数情况及返回值
§ 4.2 函数的定义和声明说明:
1、函数原型是一条程序语句,须以分号结尾
2、函数原型的参数表中可只包含参数的类型如:
int area(int,int); int area(int length,int width);
左边也是合法的,只关心参数的个数和类型
§ 4.2 函数的定义和声明函数的定义和函数声明的关系
#include<iostream.h>
#include<math.h>
void main( )
{
double x;
cout<<sin(x);
}
函数原型
double sin(double);
§ 4.2 函数的定义和声明
函数的定义与声明的关系
void main( )
{
func( );
}
void func( )
{
……
}
void func( );
void main( )
{
func( );
}
void func( )
{
……
}
§ 4.2 函数的定义和声明
函数声明的原则:
如果一个函数定义在先,调用在后,调用前可不必声明如果一个函数定义在后,调用在先,调用前必须声明
§ 4.2 函数的定义和声明
总 结
1、函数在使用前必须先声明或先定义
2、函数的定义只有一次,但函数的声明可以出现多次
3、函数的定义和声明必须在参数、返回值方面保持一致
§ 4.2 函数的定义和声明
例:原型 int area(int,int);
调用 c=area(10,5,8);
例:
void fun(int,float);
int main( )
{
int a;
float b;
fun(a,b);
}
void fun(int,int)
{
……
}
//参数个数不同参数类型不一致
§ 4.2 函数的定义和声明
引用本文件外的函数时,需用 #include将包含该函数的文件嵌入到当前位置
#include有两种形式:
形式一,#include<文件名 >
形式二,#include,文件名,
形式一用于 C++提供的库函数,存放在 C++
系统目录中的 include子目录中形式二用于程序员开发的模块,存放在工作目录中
§ 4.3 函数的调用
1,函数的调用和返回
2,函数的调用过程
3,函数调用的实现 —栈
§ 4.3 函数的调用
函数通过调用发挥作用
在面向对象思想中,函数调用成为对象之间发送消息的工具
int A( )
{
B( );
}
int classA::A( )
{
classB::B( );
}
§ 4.3 函数的调用
⑴ 函数调用语句函数名 (参数表 );
参数表,由0个,1个或多个实际参数组成
实参的个数由形参决定,调用函数时实参给形参初始化
实参对形参初始化是按其位置对应进行
§ 4.3 函数的调用
⑵ 函数的返回函数返回的条件:
① 执行到函数的最后一条语句
② 遇到 return语句(语句格式如下)
return <表达式 >;

return;
§ 4.3 函数的调用
return <表达式 >;语句的实现过程
1,先计算出表达式的值;
2,当表达式类型与函数类型不同时,将表达式的类型自动、强制性地转换为函数的类型,可能出现不保值的情况
3,将表达式的值返回给调用函数
4,将程序执行的控制权由被调函数转向调用函数,执行调用函数后面的语句
§ 4.3 函数的调用
return; 语句无返回值,只返回程序执行的控制权
对于无返回值的函数须用 void来说明类型
在函数中 return;语句可有可无
若没有 return;语句,执行到函数体的最后一条语句时返回调用函数
§ 4.3 函数的调用
main函数
main函数是由系统调用的第一个函数
main函数直接或间接调用程序中所有其他函数
main函数的参数通过命令行提供:
如,copy arg1 arg2
main函数返回值给调用它的系统
( 0为正常返回,1为异常返回)
main函数默认的类型是 int
§ 4.3 函数的调用函数的调用过程
main( )
{
……
……
a( );
……
……
}
a( )
{
……
……
b( );
……
……
}
b( )
{
……
……
……
……
……
}
§ 4.3 函数的调用
3.函数调用的实现 --栈
栈中存放的内容:
-调用函数的返回地址
-传递参数
-被调用函数的局部变量参数 2
返回地址参数 1
局部变量
§ 4.3 函数的调用函数调用过程:
① 首先建立被调用函数的栈空间;
② 保护调用函数的运行状态和返回地址
③ 传递函数实参给形参;
④ 执行被调用函数的函数体内语句;
⑤ 将控制权转交给调用函数
§ 4.3 函数的调用函数调用时栈的变化过程
main()
a( )
b( )
C( )main()
main()
main()
a( )
main()
a( )
b( )
main()
a( )
b( )
main()
a( )
§ 4.3 函数的调用函数调用的实现 —— 栈说明:
函数的局部变量是临时存放的
栈空间是有限的
§ 4.4 参数传递
参数传递方式
传值调用
设置参数的默认值
§ 4.4 参数传递
参数传递方式:
⑴ 传值调用单向:调用函数
⑵ 传址调用 ( c语言中没有传址调用)
双向:调用函数 被调用函数 实参 形参被调用函数 实参 形参
§ 4.4 参数传递
C++变量的值有两种变量 本身 值 (传值调用)
变量 地址 值 (传址调用)用指针实参,常量、变量值、表达式值形参,变量
§ 4.4 参数传递
传值调用的实现函数调用时,系统先计算实参的值,再将实参的值按位置对应赋给形参。
传值调用的实现机制系统将实参拷贝一个副本(存放在被调用函数的栈中)给形参。
传值调用的特点形参值的改变不影响实参。
例,#include<iostream.h>
void swap(int,int);
void main( )
{
int a=5,b=9;
swap(a,b);
cout<<“a=“<<a<<“,”<<“b=“<<b<<endl;
}
void swap(int x,int y)
{
int temp;
temp=x;
x=y;
y=temp;
cout<<“x=“<<x<<“,”<<“y=“<<y<<endl;
}
程序运行结果:
x=9,y=5
a=5,b=9
由执行结果可知:实参的值没有改变
§ 4.4 参数传递
设置参数的默认值当需要使用相同的实参反复调用某函数时,c++允许给参数设置默认值例:
int fun(int a,int b=5,int c=8);
fun函数的三个参数中,有两个设置了默认值
§ 4.4 参数传递
例,void delay(int loops)
{
if(loops==0)
return;
for(int i=0; i<loops; i++);
}
函数声明,void delay(int loops=1000);
函数调用,delay(2500);
delay( ); //loops使用默认值 1000
§ 4.4 参数传递在设置和使用默认参数时应注意:
⑴ 默认参数在函数声明中提供,当只有函数定义时,默认参数才可出现在函数定义中
⑵ 一个函数的多个参数中,既有默认值也有非默认值时应将默认值放在参数表的最右端。
如:
int fun(int i=8,char c,int j=45); //error!
§ 4.4 参数传递
⑶ 函数调用时,给定的实参值将取代参数的默认值,没有给定实参值将使用默认值
⑷ 默认参数即可以是数值,也可以是表达式
§ 4.4 参数传递
例:分析程序的执行结果
#include<iostream.h>
void fun(int a=1,int b=2,int c=3)
{
cout<<“a=“<<a<<“,b=”<<b<<“,c=”<<c<<endl;
}
void main( )
{
fun( );
fun(9);
fun(4,5);
fun(7,8,9);
}
执行结果:
a=1,b=2,c=3
a=9,b=2,c=3
a=4,b=5,c=3
a=7,b=8,c=9
§ 4.4 参数传递例,#include<iostream.h>
int q=5,p=7;
int sum_int(int a,int b=q+p,int c=q*p);
void main( )
{
int x=5,y=10;
int s1=sum_int(x);
int s2=sum_int(x,y);
cout<<“s1=”<<s1<<?\n?<<“s2=“<<s2<<endl;
}
int sum_int(int i,int j,int k)
{
return i+j+k;
}
§ 4.5 标识符的作用域
作用域是指标识符的作用范围在 C++程序中出现的各种标识符,都有着不同的作用域
标识符的作用域规则:
标识符只能在说明它或定义它的范围内是可见的 (可进行存取或访问操作 )
§ 4.5 标识符的作用域
作用域的种类种 类 作用域程序级 组成该程序的所有文件文件级 从定义的地方起到文件结束函数级 函数体内。包括形参、函数内定义的变量块 级 从定义的地方起到相应的块尾包括 if,swicth、循环中定义的变量
作用域最大的是程序级,最小的是块级
§ 4.5 标识符的作用域
例:
int global,i;
void fun1(int para1,int para2)
{
int local1,local2,j;
if(para1==para2)
{
int k;
……
}
}
int fun2( )
{
……
}
K
块域
local1,
local2,
j
函数域
global,
i
文件域
§ 4.5 标识符的作用域关于重新定义标识符的作用域规定
在同一作用域内,不能有同名变量存在
在不同的作用域内,允许定义同名变量
§ 4.5 标识符的作用域
例,
void fun( )
{
int a;
float a;
{
float a;
……
}
……
}
//非法
//合法注:
① 块内的 float a在块内可见,
且存在
② int a在块内不可见,但存在
例:分析下列程序的输出结果
#include<iostream.h>
void main( )
{
int a=5,b=7,c=10;
cout<<a<<“,”<<b<<“,”<<c<<endl;
{
int b=8;
float c=8.8;
cout<<a<<“,”<<b<<“,”<<c<<endl;
a=b;
{
int c;
c=b;
cout<<a<<“,”<<b<<“,”<<c<<endl;
}
cout<<a<<“,”<<b<<“,”<<c<<endl;
}
cout<<a<<“,”<<b<<“,”<<c<<endl;
}
运行结果:
5,7,10
5,8,8.8
8,8,8
8,8,8.8
8,7,10
例:分析下列程序的输出结果
#include<iostream.h>
void main( )
{
int i=5;
for(; i>0; i--)
{
int i=25;
cout<<i<<?\t?;
}
cout<<?\n? <<i<<?\n?;
}
程序执行结果,25 25 25 25 25
0
§ 4.5 标识符的作用域
局部变量与全局变量在函数内或块内定义的变量为 局部变量在文件内或程序内定义的变量为 全局变量局部变量的特点,提高函数的独立性和安全性全局变量的特点,可作为函数之间通信的一种方式,但这种方式是不安全的,
不建议使用。
§ 4.5 标识符的作用域
函数之间的通信方式有两种:
⑴ 参数传递和返回值
⑵ 使用全局变量在软件开发中,应尽量用参数传递取代全局变量,少用或不用全局变量
例:演示局部变量与全局变量 (书上 96页程序 4.4.1)
#include<iostream.h>
void func1( );
void func2( );
void main( )
{
cout<<“No variables defined in main\n”;
func1( );
}
int age;
void func1( )
{
age=45;
cout<<“Age in func1( ) is”<<age<<?\n?;
func2( );
}
void func2( )
{
cout<<“Age in func2( ) is”<<age<<?\n?;
}
//问题,int age;移到不同位置时,其结果如何?
§ 4.6 变量的生存期
变量的存储类型
C++程序的存储组织
auto和 register变量
extern变量
static变量
§ 4.6 变量的生存期
变量的存储类型
C++变量除有数据类型外还有存储类型数据类型,决定变量的存储方式,取值范围,
可进行的操作存储类型,决定变量的生存期,分配内存的方式,初始化情况
§ 4.6 变量的生存期
变量具有下列四种存储类型之一:
auto 自动型
register 寄存器型
static 静态型
extern 外部型修饰局部变量修饰局部、全局变量
§ 4.6 变量的生存期
变量的存储类型在定义变量时声明
格式:
存储类型 数据类型 变量名;
例:
auto int i;
static char c;
默认存储类型:
局部变量 auto
全局变量 extern
§ 4.6 变量的生存期
C ++程序的存储组织内存分布,C++将内存分为四个部分程序代码程序数据堆栈静态区动态区全局变量静态变量动态申请的变量局部变量静态局部变量静态全局变量
§ 4.6 变量的生存期
静态区:
⑴ 该区内的变量具有全局寿命
⑵ 初始化为 0,且只初始化一次
动态区:
⑴ 程序运行过程中根据需要动态分配与释放内存空间
⑵ 必须初始化,否则初始化值随机
§ 4.6 变量的生存期
例:
int i; //局部变量
cout<<i; //未初始化就使用(垃圾值)
输出结果:
-26956
一个程序两次调用同一个函数,分配给该函数中局部变量的内存地址可能是不同的
§ 4.6 变量的生存期
例:
{
……
int sum; //sum没有确定值
for(int i=1; i<=10; i++)
sum+=i;
cout<<sum<<endl;
}
求和的值是无意义的
应将 int sum;改为 int sum=0;
§ 4.6 变量的生存期
auto 和 register 变量特点:
⑴ 只修饰局部变量
⑵ 局部寿命(生命期与作用域一致)
⑶ 必须初始化或赋值后才能使用
(没有默认值)
§ 4.6 变量的生存期
auto 与 register 的区别
⑴ register 的存取速度比 auto快
⑵ 缺省是 auto
(因此,局部变量一般不指定存储类型)
§ 4.6 变量的生存期
extern 变量特点:
⑴ 全局寿命(作用域最大,是整个程序)
⑵ extern变量的定义和说明是两回事,
只能定义一次,可以说明多次
⑶ 定义时,可省略 extern,应 定义在函数体之外,可出现在程序头、中、尾
§ 4.6 变量的生存期
⑷ 说明时,须加 extern,下列情况必须说明:
①同一文件中的外部变量,如果定义在后,
引用在先,引用前必须说明
②在程序的一个文件中定义的外部变量,
要在程序中的另一个文件中引用,则引用前必须说明
⑸ 外部变量定义后,具有默认值:
int型为 0,char型为空,浮点型为 0.0
例:
#include<iostream.h>
void main( )
{
extern int a;
int fun(int);
int y=fun(a);
cout<<y<<endl;
}
int a=5;
int fun(int x)
{
int b=6;
return (a+b)*x;
}
//说明外部变量 a,因为 a的定义在后
//定义外部变量 a,作用域是整个程序
//b是 auto型,作用域是 fun()函数内
//y是 auto型,作用域是 main()函数内
//程序执行结果,55
§ 4.6 变量的生存期
static变量特点:
⑴ 说明或定义时,须加 static
⑵ 分为静态局部变量和静态全局变量
⑶ 作用域与生命期不一致
⑷ 定义或说明后具有默认值(同 extern)
§ 4.6 变量的生存期
静态变量可分为:
静态局部变量和静态全局变量
二者的区别:
静态局部变量:定义在函数体内或程序块内静态全局变量:定义在函数体外
§ 4.6 变量的生存期
静态局部变量:
作用域是在定义它的函数体内或程序块内,而生命期是整个程序
(作用域内可见且存在,作用域外不可见但存在)
§ 4.6 变量的生存期
静态局部变量与 auto变量的区别:
① auto变量的作用域与生命期一致
② auto变量每次调用,被重新初始化,
static变量每次调用都保留改变后的值
例:静态变量用于调用计数
void CountFun( )
{
static int count=0;
count++;
cout<<“You have called me,<<count<<“times!”;
}
§ 4.6 变量的生存期
静态全局变量作用域 是在定义或说明它的文件中,
从定义或说明处开始到文件尾生命期 是整个程序
例,分析程序执行结果
#include<iostream.h>
void main( )
{
int a=3;
static int b; //静态局部变量
b+=fun(a);
cout<<“a=“<<a<<“,b=“<<b<<endl;
b+=fun(a);
cout<<“a=“<<a<<“,b=“<<b<<endl;
}
static int a=10; //静态全局变量
int fun(int x)
{
static int b=5; //静态局部变量
b+=a;
cout<<“a=“<<a<<“,b=“<<b<<endl;
return b;
}
§ 4.6 变量的生存期
程序执行结果
a=10,b=15
a=3,b=15
a=10,b=25
a=3,b=40
§ 4.7 递归函数
直接递归与间接递归
递归调用的过程
递归设计的要点
举例:汉诺塔问题
递归评价
§ 4.7 递归函数
递归函数有两种:
直接递归 ——自己调用自己间接递归 ——A调用 B,B调用 A
§ 4.7 递归函数
直接递归与间接递归直接递归 间接递归
A(int n) A(int n)
{ {
…… ……
A(n-1); B(n-1);
…… ……
} }
B(int n)
{
……
A(n-1);
……
}
§ 4.7 递归函数
递归设计的要点
递归结束条件栈空间是有限的
复杂性降低
f(n)变为 f(n-1)或者 f(n-2)的问题,参数应逐渐逼近递归结束条件
递归操作每次递归能完成一些操作(具有一些功能)
§ 4.7 递归函数
递归调用的过程 (两个阶段)
递推,先将原问题不断分解成新的子问题,
逐渐从未知向已知方向推测,最后到达已知条件,即递归结束条件回归,从已知条件出发,按,递推,的逆过程,
逐一求值回归,最后到达递归开始处,
结束回归,完成递归调用
§ 4.7 递归函数
求 n!的递归算法求值过程说明上述两个阶段
3!=3*2! 3!=3*2!=3*2=6
2!=2*1! 2!=2*1!=2*1=2
1!=1*0! 1!=1*0!=1*1=1
0!=1
递推 ①
递推 ②
递推 ③ 回归 ①
回归 ③
回归 ②
§ 4.7 递归函数
求 n!的函数定义
long fact(int n)
{
if(n==0) //递归结束条件
return 1;
return n*fact(n-1); //递归调用语句
}
例,从键盘输入一个正整数,编程求出该数的阶乘
#include<iostream.h>
long fact(int n);
void main( )
{
int n;
cout<<“Input a positive integer:”;
cin>>n;
long fa=fact(n);
cout<<n<<“!=“<<fa<<endl;
}
long fact(int n)
{
long p;
if(n==0)
p=1;
else
p=n*fact(n-1);
return p;
}
§ 4.7 递归函数
例:汉诺塔问题
A B C
§ 4.7 递归函数
例:汉诺塔问题
A CB



§ 4.7 递归函数
⑴ 递归结束条件
if(n==1)
将盘子直接从 A移到 C
⑵ 问题简化
move(n,a,b,c)
move(n-1,a,c,b)
move(1,a,b,c)
move(n-1,b,a,c)
⑶ 递归操作 —— 移动盘子
cout<<a<<“to”<<b<<endl;
§ 4.7 递归函数
递归函数设计 ( 汉诺塔问题 )
void move(int n,char a,char b,char c)
{
if(n==1)
cout<<a<<“to”<<c<<endl;
else
{
move(n-1,a,c,b);
move(1,a,b,c);
move(n-1,b,a,c);
}
}
递归结束条件递归操作问题简化
§ 4.7 递归函数
递归评价好处:
程序简单可读性好坏处:
效率低函数调用消耗时间和栈空间
§ 4.8 C++的库函数
C++编译系统提供了几百个库函数
按其功能分类
每一类对应一个,h(头文件)
每个头文件中包含有该类每个库函数的原型
§ 4.8 C++的库函数
例如:
math.h——指数、对数、绝对值等数学函数
ctype.h——判断字母、数字、大小写等
string.h——字符串处理函数
conio.h——屏幕处理函数
graph.h——图形处理函数
……….
§ 4.8 C++的库函数
库函数的用法
⑴ 了解你所使用的 C++系统提供了那些库函数
⑵ 要知道某个库函数的说明在哪个头文件中
⑶ 调用一个库函数时,要搞清楚该函数的功能、
参数和返回值要求第四章 小结
函数应用
⑴ 将什么定义为函数(有两个原则)
-具有相对独立的功能
-经常使用
⑵ 能用简单的话描述出函数如:初始化,求和,读取文件,交换两个数,打印等