第五章 - C程序设计基础

Download Report

Transcript 第五章 - C程序设计基础

Chap 5 函数
5.1 计算圆柱体积
5.2 数字金字塔
5.3 复数运算
本章要点

函数的作用?如何确定函数功能?

怎样定义函数?如何调用函数?定义函数与声明
函数有何区别?

什么是函数的参数?怎样确定函数的参数?

在函数调用时,参数是如何传递数据的?

变量与函数有什么关系?如何使用局部变量和全
局变量?

什么是静态变量?
5.1 计算圆柱体积
5.1.1 程序解析
5.1.2 函数的定义
5.1.3 函数的调用
5.1.4 函数程序设计
5.1.1 程序解析-计算圆柱体积
【例5-1】输入圆柱体的高h和半径r,求圆柱体积,
volume=π*r2*h。要求定义和调用函数 cylinder
(r, h)计算圆柱体的体积。
定义函数cylinder()如下:
double cylinder(double r, double h)
{ return 3.1415926 * r * r * h ; }
/* 计算圆柱体积 */
例5-1程序
#include <stdio.h>
int main( void )
{ double height, radius, volume;
double cylinder (double r, double h); /* 函数声明*/
printf ("Enter radius and height: ");
scanf ("%lf%lf", &radius, &height);
volume = cylinder (radius, height); /* 调用函数*/
printf ("Volume = %.3f\n", volume);
return 0; 运行结果: Enter radius and height: 3.0 10
Volume = 282.743
}
double cylinder(double r, double h)
/* 函数定义*/
{ return 3.1415926 * r * r * h ; }
5.1.2 函数的定义
函数是指完成一个特定工作的独立程序模块。
 函数分类
 从函数定义的角度:
自定义函数(用户定义)和系统库函数(系统定义)
 从主调与被调函数之间数据传送的角度:
有参函数和无参函数
 从有无返回值的角度:
有返回值函数和无返回值函数
 main()也是一个函数,C程序由一个main()或多个
函数构成。

5.1.2 函数的定义
1. 函数定义的一般格式:
函数类型 函数名(形式参数说明表) /*函数首部*/
{ 函数体 }
/* 函数体 */
 函数类型:指函数返回值的类型,如:int float
double char void 等。缺省为int,无返回值的
函数,函数类型应使用 void。
 函数名:函数的名称,即一个自定义标识符。
 形参说明表:说明形式参数,格式为:
类型1 形参1, 类型2 形参2, ..., 类型n 形参n
 函数体:用大括号括起来的若干语句,体现函数
的实现过程。
5.1.2 函数的定义
2. 有返回值的函数的定义
 函数的返回值是通过函数中用return语句实现:
return 表达式; 或 return (表达式);
 return
语句的功能是结束本函数的执行,将表达式的
值返回给主调函数。 只能返回一个值。
 如果表达式缺省,则无直接的返回值。
 如果函数类型和return语句中表达式的值类型不一致,
则以函数类型为准。
 函数终止执行有两种情况:一是遇到return语句或其他
终止函数执行的语句;二是执行到函数体的右大括号。
5.1.2 函数的定义


有返回值函数的函数类型应定义为要返回的运算
结果的值类型,一般不能是void。
有返回值函数中至少有一条,也可以有多条含有
返回值表达式的return语句。
例如,判断一个整数是否为素数的函数prime():
int prime(int m)
{ int i;
if(m < 2) return 0;
for(i = 2; i < m; i++)
if(m%i == 0) return 0;
return 1;
}
5.1.2 函数的定义
3. 无返回值的函数的定义

无返回值函数的函数类型应定义为 void,表示无
返回值,不应缺省,因为缺省默认为 int。

无返回值函数中可以没有,也可以有不含返回值
表达式的return语句。

无返回值函数虽然不直接返回一个值,但它的作
用通常以屏幕输出等方式或其他途径体现。
5.1.3 函数的调用
1. 函数调用的形式
函数调用的一般形式为:
函数名(实参表)
 实参可以是常量、变量和表达式。
 实参的类型,个数和顺序应与定义时的形参一致。
2. 函数调用的过程
当主调函数(如 main() 函数)执行到某个函数调用,
主调函数就暂停执行,将实参的值传递给形参并
转而执行该被调函数,该被调函数执行完后,将
返回主调函数,再从原先暂停的位置继续执行。
5.1.3 函数的调用
3. 参数传递
 函数定义时的参数称为形式参数,简称形参。
double cylinder (double r, double h) { …… }
 函数调用时的参数称为实际参数,简称实参。
volume = cylinder (radius, height);


