最近遇到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 * 是一个跳跃力未定的指针

二:跳跃力什么时候定?

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


#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_intcompare_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的地址解释为int2,但是再加上4个字节,这4个字节的内存值是不确定,脏数据,因此最后的输出也是脏数据。

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

    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. Shell系列(17)- 配置文件功能(待完善)

    配置文件功能 文件名 功能 相关联命令 /etc/profile USER变量 LOGNAME变量 MAIL变量 PATH变量 HOSTNAME变量 umask 调用/etc/profile.d/*. ...

  2. python学习笔记(一)-基础知识

    O.解释型语言和编译型语言 编译型语言就是先把写好的程序翻译成计算机语言然后执行,就是所谓的一次编译到处运行,比如c.c++就是编译型语言,这样的语言特点是运行速度快,但是需要事先把程序编译好才可以. ...

  3. P3643-[APIO2016]划艇【dp】

    正题 题目链接:https://www.luogu.com.cn/problem/P3643 题目大意 求有多少个\(n\)个数的序列\(x\)满足,\(x_i\in \{0\}\cup[a_i,b_ ...

  4. P2179-[NOI2012]骑行川藏【导数,二分】

    正题 题目链接:https://www.luogu.com.cn/problem/P2179 题目大意 给出\(E\)和\(n\)个\(s_i,k_i,u_i\)求一个序列\(v_i\)满足 \[\s ...

  5. P4922-[MtOI2018]崩坏3?非酋之战!【dp】

    正题 题目链接:https://www.luogu.com.cn/problem/P4922 题目大意 题目好长直接放了 在崩坏 3 中有一个叫做天命基地的地方,女武神们将在基地中开派对与敌人们厮杀. ...

  6. Java——多线程编程学习/01

    原文章:http://www.cnblogs.com/QG-whz/p/8351298.html  注:建议去看原博主的文章,单就这个知识点而论,比书本讲的透彻,如有违规,联系必删! 并发环境下进行编 ...

  7. 踩坑系列《十三》解决时间戳long转换int溢出(即转换值为负数)

    最近业务需求,需要使用到 int 类型的时间戳,所以在使用时间戳的时候,由于java自带的 System.currentTimeMillis() 返回的类型是long,强行转换一波的话,是会出现数据溢 ...

  8. heoi2020树

    _ _01trie树合并 _ _ 在考场上一直想用数据结构维护,还花了好长时间算 $(a+1)^(b+1)$,现在看来当时好像在犯傻........ 异或有个神奇的工具是 01trie 树,此题就用此 ...

  9. 8.3 MHA 搭建

    操作步骤 1.配置主从 1.1 master /etc/my.cnf server-id log-bin skip-name-resolve 1.2 master 建立repl用户 grant rep ...

  10. bzoj1972 SDOI2010-----猪国杀(模拟)

    题目自己去找吧 记得数据范围是<=10 注意事项: 1.牌库空的时候,要不断的抽第一张牌 2.反贼的决斗永远是向主公发的 3.每次判定无懈的时候,都是从使用锦囊的那个牌开始,记得敌意和殷勤的判断 ...