|
单片机c语言,10小时学会C 语言 (八)
第八章 函数与呼叫
C 语言的程序,是由一堆函数(Function)或函式(Routine)所组成的。
○ 什么是函数?以 sin 这个函数为例:
┌────────────────────────────────────────┐
│ sin: sine function │ 正弦函数。
│ │
│ double sin(double x); │ sin 的语法
│ │
│ Prototype in math.h │ 必须 #include
│ │
│ x is in radians. │ x 的单位是径度(rad)
│ Returns a value in the range -1 to 1. │ 传回数值在 -1 到 1 之间。
└────────────────────────────────────────┘
A = sin( M_PI/2 ); 我们都知道 sin(π/2)=1.0 ,所以这一行叙述相当于 A=1.0;我们可以把 sin() 当成一个黑箱子,给它角度(rad),它会输出相对的 sine 值。
角度 ┌──────────────┐
x ─────────→┤ sin() ├─────→ sin(x)
(rad) └──────────────┘
在 C 语言中,函数的输入可以多个,例如: pow( X, Y) 可以计算 X 的 Y 次方。
┌──────────────────────────────────┐
│ pow: power function, x to the y │ 乘幂函数,X 的 Y 次方。
│ │
│ double pow(double x, double y); │ pow 的语法
│ │
│ Prototype in math.h │ 必须 #include
└──────────────────────────────────┘
但是函数的输出值,也就是回传值,只能有一个。
○ 什么是函式?函式的英文为 routine ,原意是「例行公事」,也就是一些一成不变做完甲这个事后,就做乙,再做丙...。到现在为止,我们常用的 printf() 、 scanf() 都算是函式,而且在每一个程序也都用建立了 main() 这个函式。通常称 main() 为主函式,而其它的都称做副函式(sub-routine)。在计算机语言中,函数、函式已经混在一起了,像教科书中的作者用函数,而笔者则是用惯了函式。
◎ 函式的宣告与定义
函式宣告与定义的格式如下:
┌─────────────────────────────────────────────────────┐
│传回值型别 函式名称(型别 参数1, 型别 参数2, ...) │
│{ │
│ 函式主体──包含宣告函式内部变量及指令群 │
│} │
└─────────────────────────────────────────────────────┘
其中,第一列 传回值型别 函式名称(型别 参数1, 型别 参数2, ...)我们称之为函式的宣告,而大括号 { } 内则是函式的定义。
传回值型别,表示函式传回数值的数据型别,如果省略,则 C 语言会视为是内定的整数型别。在函式中,我们可以用 return(回传数值); 让函式传回数值,并且结束函式的执行。参数(或称自变量)可以使函式具有变化性,如果省略,则表示此函式不需要参数。
○ 养成好习惯
如果省略传回值型别,等于定义了整数型别,那要是我们所设计的函式没有传回数值,应该怎么办呢?在 C 语言中,有一种资料型别叫 void ,void 表示「空」的数据型态,所以
void 函式名称(型别 参数1, 型别 参数2, ...)
{
...
}
就表示这个函式没有传回任何数值。
同样地,如果设计的函式不用参数,也可以用 void 来表示:
传回值型别 函式名称( void )
{
...
}
就表示这个函式不需要任何参数。
◎ 呼叫函式
C 语言在使用变量或是函式之前,都必须先宣告所使用的变量或是函式。这也是为什么,我们所写的程序都要 #include
,因为,在 stdio.h 中,宣告了 printf() 、 scanf() 等函式的原型(prototype)。同样地,在使用自己所定义的函式前,也要先宣告。前一节说过,在定义函式的同时,也做了宣告。如此,我们可以将自己写的函式放在 main() 的前面,再由 main() 来呼叫。如同课本的范例:
以下是课本的范例:让计算机发出「哔」!
┌────────────────────────────────────────────────────────────────┐
│beep.c │
├────────────────────────────────────────────────────────────────┤
1│#include/* 宣告 printf() 的原型 */ │
2│#include/* 宣告 getche() 的原型 */ │
3│ │
4│void beep(void) /* 副函式 beep() 的宣告 */ │
5│{ /* 开始定义 beep() */ │
6│ printf("\a"); /* 「哔」一声 */ │
7│} /* beep() 定义结束 */ │
8│ │
9│void main(void) /* 主函式 main() 的宣告 */ │
10│{ /* 开始定义 main() */ │
11│ beep(); /* 呼叫 beep() */ │
12│ printf("Press any key to continue..."); /* 呼叫 printf() */ │
13│ getche(); /* 呼叫 getche() */ │
14│ beep(); /* 呼叫 beep() */ │
15│} /* main() 定义结束 */ │
├───────────────────────────────────────────────────────────────┤
│「哔」!一声 │
│Press any key to continue... │
│「哔」!一声 │
└───────────────────────────────────────────────────────────────┘
void 既然是「空」的数据型态,所以在呼叫时就是「空的」。如果,你还记得的话,getche() 会传回使用者所按下的键值,但是在这里,我们只是要使用者按下任意键,所以读入的键值是多少我们并不在意,可以不管它,就如同呼叫不会传回数值的 beep() 函式一样。printf() 也是有传回值的,它传回输出的 byte 数,而这个数值,我们通常也不会在意。
以下是课本的范例:以条形图来比较数值的大小。
┌────────────────────────────────────────────────────────────────┐
│bar.c │
├────────────────────────────────────────────────────────────────┤
1│#include/* 宣告 printf() 的原型 */ │
2│ │
3│void bar(int i) /* 副函式 bar() 的宣告 */ │
4│{ /* 开始定义 bar() */ │
5│ int j; │
6│ │
7│ for( j=0 ; j 8│ printf("*"); │
9│ printf("\n"); │
10│} /* bar() 定义结束 */ │
11│ │
12│void main(void) │
13│{ │
14│ printf("Merry\t"); │
15│ bar(30); /* 输入参数为 30 */ │
16│ printf("John\t"); │
17│ bar(40); /* 输入参数为 40 */ │
18│ printf("Johnson\t"); │
19│ bar(20); /* 输入参数为 20 */ │
20│ printf("Sposh\t"); │
21│ bar(50); /* 输入参数为 50 */ │
22│} │
├────────────────────────────────────────────────────────────────┤
│Merry ****************************** │
│John **************************************** │
│Johnson ******************** │
│Sposh ************************************************** │
└────────────────────────────────────────────────────────────────┘
各位可能会发现,课本在宣告 bar() 函式时,好像跟老师所用的不一样:
bar(i)
int i; /* 宣告参数的数据型别 */
{
...
}
这种宣告参数的方式,是旧的 C 语言格式,目前的 TC 仍然可以使用。
在呼叫有参数的函式时,给定的参数会代入函式中相对的变数。如: bar(30); 就是给定 30 作为输入参数,此例来说,就是先设定 i = 30 ,再开始执行第 5 行以后的指令。
以下是课本的范例:计算圆面积的副函式。
┌────────────────────────────────────────────────────────────────┐
│area.c │
├────────────────────────────────────────────────────────────────┤
1│#include/* 宣告 printf(), scanf() 的原型 */ │
2│ │
3│float areas(float r) │
4│{ /* 2 */ │
5│ return( 3.14159 * r * r ); /* 圆面积 = πr */ │
6│} │
7│ │
8│void main(void) │
9│{ │
10│ float radius; │
11│ │
12│ printf("Enter the radius = "); │
13│ scanf("%f", &radius); │
14│ printf("Area of this circle = %f\n", areas( radius ) ); │
15│} │
├────────────────────────────────────────────────────────────────┤
│Enter the radius = 6 │
│Area of this circle = 113.097240 │
└────────────────────────────────────────────────────────────────┘
在第 14 行中, areas( radius ) 表示以 radius 为参数呼叫 areas() 函式,areas() 会传回一个 float 型别的数值。我们可以把 areas( radius ) 整个当成一个变数来看,而它的数值与 radius 有关。
例如,我们要计算两个半径分别为 r1 及 r2 的圆面积总和:
totalarea = areas( r1 ) + areas( r2 ) ;
以下是课本的范例:求任意二个整数的最大值。
┌────────────────────────────────────────────────────────────────┐
│max.c │
├────────────────────────────────────────────────────────────────┤
1│#include/* 宣告 printf(), scanf() 的原型 */ │
2│ │
3│int max(int i, int j) │
4│{ │
5│ if( i > j ) return i; /* 若 i > j 则传回较大的 i 值 */ │
6│ else return j; /* 否则是 j 比较大,就传回 j 值 */ │
7│} │
8│ │
9│void main(void) │
10│{ │
11│ int i, j; │
12│ │
13│ printf("Enter 2 integers : "); │
14│ scanf("%d %d", &i, &j); │
15│ printf("Maximum of %d and %d is %d.\n", i, j, max(i,j) ); │
16│} │
├────────────────────────────────────────────────────────────────┤
│Enter 2 integers : 6 8 │
│Maximum of 6 and 8 is 8. │
└────────────────────────────────────────────────────────────────┘
事实上, TC2 有提供 max() 这个函式,你可以在 TC 的整合环境下的编辑窗口内输入 max ,将光标移到 max 字上,按下 Ctrl+F1 就可以看到说明
┌────────────────── Help ──────────────────────┐
│ Macros: max, min │宏指令:max, min
│ These macros generate inline code to find │以宏的方式求得两数
│ the maximum or minimum value of two │的最大值或最小值。
│ integers. │
│ │
│ max(a,b) maximum of two integers a and b │求 a,b 的最大值
│ min(a,b) minimum of two integers a and b │求 a,b 的最小值
│ │
│ Defined in stdlib.h │必须 #include
└──────────────────────────────────────────────┘
由上可知, max() 是定义在 stdlib.h 内,所以我们可以改写上例如下:
┌────────────────────────────────────────────────────────────────┐
│max1.c │
├────────────────────────────────────────────────────────────────┤
1│#include/* 宣告 printf(), scanf() 的原型 */ │
2│#include/* 定义 max() 的原型 */ │
3│ │
4│void main(void) │
5│{ │
6│ int i, j; │
7│ │
8│ printf("Enter 2 integers : "); │
9│ scanf("%d %d", &i, &j); │
10│ printf("Maximum of %d and %d is %d.\n", i, j, max(i,j) ); │
11│} │
├────────────────────────────────────────────────────────────────┤
│Enter 2 integers : 6 8 │
│Maximum of 6 and 8 is 8. │
└────────────────────────────────────────────────────────────────┘
在这里只是要告诉各位,TC 它有提供非常多的内建函式,有一些功能我们并不须要再去设计一次。如果要作练习,那就另当别论。建议有意要写程序的人,多多参考「参考手册」,大略知道所使用的语言提供了那些内建函式,要用时,只要依照它的格式来呼叫它就可以了。要不然,你可以花了许多时间在写一个内建函式,而知道的人,只要 #include ??.h 就可以直接使用它。
◎ 整体变量(Global Variable)
在前面的例子中,所有的变量都只能在所宣告的函式内使用,函式与函式之间只能用传回值或是参数来传递数值。除了用传回值与参数外,在 C 语言中还可以使用「整体变量」。什么是「整体变量」?由字面上来看,就是适用于程序整体,都可以使用的变量。相对于整体变量的就是「区域变量(Local Variable)」。同样地,由字面上来看,就是只适用于程序的某一个区域所能使用的变量。
我们要如何判断变量可以使用的范围呢?以下就来谈谈变数的生命周期。
以一个副函式来说:
void sub1(void)
{
int i; ←── 变数 i 的「生」
...
} ←────── 变数 i 的「灭」
我们所写的函式,都会用一对大括号 { ... } 括起来,而在大括号内宣告的变量,就适用于大括号内。当这个函式执行结束时,在大括号内所宣告的变量,也就消逝不见。这种变量,就是区域变量,它能活动的区域,就是在所宣告的大括号内。
事实上,在 TC2.0 中,你可以在函式中任意加入一对大括号 { } ,在大括号中,前面可宣告变量,之后可以是程序代码。改写前面 bar.c 为例:
┌────────────────────────────────────────────────────────────────┐
│bar2.c │
├────────────────────────────────────────────────────────────────┤
1│#include/* 宣告 printf() 的原型 */ │
2│ │
3│void bar(int i) │
4│{ /* 开始定义 bar() */ │
5│ int j; │
6│ for( j=0 ; j 7│ printf("\n"); │
8│} /* bar() 定义结束 */ │
9│ │
10│void main(void) │
11│{ │
12│ int i = 50; ←───────────────────────────────────────────┐外 │
13│ bar(i); /* 使用外层的 i 为 50 */ │层 │
14│ { │i │
15│ int i = 20; ←──────────────────────────────────┐内层 │的 │
16│ bar(i); /* 使用内层的 i 为 20 */ │i 的 │生 │
17│ i = i + 10; │生命 │命 │
18│ bar(i); /* 使用内层的 i 为 30 */ │周期 │周 │
19│ } ←─────────────────────────────────┘ │期 │
20│ i = i + 10; │ │
21│ bar(i); /* 使用外层的 i 为 60 */ │ │
22│} ←──────────────────────────────────────────┘ │
├────────────────────────────────────────────────────────────────┤
│************************************************** │
│******************** │
│****************************** │
│************************************************************ │
└────────────────────────────────────────────────────────────────┘
在主程序中,故意宣告两个同名的整数变量 i,它们分别在两组巢状的大括号内。在外层的 i 适用于整个 main() 函式。不巧的是,内层大括号也宣告了一个 i,使得在内层的程序代码只能使用内层宣告的 i。内层大括号执行结束后,内层的 i就寿终正寝,外层的 i 就又开始有作用了。如果,你在内层不另外宣告 i 这个变量,那在内层的程序也可以使用外层的 i,你可以把第 15 行的程序代码 remark 掉,看看结果有什么不一样。
总归一句话,在大括号内所宣告的变量,就是区域变量。随着大括号的结束,这一区的变数也就失去作用。等到下一次再执行到这段程序代码时,这一区的变数才会重生。
那整体变量要如何宣告呢?由上可知,只要是在大括号内宣告的就是区域变量,那整体变量,就是要宣告在大括号以外的地方。例:
┌── #include<...>
│
│ int i; ←──────────────────────────────────┐整体变量
程│ │
│ void sub1(int a) ←─────────────┐参数 │
│ { │同样只适用于 │
│ int j; ←───────────┐区域变量│函式之内 │
│ ... │ │ │
│ } ←──────────┴───────┘ │
│ │
式│ void sub2( b ) │
│ int b; ←───────────────────┐是参数, │
│ { │不是整体变量,│
│ int k; ←───────────┐区域变量│同样只适用于 │
│ ... │ │函式之内 │
│ } ←───────────┴───────┘ │
│ │
│ void main(void) │
码│ { │
│ int c; ←────────────┐区域变量 │
│ ... │ │
└─ } ←─────────────┴───────────────────┘
☆ Call by Value v.s. Call by Address ☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆
○ Call by Value 传值呼叫
前面我们所设计的副函式都是使用「传值呼叫」,也就是副函式的参数变量不是指针型变量。以副函式 bar() 为例,我们把它改写如下,它的功能同上例:
┌────────────────────────────────────────────────────────────────┐
│bar3.c │
├────────────────────────────────────────────────────────────────┤
1│#include/* 宣告 printf() 的原型 */ │
2│ │
3│void bar(int i) │
4│{ │
5│ for( ; i>0 ; i-- ) /* i 值,由传进来的数值递减至 0 */ │
6│ printf("*"); │
7│ printf("\n"); │
8│} │
9│ │
10│void main(void) │
11│{ │
12│ int i = 50; │
13│ int j = 30; │
14│ │
15│ bar( 20 ); /* 以数值 20 呼叫 bar() 函式 */ │
16│ │
17│ bar( j ); /* 以 j 变量的数值 30 呼叫 bar() 函式 */ │
18│ printf("j = %d\n", j ); /* 秀出 j 的数值 */ │
19│ │
20│ bar( i ); /* 以 i 变量的数值 50 呼叫 bar() 函式 */ │
21│ printf("i = %d\n", i ); /* 秀出 i 的数值 */ │
22│} │
├────────────────────────────────────────────────────────────────┤
│******************** │
│****************************** │
│j = 30 │
│************************************************** │
│i = 50 │
└────────────────────────────────────────────────────────────────┘
bar(20); 表示:把 20 这个数值传给 bar() 函式做为参数 i 的数值,我们可以这样看被呼叫的 bar() 函式:
void bar(...)
{
int i = 20; ──────────────┐ 传进来的数值是 20
for( ; i>0 ; i-- ) │
printf("*"); │
printf("\n"); │
} ←─────────────┘ 变数 i 的灭亡
bar(j); 表示:把 j 这个变数的数值 30 传给 bar() 函式做为参数 i 的数值,我们可以这样看被呼叫的 bar() 函式:
void bar(...)
{
int i = 30; ──────────────┐ 传进来的数值是变数 j 的数值 30
for( ; i>0 ; i-- ) │
printf("*"); │
printf("\n"); │
} ←─────────────┘ 变数 i 的灭亡
由于是传入变量的数值,所以不论函式如何改变传入的数值,都不会影响原来传入的变数。请各位不要被变量名称所蒙骗,即使你使用与函式参数同名的变量,它一样只是传值,如范例中的
bar(i); 表示:把 i 这个变数的数值 50 传给 bar() 函式做为参数 i 的数值,我们可以这样看被呼叫的 bar() 函式:
void bar(...)
{
int i = 50; ──────────────┐ 传进来的数值是变数 i 的数值 50
for( ; i>0 ; i-- ) │
printf("*"); │
printf("\n"); │
} ←─────────────┘ 变数 i 的灭亡
总结来说,传值呼叫,就是不会改变所传入变量的数值。如果想要改变传入的变量,就要使用「传址呼叫」。
○ Call by Address 传址呼叫
我们一直在使用的 scanf() 函式,就是一个传址呼叫的函式,其一般型式如下:
┌────────────────────────────────────────────────────────┐
│ scanf("控制字符串" , &变量1 , &变量2 , ... ); │
└────────────────────────────────────────────────────────┘
我们传给 scanf() 的参数是变量的地址,所以 scanf() 可以把读入的字符串依控制字符串的格式转换成数值,存到变量的地址,这样在结束 scanf() 之后,我们传给 scanf() 的变量数值,就会是 scanf() 所读入的数值。前面说过,函式的传回数值只有一个,而利用传址呼叫的设计,就可以传回更多的数值。以下就来看一个传址呼叫的范例:将两个整数值互换。
┌────────────────────────────────────────────────────────────────┐
│swap.c │
├────────────────────────────────────────────────────────────────┤
1│#include/* 宣告 printf() 的原型 */ │
2│ │
3│void swap(int *a, int *b) │
4│{ │
5│ int backup = *a ; /* 把 *a 的数值存到 backup 变数内 */ │
6│ *a = *b ; /* 把 *b 的数值存到 *a */ │
7│ *b = backup ; /* 把 backup 的数值存到 *b 完成互换 */ │
8│} │
9│ │
10│void main(void) │
11│{ │
12│ int i = 50 , j = 30 ; │
13│ │
14│ printf("Before swap(): i = %d j = %d\n", i, j); │
15│ │
16│ printf("Swapping i & j ...\n"); │
17│ swap( &i , &j ); /* 传入 i 与 j 的地址 */ │
18│ │
19│ printf("After swap(): i = %d j = %d\n", i, j); │
20│} │
├────────────────────────────────────────────────────────────────┤
│Before swap(): i = 50 j = 30 │
│Swapping i & j ... │
│After swap(): i = 30 j = 50 │
└────────────────────────────────────────────────────────────────┘
单片机教程,五系列(55讲)电子书全集下载论坛精选:
■ 单片机c语言,10小时学会C 语言 (一)
第一章 C 语言简介与Turbo C 的使用
■ 单片机c语言,10小时学会C 语言 (二)
第二章 C 程序的结构
■ 单片机c语言,10小时学会C 语言 (三)
第三章 常数与变数
■ 单片机c语言,10小时学会C 语言 (四)
第四章 基本输出入函式
■ 单片机c语言,10小时学会C 语言 (五)
第五章 流程图与抉择指令
■ 单片机c语言,10小时学会C 语言 (六)
第六章 循环与自动重复
■ 单片机c语言,10小时学会C 语言 (七)
第七章 数组与指针
■ 单片机c语言,10小时学会C 语言 (八)
第八章 函数与呼叫
■ 单片机c语言,10小时学会C 语言 (九)
第九章 档案存取
asdasdasdas
13
23
还有这种事?
无内容
xzfdg
歇息
好
好
回帖是一种美德
怎么没有东东呢?

