一、函数是什么?
函数有很多说法,维基百科中对函数的定义:子程序
子程序是一个大型程序中的某部分代码,由一个或多个语句块组成。负责完成某项特定任务,而且相较于其他代码,具备相对的独立性。
一般会有参数并有返回值,提供对过程的封装和细节诶的隐藏。这些代码通常被集成为软件库。
二、C语言中函数的分类
2.1 库函数
我们在开发的过程中每个程序员都可能用的到的基础功能(printf、strcpy、pow),为了支持可移植性和提高程序的效率,C语言便有了一系列的类似的可以函数,方便程序员进行软件开发。
通过这个链接可以学习库函数的使用:
可以将库函数归纳为6种:
IO函数(输入输出函数) scanf printf …
字符串操作函数 issupper …
内存操作函数 memset …
时间/日期函数 time …
数学函数 sqrt pow …
其他库函数 …
我们参照文档,学习几个库函数
strcpy (复制字符串)
其形式为char * strcpy ( char * destination, const char * source );
destination目的地
source源
如图:
menset(填充内存块)
其形式为void * memset ( void * ptr, int value, size_t num );
如图:
注意:使用库函数,必须包含#include对应的头文件。
2.2 自定义函数
当然库函数不能实现所有的功能,所以更重要的还是自定义函数,例如比较两个数的最大值
#include <stdio.h>
int 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 ret=max(a,b);//将实参a和b传给形参x,y,并将自定义函数返回的值赋给变量ret
printf("%d", ret);
return 0;
}
输入2和3,输出最大值3
再例如自定义函数实现交换两个数
int swap(int* x, int* y)
{
int temp = *x;
*x = *y;
*y = temp;//*解引用操作符,通过地址直接找到实参的值,然后才改变实参的值
}
int main()
{
int a = 0;
int b = 0;
scanf("a=%d b=%d", &a, &b);//这里要使用&,将a和b的地址传过去,注意的是在自定义实现函数中,仅仅改变形参是不会影响实参的,所以就要通过地址来直接改变实参的值
int ret = swap(&a, &b);
printf("a=%d b=%d", a, b);
return 0;
}
输入a=2 b=3输出交换后的a=3 b=2
三、函数的参数
3.1 实际参数
真实传给函数的参数,叫实参
实参可以是:常量、变量、表达式、函数等。
注意:无论实参是何种类型的量,在进行函数调用时,他们都必须有确定的值,以便把这些值传送给形参
3.2 形式参数
形式参数是指函数名后括号中的变量
注意:形式参数只有在函数被调用的过程中才实例化(分配内存空间),所以叫做形式参数。当形式参数调用完之后分配的空间就会自动销毁。因此形式参数只在自定义的函数中有效。
这样可以看出上面交换例子的案例为什么要以地址的形式传过去,而不是直接传值过去
现在我们对函数的实参和形参进行分析:
通过对比两图就可以发现传值和传址的不一样了,所以我们可以简单地认为:形参实例化之后其实相当于实参的一份临时拷贝。
四、函数的调用
4.1 传值调用
函数的形参和实参分别占有不同的内存空间,对形参的修改不会影响实参。
4.2 传址调用
传址调用是把函数外部创建变量和内存地址传递给函数参数的一种调用函数的方式。
这种传参方式可以让函数和函数外边的变量建立起真正的联系,也就是函数内部可以直接操作外部的变量。
这两者的调用就如上面的交换两个数的代码所示。
五、函数的嵌套调用和链式访问
函数和函数之间可以进行相互调用
5.1 嵌套调用
例如:实现两数之和
#include <stdio.h>
int Add_1(int x, int y)
{
return x + y;//将值返回给函数Add_1
}
int Add(int x, int y)
{
return Add_1(x, y);//将函数Add_1的值返回给Add
}
int main()
{
int a = 2;
int b = 3;
int c=Add(a,b);
printf("%d", c);
return 0;
}
注意:函数可以嵌套调用,但是不可以嵌套定义!
5.2 链式访问
例如:
int main()
{
int a = 2;
printf("%d ", printf("%d ", printf("%d ", a)));//printf的返回值是在屏幕上打印的字符的个数
return 0;
}
其访问顺序及如何打印如下:
故其结果为:
六、函数的声明和定义
6.1函数声明
1告诉编译器有一个函数叫什么、参数是什么、返回类型是什么。但是具体是否存在,函数声明决定不了。
2.函数的声明一般出现在函数的使用之前。满足先声明后使用。
3.函数的声明一般放在头文件中。以.h的形式为后缀,例如:#include game.h
int menu(); 这就是一个函数声明形式,返回类型为整形,函数名为menu,参数为空,注意声明是一条语句需要加分号。
void caf();
int nba(int x,int y);//此声明多了参数
6.2函数定义
int menu()
{
int a=0;
int b=0;
return a+b;
}//这一部分是函数的定义,其包括了函数的声明,可以说函数的定义是一种特殊的函数声明
int main()
{
int c=menu();
printf("%d",c);
return 0;
}
七、函数递归
7.1什么是函数递归
递归作为一种算法,一个过程或函数在其定义或说明中有直接或间接调用自身的一种方法,能够大事化小,减少了程序的代码量。
7.2递归的两个必要条件
.存在限制条件,当满足这个限制条件的时候,递归便不再继续。
.每次递归调用之后越来越接近这个限制条件。
7.2.1一个递归练习及图解
void print(int x)
{
if (x > 9)
{
print(x / 10);
}
printf("%d ", x % 10);
}
int main()
{
int num = 1234;
print(num);
return 0;
}
这是对次函数递归的一个图解:
最终打印结果为:
7.3递归与迭代
7.3.1什么是迭代
迭代是指通过循环重复执行某个操作的过程。迭代不涉及函数调用本身,每次迭代会根据上一次迭代的结果进行计算,并不断更新结果。迭代适用于大部分问题的解决,并且在性能上通常优于递归,因为迭代不会造成函数调用栈的过多增长。
7.3.2对比递归和迭代区别
n的阶乘:
int mul(int n)
{
if (n <= 1)
return 1;
else
return n * mul(n - 1);
}
int main()
{
//n的阶乘
int n = 0;
scnaf("%d", &n);
int a=mul(n);
printf("%d", a);
return 0;
}
斐波那契数列:
int fin(int x)
{
if (x <= 2)
return 1;
else
return fib(x - 2) + fib(x - 1);
}
int main()
{
int a = 0;
scanf("%d", &a);
int b = fib(a);
printf("%d", b);
return 0;
}
以上两者是利用递归方式来求解n的阶乘和斐波那契数列,但是当n的值足够大以及a的值足够大,会发现递归时在不断的开辟空间,特别耗时间,甚至出现栈溢出。对于以上结果可以采用迭代的方式来进行。
n的阶乘:
int main()
{
int a = 0;
scanf("%d", &a);
int sum = 0;
while (a > 1)
{
sum *= a;
a -= 1;
}
printf("%d", sum);
return 0;
}
斐波那契数列:
int main()
{
int n = 0;
scanf("%d", &n);
int a = 1;
int b = 1;
int c = 0;
while (n > 2)
{
c = a + b;
a = b;
b = c;
n -= 1;
}
printf("%d", c);
return 0;
}
提示:
1.许多问题以递归的形式进行解释的,这只是因为他比非递归的形势更为清晰
2.但是这些为题的迭代实现往往比递归实现效率更高,虽然代码的可读性稍微差些
3.当一个问题相当复杂,难以用迭代实现时,此时递归实现的简洁性可以补偿。
end~