目录:
1.函数的概念:
1)函数是将一行或多行的代码组织在一起,可以实现一定功能的程序。函数在C/C++语言中也叫做子程序,在JAVA等一些面向对象的语言中也叫方法。
2)所有的高级语言中都有函数或子程序这个概念,一个较大的程序一般应分为若干个程序块,每一个模块用来实现一个特定的功能。不能让main函数做几千几万行代码的事,要建立不同的函数来分工处理任务。
2.函数的作用:
a)如果把main函数比作公司的总经理,那么其他函数经理下面的部门经理或员工;
b)不能让main函数做几千几万行代码的事,要建立不同的函数来分工处理任务;
c)一般情况下,一个程序内的全部函数之间是以树状相互调用的。就如同一个公司的总经理安排工作要调用部门经理,部门经理再调用部门的员工等,层层调用到达树状结构的枝与叶;
d)函数的可以让代码更加容易维护,就如同文章中有各个不同段落;
e)函数)让代码重用性高,比如可以对同一个函数多次调用,不用多次编写相同的代码。
下面展示 相关代码片
。
[例6-1]不调用函数:先求2的5次方,再求3的4次方,再求x的y次方
#include<stdio.h>
int main()
{
int i = -1;
int x = 2,y=5,n=1;
while(++i<y)
n*=x;
printf("%d的%d次方是:%d\n",x,y,n);
i=-1,x=3,y=4,n=1;
while(++i<y)
n*=x;
printf("%d的%d次方是:%d\n",x,y,n);
printf("请输入任意两个整数(以空格间隔):");
scanf("%d%d",&x,&y);
i=-1,n=1;
while(++i<y)
n*=x;
printf("%d的%d次方是:%d\n",x,y,n);
return 0;
}
a)先用口算或笔算方式算出代码中三个变量运算后的结果,观察与程序打印的结果是否一致;
b)请思考:本例中有三段循环代码,要是实现的功能是否相似?有什么办法可以减少重复的代码?
3.函数的定义
为了解决上一节的例程中,重复代码过多的问题,我们来学习函数的定义:
要定义一个函数,一般要先思考清楚这个函数要实现怎样的功能。
经过对上一节中例程代码的思考之后,我们希望做这样一个函数:
只要将任意两个数值(m代表底数,n代表指数)带入这个函数之后,这个函数就能自动算出m的n次幂的结果。
图6-1 函数的定义
所谓定义一个函数,就是要设计和编写一个函数的意思。
一个完整的函数定义,包括函数头和函数体两部分。函数头就是函数大括号上方的第一行,函数体就是大括号内的一行或多行的代码。
一.参数列表的作用
1)根据程序设计者的需要,一个函数的参数列表中可能没有参数,也可能有1个、2个或者多个参数。
2)如图6-1所示,power函数中包含2个参数nNumb和nPow。
3)实际这2个参数和nRes一样,也是power函数内的两个int类型的临时变量。
4)所不同的是在程序进入函数之前,参数列表中的两个变量已经被建立好。并且在程序进入函数之前,主调函数必须要给这两个参数传入对应的数据,用于在power函数内参与计算。
5)参数传递的过程可以比喻为:
公司的经理如果要让财务部计算出A员工本月应发工资是多少,就必须先要给财务部提供A员工的按月工资是多少,还要提供这个员工本月出勤与请假的天数。这样财务才能算出这个员工的本月应发工资是多少,否则如果不给必要的数据财务部也是束手无策的。
如果经理又要财务部计算出B员工本月应发工资是多少,那他就必须要提供给财务部B员工的月工资额和本月出勤天数;如果经理要计算C、D、E…员工的应发工资,那他都必须要提供员工的数据给财务部门,财务部门才能为他计算出所需的结果。
下面展示 相关代码片
。
[例6-2]调用函数:先求2的5次方,再求3的4次方,再求x的y次方
#include<stdio.h>
int power(int nNumb, int nPow)
{
int nRes = 1;
while (nPow--)
nRes *= nNumb;
return nRes;
}
int main()
{//先求2的5次方,再求3的4次方,再求m的5次方。
int x = 0,y=0;
int n = power(2,5);
printf("%d的%d次方是:%d\n",2,5,n);
x = 3,y=4;
n = power(x,y);
printf("%d的%d次方是:%d\n",x,y,n);
printf("请输入任意两个整数(以空格间隔):");
scanf("%d%d",&x,&y);
printf("%d的%d次方是:%d\n",x,y,power(x,y));
return 0;
}
a)先用口算或笔算方式算出三次调用power函数后的打印结果,再观察是否与程序打印的结果一致;
b)在调试模式下按F10快捷键单步执行,在程序运行到了main函数中的power函数调用处,一定要按F11快捷键让程序进入到power函数内部;
c)在进入到power函数时,一定要先观察函数内的三个变量内的数值是多少?(包括:2个参数变量nNumb和nPow,以及1个函数内临时变量nRes) ;
d)在每次进入power函数之后,都继续按F10快捷键单步跟踪函数内的循环过程,认真观察程序在函数内执行每一步运算之后变量内的数据变化情况。
注意:一般C语言编译器大多都是F10快捷键代表逐过程(Step Over),F11快捷键代表逐语句(Step Into)。
VS系列编译器以及**早期VC++**各个版本编译器都是如此,如果是你用的是比较特殊的C语言编译器(例如:苹果电脑的xCode等),那你就只能自己多学习一下该编译器的相关调试快捷键了。
二.返回值的类型
按照返回值类型来划分,函数可以分为:
有返回值的函数 无返回值的函数 :两个种类。
1)、有返回值的函数的特点是:
a)函数的返回值类型是具体的类型(如:int、short和double等),而不是void类型;
b)有返回值类型的函数内,必须有return语句用于返回运算结果给主调函数;
c)有返回值类型的函数内,如果没有return语句的话代码编译时会出错。
d)有返回值类型的函数返回后,往往会把返回值传递给一个变量保存起来。
2)、无返回值的函数的特点是:
a)函数的返回值类型是void类型,而不是其他任何具体的类型;
b)无返回值类型的函数内,即使没有return语句编译也不会出错;
c)无返回值类型的函数内也可以有return语句,但return之后也不附带任何变量或表达式;
d)主调函数在调用无返回值类型的函数时,不允许使用变量或表达式来接收被调函数的返回值。
下面展示 相关代码片
。
[例6-3]测试无返回值函数
#include<stdio.h>
int power(int nNumb, int nPow)
{//有返回值函数
int nRes = 1;
while (nPow--)
nRes *= nNumb;
return nRes;
}
void display(int nNumb,int nPow)
{//无返回值函数
int n = power(nNumb,nPow);
printf("%d的%d次方是:%d\n",nNumb,nPow,n);
}
int main()
{//先求2的5次方,再求3的4次方,再求m的5次方。
int x = 0,y=0;
display(2,5);
display(3,4);
printf("请输入任意两个整数(以空格间隔):");
scanf("%d%d",&x,&y);
display(x,y);
return 0;
}
a)先用口算或笔算方式算出三次调用display函数后的打印结果,再观察是否与程序打印的结果一致;
b)在调试模式下按F10快捷键单步执行,在程序运行到了main函数中的display函数调用处,一定要按F11快捷键让程序进入到display函数内部;
c)在每次进入display函数运行到power函数调用处时,一定要再按F11键让程序进入到power函数内部;
d)在进入到power函数时,一定要先观察函数内的三个变量内的数值是多少?(包括:2个参数变量nNumb和nPow,以及1个函数内临时变量nRes) ;
e)在每次循环结束之后观察nRes变量内的数值是多少,离开power函数回到display之后,观察n变量内的数值多少。
三.主调函数与被调函数
首先:
1)大家必须明确一个问题:编写一个函数的目的,就是要用来被其他程序或函数调用的。
2)如果一个函数编写好了,却未被任何程序或其他函数调用的话,那么这个函数就是一段废弃的代码。正如一个人活着就必须对社会有可利用的价值,否则这个人就是一个废人。
3)主调函数与被调函数是相对而言的。
在[例6-3]中,相对于power函数来看,
display函数是主调函数,
power函数是被调函数。
4)相对于main函数,display函数则是被调函数,而main函数是主调函数。
4)main函数在一个软件工程中,是所有其他函数的最终调用者。
1*可以把main函数比作是一棵大树的根,
其他函数则是大树的枝和叶。
2*也可以把main函数比作是公司的总经理,
其他函数则是部门经理或部门员工。
3*程序执行的过程是从总经理开始,
总经理调用部门经理,
部门经理再调用其他员工等等。
4*表面上看,main函数只有调用其他函数,
而没有被其他任何函数调用。
实际上,在每个软件工程中,
main函数都是被操作系统调用的。
因此,我们把main函数称之为主函数或入口函数。
四.return关键字的开发技巧
1)在有返回值函数中,return关键字主要是负责向主调函数送回本函数的计算结果。
2)在无返回值函数中,return关键字主要是让函数在运行的中途结束,不负责向主调函数送回计算结果。
3)在实际开发中,当return语句与分支和循环语句结合时,有很多种开发的技巧。
4)本文所介绍的各种return语句的开发技巧,小白们应该认真学习和牢记,以便在以后的开发中使用最好的编程技巧。
5)当然,即使现在不牢记这些代码的编写技巧,当你在参与实际程序开发工作时间久了之后自然也会领略到的。
下面展示 相关代码片
。
[例6-4]判断一个数字是否为素数(是返回1,否返回0)
#include <stdio.h>
#include <math.h>
int IsPrime(int n)
{
int i = 2;
int m = (int)sqrt(n)+1;
while (i < m )
{
if (n%i == 0)
break;
++i;
}
if(i >= m)
return 1;
else
return 0;
}
int main()
{
int n = 0;
printf("请输入任意一个数字:");
scanf("%d",&n);
if(IsPrime(n))
printf("%d是素数\n",n);
else
printf("%d不是素数\n",n);
return 0;
}
a)任意输入一个素数或者非素数的数字,之后观察程序打印的结果是否正确;
b)在调试模式下按F10快捷键单步执行,在程序运行到了main函数中的IsPrime函数调用处,一定要按F11快捷键让程序进入到IsPrime函数内部;
c)在进入到IsPrime函数时,观察函数内包括形式参数在内的三个变量内的数值是多少?
d)在循环结束之后观察i与m变量内的数值是多少,预估回到main函数之后将如何继续运行?
请思考:
IsPrime函数中的多个return语句是否可以简化为更简单的代码?
结论:
我们对[例子6-4]中的IsPrime函数进行改造,可以得到很多种更简单的代码格式。
下面展示 相关代码片
。
[改造代码-A]带return语句的双分支语句,else关键字可以省略
int IsPrime(int n)
{
int i = 2;
int m = (int)sqrt(n)+1;
while (i < m )
{
if (n%i == 0)
break;
++i;
}
if(i >= m)
return 1;
return 0;
}
按照以上代码修改[例6-4]的代码之后,编译、运行并查看程序运行结果是否与原来相同。
请思考:
是否可以将以上3行或4行的return语句合并为一行代码?
下面展示 相关代码片
。
[改造代码-B]使用三目的条件运算符将return语句合并为一行代码
int IsPrime(int n)
{
int i = 2;
int m = (int)sqrt(n)+1;
while (i < m )
{
if (n%i == 0)
break;
++i;
}
return i >= m?1:0;
}
使用以上的三目条件运算符再次修改代码,编译、运行并查看程序运行结果是否与原来相同。
请思考:
是否可以将以上的条件运算符也去掉,用更简单的一行代码实现与原来相同的程序效果?
下面展示 相关代码片
。
[改造代码-C]判断型函数直接返回比较结果
int IsPrime(int n)
{
int i = 2;
int m = (int)sqrt(n)+1;
while (i < m )
{
if (n%i == 0)
break;
++i;
}
return i >= m;
}
再次修改代码,直接将比较结果返回,编译、运行并查看程序运行结果是否与原来相同。
注意:比较运算符的运算结果本来就是1和0(真是1,假是0)。
因此,今后再遇到判断型函数时,可以考虑直接将比较运算符的结果返回。
请思考:
是否可以将以上的比较运算符也去掉,用更简单的代码实现与原来相同的程序效果?
下面展示 相关代码片
。
[改造代码-D]用return替代break语句,让程序更简单更直观
int IsPrime(int n)
{
int i = 2;
int m = (int)sqrt(n)+1;
while (i < m )
{
if (n%i == 0)
return 0;
++i;
}
return 1;
}
再次修改代码,用return替代break语句,编译、运行并查看程序运行结果是否与原来相同。
注意:
1)在中途结束的循环程序中,使用return语句代替break,往往可以让程序更简单更直观。
2)break关键字让循环结束,继续执行循环之后的代码。return是比break更强的中断关键字,不但循环会立即终止,而且当即离开当前函数。
五.形式参数与实际参数
1.形式参数(parameter) (简称形参),就是在定义函数时在函数头的参数列表内的参数。
2.在C语言中,形参可以认为是被调函数内,用来接收主调函数传递数据的临时变量。
1.实际参数(argument)(简称实参),在主调函数中调用一个函数时,函数名后面括弧中的参数,实参可以是常量、变量、表达式或者有返回值的函数等。
2.实参的个数必须与被调函数的形参个数一致,并且每个参数的类型也尽量一致。简单地说,实参与形参在个数和类型上,必须要一一对应的,否则编译的时候就可能会出现错误。
下面展示 相关代码片
。
[例6-5]多种不同形式的函数编写(参数不同、返回值也不同)
#include <stdio.h>
#include <math.h>
int IsPrime(int n)
{
int i = 2;
int m = (int)sqrt(n)+1;
while (i < m )
{
if (n%i == 0)
return 0; //不是素数
++i;
}
return 1; //素数
}
void PrintPrime(int x, int y)
{
int i = x;
int sum = 0;
printf("%d到%d之间的素数包括:\n", x, y);
while (i < y)
{
if (IsPrime(i)) //等价于if (IsPrime(i) != 0)
{
printf("%d ", i);
++sum;
}
++i;
}
printf("\n总共有%d个素数\n\n", sum);
}
int main()
{
PrintPrime(10, 80);
PrintPrime(100, 200);
PrintPrime(280, 520);
return 0;
}
a)在main函数中,对PrintPrime函数代入0个、1个或多个参数再编译,观察是否出错,例如:PrintPrime();
b)在main函数中,对PrintPrime函数代入浮点数再编译,观察是否出错。例如:PrintPrime(10.88,80.01);
c)在PrintPrime函数中,对IsPrime函数的调用处,代入2个或2个以上的参数再编译,观察是否出错;
d)在main函数中,将PrintPrime函数的返回值赋值给一个变量n再编译,观察是否出错。
结论:
a)被调函数的形参为主调函数提供了调用模板,主调函数必须依照形参的个数和类型调用被调函数;
b)如果实参的个数与形参不同编译会出错,如果实参的类型与形参不同编译也可能会出错。例如:实参是不同类型的数组或指针(下一章的内容);
c)当形参是整数或浮点数,如果实参的类型小于形参时编译也不出错。如果实参的类型大于形参时(例如:形参是整数实参是浮点数),编译会出现警告(warring);
d)调用返回值是void类型的函数时,如果将返回值打印或赋值给其他变量,编译时会出错。
六.嵌套循环与函数:
1)嵌套循环就是指在一个循环体语句中又包含另一个循环语句。
2)当一次外层循环循环执行一次,内层循环要执行0到多次。
3)每次内层结束后,外层循环再继续执行下一次循环。
4)使用嵌套循环时,内层循环和外层循环的循环控制变量不能相同,否则容易造成内外循环层的混乱。在设计嵌套循环时,尽量优先编写完内层循环之后再编写外层循环,这样思路就会比较清晰。
下面展示 相关代码片
。
[例6-6]将上一节的两个函数合并为一个函数(嵌套循环)
#include <stdio.h>
#include <math.h>
void PrintPrime(int x, int y)
{
int n = x;
int sum = 0;
printf("%d到%d之间的素数包括:\n", x, y);
while(n < y)
{
int i = 2;
int m = (int)sqrt(n)+1;
while (i < m)
{
if (n%i == 0)
break;
++i;
}
if(i==m)
{
printf("%d ", n);
++sum;
}
++n;
}
printf("\n总共有%d个素数\n\n", sum);
}
int main()
{
int x,y;
while(1)
{
printf("请输入两个正整数[-1代表退出]:");
scanf("%d",&x);
if(x==-1)
break;
scanf("%d",&y);
PrintPrime(x,y);
}
return 0;
}
结论:
a)嵌套循环的运行速度比独立函数快,因为节省了进入函数和离开函数(入栈与出栈)耗费的时间;
b)独立函数的代码清晰直观,便于程序员对代码阅读和维护,嵌套循环代码不容易阅读和理解;
BY 白巾-子木