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

在哪遇到了?

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

还有别的出现的地方呢

memcpy(),返回值和参数都有void *,那又怎么传呢?下面我们首先来说说void *是什么。

一:void *是什么?

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

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

void * 是一个跳跃力未定的指针

二:跳跃力什么时候定?

这就是它的神奇之处了,我们可以自己控制在需要的时候将它实现为需要的类型。这样的好处是:编程时候节约代码,实现泛型编程。比如我们经常写的排序算法,就可以这么写:


  1. #include <stdio.h>
  2. #include <string.h>
  3. static void Swap(char *vp1, char *vp2, int width)
  4. {
  5. char tmp;
  6. if ( vp1 != vp2 ) {
  7. while ( width-- ) {
  8. tmp = *vp1;
  9. *vp1++ = *vp2;
  10. *vp2++ = tmp;
  11. }
  12. }
  13. }
  14. void BubbleSort(void *base, int n, int elem_size,
  15. int (*compare)( void *, void * ))
  16. {
  17. int i, last, end = n - 1;
  18. char *elem_addr1, *elem_addr2;
  19. while (end > 0) {
  20. last = 0;
  21. for (i = 0; i < end; i++) {
  22. elem_addr1 = (char *)base + i * elem_size;
  23. elem_addr2 = (char *)base + (i + 1) * elem_size;
  24. if (compare( elem_addr1, elem_addr2 ) > 0) {
  25. Swap(elem_addr1, elem_addr2, elem_size);
  26. last = i;
  27. }
  28. }
  29. end = last;
  30. }
  31. }
  32. int compare_int(void *elem1, void *elem2)
  33. {
  34. return (*(int *)elem1 - *(int *)elem2);
  35. }
  36. int compare_double(void *elem1, void *elem2)
  37. {
  38. return (*(double *)elem1 > *(double *)elem2) ? 1 : 0;
  39. }
  40. int main(int argc, char *argv[])
  41. {
  42. int num_int[8] = {8,7,6,5,4,3,2,1};
  43. double num_double[8] = {8.8,7.7,6.6,5.5,4.4,3.3,2.2,1.1};
  44. int i;
  45. BubbleSort(num_int, 8, sizeof(int), compare_int);
  46. for (i = 0; i < 8; i++) {
  47. printf("%d ", num_int[i]);
  48. }
  49. printf("\n");
  50. BubbleSort(num_double, 8, sizeof(double), compare_double);
  51. for (i = 0; i < 8; i++) {
  52. printf("%.1f ", num_double[i]);
  53. }
  54. printf("\n");
  55. return 0;
  56. }

上面的compare_intcompare_double就是定它跳跃力的时候。

三:再来说memcpy

我们先来看下面这段代 码:

  1. #include<stdio.h>
  2. #include<string.h>
  3. struct stu{
  4. int id;
  5. int num;
  6. };
  7. #define LEN sizeof(struct stu) /*LEN 为stu的大小*/
  8. int main(int argc,char *argv[])
  9. {
  10. struct stu stu1,stu2;
  11. stu1.id = 2;
  12. stu1.num = 3;
  13. char str[LEN];
  14. memcpy(str,&stu1,LEN); /*将stu1保存进str*/
  15. memcpy(&stu2,str,LEN); /*将str转换成stu2*/
  16. printf("%d %d\n",stu2.id,stu2.num); /*访问stu2仍然得到 2 和 3*/
  17. return 0;
  18. }

说明:str 是一个char *类型的,但是&stu是一个struct stu *类型的,就像我们前面说的那样,他们的“跳跃力”是不一样的,但是memcpy之所以能将它们都接受,就是因为它的参数是(void *)类型的。在参数传递的时候包容万象,全都接受,这才能体现人家是memcpy()吗,mem是内存,肯定可以不非要按照某种具体类型处理,具体至于还想memcpy的内部怎么处理,看下面:

http://blog.csdn.net/yangbodong22011/article/details/53227560

四:总结

void *是一种指针类型,常用在函数参数、函数返回值中需要兼容不同指针类型的地方。我们可以将别的类型的指针无需强制类型转换的赋值给void *类型。也可以将void *强制类型转换成任何别的指针类型,至于强转的类型是否合理,就需要我们程序员自己控制了。

  1. #include<stdio.h>
  2. int main(int argc,char *argv[])
  3. {
  4. int a = 2;
  5. double b = 2.0;
  6. void *c; //定义void *
  7. int *p = &a;
  8. c = p; //将int * 转成void *,
  9. double *q = (double *)c; //将void *转成double *
  10. printf("%.f\n",*q);
  11. return 0;
  12. }

结果是不是正确呢?自己试一试吧~

------ 20210827 更新
关于上述例子,其实我本身想表达指针类型确定它解释的数据范围这个观点,也就是int *解释的范围是4个字节,但是double *解释的范围是8个字节,所以上述例子中,int * 转了 void * 再到 double *,从而将它解释的范围扩大到了8个字节,那应该输出什么结果呢?答案是从 a 的地址开始,下面 8 个字节组成的 double 的数值a的地址解释为int2,但是再加上4个字节,这4个字节的内存值是不确定,脏数据,因此最后的输出也是脏数据。

下面是我机器的执行结果(p.s. 我同时输出了三个变量的地址)

  1. printf("a:%p b:%p q:%p\n", &a, &b, q);

参考链接 : www.0xffffff.org

