一、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 ", 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 ", fun(12, 13)); //通过函数指针调用 //疑惑:C语言中只有函数指针 p和*p等价的,其它类型的指针不是这样子 printf("p(12,13)结果:%d ", p(12, 13)); printf("(*p)(12,13)结果:%d ", (*p)(12, 13)); //改变指针的指向 p = fun2; printf("结果:%d ", p(12, 13)); p = fun3; printf("结果:%d ", p(12, 13)); p = fun4; printf("结果:%d ", 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 ", *p); //正常情况下打印不了45 char *q = newfun1(); //关键问题是:buf在fun1执行完毕的一瞬间出栈释放掉了 printf("fun1返回的指针中存放的是:%s ", 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]!='