一、函数概念?
1、 在linux C语言中,什么是函数?
C语言是一种面向过程的语言,C语言称之为函数式语言,可以将任何功能都封装成一个函数接口。
2、 在C语言中,封装函数意义?
在某些功能比较复杂,往往用户需要将其封装成一个函数接口,调用者使用该函数时,不需要关注过程,只需要知道传递什么参数和函数有什么返回值即可。
play_music(“xxx.mp3”); -> 参数就是这首歌名字。
听到这首歌 -> 返回值就是听到声音。
二、函数的书写规则?
1、 在linux C语言中,函数有哪些种类?
main函数: 主函数,在C语言中有且仅有一个,一般都在main函数开始执行,然后调用别的函数。
系统函数(system calls):man手册的2手册中,这些函数不需要用户写实现过程。
库函数(library calls):man手册的3手册中,这些函数不需要用户写实现过程。
自定义函数:不能在man手册查询得到,用户必须写清楚实现过程。
例子:
#include <stdio.h>
//1. main函数
int main(int argc,char *argv[])
{
//2. 调用printf()库函数
printf("helloworld!\n");
//3. 调用自定义函数
func(); //undefined reference to `func' -> func()实现过程没有定义!
return 0;
}
2、自定义函数的书写规则?
1)确定函数的函数名 -> 函数名字最好体现函数的功能
命名规则: 与C语言中变量一样。 例子: my_fun
2)确定函数的形式参数列表
有形式参数:my_fun(形式参数1,形式参数2, … ,形式参数n) -> 有多少个形参,将来就需要传递多少个实参。
无形式参数:my_fun()
什么是形式参数?
形式参数在函数中作用,首先声明好,要调用该函数,首先需要什么参数。
例子: 露营(一张大被子,一张厚席子,一个高枕头,一个绿色桶)
例子:my_fun(int a,int b) -> 说明调用该函数时,需要传递二个整型数据给这个函数!
3)确定返回值类型 -> 写在名字前面
有返回值 -> 确定返回值类型
int -> 无论这个函数是调用成功还是失败,都会返回一个int类型。
char -> 无论这个函数是调用成功还是失败,都会返回一个char类型。
float -> 无论这个函数是调用成功还是失败,都会返回一个float类型。
例子:
int my_fun(int a,int b)
return 0;
无返回值 -> void -> 代表该函数没有返回值
例子: void my_fun(int a,int b)
返回值类型 函数名字(形式参数列表) -> 自定义函数头
4)函数体 -> {}
例子:
int my_fun(int a,int b)
{ -> 功能就写在函数体中
}
3、如何调用函数?
1)确保调用函数实现过程已经写好了!
例子:
int my_fun(int a,int b)
{
printf("a = %d\n",a);
printf("b = %d\n",b);
return 0;
}
2)直接写函数名字,后面紧跟一个圆括号(),圆括号中存放东西就是实际参数(实参)。
例子:
my_fun(5,10);
结论: 形式参数值由实际参数直接初始化。 -> 这是不可逆的过程。
上述例子: a=5,b=10;
4、函数返回值情况
int my_fun(int a,int b) -> 需要接受两个参数,返回一个int类型数据。
int ret;
ret = my_fun(5,10); -> 将my_fun函数的返回值结果赋值给ret。
练习3:写一个函数实现接收三个int参数,经过约定的运算后,得到结果并打印出来。
前面两个参数是计算,最后参数计算方式。
计算方式:
1 -> +
2 -> -
3 -> *
4 -> /
fun(3,4,2) -> 结果: 3-4=-1
fun(3,4,3) -> 结果: 3*4=12
#include <stdio.h>
int my_fun(int x,int y,int z) // x=a y=b z=c
{
int ret;
switch(z)
{
case 1:
ret=x+y;
break;
case 2:
ret=x-y;
break;
case 3:
ret=x*y;
break;
case 4:
ret=x/y;
break;
default:
printf("enter error!\n");
break;
}
return ret;
}
int main(int argc,char *argv[])
{
int a,b,c,result;
while(1)
{
scanf("%d %d %d",&a,&b,&c);
result = my_fun(a,b,c);
printf("result = %d\n",result);
}
return 0;
}
练习4:以下程序运行结果是?
int fun(int a,int b)
{
if(a > b) return a;
else return b;
}
int main(int argc,char *argv[])
{
int x=3,y=8,z=6,r;
r = fun(fun(x,y),2*z);
printf("%d\n",r); //12
}
三、函数的声明?
1、 函数的定义与函数的声明有什么区别?
函数的定义包含了函数的名字,返回值类型,形式参数列表,具体实现过程。
例子:
int my_fun(int a)
{
printf("a = %d\n",a);
return 0;
}
函数的声明包含了名字,返回值类型,形式参数列表,函数声明必须写在函数调用之前。
用通俗的话来讲,函数的声明意思是: 在你调用某个函数之前你必须要告诉我,这个函数长什么样子。
例子:
int my_fun(int a);
2、 函数声明需要注意的点
1)如果函数定义写在函数调用之前,就不要声明了。
例子:
int my_fun2(); -> 需要声明
int my_fun1() -> 不需要声明
{
}
int main()
{
my_fun1();
my_fun2();
}
int my_fun2() -> 需要声明
{
}
2)一般库函数/系统函数声明都是放在头文件中
例子:
printf()函数声明写在#include <stdio.h>里面.
所以当我们调用printf()函数时,不需要自己声明,只需要包含对应的头文件即可。
四、从内存的角度分析自定义函数运行过程
1、 回顾栈区以及函数特征
1)函数内部申请的变量都是栈区申请。
2)函数返回时,在函数内部申请过的全部栈区的内存空间都要全部释放。
3)函数返回时,会返回到函数调用的地方。
例题1: 请从内存角度分析以下代码,求出结果?
int swap(int x,int y)
{
int t;
t = x;
x = y;
y = t;
return 0;
}
int main(int argc,char *argv[])
{
int a,b;
scanf("%d %d",&a,&b); //5 3
swap(a,b);
printf("a = %d,b = %d\n",a,b); //5 3
return 0;
}
例题2: 请从内存角度分析以下代码,求出结果?
int a,b;
int swap()
{
int t;
t = a;
a = b;
b = t;
return 0;
}
int main(int argc,char *argv[])
{
scanf("%d %d",&a,&b); //5 3
swap();
printf("a = %d,b = %d\n",a,b); //3 5
return 0;
}
结论:
1)形式参数与实际参数占用不同的储存单位。
2)形式参数值由实际参数直接初始化。
五、函数嵌套?
1、 什么是函数嵌套?
函数嵌套就是调用某个函数内部再调用另外一个函数。
2、 有函数嵌套程序在内存有什么特点?
如果嵌套的函数很多,就会在栈区累积非常多空间没有被释放。
3、 函数嵌套与递归函数有什么区别?
函数嵌套:自己调用别人的函数。
例子:
void fun()
{
my_fun();
}
递归函数:自己调用自己的函数。
例子:
void fun()
{
fun();
}
六、递归函数?
1、 特点?
递归函数特点自身调用自身,无限递归,没有终止的时刻。
例子:
void fun()
{
int a;
fun(); -> 无限递归,没有终止条件 -> 导致栈空间溢出!
return;
}
int main()
{
fun();
return 0;
}
结论: 为了防止栈空间溢出,所以递归函数一般都会携带终止条件,也就是说到达某个条件时,函数返回!
2、题型1: 给程序,求结果。
例题,求出下面程序的结果?
void fun(int n)
{
if(n > 1)
{
fun(n-1);
}
printf("n = %d\n",n);
return;
}
int main()
{
fun(100);
return 0;
}
答案:1~100
思路: 找出终止条件,列出几项,找规律,再画图分析!
3、 题型2: 给规则,写程序。
例题:写出下列程序。
有5个学生坐在一起,问第5个学生多少岁,他说比第4个学生大2岁。问第4个学生多少岁,他说比第3个学生大2岁。问第3个学生多少岁,他说比第2个学生大2岁。问第2个学生多少岁,他说比第1个学生大2岁。最后问第1个学生多少岁,他说他是10岁,请问第5个同学多少岁?使用递归函数来完成。
#include <stdio.h>
int get_child_age(int n)
{
int age;
if(n <= 0) // -> 搞事情
return -1;
if(n == 1) // -> 终止条件
age = 10;
if(n > 1) // -> 正常
age = get_child_age(n-1) + 2;
return age;
}
int main(int argc,char *argv[])
{
int ret;
ret = get_child_age(5);
printf("ret = %d\n",ret);
return 0;
}
思路: 写框架,分析情况 -> 最终目标与终止条件之间的关系。
练习1: 画出以上例子的内存图。
练习2: 有以下程序,求出结果?
int fun(int n) 10 9
{
if(n==1) return 1;
else return (n+fun(n-1)); 10+9+8+7+6+5+4+3+2+1
}
int main()
{
int x;
scanf("%d",&x); //若输入一个数字10。
x = fun(x); 10
printf("%d\n",x); //55
}
练习3:第1天存100块,后面每一天都比昨天多存10块,第312天存了多少?
#include <stdio.h>
int get_money(int n)
{
int money;
if(n <= 0)
return -1;
if(n == 1)
money = 100;
if(n > 1)
money = 10 + get_money(n-1);
return money;
}
int main()
{
int ret;
ret = get_money(10);
printf("ret = %d\n",ret);
return 0;
}
练习4: 使用循环方式完成练习3,画内存图分析两种方式差别。
循环 -> 多次使用一个变量
递归 -> 每次都开辟一个新的变量空间 --> 容易造成栈空间溢出!
七、回调函数
1、 什么是回调函数?
先定义好函数A(喝水),再将这个函数A作为另外一个函数B(爬山)的参数,在函数B(爬山)可以回过头调用这个参数(喝水),这个函数A就称之为回调函数。
2、 基本框架?
void funB(funA)
{
funA..... 在函数B中调用函数A
}
void funA() -> 回调函数
{
}
int main()
{
funB(funA);
}
例题: 从键盘中获取两个数值,求出两个值的和,要求使用回调函数完成。
int get_result(int a,int b,int(*p)(int,int)) -> 就把这个p当成函数名字
{
int ret;
ret = p(a,b);
return ret;
}
int add_fun(int x,int y)
{
int z;
z = x + y;
return z;
}
int main()
{
int a,b,ret;
scanf("%d %d",&a,&b);
ret = get_result(a,b,add_fun); -> 函数名字作为参数,其实是传递了函数的地址过来。
printf("ret = %d\n",ret);
}
补充:
函数指针参数怎么写?
1)先写一个 *
2)在*后面写一个变量名,使用圆括号括住 (*p)
3)确定函数是哪个? int add_fun(int x,int y)
4)把第3步函数名与形参的变量名都去掉 int (int,int)
5)把第2步结果写在第4步结果的返回值类型与形式参数列表之间 int(*p)(int,int)
3、 回调函数使用场景?
在数据库、系统编程信号通信中常常看到回调函数。
练习4: 连续从键盘中获取4个数字,求出其中的最大值,要求回调函数来完成。
int max4(int a,int b,int c,int d,int(*p)(int,int))
{
int m;
m = p(a,b);
m = p(m,c);
m = p(m,d);
return m;
}
int max2(int x,int y)
{
int z;
z = (x > y ? x : y);
return z;
}
int main()
{
int a,b,c,d,ret;
ret = max4(a,b,c,d,max2);
return 0;
}
八、变参函数
1、 什么是变参函数?
变参函数是指该函数的参数不固定,例如:printf()函数。
printf("helloworld!\n"); -> 参数为1
printf("a = %d\n",a); -> 参数为2
printf("(%d %d)\n",x,y); -> 参数为3
结论: printf()参数为"1+x"
2、如何判断一个函数是不是变参函数?
#include <stdio.h>
int printf(const char *format, ...);
const char *format: 输出字符串格式
... : 参数不定
3、 类似的变参函数有很多: printf() scanf() ioctl() fcntl()
九、内联函数?
1、 什么是内联函数?
在程序中调用函数时,需要花费一定的时间进行保护现场与恢复现场,但是使用了内联函数,就既可以使用函数,又不需要花费时间来进行保护现场与恢复现场。
封装:test.c
#include <stdio.h>
void fun()
{
printf("hello!\n");
}
int main()
{
fun();
fun();
fun();
return 0;
}
不封装:test.c
#include <stdio.h>
int main()
{
printf("hello!\n");
printf("hello!\n");
printf("hello!\n");
return 0;
}
2、 如何实现内联函数?
test.c
int main()
{
fun();
fun();
fun();
return 0;
}
head.h
#include <stdio.h>
inline void fun()
{
printf("hello!\n");
}
3、如何解决保护恢复现场问题?
1)使用内联函数 ->inline
2)不要过多封装成函数,当某些表达式组成一个功能时,才封装为一个函数。
4、在嵌入式中哪里看到内联函数?
在内核链表时看到内联函数。