C++中的void*理解

目录

1. void *是什么?

1.1 跳跃力什么时候定?

2. void*详解

2.1 void*可以指向任何类型的地址,但是带类型的指针不能指向void*的地址

2.2 void*指针只有强制类型转换以后才可以正常取值

2.3 void*指针变量和普通指针一样可以通过等于0或者NULL来初始化,表示一个空指针

2.4 当void*指针作为函数的输入和输出时,表示可以接受任意类型的输入指针和输出任意类型的指针

2.5 和void的区别

3. 作用及典型应用

3.1 函数传参时不确定类型,或者要支持多类型的传参;

3.2 当函数的返回值不考虑类型指关心大小的时候

4. 总结:

4.1 void*类型的指针其实本质就是一个过渡型的指针状态,必须要赋予类型(强制类型转换)才能正常使用。

4.2 只能单向类型转换。void*可以转化成其他类型,但是有类型的不能转化成void*。

4.3 在函数调用过程中的使用作为输入输出参数也非常好用,可以灵活使用任意类型的指针,避免只能使用固定类型的指针。


今天看到一段代码,觉得非常有意思。

void* say_hello(void* args)
{
    cout << "Hello World!" << endl;
    return 0;
}

这里的返回类型竟然是void*。一般来说如果没有返回值,那么写一个void就行了,void*到底是什么样的存在?所以做了一些测试,总结了一些void*指针的用法。

1. void *是什么?

C语言中,*类型就是指针类型。比如 int *p,double *q,虽然是不一样的指针,但是大小却一样sizeof(p) == sizeof(q),其实很容易理解,因为他们都是同一种类型*类型的。C语言是强类型的语言。对类型的区分十分严格。那这两个有什么不同点吗?有,+1就不同了,看下面的图:


也就是对于一个指针而言,如果我们在前面规定了它的类型。那就相当于决定了它的“跳跃力”。“跳跃力”就比如说上面图中int跳了4个字节,但是double跳了8个字节。

基于这样的理解,我要对void *下定义了:void * 是一个跳跃力未定的指针

1.1 跳跃力什么时候定?

这就是它的神奇之处了,我们可以自己控制在需要的时候将它实现为需要的类型。这样的好处是:编程时候节约代码,实现泛型编程。(见下文)

2. void*详解

2.1 void*可以指向任何类型的地址,但是带类型的指针不能指向void*的地址

正常来说如果两个指针类型不一样的话,两个指针变量是不可以直接相等的。例如int* a, float* b,假如令a = b,会直接发生编译错误,而void*指针可以等于任何类型的指针。但是反过来不可以,也就是说一个有类型的指针不能指向一个void*类型的变量(哪怕此时void*变量已经指向了一个有类型的地址)。

float f = 5.5;
float* pf = &f;
void* pv = pf;
float* pf2 = pv;//编译错误,有类型的指针变量不能指向void*变量

2.2 void*指针只有强制类型转换以后才可以正常取值

int main(int argc, const char * argv[]) {
    
    float f = 5.5;
    float* pf = &f;
    void* pv;
    
    pv = pf; //这句是可以的
    
    cout<<*pv<<endl;  //编译错误,这样直接对pv取值是错误的
    cout<<*(float*)pv<<endl;  //强制类型转换后可以取值
    
    return 0;
}

在令pv = pf后,此时pv和pf指向的是同一个地址,值相同,但是两者的类型是不一样的。pf作为浮点型指针,是可以直接取到浮点数的,但是pv必须要强制类型转换以后才可以取值,也就是说一个void*的指针必须要经过强制类型转换以后才有意义。

int main(int argc, const char * argv[]) {
    
    float f = 5.5;
    float* pf = &f;
    void* pv;

    pv = pf;
   
    cout<<*(float*)pv<<endl;  //强制类型转换后可以取值,值为5.5
    cout<<*(int*)pv<<endl; //强制类型转换,值为1085276160
    cout<<(int)(*(float*)pv)<<endl;//取值后再次类型转换,值为5
    
    return 0;
}

