作业分析讲解
=====================
1. 数组直接当成形参--》迷惑有些同学,表面上好像不是指针,很多同学理解为传值
数组作为形参或者作为实参都是传递的地址
void foo(int b[3][3])
{
++b; //b跟数组名a等价,是int (*)[3]数组指针
b[1][1]=9 //等价于 *(*(b+1)+1) b指向{7,9,9}
}
启发: 去面试遇到指针看不懂--》一定想办法换算成你最熟悉
遇到二级指针--》换算成一级方便查看
遇到二维数组--》换算成数组指针,或者其他类型的指针
C语言的函数
=========================
第五个: 递归函数(递归思想)
经验:分析问题的时候,发现问题解决步骤后面的步骤跟前面的步骤有数学上的关系,就可以用递归
1+.........+n //以前是用循环实现
特点:自己调用自己,这一类函数就是递归函数
缺点:递归函数如果递归的次数太多,有可能导致栈溢出
练习: 使用递归函数,实现求1到n的和
int fun(n) n+fun(n-1)-->n+n-1+fun(n-2)
第六个:函数调用的过程(理论)
计算机的内存分为不同的区域: 栈内存 堆内存
堆内存(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;
作用:跟其它类型的指针一样,通过函数指针去调用函数
指针函数:只要一个函数的返回值类型是个指针,这个函数就是指针函数
char *fun(); //指针函数
char fun(char *buf); //不是指针函数
注意: 指针函数把指针作为返回值,需要注意千万不要返回局部变量的地址(很危险,局部变量会随着函数调用完毕自动释放地址)
解决方法:指针函数返回指针--》一定要使用堆空间的指针
第九个:变参函数(原理)和回调函数(理解概念,实现代码)
变参函数:函数的参数个数,类型是不确定的
int printf(const char *format, ...);
语法格式:任何变参函数,第一个参数必须是明确,后面的参数无所谓了
变参函数必须要有 ...(三个小数点,不能多不能少)
变参函数的原理: 由确定的参数(第一个参数),通过指针(va_list va_start()从起始位置开始)遍历,推导不确定的参数(后面的变参)
回调函数: 我通过调用A函数,A函数又帮我自动去调用B函数,B函数被称为回调函数
关键点:必须要有函数指针作为A的参数,通过这个函数指针指向B
void (*signal(int sig, void (*func)(int)))(int); //不理会它的原型
练习:
1. 模仿我刚才写的回调函数,模拟信号灯
B函数
A函数 void dirve(红灯,B函数指针)
void dirve(绿灯,B函数指针)
void dirve(黄灯,B函数指针)