说透一级指针和二级指以及(void**)&在双链表中的应用

C 算法精介----链表->双向链表 双向链表 双向链表,如同名字所暗示的那样,链表元素之间由2个指针链接,双向链表中的每一个元素都有3部分组成:除了数据成员和指针next外,新增加了一个指针prev,指向其前驱元素的指针。下图清晰绘出双向链表的结构: // // DList.c // Algorithms&Data_structures // // Created by TTc on 阅读详情

因为函数参数是按值传递的,所以要想改变变量,必须传递地址。

二级指针实际上就是指针变量的地址,如果传递二级指针,函数声明必须写**。

(void**)&必须是本质上就是指针变量的地址才可以做这样的转换,并不是说把一个一级指针也可以转换,void**的本质是标识一个二级指针。

&data就是(默认数据类型 **)&data,(void **)&data和&data还是同一块内存,只不过数据类型发生变化了。

如果默认数据类型是int,&data就是(int **)&data



一级指针:

void
swap ( int *a, int *b ){
    int temp = 0;
    temp = *a;
    *a = *b;
    *b = temp; 
}

int
main ( int argc, char **argv ){
    int a,b;
    a = 16;
    b = 32;
    swap(&a, &b);
    return ( a - b );
}

二级指针:

void swap(int **a, int **b)
{
    int t;
    t =**a;
    **a =**b;
    **b=t;
}
int main()
{
    int i = 3;
    int j = 5;
    int *p = &i;
    int *q = &j;
    swap(&p, &q);
}


高级一点使用void**只是为了通用,可以交换各种类型。

void swap(void **a, void **b)
{
    void *t;
    t =*a;
    *a =*b;
    *b=t;
}
int main()
{
    int i = 3;
    int j = 5;
    int *p = &i;
    int *q = &j;
	char *s1="abc";
	char *s2="def";
    swap((void**)&p, (void**)&q);
	swap((void**)&s1, (void**)&s2);
}


注意char*是字符串指针,需要改变其对应的变量必须用地址,s1就是"abc"的起始地址,是不能被改变,要想改变s1必须用他的地址也就是&s1,所以需要void**:

void swap(void *a, void *b)
{
    void *t;
    t =a;
    a =b;
    b=t;
}
int main()
{
	char *s1="abc";
	char *s2="def";
	swap((void*)s1, (void*)s2);
}




(void**)& 本质

在看《算法精解  C语言描述》的双链表dlist.c的代码看到这样一段


算法精解:C语言描述\examples_unix\examples\chtbl\ex-1.c

int                *data;
*data = 11;
if (dlist_remove(&list, element, (void **)&data) != 0)
   return 1;


算法精解:C语言描述\examples_unix\source\dlist.c

int dlist_remove(DList *list, DListElmt *element, void **data) {

/*****************************************************************************
*                                                                            *
*  Do not allow a NULL element or removal from an empty list.                *
*                                                                            *
*****************************************************************************/

if (element == NULL || dlist_size(list) == 0)
   return -1;

/*****************************************************************************
*                                                                            *
*  Remove the element from the list.                                         *
*                                                                            *
*****************************************************************************/

*data = element->data;

if (element == list->head) {

   /**************************************************************************
   *                                                                         *
   *  Handle removal from the head of the list.                              *
   *                                                                         *
   **************************************************************************/

   list->head = element->next;

   if (list->head == NULL)
      list->tail = NULL;
   else
      element->next->prev = NULL;

   }

else {

   /**************************************************************************
   *                                                                         *
   *  Handle removal from other than the head of the list.                   *
   *                                                                         *
   **************************************************************************/

   element->prev->next = element->next;

   if (element->next == NULL)
      list->tail = element->prev;
   else
      element->next->prev = element->prev;

}

/*****************************************************************************
*                                                                            *
*  Free the storage allocated by the abstract data type.                     *
*                                                                            *
*****************************************************************************/

free(element);

/*****************************************************************************
*                                                                            *
*  Adjust the size of the list to account for the removed element.           *
*                                                                            *
*****************************************************************************/

list->size--;

return 0;

}


其实很简单:

data是指针变量;

