函数是什么

在数学里,我们经常接触并且为之头疼的就是函数,但是越头疼,反而用到的越多,数学中不开函数。同样在C语言里,也存在着函数。
在C语言里,函数是一个完成特定工作的独立程序模块,包括​​库函数​​和自定义函数两种。

库函数与自定义函数

所谓库函数,就是存放在C语言函数库中供人使用的函数,使用库函数,必须要包含相对应的头文件,像我们早期遇到的printf()、scanf()输出输入,都是库函数,在使用的时候都要包含<stdio.h>头文件。
库函数有很多种类,在这里推荐几个网站用来搜寻查找一些库函数的具体使用1、​​www.cplusplus.com​​ 2、http://zh.cppreference.com
在这里给大家举个使用过的案例:

#include<string.h>//strlen用来求字符串长度,必须包含头文件<string>
#include<stdio.h>

int main()
{
char arr[] = { "hello word!" };
int len = strlen(arr);
printf("%d", len);
//求出字符hello word!的长度,输出结果为11
return 0;
}

当然,仅仅凭借库函数的使用,是肯定满足不了我们的需求的,而这时,就需要自定义函数站出来了,自定义函数与库函数一样,有返回值类型,函数参数,以及函数名。所谓自定义函数,顾名思义,就是由自己来设计的函数,这就为每个人提供了更大的发挥空间以及操作上限。
大概形式如下:
ret_type fun_name(para1, * )
{
statement;//语句项
}
ret_type 返回类型
fun_name 函数名
para1 函数参数
在这里我们举一个例子:找出两个整数的最大值:

#include<stdio.h>//头文件包含
//在这里,get_max函数就属于自定义函数,由自己来定义
//int为返回值类型 get_max为函数名 x y为函数参数
//x,y对应下面的a,b
int get_max(int x, int y)
{
return (x > y) ? x : y;
//假如x>y,则返回x,否则,返回y
}

int main()
{
//定义两个变量
int a = 0;
int b = 0;
scanf("%d %d", &a, &b);

int c=get_max(a, b);//将函数返回的值放到c里面

printf("最大值为:%d", c);//输出最大值
}

形参与实参

实参

所谓实参,就是函数的实际参数,是真实传给函数的参数。实参可以是:常量、变量、表达式、函数等。

无论实参是何种类型的量,在进行函数调用时,它们都必须有确定的值,以便把这些值传送给形参。

形参

形式参数是指函数名后括号中的变量,因为形式参数只有在函数被调用的过程中才实例化(分配内存单元),所以叫形式参数。形式参数当函数调用完成之后就自动销毁了。因此形式参数只在函数中有效,就举上面的例子:

在上面的代码里,a,b都是实参,函数get_max里的参数x,y为形参。

再往下看:

 函数部分的详细讲解_库函数


可能很多人都会觉得是6个,但其实注意(v1,v2)这属于逗号表达式,逗号表达式是作为一个参数来使用,从左到右计算,最后返回右边的值。同理,(v3,v4)也是,所以正确答案应该是四个实参。

传值调用与传址调用

传值调用
简单来说,就是把实参的值传给形参。函数的形参和实参分别占有不同内存块,对形参的修改不会影响实参
传址调用
传址调用是把函数外部创建变量的内存地址传递给函数参数。
这种传参方式可以让函数和函数外边的变量建立起真正的联系,也就是函数内部可以直接操作函数外部的变量。
通俗来说就是:通过传址调用,形参与实参建立真正的联系,这时对形参的修改会影响实参。

举个例子来具体说明两者的区别:

//写一个函数可以交换两个整形变量的内容。
#include<stdio.h>
//形参与实参的名字是可以一样的,并不影响使用。写成int a,int b也可以的
void get(int x, int y)
{
int z = x;
x = y;
y = z;
}

int main()
{
int a = 20;
int b = 10;
get(a, b);

printf("%d %d", a, b);
//实际输出结果为20,10,由此可见,a与b并没有发生改变
return 0;
}

这个就是属于传值调用,我们可以看到在调用的函数里,我们明明把两个参数的值进行了交换,可为什么打印结果还是20 10呢?不应该是10 20嘛?
这就说明了一点,传值调用,对形参的修改并不会影响实参。再往下看:
如果写成这种形式:

//写一个函数可以交换两个整形变量的内容。
#include<stdio.h>
void get(int* x, int* y)//接收地址必须用到指针,因为指针就是用来存放地址的
//*x *y为形参,分别存放a与b的地址
{
int z = *x;
*x = *y;
*y = z;
}

int main()
{
int a = 20;
int b = 10;
get(&a, &b);//&为取地址符,这里把a和b的地址传过去
//&a,&b为实参

printf("%d %d", a, b);
//输出结果为10 20
return 0;
}

这里可以看到,对形参的修改直接影响到了实参。
所以,通过传址调用,形参与实参建立联系,此时,对形参的操作会影响到实参。

函数的嵌套调用与链式访问

函数和函数之间可以根据实际的需求进行组合的,也就是互相调用的。

#include <stdio.h>
void new_line()
{
printf("hehe\n");//打印hehe
}
void three_line()
{
int i = 0;
for (i = 0; i < 3; i++)
{
new_line();//进行3次循环,每一次循环都调用函数new_line
}
}

int main()
{
three_line();//调用函数three_line

return 0;
}

 函数部分的详细讲解_库函数_02


这里要注意:函数可以嵌套调用,但是不能嵌套定义。

嵌套定义就是这种:

 函数部分的详细讲解_#include_03

链式访问
就是把一个函数的返回值作为另外一个函数的参数:就比如这种:

#include<stdio.h>

int main()
{
printf("%d", printf("%d", printf("%d", printf("123456"))));

return 0;
}

printf它返回的是打印在屏幕上的字符个数。

 函数部分的详细讲解_递归_04

函数声明与定义

大家如果细心观察的话,就会发现我上面调用函数时,被调用的函数都放在了main函数上面,而我们书本中很多都是放在main函数下面。而我们假如也放在了下面,就会出现报错的情况,这是为什么呢?因为我们没对函数进行声明。
函数声明,就是告诉编译器有一个函数叫什么,参数是什么,返回类型是什么。函数的声明一般出现在函数的使用之前。要满足先声明后使用。

#include<stdio.h>
int add(int x, int y);
//函数声明
int main()
{
int a = 0;
int b = 0;
scanf("%d %d", &a, &b);
int c = add(a, b);

printf("%d", c);
return 0;
}

int add(int x, int y)
{
return x + y;
}

函数的定义是指函数的具体实现,交待函数的功能实现。

 函数部分的详细讲解_库函数_05

函数递归

程序调用自身的编程技巧称为​​递归​​( recursion)。递归作为一种算法在程序设计语言中广泛应用。 一个过程或函数在其定义或说明中有直接或间接调用自身的一种方法,它通常把一个大型复杂的问题层层转化为一个与原问题相似的规模较小的问题来求解,递归策略只需少量的程序就可描述出解题过程所需要的多次重复计算,大大地减少了程序的代码量。递归的能力在于用有限的语句来定义对象的无限集合。一般来说,递归需要有边界条件、递归前进段和递归返回段。当边界条件不满足时,递归前进;当边界条件满足时,递归返回。

简单来说,递归的核心思想就是把大事化小,完成递归需要有两个必要条件:
1、存在限制条件,当满足这个限制条件的时候,递归便不再继续。
2、每次递归调用之后越来越接近限制条件

这里举个例子:

//接受一个整型值(无符号),按照顺序打印它的每一位。
//例如:
//输入:1234,输出1 2 3 4

#include <stdio.h>
void print(int n)
{
if (n > 9)
{
print(n / 10);
}
printf("%d ", n % 10);
}

int main()
{
int num = 0;
scanf("%d", &num);
print(num);
return 0;
}

 函数部分的详细讲解_递归_06


再举一个例子:

#include<stdio.h>
//求Fun(2)
int Fun(int n)
{
if (n == 5)
return 2;
else
return 2 * Fun(n + 1);
}

 函数部分的详细讲解_#include_07


要执行Fun(3)就要先执行2*Fun(4),执行Fun(4)必须先执行Fun(5),Fun(5)=2,再回推过去。最后求得答案为16

递归递归,先递推,再回归