如果把一个指向float*的值的void*指针,强制转换成int*也是不对的。也就是说地址保存了什么样的变量,就要转化成哪种类型的指针,否则就会出错。

2.3 void*指针变量和普通指针一样可以通过等于0或者NULL来初始化,表示一个空指针

void* pv = 0; 
void* pv2 = NULL;
cout<<pv <<endl; //值为0x0
cout<<pv2<<endl; //值为0x0

2.4 当void*指针作为函数的输入和输出时,表示可以接受任意类型的输入指针和输出任意类型的指针

void* test(void* a)
{
    return a;
}

int main() {

    static int a = 5;
    int* pi = &a;
    
    cout<<pi<<endl;              //值为0x100001060
    cout<<test(pi)<<endl;        //值为0x100001060
    cout<<test((void*)pi)<<endl; //值为0x100001060
}

如果函数的输入类型为void*,在调用时由于是值传递,所以函数实际接收到的应该就是一个地址值。这个值可以是任意类型。

int a = 5;
int* pi = &a;

void* test()
{
    return pi; 
}

int main() {
    cout<<test()<<endl;        //值为0x100001060
}

输出时同样也是值传递,因此可以输出任意类型指针指向的地址。

2.5 和void的区别

再让我们回头看初始的那段函数:

//返回了一个空指针
void* say_hello(void* args)
{
    cout << "Hello World!" << endl;
    return 0;
}

//没有返回值
void say_hello(void* args)
{
    cout << "Hello World!" << endl;
   return;
}

其实两个函数实现的内容是一样的。但是void*返回类型的函数返回了一个空指针,而void型没有返回值。

3. 作用及典型应用

3.1 函数传参时不确定类型,或者要支持多类型的传参;

void function(int dataType, void* data) {
 
    // 根据dataType的不同值,进行不同的转换
    switch (dataType) {
 
        case 0:
            int* a = (int*)data;
 
        case 1:
            char* a = (char*)data;
 
        ...
    }
}

3.2 当函数的返回值不考虑类型指关心大小的时候

void * memcpy(void *dest, const void *src, size_t len);
void * memset ( void * buffer, int c, size_t num );

memcpy和memset对外接收任何类型的指针,这样是合理并且必要的,因为这是内存操作函数,是对bit进行操作的,考虑数据类型是没有任何意义的。

int *a=NULL;

a=(int *)malloc(sizeof(int));//返回的是void*,所以赋值给其他指针类型要强转一下

同样的,malloc函数只关注你要多大的内存,你需要把它怎么划分是你的事情,但是你需要显式的表明你是怎么划分的。这里语法要求是必须的,void *类型转为其他类型必须强制类型转换。

4. 总结:

4.1 void*类型的指针其实本质就是一个过渡型的指针状态,必须要赋予类型(强制类型转换)才能正常使用。

void *的范围较大,所以强制转换,使其进行范围缩小。

4.2 只能单向类型转换。void*可以转化成其他类型,但是有类型的不能转化成void*。

任何类型的指针都可以直接赋值给void *型指针,无需进行强制类型转换,相当于void *包含了其他类型的指针。

4.3 在函数调用过程中的使用作为输入输出参数也非常好用,可以灵活使用任意类型的指针,避免只能使用固定类型的指针。

