C语言学习(10)
一、C语言的函数
第五个: 递归函数(递归思想)
经验:分析问题的时候,发现问题解决步骤后面的步骤跟前面的步骤有数学上的关系,就可以用递归
1+.........+n //以前是用循环实现
特点:自己调用自己,这一类函数就是递归函数
缺点:递归函数如果递归的次数太多,有可能导致栈溢出
练习: 使用递归函数,实现求1到n的和
int fun(n) n+fun(n-1)-->n+n-1+fun(n-2)
#include <stdio.h> int fun(n) //求1---n的和 { //初始条件 if (n == 1) return 1; else return n + fun(n - 1); } int main() { printf("fun(100) is:%d\n", fun(100)); }
第六个:函数调用的过程(理论)
计算机的内存分为不同的区域: 栈内存 堆内存
堆内存(heap):C语言中malloc calloc realloc三个函数申请的内存空间就是堆空间
特点: 申请需要用到三个函数,使用完毕必须使用free释放掉(如果不释放会引起内存泄漏),不会随着你的程序的退出而自动释放
内存泄漏:一直申请堆内存,但是都没有释放,导致堆内存越来越少,最后没有可以使用的堆内存
栈内存(stack): 函数调用进栈出栈,局部变量保存在栈空间
特点:先进后出
栈空间是很小,不同的系统有所差别,大致8M左右
数据段(.data):细分为3个部分
.bss段
.data段
.rodata段
代码段(.text):细分为2个部分
.text段 专门用来存放C语言程序中的代码语句 printf strcpy
.init段 存放的是系统的一段启动代码(帮你找到main函数的入口地址,然后启动执行main函数)
总结: 函数调用就是一个不断地进栈(调用的时候),出栈(函数执行完毕)
如果代码中要定义比较大的数组,建议你使用堆空间(堆空间比栈空间大很多)
练习:
#include <stdio.h> 代码段 .text int n; //未初始化的全局变量--》 .bss int m = 99; //初始化的全局变量 --》 .data void fun(int n) //--》局部变量 栈 { printf("world"); //代码段 .text if (n < 10) //代码段 .text n += 8; // 栈 return 0; //代码段 .text } int main() { char *p = malloc(100); // p是局部变量 100字节堆空间 char buf[10] = "yueqian"; //局部变量 栈 fun(88); //栈 常量88 .rodata }
第七个:内联函数(简单的代码,多次使用的,你就定义成内联)
作用: 解决函数调用时候入栈出栈的资源消耗
如果函数的代码很简单,你需要反复经常使用(建议你定义成内联函数,节约函数调用时候入栈出栈的时间耗费,提高效率)
语法:inline 返回值 函数名字(参数)
{
}
缺点: 由于内联函数的底层原理: 编译器遇到内联函数,不会让它入栈,编译器会把内联函数的源码直接搬到你的main中,类似于这个代码直接写在了主函数里面(增加了主函数占用的内存),耗费了比较多的内存
第八个:函数指针和指针函数(跟数组类似的概念)
函数指针: 该指针指向一个函数
类型 * 指针名
C语言规定:函数名就是这个函数在内存中入口地址(首地址)
函数名就是个指针
char a[4]; char [4]
int fun(int a); //类型为 int (int)
int (*p)(int)=fun;
void fun(); // 类型 void ()
void (*p)()=fun;
作用:跟其它类型的指针一样,通过函数指针去调用函数
#include <stdio.h> int fun(int a, int b) //int (int,int) { return a + b; } int fun2(int a, int b) //int (int,int) { return a - b; } int fun3(int a, int b) //int (int,int) { return a * b; } int fun4(int a, int b) //int (int,int) { return a / b; } int main() { //定义函数指针指向fun int (*p)(int, int) = fun; //函数指针指向同一类型的函数 //传统方法 通过函数名调用 printf("结果:%d\n", fun(12, 13)); //通过函数指针调用 //疑惑:C语言中只有函数指针 p和*p等价的,其它类型的指针不是这样子 printf("p(12,13)结果:%d\n", p(12, 13)); printf("(*p)(12,13)结果:%d\n", (*p)(12, 13)); //改变指针的指向 p = fun2; printf("结果:%d\n", p(12, 13)); p = fun3; printf("结果:%d\n", p(12, 13)); p = fun4; printf("结果:%d\n", p(12, 13)); }
指针函数:只要一个函数的返回值类型是个指针,这个函数就是指针函数
char *fun(); //指针函数
char fun(char *buf); //不是指针函数
注意: 指针函数把指针作为返回值,需要注意千万不要返回局部变量的地址(很危险,局部变量会随着函数调用完毕自动释放地址)
解决方法:指针函数返回指针--》一定要使用堆空间的指针
#include <stdio.h> #include <string.h> #include <stdlib.h> /* 指针函数只有一个点需要重点注意: */ /* int *fun() //只要返回值是指针,fun就是指针函数 { int m=45; return &m; //warning: function returns address of local variable (局部变量) } */ int *newfun() //只要返回值是指针,fun就是指针函数 { int m = 45; int *p = malloc(4); //堆空间,不主动释放 *p = m; return p; } /* char *fun1() { char buf[10]="hello"; //局部变量--》栈空间 return buf; //warning: function returns address of local variable } */ char *newfun1() { char buf[10] = "hello"; //局部变量--》栈空间 char *p = malloc(10); //堆空间,不主动释放 strcpy(p, buf); return p; } int main() { int *p = newfun(); printf("fun返回的指针中存放的是:%d\n", *p); //正常情况下打印不了45 char *q = newfun1(); //关键问题是:buf在fun1执行完毕的一瞬间出栈释放掉了 printf("fun1返回的指针中存放的是:%s\n", q); //释放堆空间 free(p); free(q); }
第九个:变参函数(原理)和回调函数(理解概念,实现代码)
变参函数:函数的参数个数,类型是不确定的
int printf(const char *format, ...);
语法格式:任何变参函数,第一个参数必须是明确,后面的参数无所谓了
变参函数必须要有 ...(三个小数点,不能多不能少)
变参函数的原理: 由确定的参数(第一个参数),通过指针(va_list va_start()从起始位置开始)遍历,推导不确定的参数(后面的变参)
1 #include <stdio.h> 2 #include <stdarg.h> //实现变参函数必须包含此头文件 3 double sumup(char *format,...) 4 { 5 va_list p; //定义一个指向参数栈顶的指针 6 va_start(p,format); //让该指针指向第一个参数(变参函数第一个参数必须是确定的) 7 char args[10]; 8 int i,argnum=0; 9 for(i=0; format[i]!='\0'; i++) 10 { 11 if(format[i]=='#') 12 { 13 args[argnum]=format[++i]; 14 argnum++; 15 } 16 } 17 double result=0.0; 18 int argint; 19 double argdouble; 20 for(i=0; i<argnum; i++) 21 { 22 //注意:char和short会被自动提升为int float会被提升为double 23 switch(args[i]) 24 { 25 case 'i': //整数 char和short都会被提升为int,不能分开写 26 case 'c': 27 case 's': 28 //va_arg返回参数的值 29 argint=va_arg(p,int); //va_arg是系统定义的宏,让指针p回溯,并告诉p这个参数是int类型的 30 result+=argint; 31 break; 32 case 'f': 33 argdouble=va_arg(p,double); //va_arg是系统定义的宏,让指针p回溯,并告诉p这个参数是int类型的 34 result+=argdouble; 35 break; 36 default: 37 printf("format error\n"); 38 break; 39 } 40 } 41 va_end(p); //释放掉这个指针 42 return result; 43 } 44 int main() 45 { 46 int i=100; 47 char c='w'; 48 short s=88; 49 float f=3.14; 50 51 double answer=sumup("#i#c#s#f",i,c,s,f); //自定义#i表示整数 #c表示字符以此类推 52 printf("answer is:%lf\n",answer); 53 return 0; 54 }
回调函数: 我通过调用A函数,A函数又帮我自动去调用B函数,B函数被称为回调函数
关键点:必须要有函数指针作为A的参数,通过这个函数指针指向B
void (*signal(int sig, void (*func)(int)))(int); //不理会它的原型
1 #include <stdio.h> 2 #include <string.h> 3 /* 4 用代码模拟开车遇到红绿灯 5 6 */ 7 //B函数 --》信号灯,回调函数 8 void led(char *ledcolor) //void (char *) 9 { 10 if(strcmp(ledcolor,"red")==0) //红灯 11 printf("我停车!\n"); 12 if(strcmp(ledcolor,"green")==0) //绿灯 13 printf("我踩油门!\n"); 14 if(strcmp(ledcolor,"yellow")==0) //黄灯 15 printf("我缓慢行驶,随时停车!\n"); 16 } 17 18 //A函数 --》开车 19 /* 20 参数:ledcolor ---》红绿灯的颜色 21 p ---》指向B函数的一个指针 22 */ 23 void drive(void (*p)(char *),char *ledcolor) //p=chef name=name 24 { 25 //A函数里面自动调用B函数,根据不同的老师,有不同的选择 26 p(ledcolor); //函数指针调用对应的函数 27 } 28 29 int main() 30 { 31 //我直接调用的是A函数 32 drive(led,"red"); //函数名就是指针,当成实参传递 33 }