【C】函数真的难嘛?其实一点也不难,原理很简单。

什么是函数

程序是由多个零件组合而成的,而函数就是这种“零件”的一个较小单位。

main函数和库函数

C语言程序中,main函数是必不可少的。程序运行的时候,会执行main函数的主题部分。main函数中使用了printf、scanf、puts等函数。由C语言提供的这些为数众多的函数称为库函数。

什么是函数

当然,我们也可以自己创建函数。而实际上,我们也必须亲自动手创建各种函数。下面我们来自己创建一个简单的函数。
创建一个函数,接收两个整数参数,返回较大整数的值。
printf函数和scanf函数等创建得比较好得函数,即使不知道其内容,只要了解使用方法,也可以轻松使用。

函数定义

首先来说下函数的创建方法,这里我们来定义一个名为max2的函数,如下所示:
【C】函数真的难嘛?其实一点也不难,原理很简单。_第1张图片
这里的函数定义由多个部分构成。

函数头

该部分表示函数的名称和格式。虽然称为头函数,实际上说它是函数的“脸”可能更加的合适。

返回类型

函数返回的值——返回值的类型。该函数的情况下,返回的是两个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函数。
运行结果:
【C】函数真的难嘛?其实一点也不难,原理很简单。_第2张图片【C】函数真的难嘛?其实一点也不难,原理很简单。_第3张图片

  1. 函数的调用形式是在函数后面加上小括号,这个小括号称为函数调用运算符。使用函数调用运算符的表达式就称为函数调用表达式。
  2. 函数调用运算符括起来的是实参,当实参不止一个的时候,要是使用逗号将其分开。
  3. 进行函数调用后,程序的流程将一下子跳转到该函数处,main函数的执行就会中断,直到该函数调用完成之后才会继续执行main函数中的代码。
  4. 进行函数调用后,程序的流程会转到被调用函数处,这时,传递过来的实参值会被赋给函数接收的形参。
  5. 形参的初始化完成后,将执行函数体。在程序中遇到return 语句,或者执行到函数体最后的大括号是,就会从该函数跳到调用函数。

【C】函数真的难嘛?其实一点也不难,原理很简单。_第4张图片
如上图所示,执行到return b; return语句执行结束后,程序就会返回到原来进行调用的地方,再次执行被中断的main函数。
函数调用运算符的总结如下图:
image.png
函数调用的时候传递的只是函数的参数的值,因此调用函数时使用的实参既可以时变量,也可以是常量。
例如:下面的函数调用,将输出变量n1和5中比较哪一个值大
Max(n1,5);
另外需要注意的是,实参和形参是完全不同的两个东西,因此不用担心实参和形参的名字一样的问题。
上面我还提到了return 语句,它的结构如下图所示;
【C】函数真的难嘛?其实一点也不难,原理很简单。_第5张图片
函数返回的是“表达式“的值,不能返回两个以上的值。

三个数中的最大值

下面我来说下写求三个整数中的最大值的函数,函数接收的形参,以及函数内定义的变量,都是该函数自己的东西。在函数max3的形参a、b、c和main函数的变量a、b、c虽然名称相同,但是分别是不同的东西。结构如下:
【C】函数真的难嘛?其实一点也不难,原理很简单。_第6张图片
代码如下:

#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;
}

运行结果:
【C】函数真的难嘛?其实一点也不难,原理很简单。_第7张图片
函数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指令,就可以把中的全部内容都读取到程序中。包含库函数的函数原型声明的称为头文件,而取得头文件内容的#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 类型数组中的元素的最大值,然后返回该值。

函数的传递和const类型的修饰符

被调用函数中作为形参接收到的数组,就是函数调用时被作为实参的数组。
因此,对接受的数组元素进行修改,也会反映到调用时传入的数组中,下面让我们看以下代码:

#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;
}

代码运行结果:
【C】函数真的难嘛?其实一点也不难,原理很简单。_第8张图片

作用域和存储期

要创建大规模程序,必须首先理解作用域和存储期。

作用域和标识符的可见性

在下面的程序中对变量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;
}

运行结果:
【C】函数真的难嘛?其实一点也不难,原理很简单。_第9张图片
首先我们来看下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;
}

运行结果:
【C】函数真的难嘛?其实一点也不难,原理很简单。_第10张图片
在函数func中声明了sx和ax两个变量,但是声明sx的时候我们使用了存储类说明符static。可能正因为如此,虽然使用相同的值进行初始化并递增的,但最终的ax和sx的值并不相同。

  • 自动存储期

在函数中不使用存储类说明符static而定义出的对象(变量),被赋予了自动存储期,它具有以下特点:
程序执行到对象声明的时候就创建出了相对应的对象,而执行到包含该声明的程序块的结尾,也就是大括号的时候,该对象就会消失。

  • 静态存储期

在函数中使用static定义出来的对象,或者在函数外声明定义出来的对象都被赋予了静态存储期,它具有以下特点:
在程序开始执行的时候,就具体地说是在main函数执行之前的准备阶段被创建出来,在程序结束的时候消失。
也就是说,该对象拥有了”永久“的寿命。另外如果不显示地进行初始化,则该对象会自动初始化为0。
对象的存储期如下图:
【C】函数真的难嘛?其实一点也不难,原理很简单。_第11张图片

总结

  • 将多个处理集中到一起进行时,可以使用函数这一程序的零件。返回类型、函数名、形参这三个部分决定了函数的特征。不接收参数的函数,其形参类型为void。
  • 函数体是复合语句(程序块)。如果有仅在函数中使用的变量,原则上应在该函数中声明和使用。
  • 函数调用的形式是在函数名后面加上小括号,这个括号称为函数调用运算符。如果没有实参,则小括号为空。有多个实参的情况下,使用逗号隔开。
  • 进行函数调用后,程序的流程将一下子跳转到该函数处。

【C】函数真的难嘛?其实一点也不难,原理很简单。_第12张图片
【C】函数真的难嘛?其实一点也不难,原理很简单。_第13张图片
【C】函数真的难嘛?其实一点也不难,原理很简单。_第14张图片
以上就是我关于函数的介绍,希望都帮到大家。也请大家给我指点不足,谢谢!!!

你可能感兴趣的:(c语言,c++,开发语言)