You are on page 1of 38

第五章 函数和编译预处理

函数的引 入: 做练习
【 Ex4_1_1 】输入 x ,求 sinx 的值 和 x 的平方根 。
【 Ex4_1_2 】输入 x , y ,求 它们 的最大 值。
【 Ex4_1_3 】输入 x , y , z, 调用求 最大 值的函 数求 其
最大 值。
【 Ex4_1_4 】输入 x , y ,调用 函数 求其最 大公 约数 。
【 Ex4_1_5 】输入 整数 m , 用判 素数函 数进 行判 别。

【 Ex4_1 】演示 上述例 子。


本章内容
5.1 函数的定义和调 用
5.2 函数的形参、实 参、返回值 及原型
说明
5.3 作用域和存储类
5.4 函数的嵌套和递 归调用
5.5 内联函数
5.6 具有缺省参数值 和参数个数 可变的
函数
5.7 函数重载
5.8 编译预处理和程 序的多文件 组织
5.1 函数的定义和调用

函数 就是 一个功 能模 块。将 其定义 出来 后,可 供其 它


程序 或程 序的其 他部 分使用 (调 用)。
函数 包括 系统定 义的 库函数 和用户 的自定义 函数 。

你应该已 经会 调用库 函数 了!请 记得 把定义 该函


数的文件 include 进来。

自定义函 数使 用的例 子: BookManage


1 函数的定义

( 1 )无参 函数的 定义 格式:


《函数返 回值类 型》函数名 ( void){…}

例如:定 义一 个打印 表头 的函数

void PrintHead( ){
cout<<“********************”;
cout<<“* Example Table *”;
cout<<“********************”;
}
( 2 )有参 函数的 定义 格式:
《函 数返 回值类 型》 函数名 ( 形参列 表 ){…}

例如:定 义求 两个数 最大 值的函 数


double max(double x, double y){
double max(double x, double y){ double m;
if (x>y) return x; if (x>y) m=x;
else return y; else m=y;
} return m;
}

注意函 数中的 return 语句


对函数含 义的 理解:
函数头中 的信 息—— 自变 量 、 函数 值、其
他变量
形参表 中的 变量和 函数 体里 的变量 有何不 同?
2 函数的调用

调用与 定义 要相匹 配。

函数 的调 用格式 :
函数 名 ( ) // 无参函 数
函数 名 ( 实参表 ) // 有参函数

函数的 调用 过程: 主程 序- 调用语句 -函 数- 返回主


程序 调用 处。

调试【 例 5.1 】
5.1
结束
5.2 函数的形参、实参、返回值
和函数原型说明
1 函数的 形参和 实参
调用函 数相 当于代 公式 的过 程,实 参值带 入形 参进行
处理 。
例【 5.2 】实参 与形参 变量 的对应 关系 (p64-65)

2 函数的 返回值
函数值 返回 主调函 数的 唯一 途径: return 语句。语 句
格式 为
return ( 表达式 );
将表达 式的 值转换 为函 数返 回值类 型返回 给主 调函数

maxi(int a,int b){return a>b?a:b;}
float maxf(float x,float y){return x>y?x:y;}

void main(){
float x=3.4, y=5.6;
char c1=‘a’, c2=‘b’;
int i=20, j=30;
cout<<maxi(x,y)<<endl;
cout<<maxf(x,y)<<endl;
cout<<maxi(c1,c2)<<endl;
cout<<maxf(c1,c2)<<endl;
cout<<maxi(i+j,45+y)<<endl;
}
int maxf(float x,float y){return x>y?x:y;}
void main(){
float x=3.4, y=5.6;
cout<<maxi(x,y)<<endl;
}

问: 打印 结果是 ?
3 函数原 型说明
协调 “结 构化设 计的 思想” 与“先 定义 后调用 ”之 间
的矛 盾。 说明格 式即 函数头 ,但 可以 省略形参 名。

4 传值调 用
形参 和实 参的结 合方 式有传 值和传 引用 方式。 将实 参
值传 递给 形参变 量的 结合方 式称 为传值 调用。 请看 下例:
【 P68 例】 交换 变量值。

5 C++ 库函 数
C++ 定义 了许 多函数 ,并 分类定 义在不 同的 文件中 。
使用 库函 数必需 将定 义该函 数的 文件包 含在程 序头 部。

5.2
结束
void swap(float x,float y){
float t;
t=x; x=y; y=t;
}
void main(){
float a=40,b=70;
cout<<a<<‘\t’<<b<<endl;
swap(a,b);
cout<<a<<‘\t’<<b<<endl;
}
5.3 作用域和存储类

为什 么函 数调用 时形 参和实 参可以 同名 也可不 同名 ?


为什 么通 过调用 交换 函数并 没有 实现两 个变量 的交 换?
本章 重点 概念: 作用 域、全 局变量 、局 部变量 、静 态
存储 类型 。

