c语言中的函数

本文深入讲解C语言中的函数概念,包括函数定义、调用、参数传递及返回值处理等内容,并通过实例演示如何利用函数实现代码复用。

这个是我c语言讲义的第三章,有些同学催促过几次,刚写完,现发上来,下一章讲述基本算法,感兴趣请关注。

1.什么是函数

说到函数,大家自然会联想到数学中的函数,但是我们程序中的函数和数学中的函数有很大区别,C语言可以编写数学函数,完成数学计算的功能,但函数远远不是计算那么简单.
简单的说函数就是把一段代码封装起来,起一个名字,当我们需要使用这段代码的时候,调用这个函数就可以了,这个看似简单的东西,却是C语言代码组织的关键,其重要性不亚于房屋的主结构,因此在讲述了存储之后,直接进入了函数。函数不但对于C语言重要,对于其他的编程语言一样重要。C语言的代码除了声明和定义的语句可以在函数外面,其他的代码都是存在于函数里,代码里调用函数也占了相当的一部分,所以写程序就是写函数。正是有了函数,一个复杂庞大的系统,经过层层分解,生成了大量的函数,每一个函数都很简单,由一个函数开始,但经过层层调用却完成了异常复杂的功能,就像一台神奇的机器,你组装好了每一个零件,按下一个开关,机器就开始运转起来。
开始写函数,就是开始写程序了,写程序前必须要说一件事情,就是什么是好程序,好代码的标准是什么,在你写代码之前这个标准一定要明确,而且一直要向这个标准看齐.在我看来,好代码的标准有以下几点,按重要性列出:
1.代码没有重复,雷同,尽可能的复用写好的代码。复用是指调用,不是CopyPaste,这个是重中之重,软件开发的很多技术都是以复用为目的的,函数就是代码复用的最常用最简单的方法,所以一定要养成多写函数的习惯。
2.代码易读性要好,写代码不是展现你语法水平有多高,尽量使用易读的语句,看起来容易,找错误也容易。在我的课程里只用最简单的语句写程序,可能应付考试不行,但写一个软件还是够用。
3.代码要有一个好的格式。这个比较容易,我这有个简单的格式,“{”和“}”占一行,“{”下一行要缩进, 对应的“{”和“}”要对齐,我在一本书上看到的,很好用,记住了。
4.注释要加好。注释主要是关于函数的说明,函数的算法说明,作者信息,编写时间,修改记录等,有写好的函数注释模板,大家可以上网找一个,有的开发工具能生成。
下面我就先来写第一函数,这个函数是给游戏里所有的格子初始化。
int init()
{
      Ball ba[9][9];
   int num;
   ba[0][0].x=0;
   ba[0][0].y=0;
ba[0][0].isHaveBall=false;
ba[0][0].ballColor=0;
      ba[0][1].x=0;
   ba[0][1].y=1;
ba[0][1].isHaveBall=false;
ba[0][1].ballColor=0;
   ……
ba[8][8].x=8;
   ba[8][8].y=8;
ba[8][8].isHaveBall=false;
ba[8][8].ballColor=0;
num=81;
return num;//return 81    都可以
}
我们来看函数格式:
int init()
{
}
int 是返回值类型,init是函数的名字,小括号里可以放函数参数,没有的话可以空着,大括号里就是函数的代码。
函数调用函数写好了,不一定就会运行,只有在调用的时候,才能运行,调用的时候写函数的名字加括号就可以了,如果希望保存返回值,可以把函数放在等号的右侧,左侧放一个开好的空间,就可以把返回值保存起来。看代码:
int a;
a=init();      //函数调用 
要调用一个函数,必须在调用函数的代码之前定义函数或声明函数。
定义函数我们上面写的函数包括了函数名字和返回类型,和函数体,就是函数的定义。
声明函数只写出函数名字,返回类型和参数(后面讲),不写函数体,叫做函数的声明,写法如下:
int init()//注意没有{},有;
为了函数调用方便,不用考虑前后关系,通常把函数的声明放在程序的最前面。只要有函数的声明,即使没有函数定义,调用该函数,编译也不会报错,但是在连接的时候,要加上含有该函数定义的obj或者LIB,连接才可以成功。
 
返回值 int是函数的返回值类型,函数的返回值是函数在运行结束前返回到函数外边的值,是把函数里面的数据传到到函数外面的主要方法.例如:
函数的返回值类型是int ,所以开个int空间来存储。注意函数最后一句话return后面的数就是返回的值;return不一定写在函数的最后面,但是运行到return,函数就结束运行,返回到调用函数的下一句。函数可以没有返回值,可以用void 来修饰,如:
void init();
没有返回值,就可以不写return语句,如果需要结束函数,可直接调用return ,后面不加任何内容,但是如果有返回值必须有return 语句,而且return的数据类型必须和指定的返回值类型一致。
 
全局变量和局部变量
变量就是我们上节课讲的空间,在这个函数里我们开了如下空间:
Ball ba[9][9];
int num;
这两个变量就是局部变量。在函数里开的空间叫做局部变量,局部变量的特点是:
1.在函数执行期间,局部变量存在,函数结束后不再存在,如果第二次去调用函数,第一次调用时的空间里的内容都已经不存在了,因此需要把函数里变量的内容传到函数外面,一定要在函数结束前进行处理。
2.局部变量有作用域,只有同一作用域的局部变量可以互相访问。通常一对{}里就是一个作用域,函数体本身在一对{}里,就是一个作用域,代码里面可能还包含多层{},外层作用域的变量内层可以访问,内层作用域的变量外层不可以访问。
我们现在把81Ball类型的空间放在函数里就是错误的,因为当函数运行完以后,这些空间就不能再进行访问,我们需在这些空间在其他的函数里也可以访问,必须把他们放在一个别的函数都可以访问的地方才可以。
全局变量全局变量顾名思仪,在全部地方都可以访问的变量,这个变量定义在函数的外边,在所有的函数里都可以访问,当然,变量的声明要放在访问之前。我们存棋盘的数据用全局变量是最合适的,我们把Ball类型的二维数组改成全局变量。代码如下:
Ball ba[9][9];
int init()
{
      int num;
   ba[0][0].x=0;
   ba[0][0].y=0;
ba[0][0].isHaveBall=false;
ba[0][0].ballColor=0;
      ……
 
       for 循环语句刚才的代码里给81个空间赋值,我们写了81遍,显然是不可取的,我们可以采用循环语句来把重复的代码去掉,注意我开始讲的,代码不能有重复,采用循环是消除重复的一个好办法。循环语句可一把同一条语句执行多遍,看代码:
       for(int i=0i<9i++)
{
      ba[0][0].x=0;
   ba[0][0].y=0;
ba[0][0].isHaveBall=false;
ba[0][0].ballColor=0;
}
看一下for语句的格式,for 是关键字,后面跟一对(),小括号后面是要重复的语句,只有一句话的时候,可以不加{};多条语句的话一定要加{}{}后面不用加;,小括号里的内容分3段,用2个;隔开。
第一段,可以写一条语句,在循环之前被执行,只执行一次,这里我们开了空间i,并赋值为0
第二段是一个表达式,表达式就是我们在数学里常看到算式,有+-*/等算术运算符,还有>,<,>=,<=关系运算符,还有&&,||,!等逻辑运算符,这些运算符可以在一起使用,正常是从左往右计算,但不同的运算符的优先级不一样,像乘法高于加法,先计算优先级高的算式,函数的优先级有张表,记不清的时候可以查一下。算术表达式的结果是一个数字,这个数字用在逻辑运算时,数字为0就是false,数字不为0就是true。在这里,当表达式的结果为真,或者说不等于0,表示条件成立,进入循环语句,继续执行,否则退出循环,循环开始之前和每次循环结束之后,都会进行这种判断。
第三段,又是一条语句,当循环的内容执行完之后,就要执行这条语句,我们通常在这里对循环变量进行修改,比如i=i+1, (还可以写成i++),这样每循环一次i的值就增加1,当i的值等于9,正好循环了9遍,此时表达式的结果为false,循环结束。我们同常用 i,做数组的下标,那么每次循环我们就可以访问不同的数组元素。
刚才的语句只是把同一个空间修改了9次,要给二维数组赋值,通常需要一个二重循环看代码:
for(int i=0i<9i++)
{    
       for(int j=0j<9j++)
       {
             ba[i][j].x=i;
          ba[i][j].y=j;
ba[i][j].isHaveBall=false;
ba[i][j].ballColor=0;
              }
}
注意里面的i,j,每循环一次,ij的值就会变,每次循环执行的内容就会变.
参数 我们再回过头来看 init函数,我们会发现一个问题,这个函数只能给全局变量ba代表的二维数组进行初始化,如果我还有一个同样的全局变量,或者一个同样类型的局部变量,这个函数就完全用不了了,我们就必须把类似的代码重写好多遍,这又违背了不重复的规定,如果我们的函数事先不指定二维数组,而是在调用的时候告诉函数一个指定的二维数组,那么这个函数就可以为这个指定的二维数组服务,如何把信息传递给函数呢,那就要用到参数,函数的参数就是给函数传递信息的主要途径。有了参数,函数的功能就会变的复杂,智能,能够适应更多场合,也就减少了更多的重复。
参数的定义参数就是函数名后面小括号里的声明的变量.看个简单函数:
int add(int a,int b);
{
       int c;
       c=a+b;
       return c;//return a+b; 这么写也可以,这里主要是为了说明参数的用法
}
ab就是参数.那么参数如何使用,有什么特性,我总结了三句话,当你感到参数不会使用的时候,想想这三句话,可能就会有思路了,哪三句话呢,请看:
1.参数是函数内部的变量
2.调用函数时,要按照参数的顺序给参数赋值
3.传递数组的只传递数组的首地址 
解释一下:
看代码,一个求int数组最大值的函数:
int getMax(int a[],int n)
{
       int k=a[0];
int i;
              for(i=0;i<n;i++)
              {
                     if(k<a[i])
                     {
                 k=a[i]
}
}
return k;
}
调用代码:
void main()
{
       int m[]={34,23,67,78,32,12};
       int k=0;
           k=getMax(m,6);
}
第一句,参数在使用起来,和使用函数内的局部变量是一样的,参数可以和局部变量互相访问,他们具有相同的作用域.可以参考上面的程序代码,数组aint 空间n就是参数,在函数里可以
第二句说明了参数在调用函数时的传递过程,同时也说明了参数的重要作用,通过给参数赋值达到向函数内部传递信息。来看一段代码调用上面add函数的代码:
int m;
int n=3;
m=add(n,5);
在调用函数的时候,n的值和常数5,分别赋给了函数内的两个变量a,b,并开始执行函数内的代码。
第三句话,看函数getMax的定义,第一个参数是一个数组,调用时我们传入一个变量m,以前讲过,m的含义是&m[0],既首元素的地址,我们在传递数组的时候,实际上传入了一个数组元素的地址,所以传递数组的时候还可以写成:
getMax(int *a,int n)
这种写法和刚才定义的函数功能是一样。
这时候,刚才的函数可以改成这样:
int init(Ball ba[9][9])
{
for(int i=0i<9i++)
    for(int j=0j<9j++)
    {
          ba[i][j].x=i;
       ba[i][j].y=j;
ba[i][j].isHaveBall=false;
ba[i][j].ballColor=0;
           }
}
}
   这个函数不再是对全局变量进行初始化,而是对传入的变量进行初始化,言外之义,就是能够应用到更多的场合,减少了代码的重复。
 
为了巩固上面三句话的效果,我特意准备了一些函数,让大家试着调用一下,把可能的调用形式都写出来。
void f1(char a);
void f2(char *a);
void f3(char a[]);
void f4(char *a[]);
void f5(char **a);
void f6(char (*a)[5]);
void f7(char a[][])
我先把一些空间定义出来,供大家调用时使用。
char m[5];
char *n[5];
char s[5][5];
n[0]=&m[0];
n[1]=&m[1];
n[2]=&m[2];
n[3]=&m[3];
n[4]=&m[4];
 
大家可以自己先练习一下,在调用的时候,不但参数要传对,还有去理解传递的过程,我把答案公布一下。
f1: f1(m[0]),f1(*n[0])
f2: f2(n[0])),f2(&m[0]),f2(m)
f3: f3(n[0])),f3(&m[0]),f3(m)
f4: f4(n),f4(&n[0])
f5: f5(n),f5(&n[0])
f6: f6(s),f6(&m)
f7: f7(s)
其中f2f3f4f5是完全相同的。f6f7是关于**数组的使用,大家可以参考一下
评论 68
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值