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 表达式, ...
随机推荐
- MySql分区、分表和分库
MySql分区.分表和分库 数据库的数据量达到一定程度之后,为避免带来系统性能上的瓶颈.需要进行数据的处理,采用的手段是分区.分片.分库.分表. 一些问题的解释: 1.为什么要分表和分区? 日常开发中 ...
- 对OOP的理解
OOP是面向对象编程Object Oriented Programming,特征分别是封装.继承.多态.抽象. 封装:封装是指将对象信息状态通过访问权限修饰符隐藏在对象内部,不允许外部程序直接访问,如 ...
- 深入理解Python切片
Python序列的切片很基础同时也很重要,最近看到一个[::-1]的表达,不明所以,查了一些资料并实际操作,对Python切片有了更深刻的认识,以下结合例子详细说明.先看下切片的基本语法,一般认为切片 ...
- Java-基础-JDK动态代理
1. 简介 代理模式的定义:为其他对象提供一种代理以控制对这个对象的访问.在某些情况下,一个对象不适合或者不能直接引用另一个对象,而代理对象可以在客户端和目标对象之间起到中介的作用. 比如:我们在调用 ...
- Fiddler抓HTTPS接口数据,安装证书并不复杂,超详细的图文解说,不信你看!
@ 目录 前言 安装环境 配置网络 IP 端口 配置网络 浏览器打开下载链接 下载证书 安装证书 证书安装坑 前言 抓包是我测试工作中必须要学会的一个工具,我们都知道,抓取HTTPS接口里需要安装证书 ...
- 10.6 Nginx 高并发连接
Nginx 高并发连接 什么是IO,输入输出 Web服务器IO的整个详细过程 (1)客户发起请求到服务器网卡: (2)服务器网卡接受到请求后转交给内核 ...
- windows中抓包命令,以及保存为多个文件的方法
本文主要介绍windows中抓包命令,以及保存为多个文件的方法 说一说保存为多个文件存储数据包这个问题的由来,一般如果长时间抓包,有可能需要等上几个小时,因为这个时候抓包的内容都是存放在内存中的,几个 ...
- 4.19——数组双指针——26. 删除有序数组中的重复项 & 27. 删除有序数组中的重复项II & 80. 删除有序数组中的重复项 II
第一次做到数组双指针的题目是80: 因为python的List是可以用以下代码来删除元素的: del List[index] 所以当时的我直接用了暴力删除第三个重复元素的做法,大概代码如下: n = ...
- Wireshark 过滤器的使用
符号 例子 = = tcp.port = = 80 过滤出来TCP包含80端口的数据包 != ip.src != 127.0.0.1 ip的原地址不是127.0.0.1过滤出来 > lp.len ...
- Unity——自动化代码生成
自动化代码生成 一.前言 由于之前写过关于UI框架的文章,这篇基于之前的基础,添加了自动生成代码的功能: 如果学习过程有困惑可以跳转到之前的文章<Unity--基于UGUI的UI框架>: ...