形参:必须是变量,用于接受实参传递过来的值。
相同变量名的实参变量与形参是不同的变量,它
们分别只有效于各自所在的主调函数和被调函数。
实参:可以是常量、变量或表达式(包括有返回值
函数调用表达式)。
5.1.3 函数的调用
函数调用时,发生参数传递,即把实参的值复制
给对应的形参。
 参数传递是一种单向的值的传递,因此,函数执
行中形参值的改变不会影响实参的值。
4. 函数结果返回
 函数返回的两种情况
 完成确定的运算,有一个运算结果返回给主调
函数。
 完成指定工作,没有确定的运算结果需返回给
主调函数。

5.1.3 函数的调用


函数结果返回的形式:
 return 表达式;
 return (表达式);
【例5-2】定义判断奇偶数的函数even (n),当n为
偶数时返回1,否则返回0。
int even (int n)
{ if(n%2 == 0)
return 1;
else return 0;
}
5.1.3 函数的调用
5. 函数原型声明
 调用函数必须遵循“先定义或声明后调用”的原
则。
 要调用库函数,就用#include命令把包含该库
函数原型声明的头文件包含在源程序前部。
 函数定义在先调用在后,就不需函数声明。
 函数定义在后调用在先,就必须先行函数声明。
 函数声明的一般形式:
函数值类型 函数名(形参说明表);
其中的形参说明表可以与定义时的相同,也可以
缺省其中的形参名。
5.1.4 函数程序设计
【例5-3】输入精度e,使用格雷戈里公式求π的近
似值,精确到最后一项的绝对值小于e。要求定义
和调用函数 funpi(e) 求π的近似值。

4
1
1
3
5
 1 

1

7
分析:
与【例4-1】(使用格雷戈里公式求π的近似值)的
差别是:精度需输入;要定义和调用函数 funpi(e)。
#include <stdio.h>
int main (void)
{ double e, pi;
double funpi(double);
printf ("Enter e:");
scanf ("%lf", &e);
pi = funpi (e);
printf ("pi = %f\n", pi);
return 0;
}
运行结果:
Enter e:0.0001
pi = 3.1414
例5-3程序
double funpi(double e)
{ int den = 1, f = 1;
double item, sum=0;
do
{ item = 1.0/den;
sum = sum + f * item;
f = -f;
den = den + 2;
} while(item >= e);
return sum*4;
}
5.1.4 函数程序设计
【例5-4】求100以内的全部素数,每行输出10个。
要求定义和调用函数prime(m)判断m是否为素数,
当m为素数时返回1,否则返回0。
分析:
与【例4-10】(求100以内的全部素数)的差别仅是
要求定义和调用函数prime()。
for(m = 2; m <= 100; m++)
if (prime(m) != 0)
printf("%d ", m);
#include <stdio.h>
int main(void)
{ int count, m, prime(int);
count = 0;
for(m=2; m<=100; m++)
if(prime(m))
{ printf("%5d", m);
count ++;
if(count%10 == 0)
printf("\n");
}
运行结果:
2 3 5 7
return 0;
31 37 41 43
}
73 79 83 89
例5-4程序
int prime (int m)
{ int i;
if (m<2) return 0;
for( i=2; i<m; i++)
if (m%i == 0)
return 0;
return 1;
}
11 13 17 19 23 29
47 53 59 61 57 71
97
5.2 数字金字塔
【例5-5】输出5行的数字金字塔。
5.2.1 程序解析
5.2.2 不返回结果的函数
5.2.3 结构化程序设计思想
5.2.1 程序解析
【例5-5】输出5行的数字金字塔。
1
22
333
4444
55555
分析:




外循环:控制输出行数,行号从1开始。
内循环1:输出每行前导的空格,个数为: 总行数-行号。
内循环2:输出每行的数字,该数字及个数为行号。
每行最后须换行。
#include <stdio.h>
void pyramid (int n)
{ int i, j;
for (i = 1; i <= n; i++)
{ for (j = 1; j <= n-i; j++)
printf(" ");
for (j = 1; j <= i; j++)
printf("%-2d", i);
printf("\n");
}
}
int main (void)
{ pyramid(5);
return 0;
}
/* 函数定义 */
例5-5程序
/* 循环行数次 */
/* 输出每行前导空格 */
/* 输出每行的数字 */
/* 每个数字宽2左对齐 */
/* 换行 */
/* 用实参5调用函数 */
5.2.2 不返回结果的函数

无返回值函数的函数类型应定义为 void,表示无
返回值,不应缺省,因为缺省默认为 int。

