nullnull第5章 函数12564
江西师范大学计算机信息工程学院null第5章 函数 程序设计的艺术 函数封装 函数的定义和使用 变量的作用域和存储类型 本章小结内容提要内容提要函数定义、函数调用、函数原型、函数返回值
难点:函数的
参数
转速和进给参数表a氧化沟运行参数高温蒸汽处理医疗废物pid参数自整定算法口腔医院集中消毒供应
传递与返回值
全局变量、自动变量、静态变量、寄存器变量
难点:变量的作用域与存储类型
程序调试
结构设计与模块化
代码风格问
题
快递公司问题件快递公司问题件货款处理关于圆的周长面积重点题型关于解方程组的题及答案关于南海问题
程序设计的艺术程序设计的艺术结构化程序设计有两大最高级的艺术
算法设计艺术
结构设计艺术
C语言为程序的结构提供了两样武器
函数和模块
函数(function)是结构设计的最基本单位
“一个程序应该是轻灵自由的,它的子过程就象串在一根线上的珍珠。”
Geoffrey James的《编程之道》 数学函数(1)数学函数(1)自变量因变量函数名数学函数(2)数学函数(2)一个自变量,一个因变量两个自变量,一个因变量数学函数(3)数学函数(3)自变量因变量数学函数(4)数学函数(4)一个变量
二个变量
……
N个变量一个变量
二个变量
……
N个变量自变量与
因变量的关系数学函数(5)数学函数(5)集合A关系集合B程序设计中的函数程序设计中的函数程序设计中的函数不局限于计算
计算类,如打印阶乘表的程序……
判断推理类,如排序、查找……用函数解决问题的要点用函数解决问题的要点分而治之
函数把较大的任务分解成若干个较小的任务,并提炼出公用任务
复用
程序员可以在其他函数的基础上构造程序,而不需要从头做起
信息隐藏
设计得当的函数可以把具体操作细节对程序中不需要知道它们的那些部分隐藏掉,从而使整个程序结构清楚C中的函数(Function)C中的函数(Function)说明:
一个源程序文件由一个或多个函数组成。
一个C程序由一个或多个源程序文件组成。
C程序的执行从main函数开始,调用其他函数后流程回到main函数,在main函数中结束整个程序运行。
所有函数都是平行的,即函数定义时是互相独立的,一个函数并不从属于另一个函数。函数的分类 函数的分类 标准函数,即库函数
如sqrt(),fabs()等
用户自定义函数 函数定义(definition)函数定义(definition)返回值类型 函数名(类型 参数1, 类型 参数2, …… ) { 函数体 return 表达式; }
如果没有参数,则应该用void注明
如果不需要返回值,则应该用void定义返回值类型
返回值类型与return语句配合
当函数执行到return语句时,就中止函数的执行,返回到调用它的地方
函数内部可以定义只能自己使用的变量,称内部变量。
参数表里的变量也是内部变量函数的返回值是通过函数中的 return 语句获得的。
函数参数函数参数函数参数:
形参( 形式参数 ):
在定义函数时,定义函数名后面括号中的变量名
实参( 实际参数 ):
在主调函数中调用一个函数,调用函数名后面括号中的参数(或表达式)int Average(int x, int y)
{
int result;
result = (x + y) / 2;
return result;
}main()
{
int a = 12;
int b = 24;
int ave;
ave = Average(a, b);
printf(……);
}数据传递执行顺序实参形参函数调用(call)函数调用(call)从实参到形参是单向值传递
调用函数时,必须提供所有的参数(且必须是已赋值的)
特例,printf和scanf是采用变长变量表定义的函数,所以变量的个数不固定。
提供的参数个数、类型、顺序应与定义时相同例5.1a 计算两个整数的平均数例5.1a 计算两个整数的平均数/*
函数功能: 计算平均数
函数入口参数: 整型x,存储第一个运算数
整型y,存储第二个运算数
函数返回值: 平均数
*/
int Average(int x, int y)
{
int result;
result = (x + y) / 2;
return result;
}例5.1b 使用了Average函数的main() 例5.1b 使用了Average函数的main() main()
{
int a = 12;
int b = 24;
int ave;
ave = Average(a, b);
printf("Average of%d and %dis%d.\n", a, b, ave);
}关于单向值传递的进一步说明关于单向值传递的进一步说明值传递时,实参与形参分别占用不同的存储单元,在函数内容改变形参的值不会影响实参的值。void swap( int a, int b)
{ int temp;
temp=a;
a=b;
b=temp;
}void main()
{ int x,y;
x=10; y=20;
swap(x,y);
printf(“x=%d,y=%d”,x,y);
}函数调用(call)函数调用(call)有返回值时
放到一个数值表达式中
如
c = max(a,b);
作为另一个函数调用的参数
如
c = max(max(a,b),c);
printf("%d\n", max(a,b));
无返回值时
函数调用表达式
如
display(a,b);函数原型(prototype)函数原型(prototype)调用一个函数之前,先要对其返回值类型、函数名和参数进行声明(declare)
有助于编译器进行类型检查
声明时不要省略参数以及返回值的类型例5.1 例5.1 #include
/*
函数功能: 计算平均数
函数入口参数: 整型x,存储第一个运算数
整型y,存储第二个运算数
函数返回值: 平均数
*/
int Average(int x, int y)
{ int result;
result = (x + y) / 2;
return result;
}
main()
{
int a = 12;
int b = 24;
int ave = Average(a, b);
printf("Average of %d and %d is %d.\n", a, b, ave);
}当返回值为整型或者函数定义在函数调用前面时,可以省略原型例5.1 例5.1 #include
int Average(int x, int y); /*声明Average()函数*/
main()
{ int a = 12;
int b = 24;
int ave = Average(a, b);
printf("Average of %d and %d is %d.\n", a, b, ave);
}
/*
函数功能: 计算平均数
函数入口参数: 整型x,存储第一个运算数
整型y,存储第二个运算数
函数返回值: 平均数
*/
int Average(int x, int y)
{
int result;
result = (x + y) / 2;
return result;
}例1:编写函数isprime(int n)判断n是否为素数,如果是,则返回1,否则返回0。例1:编写函数isprime(int n)判断n是否为素数,如果是,则返回1,否则返回0。例2:编写函数gcd(int m,int n)返回m与n的最大公约数。例3:编写一个函数print()输出如下形式的九九
乘法
99乘法表99乘法表打印九九乘法表a4打印九九乘法表免费下载大九九乘法表免费打印
表。例3:编写一个函数print()输出如下形式的九九乘法表。上机题上机题编写一函数求1!+2!+3!+…+n!的和,n为参数。函数的嵌套调用main()
{
…
…
a();
}a 函数
{
b();
…
return;
}b函数
{
…
…
return;
}①③④⑤⑥⑦②在被调函数中,又调用了函数----嵌套调用函数的嵌套调用循序渐进式编程
实验4:小学生加法考试题 循序渐进式编程
实验4:小学生加法考试题 通过输入两个加数给学生出一道加法运算题输入答案正确: right
错误: Not correct! Try again!循序渐进式编程
实验4:小学生加法考试题 循序渐进式编程
实验4:小学生加法考试题 实验4:小学生加法考试题实验4:小学生加法考试题/* 函数功能: 计算两整型数之和,如果与用户输入的答案相同,则返回1,否则返回0
函数参数: 整型变量a和b,分别代表被加数和加数
函数返回值:当a加b的结果与用户输入的答案相同时,返回1,否则返回0
*/
int AddTest(int a, int b)
{
int answer;
printf("%d+%d=", a, b);
scanf("%d", &answer);
if (a+b == answer)
return 1;
else
return 0;
}void Print(int flag)
{
if (flag)
printf("Right!\n");
else
printf("Not correct!\n");
}实验4:小学生加法考试题 实验4:小学生加法考试题 main()
{
int a, b, answer;
printf("Input a,b:");
scanf("%d,%d", &a, &b);
answer = AddTest(a, b);
Print(answer);
}do{
}
while (answer == 0);实验4:小学生加法考试题 实验4:小学生加法考试题 chance = 0;
do{
chance++;
}while (answer == 0 && chance < 3);main()
{
answer = AddTest(a, b);
Print(answer, chance);
}实验4:小学生加法考试题 实验4:小学生加法考试题 srand(time(NULL));
error = 0;
score = 0;
for (i=0; i<10; i++)
{
a = rand()%10 + 1;
b = rand()%10 + 1;
answer = AddTest(a, b);
Print(answer);
if (answer == 1)
score = score + 10;
else
error++;
}作业作业P195,习题5.5~5.8软件测试软件测试测试
通过运行测试用例找出软件中的Bug
测试的目的
发现更多的Bug
测试人员的主要任务
站在使用者的角度,通过不断使用和攻击,尽量多的找出Bug
如何提高可测试性(tesability)
一条语句写一行才容易测试软件测试软件测试测试的重要性
测试人员水平越高,找到Bug的时间就越早,软件就越容易修复,产品发行就越稳定
越大型的软件开发,测试人员占整个软件产品团队的总人数的比重就越大,甚至一半以上
成功的测试在于发现迄今为止尚未发现的Bug测试只能证明程序有错,不能证明程序无错
——E.W.Dijkstra软件测试方法的分类软件测试方法的分类从代码和用户使用的角度分类
覆盖性测试
从代码特性的角度(即内部)出发的测试
单元测试,功能测试,提交测试,基本验证测试,回归测试
使用测试
从用户的角度(即外部)出发的测试
配置测试,兼容性测试,性能测试,Alpha和Beta测试,强力测试,文档和帮助文件测试软件测试方法的分类软件测试方法的分类第2种分类方法
白盒测试(结构测试)
在完全了解程序的结构和处理过程的情况下,按照程序内部的逻辑测试程序,检验程序中的每条通路是否都能按预定要求正确工作
黑盒测试(功能测试)
不考虑程序内部的结构和处理过程软件测试方法的分类软件测试方法的分类第3种分类方法
手工测试
依靠人力来查找Bug
自动测试
编写一些测试工具,让他们自动运行来查找Bug
优点:快,广泛
缺点:只能检查一些最重要的问题,如内存泄漏、死机等,无法发现一般性的日常错误,而且编写测试工具的工作量很大程序中常见的出错原因程序中常见的出错原因编译错误
指在编译过程中发现的错误,通常属于语法错误,即编写的语句不符合C语言的语法规则。
Undefined symbol ‘xxx’ 标识符xxx未定义
Expression syntax error 表达式语法错误
Too few parameter in call 函数调用时的实参少于函数的形参程序中常见的出错原因程序中常见的出错原因运行错误
指在程序运行时发生的错误
往往是由于语义上的错误造成的,即语句虽然合乎语法,但要求计算机去做不该做或做不到的事情。
例如当用0做除数时,运行后将显示“Division by zero”错误提示信息,并立即返回编辑屏幕。查看运行结果时,可在User screen(用户屏幕)上看到给出的错误提示信息。 程序中常见的出错原因程序中常见的出错原因逻辑错误
程序中有逻辑错时,不影响程序运行并得到运行结果,只是运行结果不正确。比较隐蔽,出错后不易查找。
累加求和运算时,累加和变量忘记了赋初值;
累乘运算时,累乘变量初值赋值为0;
函数声明时的返回值数据类型与实际返回的数据类型不一致,导致自动类型转换
数组下标越界(即超出了定义的范围);
程序中存在死循环; 调试的基本方法调试的基本方法“粗分细找”
定位大致的范围:归纳、推理、二分、排除
缩减输入数据
设法找到能导致失败的最小输入
采用注释的
办法
鲁班奖评选办法下载鲁班奖评选办法下载鲁班奖评选办法下载企业年金办法下载企业年金办法下载
切掉一些代码
减少有关的代码区域,调试无误后再将它们打开注释,即采用分而治之的策略将问题局部化
利用调试工具
逐条语句跟踪
插入打印语句
观看屏幕输出结果程序调试实例 —例5.11 程序调试实例 —例5.11 #include
int Factorial(int x);
main()
{ int x;
while (1) /*无限循环*/
{ printf("Please input x(-1 to quit):");
scanf("%d", &x);
if (x == -1)
break; /*循环出口*/
else
printf("The factorial of %d is %d.\n",
x, Factorial(x));
}
}程序调试实例 —例5.11 程序调试实例 —例5.11 /*
函数功能: 计算x的阶乘
函数入口参数: 整型x
函数返回值: 阶乘运算结果
*/
int Factorial(int x)
{ int i, result;
for (i=1; i<=x; i++)
result *= i;
return result;
}存在错误!!!程序调试实例 —例5.11 程序调试实例 —例5.11 /*
函数功能: 计算x的阶乘
函数入口参数: 整型x
函数返回值: 阶乘运算结果
*/
unsigned long Factorial(int x)
{ unsigned long i, result = 1;
for (i=2; i<=x; i++)
result *= i;
return result;
}关于代码风格问题关于代码风格问题缩进(indent)—保证代码整洁、层次清晰的主要手段int isprime(int n)
{int k,i;
k=sqrt((double)n);
for (i=2;i<=k;i++)
{if(n%i==0) return 0;}
return 1;
}#include
main()
{int i;
for (i=2;i<100;i++)
{if(isprime(i))
printf("%d\t",i); }
}关于代码风格问题关于代码风格问题良好风格的程序应严格采用梯形层次对应好各层次int IsPrime(int n)
{ int k, i;
k = sqrt((double)n);
for (i=2; i<=k; i++)
{
if (n % i == 0)
return 0;
}
return 1;
}#include
main()
{
int i;
for (i=2; i<100; i++)
{
if (IsPrime(i))
printf("%d\t",i);
}
}程序版式程序版式现在的许多开发环境、编辑软件都支持“自动缩进”
根据用户代码的输入,智能判断应该缩进还是反缩进,替用户完成调整缩进的工作
VC中有自动整理格式功能
只要选取需要的代码,按ALT+F8就能自动整理成微软的cpp文件格式Cpp1.cpp命名规则命名规则在Linux/Unix平台
习惯用function_name
本书采用Windows风格函数名命名
用大写字母开头、大小写混排的单词组合而成
FunctionName
变量名形式
“名词”或者“形容词+名词”
如变量名oldValue与newValue等
函数名形式
“动词”或者“动词+名词”(动宾词组)
如函数名GetMax()等 对函数接口加以注释说明 对函数接口加以注释说明 /* 函数功能:实现××××功能
函数参数: 参数1,表示×××××
参数2,表示×××××
函数返回值: ×××××
*/
返回值类型 函数名(参数表)
{ 函数体 return 表达式;
}变量的作用域 变量的作用域 指在源程序中定义变量的位置及其能被读写访问的范围
分为
局部变量(Local Variable)
全局变量(Global Variable )局部变量局部变量局部变量
在语句块内定义的变量
形参也是局部变量
特点
定义时不会自动初始化,除非程序员指定初值
进入语句块时获得内存,仅能由语句块内语句访问,退出语句块时释放内存,不再有效
并列语句块各自定义的同名变量互不干扰 全局变量全局变量全局变量
在所有函数之外定义的变量
特点
在程序中定义它的位置以后都有效
在定义点之前或在其他文件中引用,应该进行如下声明:
extern 类型名 变量名;
从程序运行起即占据内存,程序运行过程中可随时访问,程序退出时释放内存
使函数之间的数据交换更容易,也更高效
但是并不推荐使用,尽量少用
因为谁都可以改写全局变量,所以很难确定是谁改写了它例5.7 例5.7 #include
int global; /*定义全局变量*/
void GlobalPlusPlus(void);
main()
{
global = 1;
printf("Before GlobalPlusPlus(), it is %d\n", global);
GlobalPlusPlus();
printf("After GlobalPlusPlus(), it is %d\n", global);
}
例5.7 例5.7 /* 函数功能:对全局变量global加1,并打印加1之前与 之后的值
函数入口参数: 无
函数返回值: 无
*/
void GlobalPlusPlus(void)
{
printf("Before ++, it is %d\n", global);
global++;
printf("After ++, it is %d\n", global);
}Before GlobalPlusPlus(), it is 1
Before ++, it is 1
After ++, it is 2
After GlobalPlusPlus(), it is 2例5.8 例5.8 #include
void GlobalPlusPlus(void);
main()
{
int global = 1;
printf("Before GlobalPlusPlus(), it is %d\n", global);
GlobalPlusPlus();
printf("After GlobalPlusPlus(), it is %d\n", global);
}例5.8 例5.8 /* 函数功能: 对局部变量global加1,并打印加1之
前与之后的值
函数入口参数: 无
函数返回值: 无
*/
void GlobalPlusPlus(void)
{
int global = 1;
printf("Before ++, it is %d\n", global);
global++;
printf("After ++, it is %d\n", global);
}Before GlobalPlusPlus(), it is 1
Before ++, it is 1
After ++, it is 2
After GlobalPlusPlus(), it is 1变量的存储类型 变量的存储类型 指数据在内存中存储的方式
即编译器为变量分配内存的方式,它决定变量的生存期
动态存储
根据需要临时分配存储空间,离开即释放
静态存储
在程序运行期间分配固定的存储空间不释放程序区静态存储区动态存储区形参、自动变量、函数调用的现场等全局变量、
静态变量自动变量 (auto )自动变量 (auto )“自动”体现在
进入语句块时自动申请内存,退出时自动释放内存
标准定义格式
auto 类型名 变量名;
动态局部变量
缺省的存储类型
不初始化时,值是随机不确定的静态变量(static)静态变量(static)一般的内部变量
在函数退出后失效,再次进入函数,变量值重新初始化
静态变量
在变量类型前面用static修饰
static int i;
变量的值可以保存到下次进入函数,使函数具有记忆功能例5.9 例5.9 #include
/* 函数功能: 打印被调用的次数
函数入口参数: 无
函数返回值: 无
*/
void Func(void)
{
int times = 1; /*自动变量*/
printf("Func() was called %d time(s).\n", times++);
}例5.9 例5.9
main()
{
int i;
for (i=0; i<10; i++)
{
Func();
}
}
Func() was called 1 time(s).
Func() was called 1 time(s).
Func() was called 1 time(s).
Func() was called 1 time(s).
Func() was called 1 time(s).
Func() was called 1 time(s).
Func() was called 1 time(s).
Func() was called 1 time(s).
Func() was called 1 time(s).
Func() was called 1 time(s). 例5.9 例5.9
/* 函数功能: 打印被调用的次数
函数入口参数: 无
函数返回值: 无
*/
void Func(void)
{
static int times = 1; /*静态局部变量*/
printf("Func() was called %d time(s).\n", times++);
}Func() was called 1 time(s).
Func() was called 2 time(s).
Func() was called 3 time(s).
Func() was called 4 time(s).
Func() was called 5 time(s).
Func() was called 6 time(s).
Func() was called 7 time(s).
Func() was called 8 time(s).
Func() was called 9 time(s).
Func() was called 10 time(s). main()函数不变静态变量静态变量静态变量和全局变量都是静态存储类型
自动初始化为0
从静态存储区分配,生存期为整个程序运行期间
但作用域不同程序区静态存储区动态存储区形参、自动变量、函数调用的现场等全局变量、
静态变量寄存器变量(register)寄存器变量(register)寄存器
CPU的内部容量很有限、但速度极快的存储器
使用频率比较高的变量声明为register ,可以使程序更小、执行速度更快
register 类型名 变量名;
register int i;
现代编译器有能力自动把普通变量优化为寄存器变量,并且可以忽略用户的指定,所以一般无需特别声明变量为register null 习题1:写出下面程序的输出结果:
int z=5;
f()
{ static int x=2; int y=5;
x=x+2; z=z+5; y=y+z;
printf (″%5d%5d\n″, x, z);
}
main()
{ static int x=10; int y;
y=z;
printf (″%5d%5d\n″, x, z);
f();
printf (″%5d%5d\n″, x, z);
f();
}null 习题2:写出下面程序的输出结果:
int a1=300,a2=400;
sub1(int x,int y)
{ a1=x;
x=y;
y=a1;
}
main( )
{int a3=100,a4=200;
sub1(a3,a4);
sub1(a1,a2);
printf(″%d,%d,%d,%d\n″,a1,a2,a3,a4);
}
null习题3.写出下面程序的执行结果
#include
int Square(int i)
{ return i*i;
}
main()
{ int i=0;
i = Square(i);
for ( ; i<3; i++)
{ static int i=1;
i += Square(i);
printf("%d,",i);
}
printf("%d\n", i);
}习题5.5习题5.5计算
复用Factorial函数的代码
unsigned long Factorial(unsigned int number);
main()
{ unsigned int m, k;
unsigned long p;
printf("Please input m, k:");
scanf("%u, %u", &m, &k);
p = Factorial(k) / Factorial (m-k);
printf("p=%lu\n", p);
}模块化程序设计方法模块化程序设计方法什么时候需要模块化?
某一功能,如果重复实现3遍以上,即应考虑模块化,将它写成通用函数,并向小组成员发布
要尽可能复用其它人的现成模块。模块化程序设计方法模块化程序设计方法功能分解
自顶向下、逐步求精的过程
模块分解的原则
保证模块的相对独立性
高聚合、低耦合
模块的实现细节对外不可见
外部:关心做什么
内部:关心怎么做模块化程序设计方法模块化程序设计方法设计好模块接口
接口是指罗列出一个模块的所有的与外部打交道的变量等
定义好后不要轻易改动
在模块开头(文件的开头)进行函数声明函数设计的原则函数设计的原则函数的功能要单一,不要设计多用途的函数
函数的规模要小,尽量控制在50行代码以内
1986年IBM在OS/360的研究结果:大多数有错误的函数都大于500行
1991年对148,000行代码的研究表明:小于143行的函数比更长的函数更容易维护函数设计的原则函数设计的原则参数和返回值的规则
参数要书写完整,不要省略
对函数的入口参数进行有效性检查
没有参数和返回值时,用void填充
每个函数只有一个入口和一个出口,尽量不使用全局变量
尽量少用静态局部变量,以避免使函数具有“记忆”功能模块和链接 模块和链接 将一个程序分解成若干个模块,分别放在几个源文件中,形成一个项目文件(.prj )(Project)
然后,对每一个源文件(.c)分别单独进行编译
再将它们的目标代码(.obj )连同标准函数库中的函数链接在一起,形成可执行文件(.exe)。
模块之间通过互相调用函数联系起来
头文件(.h)是联系的纽带 模块和链接模块和链接例5.10可以不看
将习题5.5修改成
1个.h头文件(X5-5-1.h)
2个.c源文件(X5-5-1.c,X5-5.c)
1个.prj项目文件(X5-5.prj)
由所有源程序文件组成
X5-5-1.C
X5-5.C
参见实验指导书第133页模块和链接 模块和链接 优点:
当一个文件的代码被修改后,不必对所有程序重新编译,从而节省了程序的编译时间。
使程序更宜于维护,给多个程序员共同编制一个大型项目的代码提供了方便手段。 这一章我们学习了这一章我们学习了函数的定义、调用
变量的作用域、存储类
自动变量(auto)
外部变量(extern)
静态变量(static)
寄存器变量(register)
程序调试方法