<think>嗯,用户问的是这几个C语言的内存操作函数:memchr、memcmp、memcpy、memmove和memset。我需要逐一解释每个函数的作用、参数以及使用注意事项。首先,得确保自己对这些函数非常熟悉,特别是参数类型和返回值的部分。 先看memchr,它的作用是在内存块中搜索特定字符。参数是const void*,int类型的字符,还有size_t长度。返回的是找到位置的指针,找不到就NULL。需要提醒用户注意字符是以unsigned char形式处理的,这点可能容易忽略。 然后是memcmp,用来比较两块内存的内容。两个const void*指针和长度。返回值是整数,负数、零、正数表示大小关系。要注意比较是按字节进行的,可能受平台字节序影响,这点用户可能需要知道。 接下来是memcpy,用于内存拷贝。参数是目标、源和大小。这里有个__restrict关键字,说明源和目标不能重叠,否则行为未定义。这时候需要对比memmove,后者处理重叠区域更安全。用户可能容易混淆这两个函数,得强调区别。 memmove和memcpy功能类似,但会处理重叠,所以更安全,但可能效率稍低。需要解释为什么它能处理重叠,比如临时拷贝的可能。 最后是memset,用来设置内存块的值。参数是void*,int(会被转成unsigned char),和大小。常用于初始化或清零内存,比如初始化数组为0。但要注意,对于非字符类型,比如int数组用memset设0x01可能不会得到预期的每个int是0x01010101,这可能需要举例说明。 用户可能是C语言初学者,或者在使用这些函数时遇到了问题,比如内存操作错误。需要确保解释清楚每个函数的使用场景和潜在陷阱。例如,memcpy和memmove的区别,memset的使用限制等。同时,可能需要给出简单的例子,帮助理解。 另外,用户提供的函数声明中有一些细节,比如restrict关键字,需要解释其作用,说明memcpy不允许源和目标重叠,而memmove允许。这也是为什么memcpy可能更快的原因。 最后要总结这些函数都属于string.h或memory.h,使用时需要包含头文件。确保用户知道这些基本点,避免常见错误。</think>以下是对C语言内存操作函数的逐条解析(按执行效率和常用程度排序): $$ \text{void* memset(void* dest, int ch, size_t count);} $$ 1. 功能:将内存块前count个字节设置为ch的低字节值 2. 典型应用:缓冲区初始化/清零 3. 示例: ```c char buffer[1024]; memset(buffer, 0, sizeof(buffer)); // 清零缓冲区 ``` $$ \text{void* memcpy(void* restrict dest, const void* restrict src, size_t count);} $$ 1. 功能:从src复制count字节到不重叠的dest区域 2. 特性:使用restrict限定符保证内存不重叠,性能优于memmove 3. 示例: ```c int src[100], dest[100]; memcpy(dest, src, sizeof(src)); // 高效数组拷贝 ``` $$ \text{void* memmove(void* dest, const void* src, size_t count);} $$ 1. 功能:安全复制可能重叠的内存区域 2. 实现原理:采用临时缓冲区或反向拷贝策略 3. 典型场景: ```c char str[] = "memmove can handle overlap"; memmove(str + 4, str, 15); // 处理重叠拷贝 ``` $$ \text{int memcmp(const void* lhs, const void* rhs, size_t count);} $$ 1. 功能:按字典序比较内存内容 2. 返回值: - 负数:lhs < rhs - 零:相等 - 正数:lhs > rhs 3. 注意:比较结果与平台字节序相关 $$ \text{void* memchr(const void* ptr, int ch, size_t count);} $$ 1. 功能:在内存块中查找首个匹配字节 2. 参数:ch被解释为unsigned char 3. 返回值:找到时返回指针,否则返回NULL 共同特性: 1. 头文件:`<string.h>` 2. 参数规范: - size_t类型保证能表示任意对象大小 - void*指针提供通用内存访问能力 3. 时间复杂度:均为$O(n)$线性复杂度 注意事项: 1. memcpy与memmove选择: - 确定不重叠时用memcpy(更快) - 不确定时用memmove(更安全) 2. memset陷阱: ```c int arr[10]; memset(arr, 1, sizeof(arr)); // 每个字节设为0x01,而非设置int为1 ``` 3. 内存边界:必须确保操作范围不超过申请的内存空间 这些函数构成了C语言底层内存操作的基础,熟练掌握可有效提升系统级编程能力。
评论 11
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值