函数总结

前面讲了很多函数的内容,今天再梳理一遍,同时增加一些内容

1、再认识一下函数,比如

我们以前编写的函数框架,一直采用这样的结构。

c语言函数学习方法(C语言自学函数说明5)(1)

这就是我们前面写的C程序代码,也是一个完整的程序,有好几部分代码都是固定的,每次书写的时候都是不变的。

我们再来分析一下一般的一个C语言程序逻辑结构

在编写一个程序的时候,一般要考虑这几部分

A、数据变量的定义,也就是程序中任何地方用到的变量都必须先定义

B、变量的数据提供,也就是变量要有值来参与运算

C、业务处理部分,也就是实现函数功能的代码部分

D、结果的输出,也就是,程序运行后呈现出来的结果

那好了,这就是一个最直接的C语言函数,以及它的格式,与整个程序结构的逻辑划分。

问题是,一个大型的程序,需要根据功能进行分解,也可能需要很多的人员进行合作完成,如果把所有的代码都写在一个main函数里,会带来很多问题与麻烦,比如多人合作,每个人写的代码合并到一起后,如果出现变量重名就会报错,另外程序逻辑出现错误后,检查错误是个非常大的任务。

所以,为了代码的功能独立,模块的划分,代码的复用与移植,功能的增加与删除,减轻维护与纠错的压力,需要对项目的功能进行模块划分,而C语言程序中函数就是划分模块的单元,所以我们一般将功能独立的代码放到一个函数中,将一个完整的程序分解成多个相对独立的函数,在一个文件中我们将来看到就是一个一个独立的函数框架!

以前我们编写程序就只有一个main函数,现在我们就可能要编写成多个函数了,另外,因为一个C语言程序文件只允许有一个入口函数,就是main函数,那同一C程序文件中其他的函数就必须另外起名字了,当然,一个程序文件中有main主函数,那这个文件就可以编译链接运行了,如果一个程序文件中没有main主函数,只是一些自定义的函数,那就相当于没有入口,这些自定义函数只能编译链接,不能运行,所以就出现了这些自定义函数必须被调用的情况!所以,目前我们写程序的时候就必须是写成一个主函数main和多个自定义函数,什么时候可以只写自定义函数,而不用主函数呢?那就是你能给别人干活了,项目组分给你的任务就是做某个模块,到那个时候你就不需要管main主函数了。

好了,现在理解了吗?

接下来,我们过一遍函数的编写,以回文程序为例

还是刚才这个回文程序。

c语言函数学习方法(C语言自学函数说明5)(2)

现在的状况是函数的整体功能都在一个主函数里面,那我们按照函数的功能独立,重新将这个功能部分做成一个自定义函数,这一部分就不在main函数里面了,让main函数解放。

看下面

c语言函数学习方法(C语言自学函数说明5)(3)

能对比出来吗?这是不是某人说的,太简单了,就是送分题呢?

你看,我就没有做过多更改,本来以前编写的主函数就是完整的程序,只不过,我做了一个功能划分,只是这个功能划分呢,不是很彻底。

后面我们一点一点理解。

至少你能看到有些地方不同,有些似乎有关系一样。

c语言函数学习方法(C语言自学函数说明5)(4)

那现在,我们就把上面的这种形式叫做无参数,无返回值函数格式,这个需要记住的!这是死格式,没啥可以解释的。

特点就是你在定义函数的时候,

c语言函数学习方法(C语言自学函数说明5)(5)

前面必须是void开头,后面()里面不能有任何东西,在主函数(或者别地调用这个函数的函数)里面直接写函数名();这个格式就行。

可以理解了吗?

理解的话,继续在它上面变形。

现在的问题是上面说的,功能独立不彻底,啥意思呢,假如按照我原来的设想

void HW( )

{

}

这个函数的功能是谁给我一个值,我就能帮助谁判断出来这个值是不是回文,而不是让函数自己随意输入一个值判断去,而是有针对性的。

所以,代码应该继续简化去杂。

看下面

c语言函数学习方法(C语言自学函数说明5)(6)

继续对比,

c语言函数学习方法(C语言自学函数说明5)(7)

c语言函数学习方法(C语言自学函数说明5)(8)

通过对比,是不是现在的函数里我删掉了num变量以及给num变量输入值的语句?

这就是去掉不应该有的功能了,也就是HW函数你是帮别人判断是不是回文的,而不是,你自己输入一个值自己判断,所以,去掉这两个无用的。