首先 了解 :程序 的内 存分配 。
1 作用域

作用 域指 标识符 可以 被引用 的区域 。


演示 locoal.cpp 。

( 1 )块作用 域
在块中 定义 的标识 符, 作用 域为该 块,即 只能 在这个
块中 使用 该变量 。例 如:
if(x>y){
int t;
t=x; x=y; y=t;
}
#include<iostream.h>
void locoal();
int i=10, j=20; // 全局变量
void main(){
int i=100,j=200,k=300; // 局部 变量
cout<<“i="<<i<<"\tj="<<j<<"\tk="<<k<<endl;
cout<<"::i="<<::i<<"\t::j="<<::j<<"\tk="<<k<<endl;
locoal();
cout<<"i="<<i<<"\tj="<<j<<"\tk="<<k<<endl;
}
void locoal(){
int i=1000,j=2000;
cout<<"i="<<i<<"\tj="<<j<<"\tk="<<k<<endl;
}
( 2 )函数作 用域
在函数 中定 义的标 识符 ,作 用域为 该函数 。实 际上函
数也 是一 个块, 因此 也属于 块作 用域。

( 3 )函数原 型作 用域
在函数 原型 中定义 的标 识符 ,作用 域仅在 函数 原型中
,故 变量 名可以 省略 。

局部 变量 ( 局部作用 域 ) 与 “栈”
块执 行开 始 / 结束时系 统自 动分配 / 释放空间 。
( 4 )文件作 用域
在函数 之外 定义的 标识 符, 具有文 件作用 域。

全局 变量 、文件 作用 域、全 局数据 区。


编译 时分 配空间 ,程 序结束 释放空 间。
不同作 用域 中定义 的同名变 量各自具有 自己的 作用 域
,相 互之 间不会 干涉 (local.cpp) 。

结合内 存分 配分析 交换 变量 例子( 值形参 )。


2 存储类型
根据 变量 分配单 元的 时间, 存储类 型可 分为 动态存 储
变量 和静态存储 变量 。
动态 存储 变量在 程序 执行过 程中分 配存 储单元 ,分 配
在栈 中。 变量具 有局 部生命 期, 也称 自动变量 。
静态 存储 变量在 编译 时分配 存储空 间分 配在静 态数 据
区。 变量 具有全 局生 命期。

本节重 点介 绍静态 存储 类型 。静态 存储类 型的 标志是


用 static 修饰 。下 例演示 局部 静态变 量和 一般静 态变 量的
区别 。
5.3
结束
5.4 函数的嵌套和递归调用

一个古 老的 问题: 汉诺塔( 搬盘 子)。

A柱 B柱 C柱

怎么找 出原 则性解 法?
对比另 一个问 题: 求 n !。
 1 n0

n!  1 n 1
 n*(n-1)! n 1

n! 用什么思 想求 出的?
——“ 大事化小 、小 事化了 ”。

这个思 想可不 可以 用来搬 盘子 ?


何谓“ 大事 化小, 小事 化了 ”?
——“ 用自 己解 释自己 ”。 这种思 想称 为递归。
阶乘的定 义:

fac(int n){
if(n==0) return 1;
else return n*fac(n-1);
}
演示【 例 5.4 】,说明 递归 程序的 执行 过程。
fac(int n){
cout<<n<<'\t';
int f;
if(n==0) f=1;
else f=n*fac(n-1);
cout<<f<<'\t';
return f;
}
递归函数 在调 用时包 含两 个过程 :“递 ”和 “归
”。
递——“ 大事 化小” ,最 终“了 ”在递 归终 止条
件。故 终止 条件必 须明 确, 否则将 导致无
穷递。
【例 5.5 】求 fibonacci 数列前 20 项。

【例 5.6 】 hanoi 塔问题。


递归可解 决的 问题:
1 、通过 简单 的递归 结构完 成重 复性 工作;
2 、与顺 序相 关的问 题,如 倒序 问题 ;
3 、用其 他解 析方法 较难描 述的 问题 ,如汉 诺塔 问题

递归程序 外形 简单, 执行 过程占 用内存 资源 巨大。

C++ 所有 函数 的定义 是平 等的。


函数 调用 时,有 嵌套 调用( A 调 B , B 调 C ,…
…) ,还 有递归 调用 (自己 调用 自己) 。

5.4
结束
5.5 内 联 函数

1 内联函 数的作 用。

2 内联函 数的定 义。

3 编译系 统如何 处理 内联函 数。


内联 函数 只能用 来定 义本身 很简单 (无 复杂分 支) 的
情况 。而 系统最 终是 否按内 联函 数处理 用户无 从知 道。

5.5
结束
5.6 具有缺省参数的函数
一般情况 下函 数形参 和实 参个数 要一致 。但 有些情 况
下也 有不 一致的 。

