【C语言进阶剖析】34、多维数组和多维指针

1 指向指针的指针

  • 指针的本质是变量,会占用一定的内存空间
  • 可以定义指针的指针来保存指针变量的地址

指针的指针没有什么了不起的,它还是一个指针,只不过这个指针所指向的内存空间的元素又是一个地址。可以这么理解,你的狗狗丢了,要去找狗狗,现在你有一个门牌号,你拿着这个门牌号去找对应的房子,到了对应的屋子以后,没找到狗狗,找到的还是一个门牌号,然后你拿着这个新找到的门牌号再找对应的房子,在房子里面找到了狗狗。

看下面的代码:
在这里插入图片描述
上面的代码,pp 就是指向指针的指针,pp 指向指针 p,也就是说 pp 对应的内存空间的元素是指针 p 的地址,p 对应的内存空间的元素才是变量 i 的地址。

*pp = &i 相当于 p = &i,也就是让 p 指向 i。

问题:那为什么需要指向指针的指针呢?

  • 指针本质上也是变量
  • 对于指针也同样存在传值调用与传址调用

将一个变量通过传址调用,我们操作的是变量的地址,也就是指针;那么将一个指针通过传址调用,我们操作的不就是指向指针的指针了吗。

实例分析:重置动态空间大小

// 34-1.c
#include<stdio.h>
#include<malloc.h>
int reset(char**p, int size, int new_size)
{
    int ret = 1;
    int len = 0;
    int i = 0;
    char* pt = NULL;
    char* tmp = NULL;
    char* pp = *p;
    if (p != NULL && new_size > 0)
    {
        pt = (char*)malloc(new_size);
        tmp = pt;
        len = (size < new_size) ? size : new_size;
        for (i = 0; i < len; i++)
        {
            *tmp++ = *pp++;
        }
        free(*p);
        *p = pt;
    }
    else
    {
        ret = 0;
    }
    return ret;
}

int main()
{
    char *p = (char*)malloc(5);
    printf("%p\n", p);
    if (reset(&p, 5, 3))
    {
        printf("%p\n", p);
    }
    free(p);
    return 0;
}

函数 reset(char**p, int size, int new_size) 的作用是将原来申请的长度为 size 的数组变成长度为 new_size 的数组,并将原来的数据复制过来。由于传递的是指针,还是传址调用调用,所以函数第一个参数为指向指针的指针。

编译运行结果如下:

$ gcc 34-1.c -o 34-1
$ ./34-1
0x55f11c4c6260
0x55f11c4c6690

2 二级数组与二级指针

  • 二维数组在内存中以一维的方式排列
  • 二维数组中的第一维是一维数组,第二维才是具体的值
  • 二维数组的数组名可看作常量指针

二维数组在内存中保存的是一维数组,如下图所示:
在这里插入图片描述
实例分析:遍历二维数组

// 32-4.c
#include<stdio.h>
void printArray(int a[], int size)
{
    int i = 0;
    printf("printArray: %ld\n", sizeof(a));
    for (i = 0; i < size; i++)
    {
        printf("%d\n", a[i]);
    }
}
int main()
{
    int a[3][3] = {{0, 1, 2}, {3, 4, 5}, {6, 7, 8}};
    int* p =&a[0][0];
    int i = 0;
    int j = 0;
    for (i = 0; i < 3; i++)
    {
        for (j = 0; j < 3; j++)
        {
            printf("%d, ", *(*(a + i) + j));
        }
        printf("\n");
    }
    printf("\n");
    printArray(p, 9);
    return 0;
}

程序第 22 行,*(*(a + i) + j) ==> *(a[i] + j) ==> a[i][j],为了程序的可读性,写成 a[i][j] 更好
printArray 函数将二维数组作为一维数组,打印从数组 a 开始的 size 个元素,结果是什么呢,我们编译运行一下:

$ gcc 34-2.c -o 34-2
$ ./34-2
0, 1, 2, 
3, 4, 5, 
6, 7, 8, 

printArray: 8
0
1
2
3
4
5
6
7
8

结果显示主函数和 printArray 函数都将二维数组中的元素打印出来了,这更加验证了二维数组在内存中其实是以一维数组的方式保存的。

3 数组名

  • 一维数组名代表数组首元素的地址
    对于 int a[5],a 的数组名为 int*
  • 二维数组名同样代表数组首元素的地址
    对于 int m[2][5],m 的类型为 int(*)[5]

结论:

  1. 二维数组名可以看作是指向数组的常量指针
  2. 二维数组可以看作一维数组
  3. 二维数组中的每个元素都是同类型的一维数组

实例分析:动态申请二维数组

#include<stdio.h>
#include<malloc.h>
int** malloc2d(int row, int col)
{
    int** ret = NULL;
    if (row >0 && col > 0)
    {
        int* p = NULL;
        ret = (int**)malloc(row * sizeof(int*));
        p = (int*)malloc(row * col * sizeof(int));
        if (ret != NULL && p != NULL)
        {
            int i = 0;
            for (i = 0; i < row; i++)
            {
                ret[i] = p + i*col;
            }
        }
        else
        {
            free(ret);
            free(p);
            ret = NULL;
        }
    }
    return ret;
}

void free2d(int** p)
{
    if(*p != NULL)
    {
        free(*p);
    }
    free(p);
}
int main()
{
    int** a = malloc2d(3, 3);
    int i = 0;
    int j = 0;
    for (i = 0; i < 3; i++)
    {
        for (j = 0; j < 3; j++)
        {
            printf("%d, ", a[i][j]);
        }
        printf("\n");
    }
    free2d(a);
    return 0;
}

ret 是指针数组,p 是真正存放元素的数组,ret[0] 指向 p[0],ret[1] 指向 p[col],ret[2] 指向 p[2*col]……以此类推,ret[row-1] 指向 p[(row-1)*col],二维数组在内存中实际是以一维方式存储的。
在这里插入图片描述

$ gcc 34-3.c -o 34-3
$ ./34-3
0, 0, 0, 
0, 0, 0, 
0, 0, 0, 

4 小结

1、C 语言中只支持一维数组
2、C 语言中的数组大小必须在编译期就作为常数确定
3、C 语言中的数组元素可以是任何类型的数据
4、C 语言中的数组的元素可以是另外一个数组

已标记关键词 清除标记
©️2020 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页