C语言指针学习

一、计算机存储

在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)数组定义:

int  a[] = {0x33,0x34,0x35};数组定义还是比较简单的。

(2)指针定义:

先调用malloc函数所在的头文件:<stdlib.h>;

然后利用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 <stdio.h>
#include <stdlib.h>

/*********************/
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 <stdio.h>

/*****************************/
unsigned char AirData[20];

void SendData(const unsigned char *data,unsigned char count)
{
unsigned char i;
for(i=0;i<count;i++)
{
AirData[i]=data[i];
}
}
/*****************************/

/*****************************/
void ReceiveData(unsigned char *data,unsigned char count)
{
unsigned char i;
for(i=0;i<count;i++)
{
data[i]=AirData[i];
}
}
/*****************************/
int main(void)
{
/******************************/
unsigned char i;
unsigned char DataSend[]={0x12,0x34,0x56,0x78};
SendData(DataSend,4);

printf("\\nAirData=");
for(i=0;i<20;i++)
{
    printf("%x ",AirData\[i\]);
}

/******************************/
unsigned char DataReceive[4];
ReceiveData(DataReceive,4);
printf(“\nAirData=”);
for(i=0;i<4;i++)
{
printf(“%x “,DataReceive[i]);
}
/******************************/
return 0;
}

接下来解决float参数的发送问题:float本身是四个字节,所以发送只要把四个字节都发送过去就🆗了,因为四个字节,它在存储float时是编码后的数据存储的,所以我们可以把它看成unsigned char类型的四个单元的数组。

定义一个unsigned char*的变量,让它等于float的首地址,把四个数据发送过去,解码的时候再定义一个float*,让它等于这个数组的首地址,那它就会把这四个数当成float进行解码,解码出来就是原数据:

#include <stdio.h>

/*****************************/
unsigned char AirData[4];

void SendData(const unsigned char *data,unsigned char count)
{
unsigned char i;
for(i=0;i<count;i++)
{
AirData[i]=data[i];
}
}
/*****************************/

/*****************************/
void ReceiveData(unsigned char *data,unsigned char count)
{
unsigned char i;
for(i=0;i<count;i++)
{
data[i]=AirData[i];
}
}
/*****************************/
int main(void)
{
/******************************/
unsigned char i;
unsigned char DataSend[]={0x12,0x34,0x56,0x78};
float num=12.345;

unsigned char \*p;

p\=(unsigned char\*)&num;

SendData(p,4);

printf("AirData=");
for(i=0;i<4;i++)
{
    printf("%x ",AirData\[i\]);
}

/******************************/
unsigned char DataReceive[4];
ReceiveData(DataReceive,4);
printf(“\nAirData=”);
for(i=0;i<4;i++)
{
printf(“%x “,DataReceive[i]);
}
/******************************/
return 0;
}

编码数据:

接收解码:

#include <stdio.h>

/*****************************/
unsigned char AirData[4];

void SendData(const unsigned char *data,unsigned char count)
{
unsigned char i;
for(i=0;i<count;i++)
{
AirData[i]=data[i];
}
}
/*****************************/

/*****************************/
void ReceiveData(unsigned char *data,unsigned char count)
{
unsigned char i;
for(i=0;i<count;i++)
{
data[i]=AirData[i];
}
}
/*****************************/
int main(void)
{
/******************************/
unsigned char i;
unsigned char DataSend[]={0x12,0x34,0x56,0x78};
float num=12.345;

unsigned char \*p;

p\=(unsigned char\*)&num;

SendData(p,4);

printf("AirData=");
for(i=0;i<4;i++)
{
    printf("%x ",AirData\[i\]);
}

/******************************/
unsigned char DataReceive[4];

float \*fp;//接收解码
ReceiveData(DataReceive,4);

fp\=(float \*)DataReceive;
printf("\\nAirData=");

printf("\\nnum=%f",\*fp);

/******************************/
return 0;
}

完结…… 

2022-03-30

20:20:02