void * 是什么?的更多相关文章

  1. 如何理解typedef void (*pfun)(void)

    问题: 在刚接触typedef void (*pfun)(void) 这个结构的时候,存在疑惑,为什么typedef后只有一"块"东西,而不是两"块"东西呢?那 ...

  2. C#中的null与void

    一.null: 1.明义,null是什么意思? null是指一个变量没有指向具体对象的有效引用. 这句话什么意思呢?意思就是 1).能够使用null修饰的是变量: 2).主要指的是引用. 那么这就引出 ...

  3. 你必须知道的指针基础-7.void指针与函数指针

    一.不能动的“地址”—void指针 1.1 void指针初探 void *表示一个“不知道类型”的指针,也就不知道从这个指针地址开始多少字节为一个数据.和用int表示指针异曲同工,只是更明确是“指针” ...

  4. js中 javascript:void(0) 用法详解

    点击链接不做任何事情: <a href="#" onclick="return false">test</a> <a href=& ...

  5. html 空链接 href="#"与href="javascript:void(0)"的区别

    #包含了一个位置信息 默认的锚是#top 也就是网页的上端 而javascript:void(0) 仅仅表示一个死链接 这就是为什么有的时候页面很长浏览链接明明是#但跳动到了页首 而javascrip ...

  6. 原生JS:delete、in、typeof、instanceof、void详解

    delete.in.typeof.instanceof.void详解 本文参考MDN做的详细整理,方便大家参考[MDN](https://developer.mozilla.org/zh-CN/doc ...

  7. VS2012 Unit Test(Void, Action, Func) —— 对无返回值、使用Action或Func作为参数、多重载的方法进行单元测试

    [提示] 1. 阅读文本前希望您具备如下知识:了解单元测试,了解Dynamic,熟悉泛型(协变与逆变)和Lambda,熟悉.NET Framework提供的 Action与Func委托.2.如果您对单 ...

  8. IOS 杂笔-16 (-(void)scrollViewDidEndScrollingAnimation:方法使用注意)

    今天在写项目的时候,遇到了一件令人抓狂的事情. 正如标题所示,被这个方法弄的团团转. -(void)scrollViewDidEndScrollingAnimation:是协议里的方法. 意味当动画结 ...

  9. void main() && int main()

    C/C++ 中从来没有定义过void main( ) .C++ 之父说过: The definition void main( ) { /* ... * / } is not and never ha ...

  10. void 0作用

    undefine 是可以被赋值的. 但是void 操作符 通过 计算 void 后面的变量名后还是会返回一个undefined ,这样就保证了你的undefined即使被定义了,采用void 表达式, ...

随机推荐

  1. MySql分区、分表和分库

    MySql分区.分表和分库 数据库的数据量达到一定程度之后,为避免带来系统性能上的瓶颈.需要进行数据的处理,采用的手段是分区.分片.分库.分表. 一些问题的解释: 1.为什么要分表和分区? 日常开发中 ...

  2. 对OOP的理解

    OOP是面向对象编程Object Oriented Programming,特征分别是封装.继承.多态.抽象. 封装:封装是指将对象信息状态通过访问权限修饰符隐藏在对象内部,不允许外部程序直接访问,如 ...

  3. 深入理解Python切片

    Python序列的切片很基础同时也很重要,最近看到一个[::-1]的表达,不明所以,查了一些资料并实际操作,对Python切片有了更深刻的认识,以下结合例子详细说明.先看下切片的基本语法,一般认为切片 ...

  4. Java-基础-JDK动态代理

    1. 简介 代理模式的定义:为其他对象提供一种代理以控制对这个对象的访问.在某些情况下,一个对象不适合或者不能直接引用另一个对象,而代理对象可以在客户端和目标对象之间起到中介的作用. 比如:我们在调用 ...

  5. Fiddler抓HTTPS接口数据,安装证书并不复杂,超详细的图文解说,不信你看!

    @ 目录 前言 安装环境 配置网络 IP 端口 配置网络 浏览器打开下载链接 下载证书 安装证书 证书安装坑 前言 抓包是我测试工作中必须要学会的一个工具,我们都知道,抓取HTTPS接口里需要安装证书 ...

  6. 10.6 Nginx 高并发连接

    Nginx 高并发连接 什么是IO,输入输出      Web服务器IO的整个详细过程             (1)客户发起请求到服务器网卡:         (2)服务器网卡接受到请求后转交给内核 ...

  7. windows中抓包命令,以及保存为多个文件的方法

    本文主要介绍windows中抓包命令,以及保存为多个文件的方法 说一说保存为多个文件存储数据包这个问题的由来,一般如果长时间抓包,有可能需要等上几个小时,因为这个时候抓包的内容都是存放在内存中的,几个 ...

  8. 4.19——数组双指针——26. 删除有序数组中的重复项 & 27. 删除有序数组中的重复项II & 80. 删除有序数组中的重复项 II

    第一次做到数组双指针的题目是80: 因为python的List是可以用以下代码来删除元素的: del List[index] 所以当时的我直接用了暴力删除第三个重复元素的做法,大概代码如下: n = ...

  9. Wireshark 过滤器的使用

    符号 例子 = = tcp.port = = 80 过滤出来TCP包含80端口的数据包 != ip.src != 127.0.0.1 ip的原地址不是127.0.0.1过滤出来 > lp.len ...

  10. Unity——自动化代码生成

    自动化代码生成 一.前言 由于之前写过关于UI框架的文章,这篇基于之前的基础,添加了自动生成代码的功能: 如果学习过程有困惑可以跳转到之前的文章<Unity--基于UGUI的UI框架>: ...