【C语言】指针应用-理论结合实践,真正理解指针

简介: 【C语言】指针应用-理论结合实践,真正理解指针

传递参数

传递大容量数据

使用指针传递大容量数据的参数,主函数和子函数使用的是同一套数据,避免了参数传递过程中的数据复制,提高了运行效率,减少了内存占用

使用指针传递输出参数,利用主函数和子函数使用同一套数据的特性,实现数据的返回,课实现多返回值的设计

写一个有参数传递的函数 (值传递)

bd84ada8017408697ed5ff6315e9e659_dd0c852792f449588316ba54a80b1e7e.png

参数传递过程

dc84108b7b26a0df2afdcbe3376cce95_a30a990139ce4a67a3d28526a3c9144d.png

上面的代码在内存中的过程:

首先申请变量a的4字节内存

调用子函数,把a传递到函数参数

调用子函数的过程有定义变量数据复制的过程:先在内存中申请,定义int类型局部变量param,把数据啊,复制到param的变量的内存中

截图up主画的

287c47f2b799d389f03ca69d41fe7d17_8733ad2117604d7f921c1faf66834c42.png

变量a和变量param,存在了两个地方,隔离主函数和子函数的数据,防止出现一些安全问题。

改变子函数中param的值,主函数中变量a的数值是否会改呢?

e88443daa4688c1c698312640d9b64ee_a7bb46acc14f4bae8a9cb2ae48f13542.png

因为子函数是新申请的内存,所以改变子函数的数据, 不会影响主函数的变量

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. }

c2689495485e952b6638353100511b46_23951ac53de446ddaef20b45e944cb01.png

666,然后学霸的作业就被乱涂乱画了!学霸不希望学渣改自己的作业,要不然就跟你急!

70af2550f15b64f9557d34af56225596_0324db7ca020469fb76c604c5ffad015.png

 

给int加个修饰符const,const就是常量的意思, 变量类型改为const就是常量,只能被读不能被写

c247ea824fd7924e96ecdc3b3cc4db81_83b24abd54f44263b9d6dc5d5b75d334.png

可以看到, 想修改原本的数据,程序报错了

这种方法就是指针间接访问数据

C语言中strlen函数中的形参就是用 const char*修饰的字符数组

bbb4241d7698c65d9fa0840c39382682_c7e2e3a6d2694c6097faebfddd51bff7.png

作用:使用指针传递大容量数据的参数,主函数和子函数使用的是同一套数据,避免了参数传递过程中的数据复制,提高了运行效率,减少了内存占用

将变量改成全局变量也可以避免使用指针,自己写程序是可以这样用的

但是用全局变量不利于代码的封装,代码块之间联系性比较差

13e0514310138f245a4de236727b0398_b66f254e0f99432cb257961cbba9b20a.png

人家就是这么写的……自己还是学一下的好。

指针实现多返回值

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语言标准库中,复制字符串的函数

3098aa13b06e08fa56af7060d24be837_f1292f210048446b8ba93631f7d6d3a4.png

第二个形参类型是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. }

运行结果

4e4ba3c391441424587f2b8b11110203_99ddfb5354f04658a90bbec3bfb5ac23.png


传递返回值

将模块内的公有部分返回,让主函数持有模块得“句柄”,便于程序对指定对象的操作

假设在写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. }

dcf8a192454ed0b3935534ae15601124_0d4e78c98e324c94b43632c552607e0a.png

注意事项:

不要返回局部变量的指针!!!

不要返回局部变量的指针!!!

不要返回局部变量的指针!!!

先写一个bug看看

1a82ee07a699b540178fd1bb73fec3cc_7414f6a4597f47329739d90eea11014d.png

返回局部变量的地址就会出现问题

函数结束之后,局部变量就会被销毁,返回地址就无效了

C语言文件操作

现学现卖~

11751785355de909a37d80eb40b12ddb_0cdd0a8b327c41b4829691c59b945ca5.png

两个形参都是 const char*

返回值是FILE*        (FILE是结构体,FILE就相当于句柄)

2c689c8472cba2114c20a17520644fea_9b2bbaa9a6c649c2b77e807ef8d5b64b.png

042bb95cfa629646ad83a4af548c31a6_cd953e422df34a21b1e5dff2025131c5.png

非常的神奇吧

继续写个字符串

4accd223cbc26401f20bf0882d9e296a_ef20692bac984346adef4c5aa5b3a023.png

 

读取刚刚写的文档

d5c6e4e1ca1049b10e7b56b4d41e1202_2156751229ef4e738e122799692447a9.png

把字符串也读出来

7d686f478a42dc261c98b333a22d7f9e_24a40ffc35734f0db399d86fc0418e71.png

 

直接访问物理地址下的数据

访问硬件指定内存下的数据,如设备ID号等

将复杂格式的数据转换为字节,方便通信与存储

读取51单片机的ID号

b86f684bae6a2c97960e91f2b7315f79_296fb0f2f7a44427b4e1b9f0c6d5421d.png

 

读取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.

56674f334463fcc74277a7cd2bc29b92_bb5f8692365b4e3397d564bf5e58425c.png

我用的单片机是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类型的数据,用它来解码,就可以了

实现:

0ec456fbccff2bfdf62112b7ce2a6044_caaaba11e78e48398009b6aad8002be6.png


相关文章
|
6天前
|
C语言
C语言------指针
这篇文章是关于C语言中指针的实训,通过示例代码展示了指针的基本概念、定义、赋值、使用和传递,以及指针运算和指针在函数参数中的应用,如交换两个变量的值和找出两个数中的较小值。
C语言------指针
|
22小时前
|
存储 编译器 C语言
【C语言篇】深入理解指针2
代码 const char* pstr = "hello world."; 特别容易让初学者以为是把字符串 hello world.放 到字符指针 pstr ⾥了,但是本质是把字符串 hello world. 首字符的地址放到了pstr中。
|
22小时前
|
存储 程序员 编译器
【C语言篇】深入理解指针1
assert.h 头⽂件定义了宏 assert() ,⽤于在运⾏时确保程序符合指定条件,如果不符合,就报错终⽌运⾏。这个宏常常被称为“断⾔”。
|
4天前
|
算法 Java
双指针在数组遍历中的应用
文章深入探讨了双指针技术在数组遍历中的应用,通过实战例子详细解释了快慢指针和首尾指针的不同用法,并提供了解决LeetCode相关问题的Java代码实现。
|
4天前
|
存储 搜索推荐 C语言
C语言中的指针函数:深入探索与应用
C语言中的指针函数:深入探索与应用
|
4天前
|
编译器 C语言
C语言中的浮点数:深入探索与应用
C语言中的浮点数:深入探索与应用
|
19小时前
|
C语言
【C语言】指针速览
【C语言】指针速览
7 0
|
6天前
|
C语言
【C初阶——指针5】鹏哥C语言系列文章,基本语法知识全面讲解——指针(5)
【C初阶——指针5】鹏哥C语言系列文章,基本语法知识全面讲解——指针(5)
|
6天前
|
C语言
【C初阶——指针4】鹏哥C语言系列文章,基本语法知识全面讲解——指针(4)
【C初阶——指针4】鹏哥C语言系列文章,基本语法知识全面讲解——指针(4)
|
6天前
|
存储 编译器 C语言
【C初阶——指针3】鹏哥C语言系列文章,基本语法知识全面讲解——指针(3)
【C初阶——指针3】鹏哥C语言系列文章,基本语法知识全面讲解——指针(3)