目录
没有返回值的函数
通用性
不含形参的函数
函数返回值的初始化
作用域
文件作用域
声明和定义
函数原型声明
头文件和文件包含指令
在上节中我们简单的学习了函数的创建方法(函数定义)与函数的使用方法(函数调用),现在我们来学习更为专业的函数创建方法等知识。
没有返回值的函数
在前面的循环结构中我们学习了用*来显示出等腰三角形的程序,下面我们把显示出*的部分作为一个单独的函数,通过调用这个函数显示出直角在左下方的三角形。
#include<stdio.h>/*连续显示出多个n*/
void put_stars(int n)
{while(n-- > 0)putchar('*');
}int main()
{int i, len;printf("生成一个直角在左下方的等腰直角三角形。\n");printf("短边:");scanf("%d", &len);for(i = 1; i <= len; i++){put_stars(i);putchar('\n');}return 0;
}
在put_stars函数中只是用来显示n个*,并没有需要返回的值。这种没有返回值的函数类型,要声明为void。
void就是“空”的意思,在C语言中无论有没有返回的值都同样称为函数,而在其他编程语言中会定义为其他非函数的概念,例如子程序(fortran)或过程(Pascal)。
这里我们使用put_stars函数把二重循环简化为一重循环,从而提高程序的可读性。
通用性
我们这次来编写一个显示出直角在右下方的等腰三角形的程序。
#include<stdio.h>/*连续显示出多个ch*/
void put_chars(int ch, int n)
{while(n-- > 0)putchar(ch);
}int main()
{int i, len;printf("生成一个直角在左下方的等腰直角三角形。\n");printf("短边:");scanf("%d", &len);for(i = 1; i <= len; i++){put_chars(' ', len - i); //显示出len-i个空格put_chars('*', i);//紧接着显示出i个*putchar('\n');}return 0;
}
让我们来解释下:
本程序需要连续显示出多个空白字符,因此需要创建出put_chars来代替函数put_stars。
put_chars可以连续显示出n个通过形参传递来的字符。
前面我们学习过字符常量是int型的,这里还存在显示字符的char型,我们以后会学习到
函数put_chars和只能显示*的函数put_stars相比较更具有通用的优势。
putchar(ch);
put_chars(' ', len - i); put_chars('*', i);
ch就是put_chars用来传递字符的,既可以传递空白字符又可以传递*,相信这样单独拉出来解释大家有更加明白的认识。
不含形参的函数
我们用下面一段程序来引出不含形参的参数,大家可以先看程序中的特别的代码试着分析它的作用然后再阅读解析。
#include<stdio.h>
//返回输入的正整数
//关于这个函数的名字不必纠结,学的后面我们就能熟练起适当的函数名了
int scan_pint(void)
{int tmp;do{printf("请输入一个整数:");scanf("%d", &tmp);if(tmp <= 0)puts("\a请不要输入非负正整数。");}while(tmp <= 0);return tmp;
}
/*返回正整数倒转后的值*/
int rev_int(int num)
{int tmp = 0;if(num > 0){do{tmp = tmp * 10 + num % 10;num /= 10;}while(num > 0);}return tmp;
}
int main()
{int nx = scan_pint();//不赋予实参printf("该整数倒转后的值是%d。\n", rev_int(nx));return 0;
}
其中有几段代码是不是有点特殊呢?让我们来看看吧!
int scan_pint(void)
函数scan_pint读取从键盘输入的正整数并返回。该函数不接受形参,为了加以说明在小括号中加入void。
int nx = scan_pint()
调用方也没有必要赋予实参,所以函数调用运算符()是空的,其实在到目前为止我们学习的main函数也可以写为int main(void)
函数返回值的初始化
int nx = scan_pint();
注意main函数中声明变量nx的部分,该变量得初始值是函数scan_pint()的表达式,变量逆向使用函数scan_pint()的返回值进行初始化。
但是该初始化方法只适用于拥有自动存储期的对象,不适合拥有静态存储期的对象。
作用域
函数scan_pint和函数rev_int都包含一个拥有相同的标识符(名称)的变量tmp,但它们是各自不同的变量。
就是说scan_pint中的变量tmp是函数scan_pint特有的变量,对rev_int也是一样。
赋给变量的标识符,它的名称有一个通用的范围就是作用域。
在程序块中声明变量的名称,只在该程序块中通用,在其他区域无效,简单来说就是从变量声明的位置开始,到该声明的程序块中的大括号 } 为止这一区间内通用,这样的作用域称为块作用域(block scope)。
文件作用域
还是老样子,我们用程序来引出,大家可以先观察程序中某一语句的特别之处猜想它们的用法:
/*计算最高分*/
#include<stdio.h>#define NUMBER 5//学生人数int tensu[NUMBER];//数组定义:创建数组实体的声明(定义)int top(void);//函数top的函数原型声明int main()
{extern int tensu[];//数组的声明(可以省略)int i;printf("请输入%d名学生的分数。\n", NUMBER);for(i = 0; i < NUMBER; i++){printf("%d:", i+1);scanf("%d", &tensu[i]);}printf("最高分=%d\n", top());return 0;
}
/*返回数组tensu的最大值(函数top的函数定义)*/
int top(void)
{extern int tensu[];//数组的声明(可以省略)int i;int max = tensu[0];for(i = 1; i < NUMBER; i++){if(tensu[i] > max)max = tensu[i];}return max;
}
在函数程序块中声明的变量等标识符是该程序块中特有的部分,而像数组tensu[ ]这样,在函数外声明的变量标识符,其位置从声明的位置开始到程序的结束都是通用的。
int tensu[NUMBER];//数组定义:创建数组实体的声明(定义)
这样的作用域称为文件作用域。
声明和定义
像上面的一个语句,创建了一个元素为5、元素类型为int的数组tensu。这样创建变量实体的声明称为定义(definition)声明。
extern int tensu[];//数组的声明(可以省略)
像这样使用了extern的声明表示“使用的是在某处创建的tensu”,这里并没有真正创建出变量的实体,因此称为非定义声明。
由于数组tensu是在函数外定义的,所以只需要在main函数或top函数中明确声明要使用它就可以放心使用了。
由于数组tensu被赋予了文件作用域,因此在main函数和top函数中无需特意声明,就可以使用它。
函数原型声明
和人一样,编译器在读取数据时也是从头到尾按照顺序进行读取的,因为top函数的函数定义在main函数之后,所以在想要调用top函数,就需要让编译器知道你的想法。
int top(void);//函数top的函数原型声明
像这样明确记述了函数的返回类型,形参类型和个数的声明称为函数原型声明。
该语句要以“;”结尾
函数原型声明只声明了函数的返回值和形参等相关类型,并没有定义函数的实体。
函数定义和函数原型声明的不同之处如下所示:
函数top的函数定义——定义声明(有实体)
函数top的函数原型声明——非定义声明(没有实体)
如果top函数的需求(返回值类型,形参等)发生了改变那么函数定义和函数原型声明两部分都必须进行修改,不能只改动其中一个!
如果把函数top的函数定义写在main函数之前,就不用特意使用函数原型声明了,我们推荐把main函数放在程序最后的位置。
头文件和文件包含指令
通过函数原型声明,可以指定函数的参数以及返回值的类型等信息,这样就可以放心的使用函数了。
库函数printf或putchar等的函数原型声明都包含在<stdio.h>中,因此必须要使用以下固定的指令:
#include<stdio.h>
通过#include指令,就可以把<stdio.h>中的全部内容都读取到程序中。
包含库函数的函数原型声明的<stdio.h>称为头文件,而取得头文件的#include称为文件包含指令。
例如,putchar的函数原型声明在头文件<stdio.h>中的声明格式如下:
int putchar(int _ _ c);
编译器不同,形参的名称也可能不同。
在函数原型声明的时可以不指定形参的名称,也可以这样进行声明:
int putchar(int);
我们先学习到这里,之后我们会学习函数的通用性——可以处理任意数组、处理不同元素个数的数组等更加精细的内容。