网站首页 > 技术文章 正文
0. 从void说起
void*是C的产物,要说清楚void*我们先从void说起。
void即为不确定类型--类型不确定,从而所占内存不确定。因为C是强类型语言,所以void par = 10;之类的声明是万万不可的,即void类型不能声明实例对象。在C语言中,void的作用主要有以下两种:
- 对函数返回类型的限定,利用void对象的大小不确定来限制函数不能有任何返回值。-- 这就是我们常写的void作返回值的函数。
- 对函数参数类型的限定,当函数不允许接受参数时,必须用void来限定函数的参数:int func(void)。-- 当然现在没什么人会这么写了,直接写int func()即可。
1. void*是什么
不同于void,编译器允许你做类似于int par = 10; void* ppar = ∥之类的操作,因为无论指针指向什么类型,指针本身所占空间是一定的。我们可以认为void*就是一个通用指针,可以指向任意类型对象,因此void*常被称为“万能指针”。
我们都知道,指针有两个属性:所指对象的地址、长度。但是指针只存储被指向对象的地址,长度则取决于指针的类型,编译器根据指针的类型从指针指向的地址向后寻址,不同的类型则寻址范围不同,各种类型之间没有本质区别,只是解释内存中的数据方式不同。对于int*,解引用时,会从指定地址向后寻找4个字节作为变量的存储单元;对于char*,会解析1个字节。而我们将一个void类型的指针指向一个int类型的实例,实际上是抹去了这一实例的类型信息,因此在使用时我们要在心里清楚被抹去的类型信息。因此,void*也可以被称为“无类型指针”,任何类型指针都可以转为void*,它无条件接受各种类型指针。
2. 如何使用void*
既然如此,那么void*有什么用呢?其实,我们在很多接口中都能看到void*,例如
void* memcpy(void* dest, const void* src, sizt_t len);
void *memset(void *buffer, int c, int count);
任何类型的指针都可以传入memcpy和memset中,这也真正体现了内存操作函数的意义。void*帮我们屏蔽了冗余的变量类型信息,而直接将内存暴露在我们面前。
又如
ssize_t read(int fd, void* buf, size_t count);
那么,为何要如此设计?因为对于这种通用型接口,你不知道用户的数据类型是什么,但是你必须能够处理用户的各种类型数据,因而会使用void*。void*能包容的接受各种类型的指针。也就是说,如果你期望接口能接受任何类型的参数,你可以使用void*类型。但是在具体使用的时候,你必须转换为具体的指针类型。例如,你传入接口的是int*,那么你在使用的时候就应该按照int*使用。
3. void*的约束
3.1 void*可以指向任何类型的地址,但带类型的指针不能指向void*的地址
正常来说如果两个指针类型不一样的话,两个指针变量是不可以直接赋值的。如:
int* a, float* b;
a = b; //编译错误
而任何类型的指针可以赋值给void*指针,但是反过来不可以,也就是说一个有类型的指针不能指向一个void类型的变量(哪怕此时void*变量)已经指向了一个有类型的地址。
int a = 5;
int* pa = &a;
void* pb = pa;
int* pa2 = pb; //编译错误,有类型的指针变量不能指向void*变量
3.2 void*指针只有强制类型转换以后才能正常取值
既然是无类型指针,那么就不要尝试做下面的事情:
- 解引用
- 算数运算
由于不知道其解引用操作的内存大小,以及算数运算操作的大小,因此它的结果是未知的。
#include <stdio.h>
int main(void) {
int a = 10;
int *b = &a;
void *c = b;
*c; //*(int*)c就对了
return 0;
}
编译器警告如下:
warning: dereferencing 'void*' pointer
所以,void*指针只有强制类型转换以后才可以正常取值与运算。
4. void*与template
对于c++而言,对于某些泛型可以实现的功能但又想找一种轻量化的实现方法,void*也不失为一种不错的选择。
毫无疑问,在可能的情况下使用void*会提高编译速度,但这并不应该成为我们大量使用void*的理由,正如前面所看到的,void*会将很多隐藏的问题埋入深坑,相比template而言,编译期就能暴露的问题被推迟到运行时,隐含bug风险显著提升。
5. 总结
void*很强大,但是一定要在合适的时候使用;同时强转很逆天,一定要注意前后的类型是否真的能正确转换。
通俗的讲,void*:
- 这里有一片内存数据,我也不知道什么类型,给你了,你自己想怎么用怎么用吧,不过要用对奥。
- 我这里什么类型都能处理,你给我一片内存数据就可以了。
猜你喜欢
- 2024-10-26 C语言、嵌入式项目中一些常用知识及技巧第一弹
- 2024-10-26 C语言编程:最常见 7 道C语言面试题,还是有不少人弄不明白?
- 2024-10-26 初识C语言:简介、环境搭建、第一个HelloWorld
- 2024-10-26 C|volatile关键字使用细节及适用场合
- 2024-10-26 C语言干货:函数知识详解(变量的作用域,全局变量,静态变量)
- 2024-10-26 小白入门C语言20问20答2(新手c语言)
- 2024-10-26 C语言的简单了解及学习2(c语言的入门知识)
- 2024-10-26 C 语言基本语法(c语言的基础语法)
- 2024-10-26 C语言数据怎么描述?最全面解析,C语言基础教学档案!编号零零五
- 2024-10-26 C语言void关键字的高级玩法,6个样例代码告诉你
- 11-26Win7\8\10下一条cmd命令可查得笔记本电脑连接过的Wifi密码
- 11-26一文搞懂MySQL行锁、表锁、间隙锁详解
- 11-26电脑的wifi密码忘记了?一招教你如何找回密码,简单明了,快收藏
- 11-26代码解决忘记密码问题 教你用CMD命令查看所有连接过的WIFI密码
- 11-26CMD命令提示符能干嘛?这些功能你都知道吗?
- 11-26性能测试之慢sql分析
- 11-26论渗透信息收集的重要性
- 11-26如何查看电脑连接过的所有WiFi密码
- 最近发表
- 标签列表
-
- cmd/c (57)
- c++中::是什么意思 (57)
- sqlset (59)
- ps可以打开pdf格式吗 (58)
- phprequire_once (61)
- localstorage.removeitem (74)
- routermode (59)
- vector线程安全吗 (70)
- & (66)
- java (73)
- org.redisson (64)
- log.warn (60)
- cannotinstantiatethetype (62)
- js数组插入 (83)
- resttemplateokhttp (59)
- gormwherein (64)
- linux删除一个文件夹 (65)
- mac安装java (72)
- reader.onload (61)
- outofmemoryerror是什么意思 (64)
- flask文件上传 (63)
- eacces (67)
- 查看mysql是否启动 (70)
- java是值传递还是引用传递 (58)
- 无效的列索引 (74)