&data是data指针变量的地址所以传递他就是void**

看下面这幅调试图就明白了,起始&data就是(int **)&data,(void **)&data和&data还是同一块内存,只不过数据类型发生变化了。


注意:原书有问题就是dlist_remove之后没有free()。

相对而言,redis那种风格的删除节点更好,因为是在函数体内释放节点的,而不是外面。关于二者的比较参考:

Redis:adlist.c

/* Remove the specified node from the specified list.
 * It's up to the caller to free the private value of the node.
 *
 * This function can't fail. */
void listDelNode(list *list, listNode *node)
{
    if (node->prev)
        node->prev->next = node->next;
    else
        list->head = node->next;
    if (node->next)
        node->next->prev = node->prev;
    else
        list->tail = node->prev;
    if (list->free) list->free(node->value);
    zfree(node);
    list->len--;
}

网上也有人遇到这个问题,人家自己写了个destroy函数,其实就是free

/*destroy */  
void destroy(void *data)  
{  
    free(data);  
    return;  
}  

dlist_remove(&dlist_exp, p, (void **)&cb_ptr);  
    printf("the data of the third element: length = %d, width = %d, height = %d\n",  
            cb_ptr->length,  
            cb_ptr->width,  
            cb_ptr->height);  
    destroy(cb_ptr); //free the memory  
数据结构学习之双向链表结构   数据结构学习之单向链表结构

补充说明:

element是链表结点,这个结点里含有个数据域data,它只是个指针变量,它指向的是应用层分配的空间,
*data指向的不是element内存,而是指向element中的data域,这个域是由应用层来管理的,即当element被删除时,其element内的data域应该保存由应用层开发人员负责释放。

element是DListElmt的一个变量,变量内含有个data指针域, 接口的data域是二级指针,这两个不要混淆了.element被删除后,其data域本身所占用的资源被回收(因为是指针,占4字节),但是并不意味着data所指向的资源也被回收,这是不同的两个概念.
*data = element->data; 这句话将element内data域所指向的物理内存地址记录下来,返回给应用层处理.


《算法精解》这个哈希表的例子作者是给出了删除后要free的代码:


C语言描述\examples_unix\examples\chtbl\ex-1.c

if ((data = (char *)malloc(sizeof(char))) == NULL)
   return 1;

*data = 'G';

if ((retval = chtbl_insert(&htbl, data)) != 0)
   free(data);

fprintf(stdout, "Trying to insert G again...Value=%d (1=OK)\n", retval);

fprintf(stdout, "Removing d, G, and S\n");

c = 'd';
data = &c;

if (chtbl_remove(&htbl, (void **)&data) == 0)
   free(data);

c = 'G';
data = &c;

if (chtbl_remove(&htbl, (void **)&data) == 0)
   free(data);

其实在作者的代码结尾dlist_destroy函数也说明这一点:

算法精解:C语言描述\examples_unix\examples\chtbl\ex-1.c

dlist_destroy(&list);

算法精解:C语言描述\examples_unix\source\dlist.c