无返回值函数中可以没有,也可以有不含返回值
表达式的return语句。

无返回值函数虽然不直接返回一个值,但它的作
用通常以屏幕输出等方式或其他途径体现。

由于函数没有返回结果,函数调用不可能出现在
表达式中,通常以独立的调用语句方式,如
pyramid(5);
5.2.3 结构化程序设计思想

结构化程序设计
 是一种程序设计技术
 C语言是结构化程序设计语言

结构化程序设计强调程序设计的风格和程序结构
的规范化,提倡清晰的结构。
 它的基本思路是将一个复杂问题的求解过程划分为若
干阶段,每个阶段要处理的问题都容易被理解和处理。
 按自顶向下的方法对问题进行分析、模块化设计和结
构化编码等3个步骤。
5.2.3 结构化程序设计思想
1. 自顶向下的分析方法
 把大的复杂的问题分解成小问题后再解决。
 面对一个复杂的问题,首先进行上层(整体)的分
析,按组织或功能将问题分解成子问题。
 如果子问题仍然很复杂,再做进一步分解,直到处
理对象相对简单,容易处理为止。
 当所有的子问题都得到了解决,整个问题也就解决
了。

每一次分解都是对上一层的问题进行细化和逐步
求精,最终形成一种类似树形的层次结构,来描
述分析的结果。
5.2.3 结构化程序设计思想

一个学生成绩统计程序的例子
学生成绩统计程序
成绩输入
数据计算
数据查找
计算学生平均分 计算课程平均分
输出成绩
5.2.3 结构化程序设计思想
2. 模块化设计
 将模块组织成良好的层次系统
 顶层模块调用其下层模块以实现程序的完整功能。
 每个下层模块再调用更下层的模块,从而完成程序的
一个子功能。
 最下层的模块完成最具体的功能。

遵循模块独立性的原则,即模块之间的联系应尽
量简单
 模块用函数实现。
 一个模块只完成一个指定的功能。
 模块之间只通过带参数的函数进行调用。
5.2.3 结构化程序设计思想
3. 结构化编码

经模块化设计后,每一个模块都可以独立编码。
编程时应选用顺序、选择和循环三种控制结构

对变量、函数、常量等命名时,要见名知意,有
助于对变量含义或函数功能的理解。

在程序中增加必要的注释,增加程序的可读性。

要有良好的程序视觉组织,利用缩进格式

程序要清晰易懂,语句构造要简单直接

程序有良好的交互性,输入有提示,输出有说明
5.3 复数运算
【例5-6】分别输入2个复数的实部与虚部,用函数
实现计算2个复数之和与之积。
5.3.1 程序解析
5.3.2 局部变量和全局变量
5.3.3 变量生命周期和静态局部变量
5.3.1 程序解析
【例5-6】分别输入2个复数的实部与虚部,用函数
实现计算2个复数之和与之积。
分析:
若2个复数分别为:
c1=x1+y1i , c2=x2+y2i
则:
c1+c2 = (x1+x2) + (y1+y2)i
c1*c2 = (x1*x2-y1*y2) + (x1*y2+x2*y1)i
例5-6程
序
#include<stdio.h>
float real, imag;
/*全局变量,用于存放函数结果 */
int main(void)
{ float i1, i2, r1, r2;
/* 两个复数的实、虚部变量 */
void complex_prod(float r1,float i1,float r2,float i2);
void complex_add(float r1,float i1,float r2,float i2);
printf("Enter 1st complex number(real and imaginary): ");
scanf("%f%f", &r1, &i1);
/* 输入第1个复数 */
printf("Enter 2nd complex number(real and imaginary): ");
scanf("%f%f", &r2, &i2);
/* 输入第2个复数 */
complex_add(r1, i1, r2, i2);
/* 求复数之和 */
printf("addition of complex is %f+%fi\n",real,imag);
例5-6程序
complex_prod(r1, i1, r2, i2); /* 求复数之积 */
printf("product of complex is %f+%fi\n", real, imag);
return 0;
}
void complex_add(float r1, float i1, float r2, float i2)
{ real = r1 + r2;
imag = i1 + i2;
}
运行结果:
void
complex_prod(float r1, float i1, float r2, float i2)
complex
number(real and imaginary):1 1
{ Enter
real =1st
r1*r2
– i1*i2;
Enter
complex
number(real and imaginary):-2 3
imag 2nd
= r1*i2
+ r2*i1;
} addition of complex is -1.000000+4.000000i
product of complex is -5.000000+1.000000i
#include<stdio.h>
运行结果:
例5-6程序
float
result_real,
result_imag;
/* 全局变量,用于存放函数结果
Enter
1st complex
number(real
and imaginary):1 1*/
intEnter
main(void)
2nd complex number(real and imaginary):-2 3
{ addition
float imag1,
real1,isreal2;
/* 两个复数的实、虚部变量 */
ofimag2,
complex
-1.000000+4.000000i
/* 函数声明
*/
product
of complex
is -5.000000+1.000000i
void complex_prod(float real1, float imag1, float real2, float imag2);
void complex_add(float real1, float imag1, float real2, float imag2);
printf("Enter 1st complex number(real and imaginary): ");
scanf("%f%f", &real1, &imag1);
/* 输入第一个复数 */
printf("Enter 2nd complex number(real and imaginary): ");
scanf("%f%f", &real2, &imag2);
/* 输入第两个复数 */
complex_add(real1, imag1, real2, imag2); /* 求复数之和 */
printf("addition of complex is %f+%fi\n", result_real, result_imag);
complex_prod(real1, imag1, real2, imag2); /* 求复数之积 */
printf("product of complex is %f+%fi\n", result_real, result_imag);
return 0;
}
例5-6程序
void complex_add( float real1, float imag1,
float real2, float imag2 )
{ result_real = real1 + real2;
result_imag = imag1 + imag2;
}
void complex_prod( float real1, float imag1,
float real2, float imag2 )
{ result_real = real1*real2 - imag1*imag2;
result_imag = real1*imag2 + real2*imag1;
}
5.3.2 局部变量和全局变量

