二级指针 – pointer to pointer
简单举例说明下这是一个什么神奇的东西,首先定义
const char *c = "hello";
我们虚拟展示一块内存(地址值与地址分配,包括机器位数都是人为虚拟设置的)
定义的只读c指向字符串”hello”,c所处的地址为59,地址59处存储的内容为63,表示字符串”hello”的起始地址。
接着我们定义二级指针
const char **cp = &c;
cp是指向c的指针,cp所处的地址为57,地址57处存储内容为59,表示c的地址,故**cp指向字符串”hello”的起始地址。
延伸一下,若是三级指针,道理也是一样的。
const char ***cpp = &cp;
进入正题,到底二级指针有何具体作用?
用作二维数组:
/* allocate m*n int array */ const int m = 2, n = 5; int **array = NULL; array = (int **)malloc(m * sizeof(int *)); int i, j; for (i = 0; i < m; i++) { array[i] = (int *)malloc(n * sizeof(int)); }
在申请完内存后,array就可以利用下标进行访问了,可以和我们定义传统的二维数组访问方式一致。
int array[2][5] = {0};
但是这里需要区分是,int array[][]如此定义的二维数组在内容里的寻址空间是连续的,而int **array在申请内存后的寻址空间是分散连续的,对比图:
用作链表类指针操作:
这里主要以单向链表为例,一般来说我们对链表进行删除操作
struct list_t { int value_a; int value_b; struct list_t *next; };
struct list_t *common_list_del(struct list_t *head, const int a, const int b) { struct list_t *cur = head; struct list_t *prev = NULL; while (cur) { struct list_t * const next = cur->next; if (cur->value_a == a && cur->value_b == b) { if (prev) { prev->next = next; } else { head = next; } /* free cur */ } else { prev = cur; } cur = next; } return head; }
如果利用二级指针的特性,可以减少中间变量与代码行数 – 降低了代码复杂度,增加了理解复杂度:)
struct list_t *ptp_list_del(struct list_t **head, const int a, const int b) struct list_t **cur = head; while (*cur) { struct list_t *entry = *cur; if (entry->value_a == a && entry->value_b == b) { *cur = entry->next; /* free entry */ } else { cur = &(entry->next); } } return *head; }
在链表删除时,需要考虑重要问题之一是,若删除的是头节点则需要把头节点指针后移,在common_list_del方法中head值无法改变,但是在ptp_list_del方法中,head地址值是可变的,简化了操作。
- 大致分析下ptp_list_del方法,当不需要删除链表头时
先走第9行cur = &(entry->next),不删除当前节点(第一次循环指向的是head节点,不删除),然后保存当前节点next的地址(那么在下一次循环开始cur保存的是prev->next地址)
然后再次进入循环在第4行struct list_t *entry = *cur,因为第9行的原因此时entry保存的prev->next地址
匹配到目标节点第6行*cur = entry->next,这里把prev->next指向cur->next,完成删除 - 当需要删除链表头时
首先匹配第6行*cur = entry->next,此时*cur是指向*head,直接把*head=entry->next(也就是*head = (*head)->next)
利用二级指针删除链表元素的接口ptp_list_del,返回值可以设置为void,因为head的地址已在函数内部改变。为了链式表达返回struct list_t,若想利用一级指针,直接在函数内部改变head的地址值,则需要利用“传引用”
struct list_t *references_list_del(struct list_t *&head, struct list_t *cp_head, const int a, const int b) { struct list_t *cur = cp_head; struct list_t *prev = NULL; while (cur) { struct list_t * const next = cur->next; if (cur->value_a == a && cur->value_b == b) { if (prev) { prev->next = next; } else { head = next; } /* free cur*/ } else { prev = cur; } cur = next; } return head; }
修改指针地址-内存分配函数相关:
void wrong_malloc(char *p, const unsigned int size) { p = (char *)malloc(size); } void correct_malloc(char **p, const unsigned int size) { *p = (char *)malloc(size); } char *char_malloc(const unsigned int size) { char *p = (char *)malloc(size); return p; }
这里涉及到了函数传指针、传引用相关概念,具体分析,可以参考我的文章:深入理解C/C++形参、传指针、传引用
基于本文的简单测试代码 – 点击这里
(全文结束)
转载文章请注明出处:漫漫路 - lanindex.com