那现在呢?你把num变量和给num输入值的scanf("%ld", &num);语句删了,那num在后面要用啊,怎么办?

好,那首先你要知道num在原来程序中所起的作用,是不是就是被判断的那个变量呢,这个变量的值原来通过键盘输入,那现在呢,我不允许你再通过键盘输入,那根据HW函数要被修改的意思,是,你帮助别人来判断的,那意思就是这个num是要被重新规划了,这个num的值应该是让别人来给,别人给你提供,你才可以把这个值带入进去,对这个值进行判断。

那这个值咋办?这个num就要被开放,作为别人调用HW函数的中间连接通道,那这个num怎么定义,放在哪里呢?

c语言函数学习方法(C语言自学函数说明5)(9)

我们把num的定义呢,不再放在程序里面定义,而是放到了函数名后面的()里面了。

代码稍做了一点修改,就是去掉代码里面的num,以及对num的输入语句。然后函数的主要功能并没有发生变化,区别就是这个修改后的HW函数,不再具有输入功能了,是不能随便输入一个值的,对这个值进行判断的了。这个函数就变成了一个需要给()里面定义的num传过来一个值,然后才能判断,没人调用它,不给它传值,那它就是个架子,空壳。

好了,那现在说,让main函数调用调用它,看能不能用呢。

可以,

那main函数怎么调用?

根据HW函数功能要求,需要main函数(通用点,叫调用函数,主调函数等名字)必须给它传一个值,送给定义要求的num,然后num才能带进去帮你判断。

那主函数要调用它,怎么给它传呢?

主函数要传的那个值,在哪里?

没有?

没有你就自己定义啊!

所以,

c语言函数学习方法(C语言自学函数说明5)(10)

主函数里面为了给HW传一个值,就要解决定义一个变量,然后给变量提供值,(可以直接给值,也可以通过scanf函数形式,在键盘输入值),然后再传给HW函数。

当然,你也可以直接给值,比如

c语言函数学习方法(C语言自学函数说明5)(11)

都是可以的。那现在这个函数与前面的函数就有了区别了,区别就是()里面写东西了,我们把这种形式,叫做有参数,无返回值函数格式。

那现在你能理解这种格式了吗?

理解了那继续往下。

这个函数呢,对传过去的值进行判断后,是直接把结果输出来了,比如

c语言函数学习方法(C语言自学函数说明5)(12)

这里,也就是对回文数的判断呢,在HW函数里直接给出了,那我现在的意思呢,不允许你在这个HW函数里直接给出结果,而是要把判断的状态(结果)返回给主函数,由主函数来根据HW函数返回的结果去判断主函数所传的这个值是不是回文,那这个怎么办?

那就是说,HW函数需要给main函数返回一个表示被传过来要判断的值是不是回文的状态,那这个状态怎么设计,那我可以设计成用整数1代表是回文,用0代表不是回文,当然,你也可以设计成用字符‘Y’代表是回文,用‘N’代表不是回文。现在修改程序。

先修改HW函数

c语言函数学习方法(C语言自学函数说明5)(13)

看,我修改了两个红色框的,以及两个绿色框的。

红色框相当于一对,必须一起搭配使用,比较前面,如果使用的是void,那是不是就没有后面的那个return语句。

或者改成

c语言函数学习方法(C语言自学函数说明5)(14)

这次是改成字符了,并没有多大区别的。

这次是先把HW函数进行了修改,现在的格式就成了HW函数既有返回值,也有参数的格式。

能理解了吗?

那我再看调用函数咋办?

现在在主函数里面呢,如果调用HW函数,这边传过去一个值,那边能得出这个值是回文或者不是回文呢?怎么能知道结果呢。

HW函数只给它带回来状态,并没有直接给出是回文不是回文的结果。

那想要这个结果,main自己处理吧,根据HW给它带回来的状态结果自己判断去。

好,现在修改main函数。

c语言函数学习方法(C语言自学函数说明5)(15)

看,main函数通过调用HW对123321要进行判断,而HW函数则只给main返回来状态值,那main函数需要再对这个结果值进行处理,最终得出是回文还是不是回文的结果。

理解了吗?

当然,也可以这样做,都是很灵活的。

c语言函数学习方法(C语言自学函数说明5)(16)

在main函数里先定义一个变量,由这个变量来接收调用HW函数返回来的值,然后再利用这个值进行判断,得出回文与不是回文的结果。

好了,用字符’Y’与’N’做状态的,其实是一样的。代码略。

那函数的这部分理解了吗?

补充,变量的作用域范围

