一、计算机存储
文章图片
在C语言中,变量的存储是先存小端,再存大端,依次往下,int 类型变量占用四个字节,short类型变量占用两个字节,char类型变量占用一个字节。
需要注意的是:数组申请的存储空间,只能是连续的,哪怕是空的,没有内容,也只能占用,例如:char c[4] = {0x33,0x34,0x35},实际上只存了三个字节数据,但是却申请了四个字节的空间。
二、指针的表示
在指针的定义中:int *p,*其实是和int 联系在一起的,p只是变量名字,但是,如果这样写:int *p,c;
那么,c不是指针类型,而是int类型。
文章图片
计算机几位操作系统指的是一次性能处理几位的数据,由上图可知,64位操作系统,一个指针占用8个字节的空间。
三、指针操作
文章图片
p++,由上图可知,加一个数据宽度,不同类型的变量有不同的数据宽度:
(1)int 型:因为int占四个字节,所以数据宽度为四。
文章图片
(2)short型加2。
(3)char型加1。
文章图片
--也是一样。
注意:指针加加对于只定义了的单独变量来说,它的下一个存储空间是未知的,我们并不知道下一个空间里内容是什么,强行访问会出错,所以加加减减一般都用在定义的数组中。
四、指针操作
文章图片
数组其实就是指针,如上图所示。
文章图片
如上图所示的程序书写,直接将a赋值给p说明指针和数组就是一样的。从后面的输出也可以看出,输出结果一样。char类型一个字节,加一跳一个字节,但是变成int类型后,加一跳四个字节。
五、数组定义与指针定义
(1)数组定义:
inta[] = {0x33,0x34,0x35};
数组定义还是比较简单的。
(2)指针定义:
先调用malloc函数所在的头文件:;
然后利用malloc函数申请内存:
文章图片
malloc括号里的内容意思为申请3个内存单元,每个内存单元占用四个字节。malloc函数的返回值为*类型,即返回给一个指针,所以先定义了int *a,然后把申请到的空间给了a指针。后续给a赋值的操作类似,如上图。
整体来看,上图中指针的一系列操作等同于上图数组的操作。
六、补充
打印函数以十六进制显示:printf("%x\n", 变量);
七、指针的应用——子函数与主函数传递参数
文章图片
首先介绍不使用指针,直接进行参数传递,如下图:
文章图片
这里将主函数中的a传递给子函数时,相当于将a先复制一遍,再赋值给param,如上图右边红色存储示意图所示,这样做在单个变量这种小数据量时的操作是可以的,但是如果要传一个数组呢?这样做就会导致内存的浪费。但是它也是有优点的,如下图:
文章图片
在子函数中更改参数,但是不影响主函数的a,因为刚才说过了,它传参的原理是再复制一遍。综上,优点:不会破坏原有的参数的值;缺点:占用内存,传递大数据量时不宜使用。
接下来介绍指针传参:
文章图片
定义一个子函数,子函数需要的参数有指针和数组元素的个数,在主函数里,定义了一个数组,一个变量,用变量接收数组中最大的那个元素。
文章图片
如红色内存示意图所示,a3·数组就占用了那6×4个字节的内存,max占用4个字节,在子函数中定义了一个指针变量(要传入的参数,是个局部变量),这个指针变量array同样也申请了一块内存,占用8个字节,在主函数中将a传入,那么array存的就是a的首地址。所以这样的话只新建了8个字节的开销,它访问的还是主函数数组那一段存储空间。它俩共用一套数组,避免了再次赋值。在子函数这个调用程序结束后,array被释放掉。但是这样的缺点就是避免不了数据的更改:
文章图片
在子函数中把array[1]的值改为66,然后打印出来看看:
文章图片
可以看到,原来数组中的值已经被修改了。综上所述,优点:不用复制,节省空间和时间;缺点:数据易被修改。
综合改进版:加入const:
文章图片
加了const后,说明传入的参数是只读,不可修改的,若强行修改,如上图,编译器会报错,相当于加入了一种保护机制。
C语言中还有很多这种传递地址的函数,如:
文章图片
,函数,看它下面的解释,也是用的const类型。
七、指针的应用——函数多返回值
在C语言的一般定义的子函数中,只能return一个返回值,如果想要返回多个值就不行了。但是正是利用上述传入地址参数,共同使用一个数组的特性,可以实现数据的更改,将这一缺点转为优点,它的可以更改的属性,使得在一般意义上,实现了多个值的返回:
文章图片
...
文章图片
...
文章图片
文章图片
返回值MAX = 30,count = 3,在意义上实现了值的返回。下面通过存储示意图再做解释:
文章图片
首先划定了MAX的空间4个字节,然后调用子函数的话,定义了max的空间8个字节,把&MAX传入,那么max中存的就是MAX的首地址。接着在子函数中给*max写入数据,相当于在MAX空间写数据,将MAX的值更改,实现值的返回。当子函数返回之后,max被销毁。由于参数可以随便多,所以返回值可以随便多。同样,在标准C语言库中,我们也可以找到类似的函数:
文章图片
,结合下面的解释可以知道,const是传入的不可以更改数据,而那个char *则是可以修改,也就是我们想要的返回值。
strcpy函数使用示例:注意strcpy的函数解释中虽然要求传的参数是const,但是正如上面自定义的FindMaxAndCount函数,在主函数调用中传递进参数,并不需要再加一遍const,直接把地址传进去就好了,这里也是一样,我们直接把地址传进去,不需要再额外的加const这个关键词。strcpy这个函数就实现了字符串数组的复制。需要补充的是,打印函数printf正如第一个C语言程序一样,打印hellow world!时,我们直接这样写:printf("Hellow world!"),不需要像%d、%x、%c这样的打印方式的关键词,所以这里也是一样,直接打印就好。
文章图片
八、指针的应用——函数返回值为指针
定义一个函数,该函数的返回值为指针类型,通过返回的地址,拿到数组的地址,间接访问数组。如下所示,在打印时,可以写*pt,也可以写pt[]加下角标的形式,这些都是等价的。
#include #include /*********************/ int Time[] = {23,59,55}; int *GetTime(void)//int *看成一个整体,意思是该函数的返回值为int *类型。 { return Time; } /*********************/ int main() { int *pt; pt = GetTime(); printf("pt[0]=%d\n",*pt); printf("pt[0]=%d\n",pt[0]); printf("pt[1]=%d\n",pt[1]); printf("pt[2]=%d\n",pt[2]); return 0; }
但是不能够把局部变量返回,因为局部变量在执行完此子函数后就会被销毁,如下是错误的。
/*********************/int *GetTime(void)//int *看成一个整体,意思是该函数的返回值为int *类型。 { int Time[] = {23,59,55}; return Time; } /*********************/
九、指针的实际操作——文件的应用 fopen函数,
文章图片
,第一个参数为const char *,按照我们之前学的,就是用指针传递一个大容量的数组,在这里根据Filename可知,是文件;第二个也是const char *,是mode,可查阅该函数的定义得知mode可以是什么值。返回值为FILE *,FILE是个结构体,FILE *就是文件的句柄,拿到这个FILE *我们就持有了文件,我们才可以对它进行操作。
注意:FILE *与char *、int *对比可知,它是一个新的数据类型,就是FILE。
既然fopen返回值为FILE *,那么我们就用FILE *去接它:
文章图片
,当然名字是随便起的,这里起名为f。
在最后还需要一个fclose函数把文件关掉,如下:
int main(void) { FILE *f=fopen("E:\\text.txt","w"); fclose(f); return 0; }
然后我们就可以在fopen和fclose之间去给该文件写东西(因为fopen中此次选择的是w:只写):
int main(void) { FILE *f=fopen("E:\\text.txt","w"); fputc('A',f); fputs("Hellow World!",f); fclose(f); return 0; }
文章图片
根据运行结果可知,文件写成功。
接下来,改成以只读的形式打开:
int main(void) { char a; FILE *f=fopen("E:\\text.txt","r"); a=fgetc(f); fclose(f); printf("%c\n", a); return 0; }
文章图片
只读成功。读出字符数组:
int main(void) { char a; char s[15]; FILE *f=fopen("E:\\text.txt","r"); a=fgetc(f); fgets(s,15,f); //把f中15个读出写到s中 fclose(f); printf("%c\n", a); printf(s); return 0; }
C语言文件操作完全攻略 (biancheng.net)
十、指针在嵌入式中的使用
文章图片
指针读取ID号:
文章图片
#include "reg52.h"void main() { unsigned char *p; LCD_Init(); p=(unsigned char *)0xF1; //强制类型转换,0xF1为ID号存储地址LCD_ShowHexNum(2,1,*p,2); LCD_ShowHexNum(2,3,*(p+1),2); LCD_ShowHexNum(2,5,*(p+2),2); LCD_ShowHexNum(2,7,*(p+3),2); LCD_ShowHexNum(2,9,*(p+4),2); LCD_ShowHexNum(2,11,*(p+5),2); LCD_ShowHexNum(2,13,*(p+6),2); while(1); }
也可以这么写:
LCD_ShowHexNum(2,1,*((unsigned char *)0xF1),2); //也可以这么写,先强制转换,再取内容
在程序存储器最后7个字节单元也存放着ID号,我们试一下在程序存储器读取出ID号:由于unsigned char *是访问内部RAM的,想要访问程序空间得加一个code:
void main() { unsigned char code*p; LCD_Init(); p=(unsigned char code *)0x1FF9; //强制类型转换,0xF1为ID号存储地址LCD_ShowHexNum(2,1,*p,2); //也可以这么写,先强制转换,再取内容 LCD_ShowHexNum(2,3,*(p+1),2); LCD_ShowHexNum(2,5,*(p+2),2); LCD_ShowHexNum(2,7,*(p+3),2); LCD_ShowHexNum(2,9,*(p+4),2); LCD_ShowHexNum(2,11,*(p+5),2); LCD_ShowHexNum(2,13,*(p+6),2); while(1); }
十.一、将复杂格式数据转换为字节
先用软件模拟无线模块,构建虚拟无线模块,遵循一个字节一个字节的发送原则:
#include /*****************************/ unsigned char AirData[20]; void SendData(const unsigned char *data,unsigned char count) { unsigned char i; for(i=0; i
接下来解决float参数的发送问题:float本身是四个字节,所以发送只要把四个字节都发送过去就了,因为四个字节,它在存储float时是编码后的数据存储的,所以我们可以把它看成unsigned char类型的四个单元的数组。
定义一个unsigned char*的变量,让它等于float的首地址,把四个数据发送过去,解码的时候再定义一个float*,让它等于这个数组的首地址,那它就会把这四个数当成float进行解码,解码出来就是原数据:
#include /*****************************/ unsigned char AirData[4]; void SendData(const unsigned char *data,unsigned char count) { unsigned char i; for(i=0; i
编码数据:
文章图片
接收解码:
#include /*****************************/ unsigned char AirData[4]; void SendData(const unsigned char *data,unsigned char count) { unsigned char i; for(i=0; i
文章图片
完结......
2022-03-30
20:20:02
【C语言指针学习】
推荐阅读