局部变量
 在函数内定义的变量(包括形参)
作用范围:本函数内部
 在复合语句内定义的变量
作用范围:复合语句内部
 函数调用结束或复合语句执行完毕,为其局部
变量分配的内存便释放,但静态局部变量除外。
 使用局部变量可以避免各函数间的变量相互干
扰,尤其是同名变量。
5.3.2 局部变量和全局变量
 当函数的局部变量和复合语句的局部变量同名
时,则在复合语句的局部变量的作用范围内由
复合语句的局部变量起作用,函数的局部变量
被“屏蔽”,即不起作用。
5.3.2 局部变量和全局变量
【示例】在复合语句中定义局部变量。
#include <stdio.h>
作用范围
int main (void)
abb
{ int a, b = 2;
运行结果:
a = 1;
a=16,b=13
{ a = a + b;
/* a=1+2=3 */
int b = 10; /* b=10
*/
a=16,b=2
b = a + b;
/* b=3+10=13 */
a = a + b;
/* a=3+13=16 */
printf ("a=%d,b=%d\n" , a, b);
}
printf ("a=%d,b=%d\n" , a, b);
return 0;
}
5.3.2 局部变量和全局变量

全局变量
 在函数外部定义的变量,不从属于任一函数。
 全局变量的作用范围是从定义处起始到所在程
序文件的结束。
 全局变量和局部变量同名时,则在局部变量的
