C语言中指针的使用
指针,是C语言中最常用也最复杂最常造成混淆的数据类型。指针其实对应的是存储数据的内存地址,CPU中都有按内存地址取数据的指令,而指针的操作就是这种汇编指令的对应,因此会带来编译的高效率。指针是C语言的优势所在,但也是难点所在。
1)指针变量的定义:
存储地址值的变量称为指针变量,定义为:
其中,type声明了指针指向的变量的数据类型,而varname则是存放这个变量的地址。注意,这个type类型描述的是变量的内容的数据类型。例如:
定义了一个整型指针变量 pa,该指针变量只能指向基类型为 int 的整型变量,即只能保存整型变量的地址。
2)使用已定义好的指针变量:
其中,&表示取变量a的存储空间地址,将这个地址赋值给指针变量pa。这是最常见的指针变量用法。变量a的数据类型为int,而指针变量的数据类型也是int,因此可以直接赋值。
还有一种用法容易出现混淆:
实际上此时p前的*仅是定义指针变量p的标识,还是把&a赋给p,也就是在整数变量存储空间的地址赋值给了指针变量p。
3)同类型的指针变量可以相互赋值:
这样,指针变量p2中也存放了整型变量a的地址。
但因为指针pa和pc中存放的数据类型不同,二者之间就不能直接赋值。
4)指针变量的引用:
访问内存空间中的数据,一般分为直接访问和间接访问。普通变量,如整型变量、浮点数变量、字符变量等,就是对应内存单元的名字,通过这种变量名的操作就是直接访问变量的内存单元。
内存单元也有地址,通过地址来访问该内存空间,存入或读取数据,就是间接访问。而指针变量存放的就是内存单元的地址,因此通过指针变量的存取数据就是间接访问。
其中,a为整型变量,指针p则存放变量a的地址。如果直接用a进行赋值等操作,就是对内存数据的直接访问,而通过指针p对数据操作就是间接访问。示例:
更明显的指针变量赋值语句:
就相当于a=6的赋值操作。赋值语句左侧的*表示取指针变量的内容数据。
也可以将一个指针变量传递到函数中使用。如果定义了一个数据缓冲区:
可以把此数据缓冲区的首地址传入一个函数:
此函数定义中使用一个指针变量:
函数中使用此指针变量:
5)不确定类型指针void *:
指针变量定义时,前面的数据类型指示的是指针变量对应的内存单元中存放的数据的类型。如果这个类型为void,则为不确定类型指针,如:
不确定类型指针可以接受任何类型的赋值,示例:
任何类型的指针都可以直接赋值给它,无需进行强制类型转换. 我们可以认为void就是一张白纸,可以在上面写任何类型的数值。
但void *的指针变量要赋值给任何类型的变量则需要进行强制类型转换,示例:
void* 在转换为其他数据类型时,赋值给void* 的类型和目标类型必须保持一致。简单地说:void* 类型接受了int * 的赋值后 这个void * 不能转化为其他类型,必须转换为int *类型。
比如,一个实时操作系统中任务间数据传递使用os_msg_post()函数,其中传递的数据格式为一个结构:
其中的数据param就使用了不确定类型指针。为了传递一个数据,可以使用:
而接收数据的任务函数为:
6)剖析比较复杂的指针表示方式:
除了定义指针变量的时候,变量前面有*为定义类型,其他时候均为指针的取值操作,拿的是指针变量中存放的值。
对于*p 可看成看成*(p),变量p对应一个数据的存放地址,通过这个地址可以得到其中的数值。如果出现连续几个*,需要从右往左依次抛开,即***m就是*(*(*m))),看的时候需要从最里层一层一层抛开,几个*查找几层。
结果:m存放的q的地址。
先从内存中拿到m存放的数据add1(m中的数据),再从内存中找到add1那片内存,取出存放的数据add2(*q中存放的数据),完成了*(m)操作,再从内存中找到add2那片内存,取出存放的数据add3(*(*(m)) 中存放的数据),完成了*(*(m))操作,再从内存中找到add3那片内存,取出存放的数据5(*(*( *(m)))中存放的数据),完成了*(*(*(m)))操作。
其他一些说明:
1)指针变量的定义:
存储地址值的变量称为指针变量,定义为:
type * varname;
其中,type声明了指针指向的变量的数据类型,而varname则是存放这个变量的地址。注意,这个type类型描述的是变量的内容的数据类型。例如:
int *pa;
定义了一个整型指针变量 pa,该指针变量只能指向基类型为 int 的整型变量,即只能保存整型变量的地址。
2)使用已定义好的指针变量:
int a,*pa;
pa=&a;
其中,&表示取变量a的存储空间地址,将这个地址赋值给指针变量pa。这是最常见的指针变量用法。变量a的数据类型为int,而指针变量的数据类型也是int,因此可以直接赋值。
还有一种用法容易出现混淆:
int a,*p=&a;
实际上此时p前的*仅是定义指针变量p的标识,还是把&a赋给p,也就是在整数变量存储空间的地址赋值给了指针变量p。
3)同类型的指针变量可以相互赋值:
int a,*p1,*p2;
p1=&a;
p2=p1;
这样,指针变量p2中也存放了整型变量a的地址。
int a=3,*pa=&a;
char c='d',*pc=&c;
但因为指针pa和pc中存放的数据类型不同,二者之间就不能直接赋值。
4)指针变量的引用:
访问内存空间中的数据,一般分为直接访问和间接访问。普通变量,如整型变量、浮点数变量、字符变量等,就是对应内存单元的名字,通过这种变量名的操作就是直接访问变量的内存单元。
内存单元也有地址,通过地址来访问该内存空间,存入或读取数据,就是间接访问。而指针变量存放的就是内存单元的地址,因此通过指针变量的存取数据就是间接访问。
int *p,a=3;
p=&a;
其中,a为整型变量,指针p则存放变量a的地址。如果直接用a进行赋值等操作,就是对内存数据的直接访问,而通过指针p对数据操作就是间接访问。示例:
printf("a=%d\n",a); //通过名字,直接访问变量a空间(读取)
printf("a=%d\n",*p); //通过地址,间接访问变量a空间(读取)
更明显的指针变量赋值语句:
*p=6;
就相当于a=6的赋值操作。赋值语句左侧的*表示取指针变量的内容数据。
也可以将一个指针变量传递到函数中使用。如果定义了一个数据缓冲区:
#define rcvbuflen 1024
uint8_t rcvbuf[rcvbuflen]={0};
可以把此数据缓冲区的首地址传入一个函数:
rcvdata_buf(Size, &rcvbuf[0]);
此函数定义中使用一个指针变量:
void rcvdata_buf(uint16_t rcvsize, uint8_t *buf);
函数中使用此指针变量:
memmove(&writeWord,buf,4);
5)不确定类型指针void *:
指针变量定义时,前面的数据类型指示的是指针变量对应的内存单元中存放的数据的类型。如果这个类型为void,则为不确定类型指针,如:
void *buffer;
不确定类型指针可以接受任何类型的赋值,示例:
void *a = NULL;
int *b = NULL;
a = b;
任何类型的指针都可以直接赋值给它,无需进行强制类型转换. 我们可以认为void就是一张白纸,可以在上面写任何类型的数值。
但void *的指针变量要赋值给任何类型的变量则需要进行强制类型转换,示例:
int *a = NULL;
void *b;
a = (int *)b;
void* 在转换为其他数据类型时,赋值给void* 的类型和目标类型必须保持一致。简单地说:void* 类型接受了int * 的赋值后 这个void * 不能转化为其他类型,必须转换为int *类型。
比如,一个实时操作系统中任务间数据传递使用os_msg_post()函数,其中传递的数据格式为一个结构:
typedef struct _os_event_
{
uint16_t event_id;
uint16_t src_task_id;
void *param;
uint16_t param_len;
} os_event_t;
其中的数据param就使用了不确定类型指针。为了传递一个数据,可以使用:
uint8_t *data=buf;
os_event_t msg;
msg.event_id=EVENT1;
msg.param=&data;
msg.param_len=sizeof(data);
os_msg_post(my_task_id,&msg);
而接收数据的任务函数为:
int my_task_func(os_event_t *evt)
{
msg_handler(evt->event_id,*(unit8_t *)evt->param);
}
6)剖析比较复杂的指针表示方式:
除了定义指针变量的时候,变量前面有*为定义类型,其他时候均为指针的取值操作,拿的是指针变量中存放的值。
对于*p 可看成看成*(p),变量p对应一个数据的存放地址,通过这个地址可以得到其中的数值。如果出现连续几个*,需要从右往左依次抛开,即***m就是*(*(*m))),看的时候需要从最里层一层一层抛开,几个*查找几层。
int ***m;
m = &q;
printf("m = %d\n",m);
结果:m存放的q的地址。
***m = 5;
先从内存中拿到m存放的数据add1(m中的数据),再从内存中找到add1那片内存,取出存放的数据add2(*q中存放的数据),完成了*(m)操作,再从内存中找到add2那片内存,取出存放的数据add3(*(*(m)) 中存放的数据),完成了*(*(m))操作,再从内存中找到add3那片内存,取出存放的数据5(*(*( *(m)))中存放的数据),完成了*(*(*(m)))操作。
其他一些说明:
int p; //这是一个普通的整型变量
int *p; //首先从P 处开始,先与*结合,所以说明P 是一个指针,然后再与int 结合,说明指针所指向的内容的类型为int 型.所以P是一个返回整型数据的指针
int p[3]; //首先从P 处开始,先与[]结合,说明P 是一个数组,然后与int 结合,说明数组里的元素是整型的,所以P 是一个由整型数据组成的数组
int *p[3]; //首先从P 处开始,先与[]结合,因为其优先级比*高,所以P 是一个数组,然后再与*结合,说明数组里的元素是指针类型,然后再与int 结合,说明指针所指向的内容的类型是整型的,所以P 是一个由返回整型数据的指针所组成的数组
int (*p)[3]; //首先从P 处开始,先与*结合,说明P 是一个指针然后再与[]结合(与"()"这步可以忽略,只是为了改变优先级),说明指针所指向的内容是一个数组,然后再与int 结合,说明数组里的元素是整型的.所以P 是一个指向由整型数据组成的数组的指针
int **p; //首先从P 开始,先与*结合,说是P 是一个指针,然后再与*结合,说明指针所指向的元素是指针,然后再与int 结合,说明该指针所指向的元素是整型数据.由于二级指针以及更高级的指针极少用在复杂的类型中,所以后面更复杂的类型我们就不考虑多级指针了,最多只考虑一级指针.
int p(int); //从P 处起,先与()结合,说明P 是一个函数,然后进入()里分析,说明该函数有一个整型变量的参数,然后再与外面的int 结合,说明函数的返回值是一个整型数据
int (*p)(int); //从P 处开始,先与指针结合,说明P 是一个指针,然后与()结合,说明指针指向的是一个函数,然后再与()里的int 结合,说明函数有一个int 型的参数,再与最外层的int 结合,说明函数的返回类型是整型,所以P 是一个指向有一个整型参数且返回类型为整型的函数的指针
int *(*p(int))[3]; //可以先跳过,不看这个类型,过于复杂从P 开始,先与()结合,说明P 是一个函数,然后进入()里面,与int 结合,说明函数有一个整型变量参数,然后再与外面的*结合,说明函数返回的是一个指针,,然后到最外面一层,先与[]结合,说明返回的指针指向的是一个数组,然后再与*结合,说明数组里的元素是指针,然后再与int 结合,说明指针指向的内容是整型数据.所以P 是一个参数为一个整数据且返回一个指向由整型指针变量组成的数组的指针变量的函数.