void dlist_destroy(DList *list) {

void               *data;

/*****************************************************************************
*                                                                            *
*  Remove each element.                                                      *
*                                                                            *
*****************************************************************************/

while (dlist_size(list) > 0) {

   if (dlist_remove(list, dlist_tail(list), (void **)&data) == 0 && list->
      destroy != NULL) {

      /***********************************************************************
      *                                                                      *
      *  Call a user-defined function to free dynamically allocated data.    *
      *                                                                      *
      ***********************************************************************/

      list->destroy(data);

   }

}
这里是调用了list->destroy的。

SAP 物料主数据中 Base Unit of Measure 的变更逻辑与落地检查 摘要:SAP物料主数据中的Base Unit of Measure(基本计量单位)并非普通字段,它关联库存、采购、销售等核心业务数据。官方建议在物料创建初期慎重选择该单位,因其变更将影响历史业务数据的语义一致性。系统对修改设有严格限制:库存必须为零,且无相关采购订单等未清业务。实际项目中,更推荐使用替代计量单位而非直接修改基础单位,以避免数据断层风险。变更前需全面检查相关业务对象,并建立完善的治理流程。(149字) 阅读详情

相关推荐

C语言中的万能指针void*

废话不说: void* 类型的指针变量可以接受任何变量的地址。但是特别需要注意的是:它仅仅是拥有变量的地址而已, 不能对它进行解引用, 否则错误。。。 1. 先看一个简单的例子 void Test() { int num = 88; void* pVoid = # std::cout << *pVoid << std::endl; // Error std::cout << *(int*)(pVoid) << std

十三阿哥的博客 1824

论void void*及void**类型

1、void的作用 c语言中,void为“不确定类型”,不可以用void来声明变量。如:void a = 10;如果出现这样语句编译器会报错:variable or field ‘a’ declared void。在C语言中void 常常用于:对函数返回类型的限定和对函数参数限定:    (1)对函数返回类型的限定:当函数不需要返回类型是必须用void 来限定返回类型,限定了函数的返回类型为void后函数不能有返回值;如:void fun(int a);  (2)对函数参数类型的限定:当函数不允许接受

tecoes的博客 4435

解密QtIFW高级配置:用XML+JS打造品牌化安装界面(含动态水印/多语言技巧)

本文深入探讨了如何利用Qt Installer Framework(QtIFW)的高级配置功能,通过XML和JavaScript打造专业、品牌化的软件安装界面。文章详细解析了企业级项目目录结构设计、模块化配置文件编写、CSS样式表深度定制、动态水印实现以及智能多语言许可证管理等核心技巧,旨在帮助开发者超越基础打包,为Windows和Linux平台构建具有卓越用户体验的安装程序。

read5的博客 528

符号(void *)何解?符号(void **)又何解??

符号(void *)何解?符号(void **)又何解?? http://bbs.youkuaiyun.com/topics/70050852 对于多级指针或者数组,要掌握正确的识别方法: void*是说:这是一个指针,去掉一个(*)就是它所指向的,在这里是指向放void型的地方; void**是说:这也是一个指针,去掉一个(*)就是它所指向的,它指向一个放void*型的地方. 如下 #include<iostream> intmain(intargc,int*argv[]) { ...

qq_20312079的博客 1283

手把手教你搭建硬件在回路(HIL)仿真系统:从Simulink建模到实时测试

本文详细介绍了如何从零开始搭建硬件在回路(HIL)仿真系统,涵盖Simulink建模、实时仿真、代码生成及调试等关键步骤。通过具体配置示例和优化技巧,帮助开发者解决复杂控制系统验证中的技术难题,提升半实物仿真的效率和可靠性。

weixin_30952535的博客 445

ShardingSphere-jdbc 5.5.0 + spring boot 基础配置 - 实战篇

shardingsphere 5.5.0移除了sm4算法,自己加上即可。增加spi扩展org.apache.shardingsphere.encrypt.spi.EncryptAlgorithm增加以下代码:SM4算法代码/***/static {@Getter@Overrideif (!

qq_21903999的博客 4745

关于void**问题的整理

初次接触动态数组的编写时候在晚上学习的程序是这样定义动态数组的 struct dynamicArray { void** pAddr; //数组的容量 int m_Capacity; //数组大小 int m_Size; }; 此处虽然大概知道之所以使用void**可能是因为保护数据类型,但是一直不明什么为什么时void**而不是void*。 直到自己尝试去写程序实现发现当程序这样写时 会提示%d对应的是int类型,而p[0]...

weixin_44905815的博客 499

本地部署大模型 vs 网页版大模型区别

fill:#333;color:#333;color:#333;fill:none;本地服务器/工作站硬件资源私有化部署GPU集群高速存储模型文件训练框架推理引擎。

Ds-Rag的博 1712

关于(void**)&的理解

转来的,照例,祭出原地址~ 因为函数参数是按值传递的,所以要想改变变量,必须传递地址。 二级指针实际上就是指针变量的地址,如果传递二级指针,函数声明必须写**。 (void**)&必须是本质上就是指针变量的地址才可以做这样的转换,并不是说把一个一级指针也可以转换,void**的本质是标识一个二级指针。 &data就是(默认数据类型 **)&data,(v

槑宝包 3万+

xsens IMU配置

1.安装驱动 sudo apt-get update sudo apt-get install ros-kinetic-xsens-driver 2.配置IMU 设置输出四元数,加速度、角速度、地磁 cd /opt/ros/kinetic/lib/xsens_driver python mtdevice.py --configure "oq,aa,wr,mf" 3.启动IMU roslaunch xsens_driver xsens_driver.launch 转(https://www.cnblo

qq_41585680的博客 2325

Typora绘制思维导图

我们观察一下,其实思维导图就是树状结构的数据嘛,作为程序员,拿语言写树状结构的数据是很平常的事情。而且写代码可比画图省事多了,查阅资料可以知道,有一种`mermaid`语法,就是做这件事情的,支持 UML,甘特图,流程图等。

Peter Cheung的博客 7933

结构体的两种声明方式:堆上和栈上以及在双链表的应用

一、头文件 gcc 在编译时寻找所需要的头文件 : ※搜寻会从-I开始 ※然后找gcc的环境变量 C_INCLUDE_PATH,CPLUS_INCLUDE_PATH,OBJC_INCLUDE_PATH (这些变量在windows下对应的是include环境变量) ※再找内定目录 /usr/include /usr/local/include /usr/lib/gcc-

编程的本质是数学问题 2337

【FF-A】第四章 Concepts

SPM的责任分为两个组件:SPM调度器(SPMD)和SPM核心(SPMC)。这两个组件都可以访问整个物理地址空间,并且是受信任的计算基础的一部分。当无需区分这两个组件时,术语SPM被使用。这些组件的责任如下所示。SPMD 位于 EL3 中,以 AArch64 或 AArch32 执行状态运行。SPMC 可以与 SPMD 共存在 EL3 中,也可以位于相邻的异常级别,即 S-EL1 或 S-EL2。SPM 在启动时初始化 EL3 LSP,并在运行时促进它们与其他端点之间的通信。

baron-周贺贺-代码改变世界ctw 1195

一级指针和二级指以及(void**)&在双链表中的应用

因为函数参数是按值传递的,所以要想改变变量,必须传递地址。二级指针实际上就是指针变量的地址,如果传递二级指针,函数声明必须写**。(void**)&必须是本质上就是指针变量的地址才可以做这样的转换,并不是说把一个一级指针也可以转换,void**的本质是标识一个二级指针。&data就是(默认数据类型 **)&data,(void **)&data和&data还...

AplusX 660

《AgentScope-Java 深入浅出教程》第1章 AgentScope-Java 简介

AgentScope-Java 是一个面向智能体的编程框架,专门用于构建基于大语言模型(LLM)的 AI 应用程序。│ 你的业务应用 ││ AgentScope-Java 框架 ││ │ 智能体 │ │ 工具 │ │ 记忆 │ │ 模型 │ ││ LLM 服务 (DashScope/OpenAI/...) │能力说明类比ReAct 推理让智能体能够"思考"并"行动"就像人类思考后再做事工具调用让智能体能够执行外部操作就像人使用计算器、搜索引擎记忆管理让智能体能够记住对话历史。

步子哥的博客 592

jdk-8u144-linux-x64.tar.gz.

该资源为搭建Linux系统Java环境用,资源的后缀为tar.gz格式的压缩包

双链表和循环双链表复习

首先不管是单链表还是双链表或者循环双链表,都是为了提高访问效率,比如同样的插入操作,顺序表需要用数组移动元素,访问效率比较差,相反链表只需要移动指针,而且顺序表是有最大空间的,而链表没有。 双链表格式: struct node     //双向链表 {     ElemType data;   //数据域     struct node *next;   //指针域   指向下一个后继方向 ...

hujiaqi2018的博客 4233

WINCC7.3破解文件

WINCC7.3硬件够破解文件,有需要的下载!WINCC7.3硬件够破解文件,有需要的下载!

数据结构——双链表

数据结构——单链表,那么本篇的双链表会让你感到非常的安逸~无压力学会。码字不易,希望大家多多支持我呀!(三连+关注,你是我滴神!双链表是不是非常轻松呢~不像我们学单链表那时候草木皆兵,啥都要判断一下,双链表突出的就是一个结构稳定,安逸的很~

fax_player的博客 1011

LinePhone3.2.7(android端最新版本) sip客户端

SIP客户端For android:LinePhone3.2.7(最新版本),官网地址:http://www.linphone.org/

android 蓝牙通讯demo

android 下蓝牙通讯demo,实现了发现蓝牙,使能蓝牙,通过蓝牙发送消息。

双链表的实现

一、 在学习arm过程中发现这“指针函数”与“函数指针”容易搞错,所以今天,我自己想一次把它搞清楚,找了一些资料,首先它们之间的定义: 1、指针函数是指带指针的函数,即本质是一个函数。函数返回类型是某一类型的指针      类型标识符    *函数名(参数表)       int *f(x,y);   首先它是一个函数,只不过这个函数的返回值是一个地址值。函数返回值必须用同类型的指针变

编程的本质是数学问题 1099

[未解决]《系统程序员成长计划》学习交流(6)-双链表插入浮点数

【问题描述】在做《系统程序员成长计划》一书,通用双链表的过程中,发现了一个问题,所下所述: 源程序给出的测试代码如下所述: #include #include #include #include "dlist.h" DListRet print_int(void *data) { printf("%d ", (int)data); return DLIST_RE

tandesir的专栏 2737

libuv系列--queue详解

libuv系列--queue详解

coderma的博客 1142

链表的简单实现

链表属于线性表的范畴。线性表有顺序的存储结构实现,代码如下: typedef int ElemType; typedef struct{ Elem elem[maxsize]; int length; }SqList;同时线性表还有链式存储结构,包括单链表,双链表,循环链表,静态链表。本文只简单的写了链表的操作。 链表的实现      好久没用C语言写代码了,手生了,得找点感觉,

继续微笑lsj 3074

C语言链表操作全解析:从单链表到循环链表与逆序实现

链表是数据结构学习中的核心基础,它以节点和指针取代连续内存,解决了数组插入删除效率低、容量固定等痛点。理解链表原理,才能深入掌握内存管理、指针操作和算法设计。手写链表操作不仅是考研、面试和机考的常见题目,更是后续学习树、图等复杂结构的基石。在嵌入式开发、操作系统内核中,链表同样被广泛使用,如Linux内核的双向循环链表。本文从结构体定义出发,系统讲解单向链表、带头节点与不带头节点的区别、头插法尾插法、遍历、插入删除、清空操作,并进阶到循环链表、双向链表以及链表逆序的迭代与递归实现,最后总结实训和笔试中常见的

weixin_32715349的博客 352

算法精解(一):C语言描述(链表)

1.链表认知  一场病,断了好久。这几天算是基本没什么问题了。是时候继续了。 链表我想可以认为是,点到线的过程。 一个个点就是一个个链表的节点,以特定的顺序组合或链接后,行成了一条线,即链表。所以添加,删除一个点是相对较容易的(因为可以动态的追加,删除节点),但是查找某个点相对较麻烦(数组中只需要a[i]即可取得数据,链表则需要遍历)。所以,对于未知大小长度的数据来说,具有相当的优势。但已知...

qq_32579021的博客 758

Linux高级--3.2.2.4 搜狗 workflow框架 --- 有锁队列实现

两条队列 : 一条保存入队列的任务,另一条队列用来出队列执行任务(结点)。这个变量相对于结构体的地址偏移,以linkoff的形参在队列创建函数中传入。完全可以改为,有兴趣的可以自己实现。当出队的队列为空时,且入队的结点数量达到一定时,则交换两条队列的头尾指针。队列的操作函数不关心 用户数据的结构体封装,只需要里面有个next指针可以保存下个节点地址就行。即 队列操作和 用户节点的结构体类型进行分离,用户节点的结构体对于队列的所有操作函数都。好处就是队列的一整套操作函数,适用于任何封装的用户队列节点。

weixin_44209111的博客 1173
上一篇: 父进程等待子进程终止 wait, WIFEXITED, WEXITSTATUS
下一篇: 双链表的实现
unix21
博客等级 码龄14年 115粉丝 248原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值