现在程序用函数来编辑后,程序中的函数功能就被划分成几块了

那免不了会出现你定义变量同名的情况。

比如

c语言函数学习方法(C语言自学函数说明5)(17)

我在主函数里面定义了一个num,也在HW函数里()中定义了一个num,这两个一模一样,冲突吗?

不冲突!这涉及到函数中变量的作用域,就是这个变量从定义开始所能使用的范围。

一般地,在一个函数内,不允许定义同名的变量,即使类型不一样也不行,但是允许在不同的函数内定义同名的变量。原则上,在一个函数内定义的变量只在这个函数内起作用,跳出了这个函数{ }包围圈就不起作用了

所以,

c语言函数学习方法(C语言自学函数说明5)(18)

这两个num各在各的定义范围内起作用,互不影响,它们是两个变量,如果你理解不了,你可以定义成不同的变量,免得糊涂。

比如前面这样

c语言函数学习方法(C语言自学函数说明5)(19)

有返回值,无参数格式不讲解了,都类似的。

可以认为,函数说明2里面给你举的四种情况,各种设置都是搭配使用的,原则都是任意组合的,并不是一定固定的。

上面的例子,在设计函数的参数时,全部用的是普通变量,普通变量在作为参数进行传递的时候,是当作值来用的,也就是调用函数只是把变量(参数,实际参数)的值复制一份,传送给对应被调用函数的()里的变量(参数,傀儡参数,形式参数,样子参数),相当于给()里的变量(参数,傀儡参数,形式参数,样子参数)赋值,提供值的作用。也相当于替代了用其他方式给变量值。

这种用普通变量的传递方式,我们把它叫做函数间的传值调用!传值调用原则上一个变量只能传递一个值,如果传递多个值,那就必须定义多个变量。比如

void HW(int n, int m, char k, float g)

每个变量都要单独定义,相互之间用逗号隔开。

那另一种方式呢,传地址调用是啥呢?

是这样,比如,我这个主调用函数里有一个数组,现在想呢,需要定义一个自定义函数,目的是可以直接修改主调用函数里的那个数组的单元值。如果按照传值的方式,存在这样的问题,一是传值必须是一个一个变量传,那这个数组如果有很多单元格,那传值的参数需要定义多少个变量才够用呢?二十,传值的话,变量的作用域出不了函数的{ }范围,出了{ }范围就失效了。那这达不到跨函数修改数组的目的。那怎么办?

那这就是传地址的作用!

既然是传地址,传地址,那就需要理解地址,以及如何在函数中表示地址,以及定义所谓的地址。

看程序,先学习数组的地址表示形式。其他以后再学。

c语言函数学习方法(C语言自学函数说明5)(20)

这里呢,我定义了一个生成随机数的自定义函数void rand_num(int num[]),然后呢,()里面定义的是一个一维数组int num[],并且[]里面没有给数字,是空的。先认识下哦,紧接着在main函数里进行了调用。只不过,调用的时候给()里用的是在main里面定义的一个一维数组名sorce_num,那这个就要好好认识哦,我们说数组名代表整个数组空间的起始地址,是一个常量,不能更改,只能用。那现在这里就是用数组名做实参传递的,而自定义函数的形参呢,也对应是定义成数组格式,相互对应,一对一。我们把这种直接用数组名传递的方式叫传地址方式。除了数组外,传地址方式还有其他的,以后再学。

如何解释:

这样,首先定义的时候,这两个数组各自在各自的函数里,与前面讲的一样,原则上它的起作用的范围在函数的{ }内,这是正确的,没问题,也就是出了它的{ }范围不起作用的。但是,特殊的就在它反而在它的起作用范围内能操作它的范围之外的空间的单元,那你就要理解为啥它能这么干了。这就是这两个数组虽然定义在不同的地方,甚至起作用的范围也有限制,但是他们的操作地址是合并在一起的,也就是两个数组操作的是同一个空间。

这就是使用数组名进行传递,同时定义数组进行接收,接收的不是值,而是传过来的数组的地址,然后操作的时候,就操作的是这个地址的空间单元,自然,相当于这两个数组是重合的。

如果不好理解,让大家看下,现在很多人使用手机传递wps在线文档,然后你在你的手机上操作这个文档,别人在别人的手机上也操作这个文档,所有人操作的都是同一个文档。

c语言函数学习方法(C语言自学函数说明5)(21)

增加一点,看能不能看懂?

c语言函数学习方法(C语言自学函数说明5)(22)

运行结果

