无类型指针void*的学习与使用

来源:互联网 发布:vmware 共享 linux 编辑:程序博客网 时间:2024/04/30 04:13

关于指针

C/C++中的指针通常来说有两个属性:

 1. 指向变量 2. 指向对象的地址和长度

指针其实就是存储被指向变量的地址,并不保存其长度;
而且存的这个地址仅是变量的首地址,并不是该变量占据内存的所有地址空间。如:

int a=3;int *p=&a;

内存分配图示
目前大多数的C/C++编译环境中,整型int数据占4个字节的空间,如上图所示。所以指针p存储的地址(即指向的地址)为1号内存单元(首地址)。
当需要读取一个例如int型数据时,编译器根据指针的类型从指针指向的地址开始向后寻址。指针类型不同则寻址范围也不同,比如:
int*从指定地址向后寻找4字节作为变量的存储单元;
double*从指定地址向后寻找8字节作为变量的存储单元。

说到这里可能大家就会有一个问题:由于计算机内部的地址是整型数字,那么为什么不干脆用一个整型变量存储地址,还要发明指针变量呢?
如果我们从指针实现的角度讲,指针就是一个整型变量,它存储的是一个地址值,没有任何附加信息。目前为止貌似没有什么问题,其实不然。
就拿上述的代码:如果用一个整型变量b存储a的地址,即int b=&a。当对b加1时,得到的新的地址相当于对a的首地址加1,即&a+1,由于int占连续的四个存储单元(默认),此时b存储的是第二块存储单元的地址,所以根据变量b存储的地址,将无法完整的读出变量a的值,导致错误。而通过指针变量,可以解决这类问题:如果对上述代码中的指针p加1的话,实际上是p+sizeof(int),一次性增加了4个存储单元。

PS.指针本身所占据的内存区 :
  指针本身占了多大的内存?你只要用函数sizeof(指针的类型)测一下就知道了。
  指针的作用是用来对内存空间进行寻址,在32位机上,所有指针类型变量占用内存字节数都为4,因为32位机是按32位寻址的。如果在64位机上,指针占用内存大小就是:8个字节。

无类型指针void*

void *vp;

void*是一种特别的指针,因为它没有指向的类型,或者说不能根据这个类型判断出指向对象的长度。void *指针具有以下特点:

  1. 任何指针(包括函数指针)都可以赋值给void指针;
 type *p;  vp=p;

//不需转换
//只获得变量/对象地址而不获得大小
2. void指针赋值给其他类型的指针时都要进行转换;

type * p=(type *)vp;

//转换类型也就是获得指向变量/对象大小
3. void指针在强制转换成具体类型前,不能解引用;

 *vp

//错误
//因为void指针只知道,指向变量/对象的起始地址
//而不知道指向变量/对象的大小(占几个字节)所以无法正确引用
4. void指针不能参与指针运算,除非进行转换。

(type*)vp++;

//等价于:vp=vp+sizeof(type)
void*的作用

  1. 传参:通用类型
    可以作为函数模板,链表等参数的通用参数。在使用时,只需要强制类型转换就可以。
    例如内存操作函数memcpy和memset的函数原型分别为:
void* memcpy(void *dest, constvoid *src, size_t len);   void* memset(void *buffer, int c, size_t num);

这样,任何类型的指针都可以传入memcpy和memset中,这也真实地体现了内存操作函数的意义,因为它操作的对象仅仅是一片内存,而不论这片内存是什么类型。

  1. 强制类型转换
    有时候由于重载等的干扰,导致需要转换成void *,来进行取地址。
    例如,(void *)obj.member,就可以取到member的地址;直接&(obj.member)取到的实际上是obj的开始地址。
  2. 指向0的地址
    (void *)0,指向全是0的地址,相当于NULL。

下面举一个使用void*指针的demo:

#include<iostream>#include<string>using namespace std;typedef struct tag_st{    string id;    float fa[2];}ST;int main(){    ST* P = new ST;    P->id = "hello!";    P->fa[0] = 1.1;    P->fa[1] = 2.1;    ST* Q = new ST;    Q->id = "world!";    Q->fa[0] = 3.1;    Q->fa[1] = 4.1;    void * plink = P;    *(ST*)(plink) = *Q;  //plink要先强制转换一下,目的是为了让它先知道要覆盖的大小    //P的内容被Q的内容覆盖    cout << P->id << " " << P->fa[0] << " " << P->fa[1] << endl;      return 0;}

在写这个例子的时候发生一点小插曲:把第24行:* (ST * )(plink) = * Q; 写成了(ST * )(plink) = Q;结果编译报错,刚开始以为是void *指针不能被两次赋值而引发的错误。经过研究,得知是因为强制类型转化操作(目标类型是引用时除外)不能作为左值,所以编译器才会报错。另附上一篇介绍左值和右值的博客helloworld的博客。

注:关于void*指针的介绍,参考于wangicter的博客WangIcter的专栏。

1 0
原创粉丝点击