当函数定 义时 形参给 出缺 省值, 调用时 可以 不给出 实


参值 。例 如:
void delay(int n=1000){ for(;n>0;n--); }
void main(){
cout<<“ 延时 1000 个时 间单 位” <<endl;
delay(); // 未给 出实 参,按 缺省 值计算
cout<<“ 延时 500 个时 间单 位” <<endl;
delay(500); // 给出 实参 ,按给 出值 计算
}
注意 :

1 一个函 数可 以带有 多个 缺省 参数, 也可 以既有 普通


参数 又有 带缺省 值的 参数, 要求 带缺省 值的参 数列 在普通
参数 之后 。例如 :
float v(float a,float b=20,float c=30){return a*b*c;}
void main(){
float vol=v(10, 5); //5 对应谁 ?

2 有函数 原型 声明的 ,缺 省值 在声明 中指 出,定 义时


不能 再指 出。
5.6
结束
5.7 函数的重载

重载 函数 指函数 名相 同,而 形参不 同( 类型或 个数 不


同) 。
重载函 数的 作用。 请看 下例 :
sum(int a,int b){ return (a+b);}
double sum(double a,double b,double c){return(a+b+c);}
void main(){
cout<<sum(3,5)<<endl;
cout<<sum(4,6,7)<<endl;
} 5.7
结束
5.8 编译预处理和程序的多文件
组织

编译预 处理 是指在 程序 编译 之前, 系统的 预处 理程序


对源 程序 进行一 些预 先的处 理。

一个程序 的完 整执行 流程 为:

源程序文件 编译 编译 目标文件
临时文件 库文件等
.cpp 预处理 .obj


运行 可执行文件
运行结果
.exe
编译预 处理 由系统 的编 译预 处理程 序完成 。编 译预处
理指 令全 部由 # 打头。
按照功 能, 编译预 处理 分为 宏定义 、文件 包含 和条件
编译 。

5.8.1 宏定义
5.8.2 文件包含
5.8.3 程序的多文 件组织
5.8.1 宏定义
宏定 义分 为不带 参宏 定义和 代参宏 定义 。
不带 参宏 定义格 式为 :
#define 宏名 标识 符 字 符或字 符串
例如 :
#define n 30
#define pai 3.14
带参 宏定 义格式 为:
#define 宏名 标识 符 ( 参数表 ) 使用参数 的字 符或
字符 串
例如 :
#define area(r) pai*(r)*(r)
#define s(a,b) (a)+(b)
请看练习 4_2 宏定 义。

程序中的 替代 过程称 为宏展开 。

宏定义与 常量 定义、 函数 定义之 间的异 同关 系。

5.8.1
结束
5.8.2 文件包含
文件 包含 是将被 包含 的文件 嵌入到 include 指令所 在
位置 。使 被包含 文件 成为编 译对 象的一 部分。 如库 函数的
使用 。
指令 格式 为 :
#include< 文件 名 .h> // 标准搜 索,用 于库文 件

#include” 文件名 .h” // 先搜 索当 前目录 ,用 于自定

// 库文 件
自定 义头 文件的 使用 。请看 一个练 习 self_head ,输出
100 - 200 之间的素 数。将 判断 素数的 函数 放在自 定义 头
文件 中。
关于 条件 编译, 请大 家自学 相关内 容。

5.8.2
结束
5.8.3 程序的多文件组织

一个大型 程序 可以由 多个 源程序 文件组 成。


各源程序 文件 相对独 立, 可单独 编辑、 编译 。
通过连接 指令 将所有 的目 标文件 连接成 为一 个大的 、
完整 的可 执行文 件。

多文件系 统中 会涉及 全局 变量和 函数的 使用 ,即某 个


文件 中定 义的全 局变 量和函 数能 否在其 它文件 中使 用。

外部变量 及外 部变量 声明 。请看 下例:


假定 file1.cpp 中有如 下定义 :
int n=100;
void print_head(){ ……}
max(int a,int b,int c){……}

如果 file2.cpp 要使用全 局变 量 n 和函数 print_head() ,


则 file2.cpp 在使用之 前必 须有如 下声 明:
extern int n=100;
extern void print_head(); // 只要函 数头即 可

不加声 明则 编译 file2.cpp 时会提示 “标 识符未 定义 ”


;而 如果 声明不 加 extern ,则提示 全局 变量或 函数重 复定
义。
如何 使某 个文件 中定 义的全 局变量 和函 数不能 被其 它
文件 使用 ?
—— 使用静态 说明 ( static ),这样即 使在其 它文 件中
使用外部 变量声 明也 是无效 的。

由此可 见, static 对于全局 变量 和函数 是限 制了作 用


域, 而对 局部变 量则 延长了 生命 期。

第5
章结

You might also like