这个是我c语言讲义的第三章,有些同学催促过几次,刚写完,现发上来,下一章讲述基本算法,感兴趣请关注。
1.什么是函数
说到函数
,
大家自然会联想到数学中的函数
,
但是我们程序中的函数和数学中的函数有很大区别
,
用
C
语言可以编写数学函数
,
完成数学计算的功能
,
但函数远远不是计算那么简单
.
简单的说函数就是把一段代码封装起来
,
起一个名字
,
当我们需要使用这段代码的时候
,
调用这个函数就可以了
,
这个看似简单的东西
,
却是
C
语言代码组织的关键
,
其重要性不亚于房屋的主结构
,
因此在讲述了存储之后
,
直接进入了函数。函数不但对于
C
语言重要
,
对于其他的编程语言一样重要。
C
语言的代码除了声明和定义的语句可以在函数外面,其他的代码都是存在于函数里,代码里调用函数也占了相当的一部分,所以写程序就是写函数。正是有了函数,一个复杂庞大的系统,经过层层分解,生成了大量的函数,每一个函数都很简单,由一个函数开始,但经过层层调用却完成了异常复杂的功能,就像一台神奇的机器,你组装好了每一个零件,按下一个开关,机器就开始运转起来。
开始写函数,就是开始写程序了,写程序前必须要说一件事情,就是什么是好程序,好代码的标准是什么,在你写代码之前这个标准一定要明确,而且一直要向这个标准看齐.在我看来,好代码的标准有以下几点,按重要性列出:
1
.代码没有重复,雷同,尽可能的复用写好的代码。复用是指调用,不是
Copy
和
Paste
,这个是重中之重,软件开发的很多技术都是以复用为目的的,函数就是代码复用的最常用最简单的方法,所以一定要养成多写函数的习惯。
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.
局部变量有作用域,只有同一作用域的局部变量可以互相访问。通常一对
{}
里就是一个作用域,函数体本身在一对
{}
里,就是一个作用域,代码里面可能还包含多层
{}
,外层作用域的变量内层可以访问,内层作用域的变量外层不可以访问。
我们现在把
81
个
Ball
类型的空间放在函数里就是错误的,因为当函数运行完以后,这些空间就不能再进行访问,我们需在这些空间在其他的函数里也可以访问,必须把他们放在一个别的函数都可以访问的地方才可以。
全局变量
全局变量顾名思仪,在全部地方都可以访问的变量,这个变量定义在函数的外边,在所有的函数里都可以访问,当然,变量的声明要放在访问之前。我们存棋盘的数据用全局变量是最合适的,我们把
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=0
;
i<9
;
i++)
{
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=0
;
i<9
;
i++)
{
for(int j=0
;
j<9
;
j++)
{
ba[i][j].x=i;
ba[i][j].y=j;
ba[i][j].isHaveBall=false;
ba[i][j].ballColor=0;
}
}
注意里面的
i,j
,每循环一次,
i
或
j
的值就会变,每次循环执行的内容就会变.
参数
我们再回过头来看
init
函数,我们会发现一个问题,这个函数只能给全局变量
ba
代表的二维数组进行初始化,如果我还有一个同样的全局变量,或者一个同样类型的局部变量,这个函数就完全用不了了,我们就必须把类似的代码重写好多遍,这又违背了不重复的规定,如果我们的函数事先不指定二维数组,而是在调用的时候告诉函数一个指定的二维数组,那么这个函数就可以为这个指定的二维数组服务,如何把信息传递给函数呢,那就要用到参数,函数的参数就是给函数传递信息的主要途径。有了参数,函数的功能就会变的复杂,智能,能够适应更多场合,也就减少了更多的重复。
参数的定义
参数就是函数名后面小括号里的声明的变量
.
看个简单函数
:
int add(int a,int b);
{
int c;
c=a+b;
return c;//return a+b;
这么写也可以
,
这里主要是为了说明参数的用法
}
a
和
b
就是参数
.
那么参数如何使用
,
有什么特性
,
我总结了三句话
,
当你感到参数不会使用的时候
,
想想这三句话
,
可能就会有思路了
,
哪三句话呢
,
请看
:
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);
k=getMax(m,6);
}
第一句
,
参数在使用起来
,
和使用函数内的局部变量是一样的
,
参数可以和局部变量互相访问
,
他们具有相同的作用域
.
可以参考上面的程序代码,数组
a
和
int
空间
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=0
;
i<9
;
i++)
{
for(int j=0
;
j<9
;
j++)
{
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)
其中
f2
和
f3
,
f4
和
f5
是完全相同的。
f6
,
f7
是关于**数组的使用,大家可以参考一下