void * 是什么?
最近遇到void *的问题无法解决,发现再也无法逃避了(以前都是采取悄悄绕过原则),于是我决定直面它。
在哪遇到了?

线程创建函数pthread_create()的最后一个参数void *arg,嗯?传地址还是传值?传值好像有警告。
还有别的出现的地方呢

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

也就是对于一个指针而言,如果我们在前面规定了它的类型。那就相当于决定了它的“跳跃力”。“跳跃力”就比如说上面图中int跳了4个字节,但是double跳了8个字节。基于这样的理解,我要对void *下定义了:
void * 是一个跳跃力未定的指针
二:跳跃力什么时候定?
这就是它的神奇之处了,我们可以自己控制在需要的时候将它实现为需要的类型。这样的好处是:编程时候节约代码,实现泛型编程。比如我们经常写的排序算法,就可以这么写:
#include <stdio.h>
#include <string.h>
static void Swap(char *vp1, char *vp2, int width)
{
char tmp;
if ( vp1 != vp2 ) {
while ( width-- ) {
tmp = *vp1;
*vp1++ = *vp2;
*vp2++ = tmp;
}
}
}
void BubbleSort(void *base, int n, int elem_size,
int (*compare)( void *, void * ))
{
int i, last, end = n - 1;
char *elem_addr1, *elem_addr2;
while (end > 0) {
last = 0;
for (i = 0; i < end; i++) {
elem_addr1 = (char *)base + i * elem_size;
elem_addr2 = (char *)base + (i + 1) * elem_size;
if (compare( elem_addr1, elem_addr2 ) > 0) {
Swap(elem_addr1, elem_addr2, elem_size);
last = i;
}
}
end = last;
}
}
int compare_int(void *elem1, void *elem2)
{
return (*(int *)elem1 - *(int *)elem2);
}
int compare_double(void *elem1, void *elem2)
{
return (*(double *)elem1 > *(double *)elem2) ? 1 : 0;
}
int main(int argc, char *argv[])
{
int num_int[8] = {8,7,6,5,4,3,2,1};
double num_double[8] = {8.8,7.7,6.6,5.5,4.4,3.3,2.2,1.1};
int i;
BubbleSort(num_int, 8, sizeof(int), compare_int);
for (i = 0; i < 8; i++) {
printf("%d ", num_int[i]);
}
printf("\n");
BubbleSort(num_double, 8, sizeof(double), compare_double);
for (i = 0; i < 8; i++) {
printf("%.1f ", num_double[i]);
}
printf("\n");
return 0;
}

上面的compare_int和compare_double就是定它跳跃力的时候。
三:再来说memcpy
我们先来看下面这段代 码:
#include<stdio.h>
#include<string.h>
struct stu{
int id;
int num;
};
#define LEN sizeof(struct stu) /*LEN 为stu的大小*/
int main(int argc,char *argv[])
{
struct stu stu1,stu2;
stu1.id = 2;
stu1.num = 3;
char str[LEN];
memcpy(str,&stu1,LEN); /*将stu1保存进str*/
memcpy(&stu2,str,LEN); /*将str转换成stu2*/
printf("%d %d\n",stu2.id,stu2.num); /*访问stu2仍然得到 2 和 3*/
return 0;
}

说明:str 是一个char *类型的,但是&stu是一个struct stu *类型的,就像我们前面说的那样,他们的“跳跃力”是不一样的,但是memcpy之所以能将它们都接受,就是因为它的参数是(void *)类型的。在参数传递的时候包容万象,全都接受,这才能体现人家是memcpy()吗,mem是内存,肯定可以不非要按照某种具体类型处理,具体至于还想memcpy的内部怎么处理,看下面:
http://blog.csdn.net/yangbodong22011/article/details/53227560
四:总结
void *是一种指针类型,常用在函数参数、函数返回值中需要兼容不同指针类型的地方。我们可以将别的类型的指针无需强制类型转换的赋值给void *类型。也可以将void *强制类型转换成任何别的指针类型,至于强转的类型是否合理,就需要我们程序员自己控制了。
#include<stdio.h>
int main(int argc,char *argv[])
{
int a = 2;
double b = 2.0;
void *c; //定义void *
int *p = &a;
c = p; //将int * 转成void *,
double *q = (double *)c; //将void *转成double *
printf("%.f\n",*q);
return 0;
}
结果是不是正确呢?自己试一试吧~
------ 20210827 更新
关于上述例子,其实我本身想表达指针类型确定它解释的数据范围这个观点,也就是int *解释的范围是4个字节,但是double *解释的范围是8个字节,所以上述例子中,int * 转了 void * 再到 double *,从而将它解释的范围扩大到了8个字节,那应该输出什么结果呢?答案是从 a 的地址开始,下面 8 个字节组成的 double 的数值,a的地址解释为int是2,但是再加上4个字节,这4个字节的内存值是不确定,脏数据,因此最后的输出也是脏数据。
下面是我机器的执行结果(p.s. 我同时输出了三个变量的地址)
printf("a:%p b:%p q:%p\n", &a, &b, q);