c语言函数学习方法(C语言自学函数说明5)(23)

看看是否能理解?为什么前面两个是0?

函数的调用类型

前面介绍的各种调用形式,都是这样的

在一个C程序文件中,有一个main函数,有多个用户自定义函数,而且都是自定义函数被main函数调用的,我们把这种不同函数之间的调用,叫做它调用,一般的,大部分是这种,比如,给你的那个月历函数程序就是典型的它调用形式。

#include "stdio.h"

#include "stdlib.h"

//下面定义三个自定义函数

//函数1

//判断闰年的函数,返回一个旗帜值

int is_leap_year(int year_1)

{

int flag=0;

if(((!(year_1%4))&&(year_10!=0))||(!(year_1@0)))

flag=1;

//printf("flag:%d\n",flag);

return flag;

}

//函数2,在函数2里面要调用函数1

//设定每个月的天数

//默认先设置天数为最小28天,然后根据不同的月份调整天数

//这个函数中需要用到一个year和一个month,year是为了调整二月份天数而设定的,因为要判断是否是闰年

//month是对不同的月,天数不一样而要求的,因为必须对不同月进行判断

int every_month_day(int year_2,int month_2)

{

int month_day=28;

//这里开始用到month变量,对固定月份分别判断调整天数

if(month_2==1||month_2==3||month_2==5||month_2==7||month_2==8||month_2==10||month_2==12)

month_day=31;

if(month_2==4||month_2==6||month_2==9||month_2==11)

month_day=30;

//这里需要用到year这个变量对二月份进行判断调整天数

if(is_leap_year(year_2))

month_day=29;

//printf("month_day:%d\n",month_day);

return month_day;

}

//函数3,实现打印月历功能,不增加星期对应功能

void print_month(int year_3,int month_3)

{

int i,day;

//这个day是调用函数2来得到对应年月的那个月份天数

//在函数3里面接收到的year与month接着又传给函数2了

day=every_month_day(year_3,month_3);

//有了具体day,那我就可以打印月历了。哈。

printf("\n\n%d年%d月的月历:\n",year_3,month_3);

printf("日 一 二 三 四 五 六\n");

printf("==========================\n");

for(i=1;i<=day;i )

{

printf("d ",i);

if(!(i%7))

printf("\n");

}

printf("\n");

}

//下面是主函数

void main()

{

int main_year,main_month;

printf("请输入一个年份值:");

scanf("%d",&main_year);

printf("请输入一个月份值:");

scanf("%d",&main_month);

//主函数只需要调用打印月历函数就行,不调用判断闰年和调整月份天数

print_month(main_year,main_month);

getchar();

}

结构上是

主函数main ----->(调用)函数3----->(调用)函数2----->(调用)函数1

主函数的级别比较高,不能被用户自定义函数调用,是被电脑的操作系统调用的。

查看书上或者网上的说明。其实main函数的格式是这样的

int main(int argc,char* argv[])

也是带参数或者有返回值的,只不过,我们平时用不到,没有到控制台让系统调用main函数。

main函数是在程序装到内存之后由操作系统调用的。操作系统调用的时候会传递两个参数进去,一个是argc,一个是*argv[],其中argc代表参数个数,这里我需要解释的是程序名本身也是参数,所以在命令行下无参数运行时操作系统会给main第一个参数传值1;一个参数会传值2;argv代表参数字符串,argv[0]代表程序名本身,argv[1]代表程序接收的第一个参数,以此类推!你只要知道main函数是由操作系统调用就好。

除此外,还有一种情况

是自己调用自己。这种叫自调用。

c语言函数学习方法(C语言自学函数说明5)(24)

再看一个

c语言函数学习方法(C语言自学函数说明5)(25)

c语言函数学习方法(C语言自学函数说明5)(26)

很好玩哦,本来定义的一个函数功能,在实现的代码中居然还要用到自身这个函数,那就要好好理解了哦

从数学算式上理解

比如10!=1*2*3*4*5*6*7*8*9*10,

那在这个中你看1*2*3*4*5*6*7*8*9这一串串是不是本身就是9!呢,那意思就是

10!=9!*10,所以,本来是想求10的阶乘,反而求解的算式中出现了9的阶乘,按照这个式子,要想得到10!,那必须先求得9!,继续往下也就是9!必须先求得8!,依次类推,必须到最后能知道结果为止。

画出图来,就是

c语言函数学习方法(C语言自学函数说明5)(27)

c语言函数学习方法(C语言自学函数说明5)(28)

,