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

关于指针

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的专栏

<think>嗯,用户想了解void指针,也就是void*。首先,我需要明确void指针的基本概念,它在C/C++中的作用,以及为什么它很重要。我记得void指针是一种通用指针类型,可以指向任何数据类型,但具体怎么用呢? 首先,void指针的定义。它没有具体的类型,所以不能直接解引用,对吧?比如,不能直接用*操作符来获取值,必须先转换成具体类型指针。这点需要强调,因为这是使用void指针时常见的错误。 然后是用途。用户可能想知道为什么需要void指针。比如在内存操作函数如malloc、memcpy中,它们接收void*参数,因为需要处理不同类型的数据。还有在实现泛型编程时,比如回调函数,用void*传递任意类型的数据。这时候可能需要举例说明,比如qsort函数,它的比较函数就用void*参数来处理不同类型的数据。 接下来,使用注意事项。除了不能直接解引用外,还有类型转换的问题。使用前必须显式转换,否则编译器会报错。比如,malloc返回void*,赋值给其他指针类型时需要强制转换,不过在C++中必须转换,而C中可以隐式转换,这点可能需要区分说明。 还有指针运算的问题。void指针不能进行算术运算,因为不知道指向的数据类型大小。比如,int*加1会移动4字节(假设int是4字节),但void*加1是未定义的行为,编译器不知道步长是多少。所以必须转换成具体类型才能进行运算。 可能用户还会关心void指针的安全性问题。因为类型不安全,容易出错,比如转换错误导致的数据损坏。这时候可以提到现代C++中更安全的替代品,比如模板和智能指针,但void*在底层编程和特定场景中仍然有用。 用户可能的背景是学习C/C++,可能在处理动态内存分配或者泛型函数时遇到了void指针的需求。需要确保解释清晰,步骤分明,避免过于技术性的术语,但又要准确。比如,用例子说明malloc的使用,或者如何通过void*传递结构体。 是否需要对比C和C++中的差异?比如在C中,void*可以隐式转换到其他指针类型,而C++需要显式转换。这可能对用户有帮助,特别是他们可能在不同语言环境下工作。 还要注意应用场景的举例,比如函数参数传递不确定类型的数据,或者在数据结构中存储不同类型的数据元素。例如,链表的节点数据可能用void*保存任意类型的数据,这样链表可以通用。 总结来说,回答的结构应该是:定义、特点、用途、注意事项、示例。确保每个部分清晰,用例子帮助理解,同时指出潜在的问题和最佳实践。</think>### void * 指针详解 #### 1. **基本定义** `void *` 是 C/C++ 中的一种特殊指针类型,称为 **通用指针****类型指针**。它的特点是: - **不关联任何具体数据类型**,可以指向任意类型的内存地址。 - 语法形式:`void *ptr;` #### 2. **核心特性** - **无法直接解引用** 不能直接通过 `*ptr` 访问数据,因为编译器不知道其指向的数据类型**错误示例**: ```c void *ptr = malloc(sizeof(int)); *ptr = 10; // 编译错误:void* 不能直接解引用 ``` - **必须显式类型转换** 使用前需强制转换为具体类型指针: ```c int *int_ptr = (int *)ptr; // 转换为 int* *int_ptr = 10; // 合法操作 ``` - **不支持指针算术运算** `void *` 无法进行 `ptr++` 或 `ptr + n` 等操作,因为类型未知导致步长不确定。 **正确做法**: ```c char *char_ptr = (char *)ptr; char_ptr++; // 允许(步长为 1 字节) ``` #### 3. **典型用途** - **内存管理函数** `malloc`、`calloc`、`realloc` 返回 `void *`,以支持动态分配任意类型的内存: ```c int *arr = (int *)malloc(10 * sizeof(int)); // 分配整型数组 ``` - **泛型函数设计** 用于实现类型无关的操作(如 `memcpy`、`qsort`): ```c void qsort(void *base, size_t nmemb, size_t size, int (*compar)(const void *, const void *)); ``` - **多态数据传递** 在回调函数或结构体中存储任意类型数据: ```c typedef struct { void *data; // 可指向任何类型 int type_flag; // 通过标记区分实际类型 } GenericContainer; ``` #### 4. **注意事项** - **类型安全性风险** 错误的类型转换会导致未定义行为: ```c float *f = (float *)ptr; // 若 ptr 实际指向 int,操作将出错 ``` - **C C++ 的差异** - **C**:允许隐式转换 `void *` 到其他指针类型。 ```c int *p = malloc(sizeof(int)); // C 中合法 ``` - **C++**:必须显式转换。 ```cpp int *p = (int *)malloc(sizeof(int)); // C++ 中强制要求 ``` - **替代方案(C++)** 优先使用模板、`std::any` 或智能指针(如 `std::shared_ptr<void>`)提高安全性。 #### 5. **示例代码** ```c #include <stdio.h> #include <stdlib.h> // 泛型打印函数 void print(void *data, char type) { switch(type) { case 'i': printf("int: %d\n", *(int *)data); break; case 'f': printf("float: %.2f\n", *(float *)data); break; } } int main() { int a = 42; float b = 3.14f; void *ptr1 = &a; void *ptr2 = &b; print(ptr1, 'i'); // 输出: int: 42 print(ptr2, 'f'); // 输出: float: 3.14 return 0; } ``` #### 6. **总结** - **优点**:灵活性高,适用于底层内存操作和泛型编程。 - **缺点**:需手动管理类型转换,易引发错误。 - **适用场景**:系统编程、内存工具库、跨类型数据容器。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值