一、int (*)[ ] 与 int **
1. 二维数组名a是它首元素的地址,该元素是一个一维数组,故指向二维数组的指针必须指向该类型的一维数组。即:int (*p)[ ]才是指向数组的指针。

https://blog.nowcoder.net/n/155081add1a44befbfb18dba5b77230b

2. int **p不是指向二维数组,而是一个指向指向int型指针的指针。

https://blog.nowcoder.net/n/2bff93ff79ba4b8a9901f0a8fcf8e8db

3. int (*p)[ ] 与 int **p 指向是的不同的类型,不能混淆,且二者动态申请内存的方式不同:

(1)int (*p)[ ] 为指向二维数组的指针,故可以直接按照给二维数组动态申请所需内存:

p1 = (int (*)[6]) malloc(n * 6 * sizeof(int)); // n*6 数组

(2)int **p 为指向指向int型指针的指针,故申请内存是需分两大步申请,若按(1)中申请,类型是不匹配的。例如:
若需要申请一个n*n矩阵(也就是二维数组形式的内存),则:

  • 先给n个int *类型的指针申请内存(矩阵各行的首地址在内存中的存放位置);
  • 再分别给每个int *型的指针分配所指向n个int类型的内存(矩阵每行的内容在内存中的存放位置)。

代码如下示例:

  int** matrix = (int **)malloc(sizeof(int*) * n);
  for (int i = 0; i < n; i++) {
      matrix[i] = (int *)malloc(sizeof(int) * n); // 此处是用了数组表示法表示指针,一样的
  }

这样,matrix的值是int *型的指针,即指向的是int *型,即matrix是int **型的指针;而每个int *型matrix[i](也就是*(matrix+i))指向的内存大小是n个int型数据的大小。

  • 当然各个指针也可以指向不同大小的内存,也就是每行的大小是可变的,这里就和(1)中二维数组不同了(每行是固定大小的)。
  • 注意:当函数返回值为二维数组时,调用者不知道二维数组每行的列数,所以就无法定义用来接收返回值的二维数组(定义二维数组时必须指定列数),此时就只能用int **的形式;或者每行的列数不同时也只能用int **的形式。第一种情况如:leetcode题目59.螺旋矩阵II,函数原型:
    int** generateMatrix(int n, int* returnSize, int** returnColumnSizes)  
    
    就是在这里混淆的,因为本来要返回的就是一个矩阵,就想着是二维数组,但是函数原型的返回值是int **,所以就不能直接用二维数组申请内存的方式(二维数组对应的返回值是int (*)[ ]),而需要用上述方法分两步去按照各个指针要指向的内存大小及类型去申请内存。至于为什么给出的函数原型是这个返回值类型就不知道了...可能大概也许别有深意(摊手.jpg)。
    ——于是,另两个令人疑惑的参数也就知道是干什么的了,即:int* returnSize表示返回的矩阵的行数,int** returnColumnSizes,表示每行的列数。对于调用者来说,由于列数未知(或许题目还认为各行的列数会不同),所以需要将各行的列数告知调用者,且由于行数也未知,故调用者无法定义对应的用来接收每行列数的一维数组的大小,故只能传进来一个指向int *型的指针变量returnColumnSizes,而这个指针变量的值(*returnColumnSizes)就是被调用者申请的用来存放每行列数的内存的首地址,在这些地址中存放的数据就是每行的列数。这样,调用者通过returnSize获得行数,再在行数限定的范围内去移动(*returnColumnSizes),从而获得在其中存储的每行的列数。所以,这两个参数就是为了告诉调用者函数返回值int **应该如何去解析。

注意:返回值和参数returnColumnSizes的不同,虽然都是int **型,但是后者是调用者传进来的变量,其目的是作为该函数的一个返回值即是输出参数,是为了使调用者获取值,由于函数间参数和返回值这些都只是值传递,所以给变量本身赋值是无法传出内容的,而应该是在它的地址处填入要告知调用者的东西,调用者有这个参数的地址(也就是传进来的变量值),故就能获取到这个地址内存放的内容,即存储每行列数数据的内存的一系列地址的一个起始地址,即指向int *型,所以,这个参数需要是一个其中能存放一个指向int *型指针的变量类型,即该参数是指向int *型指针的指针,即int **型。

  • 下面按照自己的理解测试了下:
#include <stdio.h>
#include <stdlib.h>

int** generateMatrix(int n, int* returnSize, int** returnColumnSizes)
{
    int** matrix = (int **)malloc(sizeof(int*) * n);
    int count = 0;
    int col = 1;
    
    *returnSize = n;
    *returnColumnSizes = (int *)malloc(sizeof(int) * n);
    for (int i = 0; i < n; i++) {
        // 数组表示法:
        // matrix[i] = (int *)malloc(sizeof(int) * n);
        // 指针表示法:
        *(matrix+i) = (int *)malloc(sizeof(int) * n);
        (*returnColumnSizes)[i] = col;
        for(int j = 0; j < col; j++) {
            matrix[i][j] = count++;
        }
        col++; // 将列数设置为变化的,用于测试
    }

    return matrix;
}