参考链接 : www.0xffffff.org
void * 是什么?的更多相关文章
- 如何理解typedef void (*pfun)(void)
问题: 在刚接触typedef void (*pfun)(void) 这个结构的时候,存在疑惑,为什么typedef后只有一"块"东西,而不是两"块"东西呢?那 ...
- C#中的null与void
一.null: 1.明义,null是什么意思? null是指一个变量没有指向具体对象的有效引用. 这句话什么意思呢?意思就是 1).能够使用null修饰的是变量: 2).主要指的是引用. 那么这就引出 ...
- 你必须知道的指针基础-7.void指针与函数指针
一.不能动的“地址”—void指针 1.1 void指针初探 void *表示一个“不知道类型”的指针,也就不知道从这个指针地址开始多少字节为一个数据.和用int表示指针异曲同工,只是更明确是“指针” ...
- js中 javascript:void(0) 用法详解
点击链接不做任何事情: <a href="#" onclick="return false">test</a> <a href=& ...
- html 空链接 href="#"与href="javascript:void(0)"的区别
#包含了一个位置信息 默认的锚是#top 也就是网页的上端 而javascript:void(0) 仅仅表示一个死链接 这就是为什么有的时候页面很长浏览链接明明是#但跳动到了页首 而javascrip ...
- 原生JS:delete、in、typeof、instanceof、void详解
delete.in.typeof.instanceof.void详解 本文参考MDN做的详细整理,方便大家参考[MDN](https://developer.mozilla.org/zh-CN/doc ...
- VS2012 Unit Test(Void, Action, Func) —— 对无返回值、使用Action或Func作为参数、多重载的方法进行单元测试
[提示] 1. 阅读文本前希望您具备如下知识:了解单元测试,了解Dynamic,熟悉泛型(协变与逆变)和Lambda,熟悉.NET Framework提供的 Action与Func委托.2.如果您对单 ...
- IOS 杂笔-16 (-(void)scrollViewDidEndScrollingAnimation:方法使用注意)
今天在写项目的时候,遇到了一件令人抓狂的事情. 正如标题所示,被这个方法弄的团团转. -(void)scrollViewDidEndScrollingAnimation:是协议里的方法. 意味当动画结 ...
- void main() && int main()
C/C++ 中从来没有定义过void main( ) .C++ 之父说过: The definition void main( ) { /* ... * / } is not and never ha ...
- void 0作用
undefine 是可以被赋值的. 但是void 操作符 通过 计算 void 后面的变量名后还是会返回一个undefined ,这样就保证了你的undefined即使被定义了,采用void 表达式, ...
随机推荐
- Elasticsearch(ES)集群的搭建
1. 概述 Elasticsearch(ES)集群支持分片和副本,能够很容易的实现负载均衡.扩容.容灾.高可用. 今天我们就来聊一下,Elasticsearch(ES)集群是如何搭建的. 2. 场景介 ...
- Spring Boot中如何配置线程池拒绝策略,妥善处理好溢出的任务
通过之前三篇关于Spring Boot异步任务实现的博文,我们分别学会了用@Async创建异步任务.为异步任务配置线程池.使用多个线程池隔离不同的异步任务.今天这篇,我们继续对上面的知识进行完善和优化 ...
- AVS 通信模块之AVSConnectionManager
AVSConnectionManager 类为客户端无缝地管理与AVS的连接 功能简介 失败时连接重试 允许后续重新连接 ping管理 AVS服务器断开时周期重连服务器 允许客户端完全启用或禁用连接管 ...
- iNeuOS工业互联网操作系统部署在华为欧拉(openEuler)国产系统,vmware、openEuler、postgresql、netcore、nginx、ineuos一站式部署
目 录 1. 概述... 3 2. 创建虚拟机&安装华为欧拉(openEuler)系统... 4 2.1 创建新的虚拟机... 4 2.2 ...
- kubeadm 命令简介
kubeadm 命令 kubeadm init 启动一个kubernetes主节点 kubeadm join 启动一个kubernetes工作节点并加入到集群中 kubeadm upgrade 更新一 ...
- VueCLI3 创建vue项目
关于旧版本 Vue CLI 的包名称由 vue-cli 改成了 @vue/cli. 如果你已经全局安装了旧版本的 vue-cli (1.x 或 2.x),你需要先通过 npm uninstall vu ...
- android 使用SQLite的基本操作
Android操作数据库SQLite使用sql语句基本操作 1:自定义自己的SQLiteOpenHelper public class DBHelper extends SQLiteOpenHelpe ...
- 踩坑系列《四》a标签的href属性拼接问题
如上所示,无法直接在 html里面的 a 标签的href属性传递参数时,只需要在 JS 中获取对应 a 标签的id,再通过 attr 方法抓到 href,进行字符串拼接即可
- 【DP】Educational DP Contest
这份 dp 题单的最后几题好难 orz. 前面的题比较简单,所以我会选取一些题来讲,其它的直接看代码理解吧 qwq. 传送门: https://atcoder.jp/contests/dp 全部 AC ...
- 教你 4 步搭建弹性可扩展的 WebAPI
作者 | 萧起 阿里云云原生团队 本文整理自<Serverless 技术公开课>,关注"Serverless"公众号,回复"入门",即可获取 Se ...