作用范围内由局部变量起作用,全局变量被
“屏 蔽”,即不起作用。
【例5-7】全局变量定义。
#include "stdio.h"
xbax
int x;
int fun( )
{ int x = 4;
return x;
}
int main(void)
{ int a = 1;
x = a;
a = fun( );
{ int b = 2;
b = a + b;
x = x + b;
}
printf("%d %d" , a, x);
return 0;
}
例5-7程序
x:
a:
x:
b:
0 1 7
1 4
4
2 6
运行结果:
47
5.3.2 局部变量和全局变量
【例5-8】用函数实现财务现金记账。先输入操作类
型(1收入,2支出,0结束),再输入操作金额,计
算现金剩余额,经多次操作直到输入操作为0结束。
要求定义并调用函数,其中现金收入与现金支出分
别用不同函数实现。
分析:
设变量cash保存现金余额值,由于它被主函数、现金
收入与现金支出函数共用,任意使用场合其意义与数值
都是明确和唯一的,因此令其为全局变量。
#include<stdio.h>
例5-8程序
float cash;
/* 定义全局变量,保存现金余额 */
void income(float number) /* 定义计算现金收入函数 */
{ cash = cash + number; }
/* 改变全局变量cash */
void expend(float number) /* 定义计算现金支出函数 */
{ cash = cash - number; }
/* 改变全局变量cash */
int main(void)
{ int choice; float value;
/* 函数声明 */
void income(float number), expend(float number);
cash = 0;
/* 初始金额=0 */
printf("Enter operate choice(0--end, 1--income,
2--expend):");
scanf("%d", &choice);
/* 输入操作类型 */
}
while (choice != 0) /*若输入0,循环结束*/ 例5-8程序
{ if (choice == 1 || choice == 2)
{ printf("Enter cash value:"); /* 输入操作现金额 */
scanf("%f", &value);
if (choice == 1)
income(value);
/* 函数调用,计算现金收入 */
else expend(value); /* 函数调用,计算现金支出 */
printf("current cash:%.2f\n", cash);
}
运行结果:
printf("Enter
operate choice(0--end, 1--income,
Enter operate choice(0--end, 1--income, 2--expend):1
2--expend):");
Enter
cash value:1000
current&choice);
cash:1000.000000 /* 继续输入操作类型 */
scanf("%d",
Enter operate choice(0--end, 1--income, 2--expend):2
}
cash value:456
return 0;Enter
current cash:544.000000
Enter operate choice(0--end, 1--income, 2--expend):0
5.3.2 局部变量和全局变量


讨论
 全局变量比局部变量自由度大,更方便 ?
引起注意
 对于规模较大的程序,过多使用全局变量会带来副
作用,导致各函数间出现相互干扰。如果整个程序
是由多人合作开发,各人都按自己的想法使用全局
变量,相互的干扰可能会更严重。
 因此在变量使用中,应尽量使用局部变量,从某个
角度看使用似乎受到了限制,但从另一个角度看,
它避免了不同函数间的相互干扰,提高了程序质量。
5.3.3 变量生存周期和静态局部变量

变量生存周期
变量从定义开始分配存储单元,到运行结束存储单元
被回收,整个过程称为变量的生存周期。

变量的存储类别
 动态存储:程序运行期间按运行的需要动态地分
配和释放存储空间。此类局部变量用 auto 关键字
定义(一般都缺省),称自动局部变量。
 静态存储:程序运行期间分配固定的存储空间。
此类局部变量用static关键字定义,称静态局部变
量,全局变量默认为静态的。
5.3.2 变量生存周期和静态局部变量

变量的存储类别与其生存周期相关
 静态存储的变量的生存周期是到程序运行结束。
 动态存储的变量的生存周期是到函数到其所在函数
体或复合语句结束。

C程序存储分布:
程序代码区
静态存储区
(全局变量,静态局部变量)
动态存储区
(自动变量等)
5.3.2 变量生存周期和静态局部变量

自动变量
 局部变量在定义时,如果没有指定存储类别,
或使用了auto说明符,则都被认为其具有自动
类别,即为自动变量。例如下面的x和y变量:
int x, y;
auto int x, y;
int auto x, y;
 自动变量一定是局部变量。
 自动变量在动态存储区分配存储单元
 自动变量的生存周期从定义处开始,到其所在
函数体或复合语句结束为止。
5.3.2 变量生命周期和静态局部变量

静态局部变量
 定义形式
static 类型名 变量表;
 生存周期延长到程序运行结束,类似全局变量。
但并不影响其局部变量的作用范围。
 与全局变量相似,在静态存储区分配存储单元,
定义时的初始化(若未赋初值则赋0)只在第一
次调用函数时进行,以后若反复调用函数是保
留上次调用函数后的值。
 示例:写出下列程序的输出结果。
#include <stdio.h>
示例程序
void fun (int k);
int main (void)
{ int k;
for (k = 1; k <= 3; k++)
fun (k);
形参 静态变量a
return 0;
输出
fun调用
k 始值 终值
}
1
0
1
0
第1次
void fun(int k)
2
1
3
1
第2次
3
3
6
3
第3次
{ static int a;
printf ("%d, ", a);
a += k ;
}
5.3.2 变量生命周期和静态局部变量
【例5-9】输入正整数n,输出 1! ~ n! 的值。要求
定义并调用含静态变量的函数fact_s(n)计算 n!。
#include <stdio.h>
double fact_s(int运行结果:
n)
double fact_s(int n);
{ static double f Input
= 1; n: 7
int main(void)
f = f * n;
1!=1
{ int i, n;
return(f);
2!=2
printf("Input n:");
}
3!=6
scanf("%d", &n);
4!=24
for(i=1; i <= n; i++)
5!=120
printf("%3d!=%.0f\n", i, fact_s(i)); /* 输出6!=720
i 和 i! */
return 0;
}
本章小结


系统介绍函数的定义和函数调用
 学习如何针对具体问题,确定需要使用函数的
功能要求,再将功能用函数程序实现
 考虑如何调用定义好的函数,实现主调函数与
被调函数的连接
 确定参数功能,掌握参数的传递实现
函数与变量间的关系,不同形式的变量在函数中
起的作用不同。
 局部变量、全局变量和静态变量
本章结束