int main(void)
{
    int *returnColumnSizes;
    int returnSize;
    int **matrix;
    int n = 3;
    int i, j;
    
    matrix = generateMatrix(n, &returnSize, &returnColumnSizes); 
    for(i = 0; i < returnSize; i++) {
        printf("row = %d, col = %d: \n", i, (*returnColumnSizes));
        for(j = 0; j < (*returnColumnSizes); j++) {
            printf("addr: matrix[%d][%d] = %p, val: %d\n", i, j, (&matrix[i][j]), matrix[i][j]);
        }
        (*returnColumnSizes)++;
    }

    getchar();
    return 0;
}
  • 对于 matrix = generateMatrix(n, &returnSize, &returnColumnSizes); 的参数:
    n对应int,&returnSize对应int *,则&returnColumnSizes对应int **,应该还可以这样理解,returnColumnSizes变量的地址(也就是&returnColumnSizes)中存放的是指向int *型的指针,也就是说&returnColumnSizes的值是指向int型的指针,对应的就是int **的定义,故传入的&returnColumnSizes的类型是匹配的。
  • 另外,*(matrix+i)相当于matrix[i],还是数组表示法更简洁清晰不易错,对于前者的理解,因为matrix+i是指向int *型地址的指针,要给这个指针指向的位置赋值,所以应该是*(matrix+i)。(感觉还是不很清楚,看书 C Primer Plus 第6版 中文版 10.3,总之还是不要用指针表示法了叭,太绕了。)
  • 上边代码运行结果:
    alt
二、函数返回二位数组、函数参数为二维数组
示例如下:(原代码:https://www.cnblogs.com/wenruo/p/4801727.html)
#include <stdio.h>
#include <stdlib.h>

/*
注意:
const int ROW = 3;
const int COL = 4;
若如此定义,编译时会报错:(使用的编译指令:gcc -std=c99 test.c -o test)
error: variably modified 'mat_pointer' at file scope
error: variably modified 'build_mat' at file scope
*/
#define ROW 3
#define COL 4

// 把 mat_pointer 声明为一个指针类型,该指针指向内含COL个int型元素的数组,即指向一个二维数组的指针类型
typedef int (* mat_pointer)[COL];

void print_mat(mat_pointer a)
{
    for (int i = 0; i < ROW; ++i)
        for (int j = 0; j < COL; ++j)
            printf("a[%d][%d]=%d%c", i, j, a[i][j], j == COL - 1 ? '\n' : '\t');
    printf("\n");
}

mat_pointer init_mat(mat_pointer a)
{
    for (int i = 0; i < ROW; ++i)
        for (int j = 0; j < COL; ++j)
            a[i][j] = i + j;
    return a;
}

int (*build_mat(void))[COL]
{
    int (* arr_pointer)[COL] = (int (*)[COL]) malloc(ROW * COL * sizeof (int));
    for (int i = 0; i < ROW; ++i) {
        for (int j = 0; j < COL; ++j)
            arr_pointer[i][j] = i * 10 + j;
    }
    return arr_pointer;
}

int main(void)
{
    int arr[ROW][COL];

    mat_pointer arr2 = init_mat(arr);
    print_mat(arr2);

    //int (*arr3)[COL] = build_mat();
    int (*arr3)[COL];
    arr3 = build_mat();
    print_mat(arr3);

    getchar();
    return 0;
}

运行结果:
alt

关于上述代码中的 int (*arr3)[COL]、typedef int (* mat_pointer)[COL]、int (*build_mat(void))[COL]:

第一个arr3是指向二维数组的指针的定义,第二个是给arr3对应的类型起了个别名mat_pointer,第三个build_mat是返回值为arr3对应的类型的函数名。可以看出:
(1)加上typedef,将变量名替换为要起的类型别名,即可用该名称取代int (*)[COL],如变量arr2的定义。
(2)将变量名替换为函数名加参数列表,即可定义返回值为该类型的函数,如build_mat函数的定义。
(3)类似的,声明一个指向某类型函数的指针也是如此,如https://blog.nowcoder.net/n/2bff93ff79ba4b8a9901f0a8fcf8e8db

参考链接:

https://www.ceyewan.top/p/bddf0945.html
https://cloud.tencent.com/developer/ask/sof/38257
https://www.cnblogs.com/wenruo/p/4801727.html
https://leetcode.cn/problems/spiral-matrix-ii/solution/luo-xuan-ju-zhen-ii-by-leetcode-solution-f7fp/