传递参数
传递大容量数据
使用指针传递大容量数据的参数,主函数和子函数使用的是同一套数据,避免了参数传递过程中的数据复制,提高了运行效率,减少了内存占用
使用指针传递输出参数,利用主函数和子函数使用同一套数据的特性,实现数据的返回,课实现多返回值的设计
写一个有参数传递的函数 (值传递)
参数传递过程
上面的代码在内存中的过程:
首先申请变量a的4字节内存
调用子函数,把a传递到函数参数
调用子函数的过程有定义变量,数据复制的过程:先在内存中申请,定义int类型局部变量param,把数据啊,复制到param的变量的内存中
截图up主画的
变量a和变量param,存在了两个地方,隔离主函数和子函数的数据,防止出现一些安全问题。
改变子函数中param的值,主函数中变量a的数值是否会改呢?
因为子函数是新申请的内存,所以改变子函数的数据, 不会影响主函数的变量
up主在这举了一个例子:学渣向学霸抄作业(a),学霸不放心学渣,担心学渣对自己的作业(a)乱涂乱画,那学霸找个新的本子,把作业抄上去(抄到param参数里),然后给学渣抄复制份作业(param),就算学渣乱涂乱画复制份作业(param),子函数运行结束后(学渣抄完),这个复制份参数(param)就销毁作废了,那也不会影响到自己的作业内容(a)
值传递的好处就在此!
不过值传递有个弊端,那学霸借学渣抄作业,值传递有定义变量、数据复制的过程。如果作业那都是选择题,那还好说,如果作业都是计算题大题,那学霸还得再抄一份才能给学渣,这不就浪费时间了嘛,费时费力,学霸才不抄呐
如果要传递的参数是一个数据量非常大的数组,用值传递,就需要复制一个相同内存大小的数组
指针传递就可以避免这个数据复制的过程,哎,学霸就把作业放那,告诉学渣作业在哪,让他自己去找,哈哈哈~_~ ~_~ ~_~
地址传递:
写一个寻找数组中最大值的函数学习一下地址传递
定义两个形参 int* 类型的变量参数,用来接收数组,count是这个数组的长度,否则不好遍历数组
1. #include <stdio.h> 2. 3. int FindMax(int *array, int count) { //用int*类型的指针接收数组这个参数, 4. int i; 5. int Max = array[0]; //初始化 6. for (i = 1; i < count; i++) { //遍历数组,从1开始 7. if (array[i] > Max) 8. Max = array[i]; 9. } 10. return Max; 11. } 12. 13. int main(void) { 14. int a[] = {100, 60, 8, 14, 10, 89}; 15. int Max; 16. Max = FindMax(a, 6); //数组名就是指针,把a做参数传入进去 17. printf("%Max = %d\n", Max); 18. 19. return 0; 20. }
调用这个函数,是间接访问数据(在数组传参的过程中)
子函数中array[0]是取出array当做指针地址下的第一个数据,然后找a的地址,找a的第0位数据
之后就遍历数组查找最大值……
这样做就只是新创建了一个int*类型的变量,只是多占用了8个字节的内存,但是没有复制一份,省程序的空间,但是这样的话,主函数和子函数共用这个数组
执行完FindMax函数之后,这个array局部变量就会被销毁,主函数仍可以继续用这个数组
地址传递的好处坏处也明朗了,好处就是学霸可以不用再抄一份数据那么大的作业了,作业就直接给学渣抄啦,不得不妥协啦,“你”好自为之吧,“你”不要给“我”的作业瞎搞~不要在子函数里瞎改我的数组
先在子函数里修改一下数据,看下对主函数有没有什么影响
1. #include <stdio.h> 2. 3. int FindMax(int *array, int count) { //用int*类型的指针接收数组这个参数,count是数组中数据的个数 4. int i; 5. int Max = array[0]; 6. printf("学霸你666啊,我帮你把作业数据改了哦~\n"); 7. array[1] = 66; 8. // for (i = 1; i < count; i++) { //先把这个注释掉 9. // if (array[i] > Max) 10. // Max = array[i]; 11. // } 12. 13. return Max; 14. } 15. 16. int main(void) { 17. int a[] = {100, 60, 8, 14, 10, 89}; 18. int Max; 19. Max = FindMax(a, 6); //数组名就是指针,把a做参数传入进去 20. printf("Max = %d\n", Max); 21. printf("a[1] = %d\n", a[1]); 22. 23. return 0; 24. }
666,然后学霸的作业就被乱涂乱画了!学霸不希望学渣改自己的作业,要不然就跟你急!
给int加个修饰符const,const就是常量的意思, 变量类型改为const就是常量,只能被读不能被写
可以看到, 想修改原本的数据,程序报错了
这种方法就是指针间接访问数据
C语言中strlen函数中的形参就是用 const char*修饰的字符数组
作用:使用指针传递大容量数据的参数,主函数和子函数使用的是同一套数据,避免了参数传递过程中的数据复制,提高了运行效率,减少了内存占用
将变量改成全局变量也可以避免使用指针,自己写程序是可以这样用的
但是用全局变量不利于代码的封装,代码块之间联系性比较差
人家就是这么写的……自己还是学一下的好。
指针实现多返回值
1. #include <stdio.h> 2. 3. void FindMaxAndCount(int *max, int *count, const int *array, int length) { 4. int i; 5. *max = array[0]; 6. *count = 1; 7. 8. for (i = 1; i < length; i++) { 9. if ( array[i] > *max ) { // *max = Max,Max其实就是返回值 10. *max = array[i]; 11. *count = 1; // *count = Count 12. } else if ( array[i] == *max) { 13. (*count)++; //一定要加括号,注意优先级是*count 14. } 15. } 16. } 17. 18. int main() { 19. int Max; 20. int Count; 21. 22. int a[] = {12, 66, 66, 8, 6, 3}; 23. 24. FindMaxAndCount( &Max, &Count, a, 6); //前两个参数是取的地址,可以被修改! 25. 26. printf("Max = %d\n", Max); 27. printf("Count = %d\n", Count); 28. 29. return 0; 30. }
在意义上实现了多返回值,也叫输出参数
具体实现步骤:
在主函数定义两个变量Max和Count,一个接收最大值,一个接收最大值次数
子函数加两个形参int *max, int *count,这两个类型是可以修改源数据的。
这两个形参就是Max和Count的地址, *max = Max *count = Count ,修改 *max和*count就会改变Max和Count,这样就可以实现多返回值,输出参数
(*count)++ 注意优先级
再学习一个实例加深印象
C语言标准库中,复制字符串的函数
第二个形参类型是const char * ,它是输入参数,不允许修改
第一个形参类型是char *,可以修改
1. #include <stdio.h> 2. #include <string.h> 3. 4. //输出参数的作用实例 5. int main(void) { 6. char str1[20]; 7. char str2[] = "Hello World!"; 8. strcpy(str1, str2); //把str2复制到str1里 (数组名就是地址) 9. 10. printf(str1); 11. printf(str2); 12. 13. return 0; 14. }
运行结果
传递返回值
将模块内的公有部分返回,让主函数持有模块得“句柄”,便于程序对指定对象的操作
假设在写51单片机的时钟程序时候,咱已经封装好了一个.c.h文件,有一些业务需求,主函数想调用这个封装好的模块里的一个数组
一般不直接把变量声明出去共用,变量声明出去增加耦合性,不利于代码封装
这种情况就可以用指针间接访问这个数组 int * GetTime(void)
这样就相当于拿到了句柄,操作就方便了不少~
1. #include <stdio.h> 2. 3. /***********************************/ 4. //假设是51单片机里封装.c模块 5. int time[] = {23, 59, 55}; 6. 7. //用指针作为返回值,就可以在主函数里调用这个数组了 8. int *GetTime(void) { //返回time数组的地址 9. /*可以做其他操作*/ 10. return time; 11. } 12. 13. /***********************************/ 14. 15. int main(void) { 16. int *pt; //定义用于接收时间的指针 17. pt = GetTime(); //pt也指向了time这个数组 18. 19. printf("pt[0] = %d\n", pt[0]); 20. printf("pt[1] = %d\n", pt[1]); 21. printf("pt[2] = %d\n", pt[2]); 22. 23. return 0; 24. }
注意事项:
不要返回局部变量的指针!!!
不要返回局部变量的指针!!!
不要返回局部变量的指针!!!
先写一个bug看看
返回局部变量的地址就会出现问题
函数结束之后,局部变量就会被销毁,返回地址就无效了
现学现卖~
两个形参都是 const char*
返回值是FILE* (FILE是结构体,FILE就相当于句柄)
非常的神奇吧
继续写个字符串
读取刚刚写的文档
把字符串也读出来
直接访问物理地址下的数据
访问硬件指定内存下的数据,如设备ID号等
将复杂格式的数据转换为字节,方便通信与存储
读取51单片机的ID号
读取51单片机RAM中的ID号
1. #include <REGX52.H> 2. #include "LCD1602.h" 3. 4. void main() 5. { 6. unsigned char *p; //定义指针 7. LCD_Init(); 8. LCD_ShowString(1, 1, "HelloWorld!"); 9. 10. p = (unsigned char *)0xF1; //p = 0xF1;是跨级赋值,但是读取的地址是没有问题的,为了保险,还是强转一下 11. //指针给定地址,直接引用指针,就可以读取ID号 12. LCD_ShowHexNum(2, 1, *p, 2); 13. // LCD_ShowHexNum(2, 1, *((unsigned char *)0xF1), 2); //读取F1的数据,和上一行效果一致 14. LCD_ShowHexNum(2, 3, *(p+1), 2); 15. LCD_ShowHexNum(2, 5, *(p+2), 2); 16. LCD_ShowHexNum(2, 7, *(p+3), 2); 17. LCD_ShowHexNum(2, 9, *(p+4), 2); 18. LCD_ShowHexNum(2,11, *(p+5), 2); 19. LCD_ShowHexNum(2,13, *(p+6), 2); 20. 21. while(1) 22. { 23. 24. } 25. } 26.
我用的单片机是STC89C52,是8K程序空间的MCU
unsigned char*访问的是内部RAM空间的数据
unsigned char code *p;才能访问程序存储器
1. #include <REGX52.H> 2. #include "LCD1602.h" 3. 4. void main() 5. { 6. unsigned char code *p; //定义指针 7. LCD_Init(); 8. LCD_ShowString(1, 1, "HelloWorld!"); 9. 10. p = (unsigned char *)0x1FF9; //p = 0xF1;是跨级赋值,但是读取的地址是没有问题的,为了保险,还是强转一下 11. //指针给定地址,直接引用指针,就可以读取ID号 12. LCD_ShowHexNum(2, 1, *p, 2); 13. // LCD_ShowHexNum(2, 1, *((unsigned char *)0xF1), 2); //读取F1的数据,和上一行效果一致 14. LCD_ShowHexNum(2, 3, *(p+1), 2); 15. LCD_ShowHexNum(2, 5, *(p+2), 2); 16. LCD_ShowHexNum(2, 7, *(p+3), 2); 17. LCD_ShowHexNum(2, 9, *(p+4), 2); 18. LCD_ShowHexNum(2,11, *(p+5), 2); 19. LCD_ShowHexNum(2,13, *(p+6), 2); 20. 21. while(1) 22. { 23. 24. } 25. }
将复杂格式的数据转换为字节,方便通信与存储
在玩51单片机的时候,经常有float类型的数据发送接收,一般都是放缩法,放大成整数发送,接收之后再除回去,这样比较麻烦
float类型的数据就是4个字节的数据,我们也可以一个字节一个字节的发送,把这四个字节看成unsigned char*型的数组,数组中有四个数,依次发送四个字节的数据,解码的时候,定义一个floa*t类型的数据,用它来解码,就可以了
实现: