程序是由多个零件组合而成的,而函数就是这种“零件”的一个较小单位。
C语言程序中,main函数是必不可少的。程序运行的时候,会执行main函数的主题部分。main函数中使用了printf、scanf、puts等函数。由C语言提供的这些为数众多的函数称为库函数。
当然,我们也可以自己创建函数。而实际上,我们也必须亲自动手创建各种函数。下面我们来自己创建一个简单的函数。
创建一个函数,接收两个整数参数,返回较大整数的值。
printf函数和scanf函数等创建得比较好得函数,即使不知道其内容,只要了解使用方法,也可以轻松使用。
首先来说下函数的创建方法,这里我们来定义一个名为max2的函数,如下所示:
这里的函数定义由多个部分构成。
该部分表示函数的名称和格式。虽然称为头函数,实际上说它是函数的“脸”可能更加的合适。
函数返回的值——返回值的类型。该函数的情况下,返回的是两个int型数值中较大的一个,所以其类型是int。
函数的名称。从其他零件调用函数时,使用函数名。
小括号括起来的部分,是用于接收辅助性提示的变量——形式参数的声明。
函数体是复合语句。仅仅在某个函数中使用的变量,原则上应在该函数中声明和使用,但是要注意不能声明和形参同名的变量,否则会发生变量名冲突。
下面是使用函数的方法,代码如下:
#include
int Max(int n, int m)
{
if (n>m)
{
return n;
}
else
{
return m;
}
}
int mian()
{
int n = 0;
int m = 0;
puts("请输入两个整数:");
printf("整数1:");
scanf("%d", &n);
printf("整数2:");
scanf("%d", &m);
printf("较大的整数的值是%d\n", Max(n, m));
return 0;
}
该程序中定义了两个函数Max和mian。程序启动时会执行的时main函数。
运行结果:
如上图所示,执行到return b; return语句执行结束后,程序就会返回到原来进行调用的地方,再次执行被中断的main函数。
函数调用运算符的总结如下图:
函数调用的时候传递的只是函数的参数的值,因此调用函数时使用的实参既可以时变量,也可以是常量。
例如:下面的函数调用,将输出变量n1和5中比较哪一个值大
Max(n1,5);
另外需要注意的是,实参和形参是完全不同的两个东西,因此不用担心实参和形参的名字一样的问题。
上面我还提到了return 语句,它的结构如下图所示;
函数返回的是“表达式“的值,不能返回两个以上的值。
下面我来说下写求三个整数中的最大值的函数,函数接收的形参,以及函数内定义的变量,都是该函数自己的东西。在函数max3的形参a、b、c和main函数的变量a、b、c虽然名称相同,但是分别是不同的东西。结构如下:
代码如下:
#include
//返回三个数中的最大值函数
int max3(int a, int b, int c)
{
int max = a;
if (b>max)
{
max = b;
}
if (c>max)
{
max = c;
}
return max;
}
int main()
{
int a = 0;
int b = 0;
int c = 0;
puts("请输入三个整数。");
printf("整数1;");
scanf("%d", &a);
printf("整数2;");
scanf("%d", &b);
printf("整数3;");
scanf("%d", &c);
printf("最大值是:%d", max3(a, b, c, ));
return 0;
}
输入两个整数,计算他们的平方差并显示,代码如下:
#include
//计算整数的平方
int sqr(int a)
{
return a * a;
}
//返回差值
int diff(int a, int b)
{
return (a > b ? a - b : b - a);
}
int main()
{
int x = 0;
int y = 0;
puts("请输入两个整数。");
printf("整数1:");
scanf("%d", &x);
printf("整数2:");
scanf("%d", &y);
printf("x和y的平方差是%d。\n", diff(sqr(x), spr(y)));
return 0;
}
运行结果:
函数sqr会返回形参a所接收值的平方,所以函数调用表达式sqr(x)和sqr(y)的判断结果是16和25,这两个数会被直接作为调用函数diff时的实参传递。因此函数表达式diff(sqr(x),sqr(y))就是diff(16,25),对该表达式进行判断,就会得到函数diff返回的9。
在自己创建的函数中也可以调用其他函数,求4个数中的最大值代码如下:
#include
//求两个数最大值
int max2(int x, int y)
{
return (x > y ? x : y);
}
//求4个数的最大值
int max4(int a, int b, int c, int d)
{
return max2(max2(a, b), maxz(b, c));
}
int main()
{
int n1 = 0;
int n2 = 0;
int n3 = 0;
int n4 = 0;
puts("请输入4个整数。");
printf("整数1:");
scanf("%d", &n1);
printf("整数2:");
scanf("%d", &n2);
printf("整数3:");
scanf("%d", &n3);
printf("整数4:");
scanf("%d", &n4);
printf("最大值是:%d\n", max4(n1, n2, n3, n4));
return 0;
}
我们可以认为函数就是程序的一个零件,例如,想要实现显示功能的时候,就调用printf这个零件。在制作零件的过程中,如果有其他方便的零件,我们也可以大量的使用。
下面是一个计算幂的函数,如果n是整数,则通过对x进行n次乘法运算得出的n次方幂,代码如下:
#include
double power(double a, int b)
{
int i = 0;
double temp = 1.0;
for ( i = 1; i <= b; i++)
{
temp *= a;
}
return temp;
}
int main()
{
double a = 0.0;
int b = 0;
printf("求a的b次方幂。\n");
printf("实数a:");
scanf("%d", &a);
printf("整数b:");
scanf("%d", &b);
printf("%.2f的%d次幂是%.2f", a, b, power(a, b));
return 0;
}
如上所示,形参a被赋上实参a的值,形参b被赋上实参b的值,像这样通过值来进行参数传递的机制称为值传递。
函数间参数的传递是通过值传递进行的。
这就相当于我们复印一本书,在复印版的书上用红色铅笔写写画画,不会对原来那本书造成任何影响。
形参a是实参a的副本,形参b是实参b的副本。所以在被调用一方的函数中,即使改变接收的形参的值,调用一方的实参也不会改变。
上面讲到了函数定义和函数调用相关的基础知识,下面来讲更加正式的函数创建方法等。
显示出一个直角在左下方的等腰 直角三角形,代码如下:
#include
void put_stars(int n)
{
while (n-->0)
{
putchar('*');
}
}
int main()
{
int i = 0;
int len = 0;
printf("生成一个直角在左下方的等腰直角三角形。\n");
printf("短边;");
scanf("%d", &len);
for ( i = 0; i < len; i++)
{
put_stars(i);
putchar('\n');
}
}
本函数只是用来进行显示的,因此没有需要返回的值,这种没有返回值的类型,要声明为void。
通过函数使用put_stars可以把用于显示三角形的二重循环简化为一重循环,从而提高程序的可读性。
显示直角在右下方的等腰直角三角形代码如下:
#include
void put_chars(int ch, int n)
{
while (n-->0)
{
putchar(ch);
}
}
int main()
{
int i = 0;
int len = 0;
printf("生成一个直角在右下方的等腰直角三角形。\n");
printf("短边:");
scanf("%d", &len);
for ( i = 0; i < len; i++)
{
put_chars(' ', len - i);
put_chars('*', i);
putchar('\n');
}
return 0;
}
本程序还需要连续显示空白字符,因此需要创建另一个函数put_chars来代替函数put_stars。该函数可以连续显示出n个通过形参传递过来的字符。
输入一个正整数并显示其倒转之后的值,代码如下:
#include
int scan_pint(void)
{
int temp = 0;
do
{
printf("请输入一个正整数;");
scanf("%d", &temp);
if (temp<=0)
{
puts("\a请不要输入非正整数。");
}
} while (temp<=0);
return temp;
}
int rev_int(int num)
{
int temp = 0;
if (num>0)
{
do
{
temp = temp * 10 + num % 10;
num /= 10;
} while (num>0);
}
return temp;
}
int main()
{
int nx = scan_pint();
printf("该整数倒转之后的值是%d。\n", rev_int(nx));
return 0;
}
函数scan_pint读取从键盘输入的正整数并返回,该函数不接收形参,为了加以说明在小括号里面写了void。
main函数中声明变量nx的部分,该变量的初始值是函数scan_pint()的调用表达式,变量nx使用函数的返回值进行初始化。
函数scan_pint和函数rev_pint都包含一个拥有相同标识符的变量,但是它们却是各自独自不同的变量。
也就是说,函数scan_pint中的temp变量是函数scan_pint特有的变量,而rev_pint中的变量temp是函数rev_pint中特有的变量。
赋给变量的标识符,它的名称都有个一通用的范围,称为作用域。
在程序块中声明的变量的名称,只在该程序块中通用,在其他区域都无效,也就是说,变量的名称从变量声明的位置开始,到包含该声明的程序块最后的大括号为止,这一区间内通用,这样的作用域称为块作用域。
输入5名学生的分数,显示其中的最高分,代码如下:
#include
#define NU 5
int tensu[NU];
int top(void);
int mian()
{
extern int tensu[];
int i = 0;
printf("请输入%d学生的分数。\n",NU);
for ( i = 0; i < NU; i++)
{
printf("%d:", i + 1);
scanf("%d", &tensu[i]);
}
printf("最高分=%d\n", top());
return 0;
}
int top(void)
{
extern int tensu[];
int i = 0;
int max = tensu[0];
for ( i = 0; i < NU; i++)
{
if (tensu[i]>max)
{
max = tensu[i];
}
}
return max;
}
在函数的程序块中声明的变量等标识符是该程序块特有的部分,而像数组tensu这样,在函数外声明的变量标识符,其名称从声明的位置开始,到该程序的结尾都是通用的。这样的作用域称为文件作用域。
在上面的程序中声明,一个名为tensu的数组,像这样创建变量实体的声明称为定义声明,另外使用extern的声明表示”使用的在某处创建的tensu“,这里并没有真正创建出变量的实体,因此称为非定义声明。
由于数组tensu是在函数外定义的,所以只需要在main函数或top函数中明确声明要使用它,就可以放心使用。
编译器在读取数据的时候,也是按照从头到尾的顺序依次读取的,因为本程序中函数top的函数定义在main函数之后,所以要想在main函数中调用top函数,编译器就需要知道。
因此就需要如下声明:
int top(void);
像这样明确记述了函数的返回类型,以及形参的类型和个数等的声明称为函数原型声明。
函数原型声明只声明了函数的返回值和形参等相关信息,并没有定义函数的实体。如果函数返回值的类型和形式参数发生了改变,那么函数定义和函数原型声明两部分都必须进行修改。
通过函数原型声明,可以指定函数的参数以及返回值的类型等信息。这样就可以放心调用该函数了。
库函数printf或者putchar等的函数原型声明都包含在
#include
通过#include指令,就可以把
函数top的工作过程如下:
找出int型数组tensu最前面NU个元素的最大值,然后返回该值。
当我们创建函数的时候就需要考虑函数的通用性。
计算英语分数和数学分数,代码如下:
#include
#define NU 5
int max_of(int v[],int n)
{
int i = 0;
int max = v[0];
for ( i = 1; i < n; i++)
{
if (v[i]>max)
{
max = v[i];
}
}
return max;
}
int main()
{
int i = 0;
int eng[NU];
int mat[NU];
int max_e = 0;
int max_m = 0;
printf("请输入%d名学生的分数。\n", NU);
for ( i = 0; i < NU; i++)
{
printf("[%d]英语:", i + 1);
scanf("%d", &eng[NU]);
printf(" 数学:");
scanf("%d", &mat[NU]);
}
max_e = max_of(eng, NU);
max_m = max_of(mat, NU);
printf("英语最高分=%d\n", max_e);
printf("数学最高分=%d\n", max_m);
return 0;
}
函数max_of的动作如下:
找出包含任意个元素的int 类型数组中的元素的最大值,然后返回该值。
被调用函数中作为形参接收到的数组,就是函数调用时被作为实参的数组。
因此,对接受的数组元素进行修改,也会反映到调用时传入的数组中,下面让我们看以下代码:
#include
void set_zero(int v[], int n)
{
int i = 0;
for ( i = 0; i < n; i++)
{
v[i] = 0;
}
}
void printf_array(const int v[], int n)
{
int i = 0;
printf("{");
for ( i = 0; i < n; i++)
{
printf("%d", v[i]);
}
printf("}");
}
int main()
{
int ary1[] = {1,2,3,4,5};
int ary2[] = { 3,2,1 };
printf("ary1=");
printf_array(ary1, 5);
putchar('\n');
printf("ary2=");
printf_array(ary2, 3);
putchar('\n');
set_zero(ary1, 5);
set_zero(ary2, 3);
printf("把0赋值给两个数组的所有元素。\n");
printf("ary1=");
printf_array(ary1, 5);
putchar('\n');
printf("ary2=");
printf_array(ary2, 3);
putchar('\n');
return 0;
}
为了解决这个问题,C语言提供了禁止在函数内修改接收到的数组内容的方法,只要在声明形参的时候加上被称为const的类型修饰符就可以了。
如果只是引用所接收的数组元素的值,而不改写的话,在声明接收数组的形参,就应该加上const。这样函数调用方就可以放心的调用函数了。
在数组中查找目标值的程序代码如下;
#include
#define NU 5
#define FAILD -1
int search(const int vx[], int key, int n)
{
int i = 0;
while (1)
{
if (i==n)
{
return FAILD;
}
if (vx[i]==key)
{
return i;
}
i++;
}
}
int main()
{
int i = 0;
int ky = 0;
int idx = 0;
int vx[NU];
for ( i = 0; i < NU; i++)
{
printf("vx[%d]:", i);
scanf("%d", &vx[i]);
}
printf("要查找的值:");
scanf("%d", &ky);
//从元素个数为NU的数组中查找ky
idx = search(vx, ky, NU);
if (idx==FAILD)
{
puts("查找失败。");
}
else
{
printf("%d是数组的第%d号元素。\n", ky, idx + 1);
}
return 0;
}
函数search从元素数为n的int型数组vx的开头,顺次查找是否存在与key值相同的元素,如果有,则返回数组元素下标。如果没有,则返回FAILD,也就是-1。
函数search中while语句的控制表达式是”1“,因此只有在执行return 语句的时候才跳出循环,否则循环体将会一直重复执行下去。
像这样,从数组的开头出发顺次搜索,找出与目标的元素的一系列操作,称为线性查找或者顺序查找。
进行循环操作的时候,需要不停判断是否满足两个结束循环条件,虽说判断很简单,但是经过数次累积之后,也是一个不小的负担。
如果数组的大小还有富余,我们就可以把想要查找的数值存储到数组的末尾的元素v[n]
中,这样一来,即使数组没有想要查找的数值,当遍历到v[n]
的时候,也会满足条件。
在数组末尾追加数据称为哨兵,使用哨兵进行查找的方法称为哨兵查找法。使用这种方法可以简化对循环结束条件的判断。
代码如下:
#include
#define NU 5
#define FAILED -1
int search(int v[], int key, int n)
{
int i = 0;
v[n] = key;
while (1)
{
if (v[i]==key)
{
break;
}
i++;
}
return (i < n ? i : FAILED);
}
int main()
{
int i = 0;
int ky = 0;
int idx = 0;
int vx[NU + 1];
for ( i = 0; i < NU; i++)
{
printf("vx[%d]:", i);
scanf("%d", &vx[i]);
}
printf("要查找的值:");
scanf("%d", &ky);
if ((idx=search(vx,ky,NU))==FAILED)
{
puts("\a查找失败。");
}
else
{
printf("%d是数组的第%d号元素。\n", ky, idx + 1);
}
return 0;
}
由于函数search需要改变数组v的内容,因此在声明形参的时候不能加入const类型修饰符。
使用赋值运算符=进行赋值
将函数search的返回值赋给变量idx。
使用相等运算符==进行相等性的判断
判断赋值表达式idx=search(vx,ky,NU)和FAILED是否相等。
求4名学生在两次考试中3课程的的总分并显示。
代码如下:
#include
void mat_add(const int a[4][3], const int b[4][3], int c[4][3])
{
int i = 0;
int j = 0;
for ( i = 0; i < 4; i++)
{
for ( j = 0; j < 3; j++)
{
c[i][j] = a[i][j] + b[i][j];
}
}
}
void mat_printf(const int m[4][3])
{
int i = 0;
int j = 0;
for ( i = 0; i < 4; i++)
{
for ( j = 0; j < 3; j++)
{
printf("%4d", m[i][j]);
}
putchar('\n');
}
}
int main()
{
int tensu1[4][3] = { {91,63,78},{67,72,46},{89,34,53},{32,54,34} };
int tensu2[4][3] = { {97,67,82},{73,43,46},{97,56,21},{85,46,35} };
int sum[4][3];
mat_add(tensu1, tensu2, sum);
puts("第一次考试的分数:");
mat_printf(tensu1);
puts("第二次考试的分数:");
mat_printf(tensu2);
puts("总分:");
mat_printf(sum);
return 0;
}
要创建大规模程序,必须首先理解作用域和存储期。
在下面的程序中对变量x的声明总共有三处,代码如下:
#include
int x = 75;
void printf_x(void)
{
printf("x=%d\n", x);
}
int main()
{
int i = 0;
int x = 999;
printf_x();
printf("x=%d\n", x);
for ( i = 0; i < 5; i++)
{
int x = i * 100;
printf("x=%d\n", x);
}
printf("x=%d\n", x);
return 0;
}
运行结果:
首先我们来看下int x = 75;
处声明的x。该变量的初始值为75,因为它在函数外面声明定义的,所以这个x拥有文件作用域。
因此,函数print_x中的”x“就是上述的x,程序执行后,屏幕上会输出
x=75 ……显示的是x的值
因为printf_x();
处调用了函数printf_x,所以会首先进行上面的打印显示。
注意:
如果两个同名变量分别拥有文件作用域和块作用域,那么只要拥有块作用域的变量是”可见“的,而拥有文件作用域的变量会被”隐藏“起来。
当同名变量都被赋予了块作用域的时候,内层的变量是”可见“的,而外层的变量会被”隐藏“起来。
在函数中声明的变量,并不是从程序开始到程序结束始终有效的,变量的生存期也就是寿命有两种,它们可以通过存储期这个概念来实现。
代码如下:
#include
int fx = 0;
void func(void)
{
static int sx = 0;
int ax = 0;
printf("%3d%3d%3d\n", ax++, sx++, fx++);
}
int main()
{
int i = 0;
puts("ax sx fx");
puts("----------");
for ( i = 0; i < 10; i++)
{
func();
}
puts("----------");
return 0;
}
运行结果:
在函数func中声明了sx和ax两个变量,但是声明sx的时候我们使用了存储类说明符static。可能正因为如此,虽然使用相同的值进行初始化并递增的,但最终的ax和sx的值并不相同。
在函数中不使用存储类说明符static而定义出的对象(变量),被赋予了自动存储期,它具有以下特点:
程序执行到对象声明的时候就创建出了相对应的对象,而执行到包含该声明的程序块的结尾,也就是大括号的时候,该对象就会消失。
在函数中使用static定义出来的对象,或者在函数外声明定义出来的对象都被赋予了静态存储期,它具有以下特点:
在程序开始执行的时候,就具体地说是在main函数执行之前的准备阶段被创建出来,在程序结束的时候消失。
也就是说,该对象拥有了”永久“的寿命。另外如果不显示地进行初始化,则该对象会自动初始化为0。
对象的存储期如下图: