第一章 函数与宏定义
-
Upload
candace-myers -
Category
Documents
-
view
89 -
download
0
description
Transcript of 第一章 函数与宏定义
第一章 函数与宏定义第一章 函数与宏定义
1.1 1.1 函数概念函数概念
1.2 1.2 变量作用域和存储类型变量作用域和存储类型
1.3 1.3 内部函数与外部函数内部函数与外部函数
1.4 1.4 递归函数设计和调用递归函数设计和调用
1.5 1.5 预处理预处理
1.6 1.6 综合范例综合范例
函数是 C 程序的基本组成单位,每个 C 程序由一个主函数 main() 和任意个函数组成。每个函数对应特定的功能。意义:
( 1 ) 便于设计复杂的大程序。
( 2 ) 便于程序的维护和扩充。
1.11.1 函数概念函数概念
3) 除 main 外 , 其它为标准库函数、用户自定义函数 ,
标准库函数:由系统提供,用户可调用。
用户自定义函数:由用户按语法规则编写。
C 程序由一个 main 和任意个函数组成。
2) 由主函数调用其他函数,其他函数也可以互相调用,且同一函数可以被一个或多个函数调用任意次。
main ( )
a b c
d x z
1) 程序从 main 开始执行 , 最后又回到 main 函数结束。
库函数简介库函数简介系统自带的标准库函数根据不同的功能作用放在
不同的头文件中。stdio.h 用于标准输入 / 输出math.h 用于数学计算ctype.h 用于字符处理string.h 用于字串处理time.h 用于时间 / 日期的处理graphics.h 用于图形操作
库函数的使用提高了编程效率。使用时只需在程序的开头加上一条语句:
#include <.h>根据使用的函数来确定
1.1.1 函数定义1.1.1 函数定义
[ 存储类型符 ] [ 返回值类型符 ] 函数名 ([ 形参说明表 ]) {
说明部分 执行部分}
函数体
例 1: 定义函数 hello ,在屏幕上输出“ Hello World!” hello ()
{ printf(“Hello World!\n”); }
例 2: 定义函数 add ,实现整数的加法运算 add (int x, int y){ int result;
result=x+y;printf(“%d”,result); }
int add (int x, int y){ int result;
result=x+y;return result; }
函数返回语句的形式有以下两种:
return ( 表达式 ); 或 return 表达式 ;
1 、函数无返回值的情况
2、函数有返回值的情况return; 或 根本没有 return 语句
例 3: 定义函数 max ,找出两个整数中的最大值并返回 int max (int x, int y)
{ int m;m=x>y? x : y ;return m; }
1.1.2 函数声明和调用1.1.2 函数声明和调用
调用函数前应对被调用函数进行声明 ( 说明 ) ,目的是告诉编译器所要使用的函数的信息,便于编译器对函数调用作精确的类型检查。
函数声明的一般形式:[ 存储类型符 ] [ 返回值类型符 ] 函数名 ( [ 形参说明表 ]);
如: int max(int x,int y);
一、 函数的声明
例 : 求二实数之和( 1-1.c )。 main ( ) { float add(float x, float y ); float a, b, c ; scanf("%f, %f",&a, &b); c=add(a, b); printf ("sum= %f" ; c); }
float add( float x, float y) { float z; z=x+y; return z; }
函数声明
当函数定义在函数调用之前时,可省略对被调函数的声明。
函数调用
函数定义
例 1 、例 2
( 1 )无返回值的情况
函数名 ([ 实参列表 ]);
( 2 )有返回值的情况
变量名=函数名 ([ 实参列表 ]);
二、 调用方式
调用时 :
调用完毕,释放形参单元。
实参 : 出现在函数调用中的参数。
形参 : 出现在函数定义中的参数。
1.1.3 函数的传值方式1.1.3 函数的传值方式
函数参数
单向传递 形参。实参值
临时分配单元给形参
例 :(1-2.c)
int max (int x, int y)
{int z;
z=x>y? x:y;
return (z) ; }
main( )
{int a, b, c;
scanf("%d, %d", &a, &b);
c=max(a, b);printf("max=%d\n", c); }
例 1-2.c 函数调用中参数传递过程为:
c=max(a, b);
int max(int x, int y) { int z;
z=x>y? x:y;
return(z);
}
abc
xyz
101515
101515
int a, b, c;
scanf("%d, %d", &a, &b);
在 main 函数中
形参变量在被调用前不占用存储单元;在调用结束后,形参所占存储单元亦被释放。
实参可以是常量、变量或表达式。 必须指定形参类型,且必须与实参的类型一致。 实参对形参的数据传递是“传值方式”。即单
向传递,不能逆传。
注意(函数参数):
例:阅读下列程序,写出运行结果 (1-3.c)
swap(int x, int y)
{int t;
t=x; x=y; y=t;
printf("x=%d, y=%d\n", x, y);}
main( )
{ int a, b;
scanf("%d, %d", &a, &b);
if (a<b) swap(a, b);
printf("a=%d, b=%d\n", a, b); }
ab
xyt
1519
1519151519
⑤ 关于 main 函数的参数 :
一般情况下, main 函数均无参数,但这并不表示 main 函数必须为无参函数,在必要时,main 函数也可以带有参数。
⑥ 实参传递给形参时 , 实参的计算有的系统自左至右 , 有的自右至左。
例 int f(int a,int b) { int c; if(a>b) c=1; else if(a==b) c=0; else c=-1; return(c); }
main(){ int i=2,p; p=f(i,++i); printf("%d",p); }
Turbo C 是按自右至左的顺序求实参表的值,所以上机运运行结果为 0 。
如: #include "stdio.h“
fun(int x, int n); main( )
{ int n=4, x=2, s;
s=fun(x, n);
printf("s=%d\n", s); }
fun(int x, int n)
{ if (n==1) return(x);
else return(xfun(x, n1)); }
① 一个函数中可以有一个以上的 return 语句,执行到哪一个 return 语句,则哪一个语句起作用。
注意(函数返回值):
② 如果函数定义中没有给出函数值类型,一律按 i
nt 型处理。
如: float max(float x, float y)
char letter(char c1, char c2)f(int a, int b)
函数值为 float 型
函数值为字符型
函数值为 int型
③ 当函数值类型与 return 语句中表达式类型不一致时,以函数值类型为准。对数值型数据,可以自动进行类型转换。即函数类型决定返回值的类型。 1-2.c int z->float z ( 这种方法初学者最好不用 )
④ 若被调函数中没有 return 语句,将不带回一个确定的、用户希望的函数值。但实际上,并非不带回值,而只是不带回有用的值,带回的是一个不确定的值。
main ( )
a=printstar( );
b=print_message( );
c=printstar( );
printstar( )
{printf("************\n"); }
print_message( )
{printf(" How do you do!\n");}
{int a, b, c;
printf("a=%d, b=%d, c=%d\n", a, b, c); }
如: (1-4.c)
}
⑤ 为了明确表示不需要函数返回值,可以用“ void” 定义函数为“无类型”。此时,不得使用 a=printstar( ) 之类的语句。
1. 编写计算圆面积的函数并调用 (1-5.c) 。2. 编写由三角形三条边求面积的函数并调用 (1-5-
0.c) 。3. 打印出 1000 以内的所有素数,要求用到函数 y
es(n), 其功能为判断某数 n 是否为素数 (1-9.c) 。4. 计算 1+ ( 1+2 ) + ( 1+2+3 ) +…+(1+2+3+
…+n) 要求( 1)n 由键盘输入 ( 2 )每项的计算由函数完成 (1-10.c) 。
练习:
a( )
{
调用 b
}
b( )
{
}
main
{
调用a
}
主函数调用其他函数,其他函数也可以互相调用。
1.1.4 函数互相调用1.1.4 函数互相调用
函数不能嵌套定义,但可嵌套调用
例:编写求组合数的函数。 (1-6.c)
组合数的计算公式为:)!(!
!
nmn
mC nm
→从程序中可以看到:
(2)二个函数的定义均在主函数之前,所以在主函数中不必再对它们进行声明 (说明 );
(1) 在定义函数时,函数 fac 、 comb 是互相独立的;
(3) 程序从主函数 main() 开始执行,首先执行的 scanf( ), 输入数,然后调用 comb(n,m), 调用 comb 的过程中,要调用 fac( ) 。 fac( ) 的调用是嵌套在函数 comb( ) 的调用中进行的 ,其嵌套调用过程如下:
输出 c
main 函数 comb 函数 fac 函数
调用 comb 函数 调用 fac 函数
例 1 :任何一个整数 n 的立方都可以表示为 n 个相邻奇数之和。其中最大奇数为 d=2m-1 ,而 m=1+2+3+…+n 。 (1-7-0.c)
1. 从键盘输入整数 n;2. 求 m=1+2+3+…+n 的值;3. 求 d=2m-1 的值;4. 求所有奇数 d, d-2, d-4, …, d-2*(n-1) 。
算法:
例 2 :定义函数 max ( a,b,c) ,功能为求三个整型数中的最大值。利用该函数,编程求五个数的最大数 (1-7.c) 。
1 、局部变量
凡在函数 (含 main 函数 ) 内部定义的变量称为局部变量。
局部性 : 局部变量仅在函数内部有效 , 它包括:
一、变量作用域
根据变量的有效作用范围,变量可分为局部变量和全局变量。
1.21.2 变量作用域及存储类型变量作用域及存储类型
2. 形式参数;
3. 在复合语句中可定义仅复合语句中有效的临时变量。 (1-11.c )
不同的函数可具有同名的变量 , 它们占不同的内存单元 , 互不影响。
1. 函数中定义的变量;
2 、全局变量
一个源文件中 , 在所有函数之外定义的变量为全局变量。
有效性 : 自定义位置开始至文件结尾全部有效。
char f2(int x,int y);
{ int i, j;
}
main ( )
}
}
例 : int p=1, q=5;
float f1(int a)
{int b, c;
}
char c1, c2;
p,q 的作用范围
c1, c2 的作用范围
1. 全局变量可以增强各函数间数据的联系。同一文件中的所有函数都能引用全局变量的值。当一个函数对其值进行改变后 ,另一个函数使用该变量的值亦相应改变。好处 : 函数之间值传递。
2. 不要随意使用全局变量。一是始终占据内存单元;二是由于函数依赖于外部定义的变量,降低了通用性。
3. 不在全局变量作用域内的函数。若要使用全局 (外 ) 变量,需在函数体内加上 extern保留字予以说明。
4. 全局和局部变量同名时 ,局部变量有效。
float f1 (int x)
{extern int a, b;
}
int a = 0 ; b= –1
main ( )
{
}
a, b 作用域
例:
例:分析 m 作为全局变量和局部变量在程序中各个部分时的值。 (1-12.c )
int m=13;
int fun(int x, int y)
{int m=3;
printf("m1=%d\n",m);
return(x*y-m); }
main()
{int a=6,b=7;
printf("m2=%d\n",m);
printf("%d\n",fun(a,b)/m); }
mx, y
a, b
全局变量 m的作用范围
二、 变量的存储类型二、 变量的存储类型
1 、变量的存储类型
程序区静态存储区动态存储区 数据 , 变量存
放
内存分配
说明变量占用存储空间的区域。
静态存储变量 : 存放于静态存储区 , 在程序整个运行过程中 , 始终占据固定的内存单元。
动态存储变量 : 存放于动态存储区 , 根据程序的运行状态 ( 如:函数调用 ) 而临时分配的单元,且单元并不固定。
常用的变量存储类型有三种:
2. 静态 (static) 型
3. 外部 (extern) 型
1. 自动 (auto) 型
局部变量既可以静态方式 , 又可以动态方式存储。
动态方式 : auto int a, b;
2 、局部变量存储形式
则 : a, b 为自动型,存入动态区。在该函数被调用时才分配单元,函数调用结束时释放。
auto 一般省略。以前用到的变量均为 auto 型 ,
( 除加 static 说明的之外 ) 。
在变量初始化方面, auto 型变量在每次函数调用时都赋一次初值。其默认初值不确定。
例: auto 型变量的初始化 (1-13-0.c )
在变量初始化方面,静态局部变量是在第一次调用函数时赋初值的,且只赋一次初值。默认初值为 0
。
则 : a, b 存入静态区。 函数中的 a, b 始终占据固定存储单元。
静态方式 : static int a, b;
如果希望在函数调用结束后仍然保留函数中的局部变量的值,则可以把该变量定义为静态局部变量。
例: static局部变量的初始化 (1-13-0.c )
例:求 n! (1-13.c )
2 、全局变量存储形式
在函数外部中定义,它们一定存放在静态存贮区中。
全局变量既可被本文件中各函数用 , 亦可被其它源文件中的函数引用。
(1) 只被本文件中的函数引用
全局变量本身一定是存放在静态区的。但若加上 static. 即 :
static int a, b;
float f1(x)
int
{
}
则表明 a,b 只被本文
件中各函数引用,即
使与其它文件中的全
局变量同名,也互不
影响。
(2) 可被其它文件中的函数引用(外部类型,默认)
int a;
main( )
{
}
extern int a;
fac(x)
int x
{
z=a
}
文件 f1.c文件 f2.c 用到 f1.c 中的 a
f2.c 中的 extern 在函数外说明 , 在函数内说明已叙述过。
总结
存储类别函数内 函数外
作用域 存在性 作用域 存在性 auto
static 局部
static 全局
不加 static 全局 ( 外部)
( 只限本文件 )
例:静态局部变量的作用域与存在性 (1-13-1.c )
1.31.3 内部函数与外部函数内部函数与外部函数
函数本身在一个文件中为全局的。即函数可被其所在文件的所有其它函数引用。
一、内部函数–––只能在本文件中调用
但函数能否被其它文件中的函数所引用呢 ?
为此分为 : 内部函数、外部函数
static 类型标识符 函数名 ( 形参表 )
例 : static int max (int a, int b)
{
}
则该函数 max 只能被本文件中的其它函数引用 , 而不能被其它文件中的函数引用。即如果在不同的文件中有同名的内部函数,将互不干扰。
既可被本文件中的函数调用,也可被其它文件中的函数调用。
extern 类型标识符 函数名 ( 形参表 )
一般系统在调用外部函数的函数中用 extern 说明外部函数。
二、外部函数
extern 可省略
例 : (1-14.c 1-15.c )
/*file1.c*/#include<stdio.h>int mod(int a, int b);extern int add(int m, int n);main(){ int x,y,result; scanf(“%d%d”,&x,&y); result=add(x,y); if (result>0) result=result-mod(x,y); printf(“result=%d\n”,result);}int mod(int a,int b){ return (a%d); }
/*file2.c*/extern int add(int m, int n){ return (m+n);}
1. 命令方式。
由多个源文件组成一个程序:
2. 建立项目文件。
3. 使用文件包含命令。
首先要生成两个源文件 (file1.c 和 file2.c) 。再构造 project文件,在编辑状态下,编辑一个后缀为prj 的文件(文件名可由用户选择,如: ff.prj) 此文件内容如下: file1
file2
后缀 .c 可有可无, file1,file2 顺序无关,如果是file1.c 和 file2.c 不在一个目录中时,可在 project文件ff.prj 中给出各自的路径。
选择 project 功能项,在 project name 输入 ff.prj 。然后按 F9 ,产生相应的执行文件 ff.exe 。
递归调用 :
1.4 1.4 递归函数设计和调用递归函数设计和调用
在函数内调用函数本身,称为函数的递归调用。函数直接调用本身,称为直接递归。函数调用其他函数 ,其他函数又调用了本函数,称为间接递归。
直接调用 int f(int x)
{ int y, z;
z=f (y);
}
间接调用
int f1 (int x)
{ int y, z;
z=f2 (y);
}
int f2 (int t)
{ int a, b;
a=f1 (y);
}
以上仅给出了递归的概念 .
显然 : 上述例子会无限递归 ( 无限执行 ) 。所以 , 在递归调用时都有条件限制。
n!=1 (n=0, 1)
n(n–1)! (n>1)
即 : 条件成立 , 调用递归 , 否则结束。
一个最常用的例子 :
求 n!
1. 从数学上定义
3. 执行过程 :
设输入 n4
main ( )
{
f=fac(4);
}
fac(4)
{
f=4fac(3);
return f;
}
fac(3)
{
f=3fac(2);
return f;
}
fac=4!
n=4 n=
3
fac=3!
fac(2)
{
f=2fac(1);
return f;
}
fac(1)
{
if (…) f =1 ;
return f;
}
n=1
fac=1
n=2
fac=2!
2. 程序 1-16.c
可简化表示为
n=1
n=2
n=3
n=4
fac=4!
fac=3!
fac=2!
fac=1
n=4
1. 求 Fibonacci 数列第 i 项的值。 (1-17.c)
Fibonacci 数列: 0 , 1 , 1 , 2 , 3 , 5 , 8 , 13 ,…
习题 :
数学公式:fib(0)=0;
fib(1)=1;
fib(n)=fib(n-1)+fib(n-2); (n>=2)
1.51.5 预处理预处理
C 语言中,除了说明语句和执行语句之外,还有一些编译预处理命令。
作用 : 向编译器发布命令,告诉编译器在对程序进行编译之前该作什么。
处理流程 : 第一次编译扫描时 , 将预编译命令处理完,然后再进行正式编译 ,生成目标代码。
1. 宏定义命令 ;
有三种类型的预处理命令 :
为了与 C 语句区别 , 这些命令均以“ #” 开头。
2. 文件包含命令 ;
3. 条件编译命令。
简化程序的书写 , 提高可读性、可移植性。
一、不带参数的宏
# define 标识符 字符串表达式
宏定义分为 : 无参数和有参数二种形式。
6.5.1 宏定义6.5.1 宏定义
例 : #define PAI 3.14159
area=PAI*r*r;
实际处理时 :
用“ 字符串表达式”替换程序中的标识符 (宏名 )
相当于: 3.14159*r*r
从开始定义的位置至文件结尾,但允许提前终止。用命令 (#undef 标识符 )
#define M 10.5
main ( )
{
# undef M
}
1. 宏定义的作用域 :
作用域范围
# include <stdio.h># define PAI 3.14159main( ){ float r, s;
r=2.0;s=PAIrr;printf("s=%5.3f\n", s);#undef PAIr=3.0;s=PAIrr;printf("s=%5.3f\n", s);
}
运行结果: s=12.566
s=28.274
如果在程序中提前终止宏定义,则编译时会提示: undefined symbol 'PAI' in function main
例:计算圆面积。 (1-18.c )
#define PI 3.14159
#define R 3.0
#define L 2R PI
#define S PI R R
main()
{
printf("L=%f\n S=%f\n ", L, S);
}
2. 可以嵌套定义 , 即可引用已定义的宏名。 (1-19.c )
注意 : 双引号“ ”中的 L 不被替换。
结论 : (1)凡程序中常用到的字符序列 , 如 : 常数 ,
公式 , 均可用宏定义。
(2) 经常会改变的数据可用宏定义。 (1-20.c)
结果: L=18.84954 /*L=23.0 3.14159*/
S=28.27131 /*S=3.141593.0 3.0*/
二、带参数的宏
形式 : #define 宏名 ( 参数表 ) 字符串 #define S(r ) 3.14159 r r
S(3.0) /* 相当于 : 3.14159 3.0 3.0*/
S(4.0) /* 相当于 : 3.14159 4.0 4.0*/
适用于字符串表达式序列中有变化的字符 ,将这部分字符可定义为参数。
例 1: r23.1415r r
为字符序列 , 但 r 是可变的。
例:计算圆的面积 (1-18.c )
main( ){int x, y, b, z; x=3; y=4; b=7; z=F(x+y);}
结果为 : z=31
#define F(a) a*b
例:( 1-21.c )分析结果
编译后 z=F(x+y) 变成 : x+y*b
1. 宏展开实质为严格的字符替换,把参数看一种 “字符串”,所以定义宏时要考虑实参 (替换 )的各种可能 , 防止出现二义性。
如程序例 1 中 : 若有 #define S( r) 3.14159rr
则 S(a+b) 的结果为 : 3.14159a+ba+b
为避免出现错误的结果 , 可将宏定义修改为 :
#define S( r) 3.1415926( r)( r)
宏定义时必须注意以下几个方面 :
例:循环执行的次数 (1-22.c )
不带参数的宏在嵌套定义时也有同样的问题:
例: (1-24.c ) 用宏实现求最大数带参数的宏与函数有一些相似的地方:
但宏定义与函数是完全不同的概念
例 : printf 中经常有 " \n ", " %d " 或程序中经常有 printf(" please input a number ")等。则可用宏定义代替,如:
2. 灵活运用宏定义 , 可使程序书写简化 .
例 : (1-23.c )
C 语言允许在一个文件预编译时 , 将另一个文件原封不动地包含进来。
9.5.2 文件包含9.5.2 文件包含
格式 : # include "文件名 "
# include "f2.c "
main ( )
{ }
例 :
f1.c
f2.c
:
:
:
预编译后 :
::
main ( )
{
}
f1.c
为 f2.c 中的内容
再进行正式编译
例:回顾 6-14.c 6-15.c
2. 一般用 .h 扩展名命名被包含文件。 h 为 head 之意。如系统 stdio.h, 系统 .h文件在子目录 INCLUDE 下。也可以包含 .c文件。
3. #include 后的文件名既可用 " ", 也可用 < > ,区别 :
“ ”首先在当前目录中找 ,然后再去标准目录中找。
< > 只在标准目录 (include 目录 ) 中找。4. 被包含文件和包含文件最后形成一个文件。因此 ,
全局变量作用域为整个文件。不必用 extern 说明。
1. 一个 include 只能写一个文件,包含多个文件需多个 include
注意 :
条件编译允许只编译源程序中满足条件的程序段
(1) 使生成的目标程序较短,从而减少了内存的开销并提高了程序的效率。
(2) 可有效地提高程序的可移植性,为一个程序提供各种不同的版本。
意义 :
9.5.3 条件编译9.5.3 条件编译
形式 1 # ifdef 标识符 程序段 1
# else
程序段 2
# endif
功能 当标识符在此之前被定义过 ( 即用 #define定义过 ), 则编译程序段 1, 否则编译程序段 2 。
其中 : 程序段即可为语句组,也可为命令行。 #else 后面部分可没有,但 #endif保留。
条件编译命令的三种形式 :
例: 1-25.c 条件编译实例
形式 2 #ifndef 标识符
程序段 1
#else
程序段 2
#endif
功能 : 与形式 1 的条件正好相反 , 即 : 当标识符未被定义,则编译程序段 1 , 否则编译程序段 2 。
形式 3 #if 表达式
程序段 1
#else
程序段 2
#endif
功能 : 当表达式 0, 则编译程序段 1, 否则编译程序段 2 。
例: 1-25.c 条件编译实例
形式 4 : #if 常量表达式 1 程序段 1 #elif 常量表达式 2 程序段 2 …… #elif 常量表达式 n 程序段 n #else 程序段 n+1 #endif
例 : 输入一任意字符串,需将其全部转化为大
写字母 , 或者将其转化为小写字母。
只编一个程序, 程序中用条件编译,决定是全部转为大写字母还是小写字母。
例: 1-26.c
1 、 习题 1.41, 1.47
练习练习