第5章 函数
函数是C程序的基本组织单位。
有关标识符:
C语言的标识符一般应遵循如下的命名规则。
1、标识符必须以字母a或下划线开头,其后的可以是字母、下划线和数字,其他字符不允许出现在标识符中。
2、标识符区分大小写字母;
3、标识符的长度,c89规定31个字符以内,c99规定63个字符以内;
4、C语言中的大多数关键字有特殊意义,不能作为标识符;
但是,C语言中预先定义了一些标识符,他们有特定的含义,通常用做固定的库函数名或预编译处理中的专门命令。如scanf、printf、sin、define、include等。
C语言允许用户标识符与预定义标识符同名,但这将使这些标识符失去系统规定的原意。为了避免误解,建议用户为标识符取名时尽量不要与系统预先定义的标准标识符(如标准函数)同名。
5.1 函数的概念和结构
先来比较两个概念:过程(procedure)、函数(function)。
过程与函数都是一组封装在一起的语句,能实现特定的功能。区别在于过程只进行某种操作,而函数会产生一个返回值。在C语言中,过程与函数的语法结构类似(在某些语言中是定义语句是不同的)。因此,C语言中的“过程”可以简单理解为没有返回值的函数。
过程语法结构如下:
void <函数名>(<参数>)
{
<语句>
return;
}
函数语法结构如下:
函数原型(又称为接口)
函数名后面括号中的参数列表告诉编译器函数要求调用者提供几个参数和其类型(因此函数原型中参数名可省);函数名左边的数据类型告诉编译器,函数向调用者返回的数据类型。
返回值类型 <函数名>(<参数>)
{
<说明语句> //声明外部变量、函数名等。
<变量定义序列>
<执行语句序列>
return (开头定义的返回值类型的常量或变量);//例如:return 0;或return x;
}
函数定义不能嵌套,函数调用可以嵌套。
函数体中的变量是局部变量,只能在函数中使用;
函数头中的参数不能再定义为局部变量,但作为局部变量使用;
参数有多个时必须单独声明,用逗号分开。不能写成int x, y这样的经典错误。
下面的过程可以实现小车右转:
void littleright(double number1, double number2)
{
digitalWrite(11,1); analogWrite(10,number1);
digitalWrite(8,0); analogWrite(9,number1);
_delay((number2) * (0.1));
return;
}
显然,笔者并不希望“右转”这个动作有什么返回值。而在上述过程中,笔者使用了其他过程如digitalWrite()等。括号中用逗号隔开的是过程的参数。笔者自己定义的过程中也存在参数。参数的数量和数据类型不受限制,不过通常不会很多。
注意,函数的定义不可以嵌套,但函数的调用可以嵌套。被调用的函数必须在调用它的函数之前声明被调用函数的原型:返回值类型 函数名(参数表);
函数的意义在于方便。首先方便了我们实现某种操作。比如一个小车程序中可能需要多次右转,有了函数则只需调用相应次数并在使用的时候修改参数。而如果复制代码然后修改相应变量,工作量大到难以忍受。其次是方便我们修改。更重要的其实是……好看。可读性是大规模程序需要着重注意的点。
有时候,函数中没有参数,没有定义变量,更没有执行语句。这时候我们就说我们定义了一个空函数。一个语法上正确的空函数的返回值类型必须是void。
函数可以自然结束或者使用return语句。显然,非void函数是不能自然结束的。而void函数可以选择自然结束或者加return;来结束。
5.2 函数的使用
使用函数时,我们必须知道它的参数个数,类型,参数的顺序,以及返回值的类型。
例如:z=pow(x,y);
上面的语句运行之后,z这个变量中储存的就是x的y次方的值。z,x,y应当都是整形或浮点型。x若为负数则y不能为小于1大于0的数,若x等于0则y不能小于等于0。如果单纯写pow(x,y);显然毫无意义。但我们可以写printf(“%d”,pow(x,y));(假设x,y为正整数)来让屏幕上输出x的y次方的值。
我们自己定义的过程和函数与头文件中包含的函数在调用的时候没有不同。
5.3 局部变量和全局变量
这个内容应该早点说的。简单点说,定义在某对{}里的变量,可以在这对{}中的{}中的语句中使用。全局变量直接定义在函数体外,不在任何一对{}之内,所以全局变量可以在任何语句中使用。例如:
#include<iostream>
#include<cstdio>
using namespace std;
int f[40001];
struct node{
int x,y,v;
}e[20001];
bool cmp(node a,node b) { return a.v>b.v; }
int find(int x) { return f[x]==x?x:f[x]=find(f[x]); }
int main()
{
int n,m;
cin >> n >> m;
for (int i=1; i<=m; i++) cin >> e[i].x >> e[i].y >> e[i].v;
for (int i=1; i<=2*n; i++) f[i]=i;
sort(e+1,e+m+1,cmp);
for (int i=1; i<=m; i++)
{
int xx=find(e[i].x);
int yy=find(e[i].y);
if (xx==yy) { cout << e[i].v << endl; system("pause"); return 0;}
f[xx]=find(e[i].y);
f[yy]=find(e[i].x);
}
cout << 0; return 0;
}
全局变量为数组f和数组e(数组的知识在下一章,总之也是一种数据结构)。在main函数中定义了变量n,m。在循环语句中定义了变量i。那么i就只能在那一个循环语句中用。在后面的语句中我重复定义了很多次i。n,m则可以在整个main函数中用。至于f和e数组,可以在程序的任意一个函数中用。浮点型和整型全局变量的初始值默认为0。
这里需要提一下变量的存储类(时间域)和作用域(空间域)
存储类说明符分为自动存储类和静态存储类。
自动存储类有auto(只能作用于变量,函数局部变量和参数)和register(只能用于局部变量和函数参数)。二者的区别是auto类变量保存在内存内,register类变量保存在寄存器中。auto通常可省。
静态存储类有extern(外部标识符,全局变量和函数名)和static(变量和函数名)。二者之后的变量函数开始执行时就存在,但static后定义的变量只能在变量所在的函数中使用,函数退出时保存数值。
函数访问之后定义的变量需要用extern声明,可被多文件范围内访问的变量或函数需要extern声明。
任何函数之外声明的标识符取文件范围,这种标识符可以从声明处到文件末尾的任何函数访问。
C89标准规定一个函数中所有局部变量必须集中定义在函数体重第一个执行语句的前面。
5.4 函数参数的传递
在C语言中函数的参数通过值传递。所有的形参都是具有规定类型的局部变量。
5.5 字符类型判断函数
isdigit():判断一个字符是否是数字
islower():判断一个字符是否是小写字母
isalnum():c是否是字母或数字
isalpha():c是否是大写或者小写字母
isupper():是否是大写字母
isxdigit():是否是十六进制数字
isspace():是否是空白符
toupper():把小写字母转换成大写字母,其他字符不做变化
tolower():把大写字母转换成小写,其他字符不做变化
5.6 递归
就是把大问题分解成小问题,对最基础的问题实现算出答案并保存,之后根据递推公式(或其他转移方式)来推导出后续答案。经典的斐波那契问题和汉诺塔问题就可以用这种方法解决。