二级指针的作用分析

二级指针 – pointer to pointer

简单举例说明下这是一个什么神奇的东西,首先定义

const char *c = "hello";

我们虚拟展示一块内存(地址值与地址分配,包括机器位数都是人为虚拟设置的)

1

定义的只读c指向字符串”hello”,c所处的地址为59,地址59处存储的内容为63,表示字符串”hello”的起始地址。

接着我们定义二级指针

const char **cp = &c;

2

cp是指向c的指针,cp所处的地址为57,地址57处存储内容为59,表示c的地址,故**cp指向字符串”hello”的起始地址。

延伸一下,若是三级指针,道理也是一样的。

const char ***cpp = &cp;
3

 

进入正题,到底二级指针有何具体作用?

用作二维数组:

    /* 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在申请内存后的寻址空间是分散连续的,对比图:
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

Leave a Comment

Your email address will not be published.