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


因为函数参数是按值传递的,所以要想改变变量,必须传递地址。
二级指针实际上就是指针变量的地址,如果传递二级指针,函数声明必须写**。
(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); }


C语言|说透一级指针和二级指以及(void**)&在双链表中的应用
文章图片

C语言|说透一级指针和二级指以及(void**)&在双链表中的应用
文章图片



高级一点使用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); }


C语言|说透一级指针和二级指以及(void**)&在双链表中的应用
文章图片



注意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); }



C语言|说透一级指针和二级指以及(void**)&在双链表中的应用
文章图片






【C语言|说透一级指针和二级指以及(void**)&在双链表中的应用】(void**)& 本质

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


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

int*data; *data = https://www.it610.com/article/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 = https://www.it610.com/article/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还是同一块内存,只不过数据类型发生变化了。

C语言|说透一级指针和二级指以及(void**)&在双链表中的应用
文章图片

注意:原书有问题就是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 = https://www.it610.com/article/element->data; 这句话将element内data域所指向的物理内存地址记录下来,返回给应用层处理.



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


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


if ((data = https://www.it610.com/article/(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=https://www.it610.com/article/%d (1=OK)/n", retval); fprintf(stdout, "Removing d, G, and S\n"); c = 'd'; data = https://www.it610.com/article/&c; if (chtbl_remove(&htbl, (void **)&data) == 0) free(data); c ='G'; data = https://www.it610.com/article/&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的。


    推荐阅读