在C语言中使用指针遍历多维数组

3
作为一项练习,我试图构建一个由随机整数组成的二维数组。我想通过使用指针算术来迭代数组并赋予随机数。
我认为我在以下For循环中遇到了问题,这个循环是我从King的C编程书籍第268页中找到的。
int *p;
for (p = &a[0][0]; p <= &a[NUM_ROWS-1] [NUM_COLUMNS - 1]; p++)

我正在尝试在自己的程序中使用类似的循环,但是程序似乎没有分配任何值。

 #include <stdio.h>
#include <stdlib.h>

int ** make_array(int in_size );
void read_array( int ** a, int n);

int main(void){
        int size;
        int **p;

        size = 5;
        p = make_array( size );
        read_array( p, size );

        return 0;
}

int ** make_array( int in_size ) {
        int i,  *p, **a;

        srand ((unsigned)time(NULL));

        a = malloc(in_size * sizeof(int*));
        for (i = 0; i < in_size ; i++) {
              a[i] = malloc( in_size * sizeof(int));
        }

        for (p = &a[0][0]; p <= &a[in_size -1 ][in_size - 1]; p++) {
                *p = rand() % 10;
        }

        return a;

}

void read_array( int **a, int n ) {
        int i, *p;
        for (p = &a[0][0] ; p <= &a[n - 1][n - 1]; p++)
                printf("%d ", *p );
}

现在我知道可以使用嵌套的for循环轻松地遍历它,这似乎是遍历数组的一种优雅方式。您有什么想法我做错了什么吗?


你的主要问题是这不是一个二维数组,而只是它的仿真。这种技术已经过时了。你只需要使用int (*a)[in_size] = malloc(sizeof(int[in_size][in_size]));来分配一个二维数组,然后你就可以一次性地得到一个连续的数组。对于这样的数组,即使是你粗糙的初始化技巧(实际上没有什么作用)也能起作用。 - Jens Gustedt
2个回答

3
int *p;
for (p = &a[0][0]; p <= &a[NUM_ROWS-1] [NUM_COLUMNS - 1]; p++)

有两种情况下是有效的:
(1) 当'a'被定义为二维数组时,例如 int a[NUM_ROWS][NUM_COLUMNS]。在这种情况下,内存块是连续的,因此可以使用指针变量迭代2D数组元素。

(2) 修改您的make_array()函数如下,以分配连续的内存块以使用上述方法。

int ** make_array( int in_size ) 
{
        int i,  *p, **a;
        int *big_chunk;

        srand ((unsigned)time(NULL));

        a = (int **)malloc(in_size * sizeof(int*));
        big_chunk = (int *)malloc(in_size * in_size * sizeof(int));  <-- change done here
        for (i = 0; i < in_size ; i++) 
        {
              a[i] = big_chunk + i * insize;                         <-- change done here
        }

        /* Other code */
}

在您原始的make_array()函数中,malloc()不能保证在malloc()调用的连续迭代中有连续的内存块。因此,使用指针来遍历二维数组元素将不正确。例如,一旦'p'到达a [0] [in_size-1],那么p = &a [0] [in_size]将与a [1] ->第二行的malloc地址不同。


非常感谢!那就是窍门,你真的帮我澄清了一些关于指针和内存工作的问题。 - user3407513
请不要强制转换malloc等函数的返回值。https://dev59.com/dHRB5IYBdhLWcg3wgHWr - Jens Gustedt

2
如您所学,C语言中没有二维数组。只有模拟二维数组索引的方法。这些方法分为两类:(1)创建指向数组的指针数组,(2)创建普通的顺序数组,并使用索引算术在二维方式下引用元素。在每种情况下,可以根据数组中元素总数(或数组大小)和要模拟的列数(或数组步幅)来考虑算术运算。通过仔细的索引,知道数组的大小和步幅,您可以将C语言的一维数组用作二维数组。为了帮助解决关于两种类型、指针和索引的任何问题,考虑以下内容:
指针类型数组
首先,当您使用“指向类型的指针数组”(例如,int **array)时,您需要分配ROWS个指向类型为COLS的数组的指针(本质上,您有ROWS个大小为COLS的数组)。然后,通过解除引用,您可以将元素索引为二维数组(例如,array[0][x],其中0 <= x < COLS读取由第一个指针指向的所有数组,array[1][x],第二个指针,依此类推...)。
要分配指向类型的指针数组,您需要分配ROWS个指针(其中ROWS等同于size/stride):
    int **array = NULL;
    ...
    array = xcalloc (size/stride, sizeof *array);

(注意: xcalloc 只是使用带有错误检查的 calloc 函数来验证分配)

在分配了 ROWS 个指针后,您为每个原始指针单独分配了一个包含 COLS (或 stride) 个元素的列数组。

    for (i = 0; i < size/stride; i++)
        array[i] = xcalloc (stride, sizeof **array);

在你分配指针类型的数组后,你需要使用两个循环来填充/操作你的数组中的数据:

    for (i = 0; i < size/stride; i++)
        for (j = 0; j < stride; j++)
            array[i][j] = rand() % 1000;

您可以使用简单的array[i][j]语法访问任何单个成员。请记住,您只需将size/stride视为您的ROWS,将stride视为您的COLS,因此,如果您正在计算ROWSCOLS的值,则可以将上述内容编写为:

    for (i = 0; i < ROWS; i++)
        for (j = 0; j < COLS; j++)
            array[i][j] = rand() % 1000;

将线性数组视为二维数组

在这种情况下,您使用传统的顺序1D数组来保存数据,声明和分配数组很简单:

    int *array = NULL;
    ...
    array = xcalloc (size, sizeof *array);

注意:要以类似于访问指向数组的指针的情况相同的逻辑存储数组中的值,以便以模拟二维方式访问数组元素。从循环的角度来看,您将使用与指向数组的指针相同的逻辑来访问数组的值。唯一的区别在于索引的计算:

    for (i = 0; i < size/stride; i++)
        for (j = 0; j < stride; j++)
            array[i * stride + j] = rand() % 1000;

在这里,需要更仔细地了解您如何模拟对array[i][j]的访问。注意:数组的索引:

    array[i * stride + j] = rand() % 1000;

当您拥有一个线性的一维元素数组时,允许您以二维方式处理和访问值的索引由array[i * stride + j]给出,其中ij表示ROWSCOLS
将所有这些放在一起的几个示例将向您展示所有组件是如何配合的:
示例 - 指针类型的数组
#include <stdio.h>
#include <stdlib.h>
#include <time.h>

void *xcalloc (size_t n, size_t s);

int main (int argc, char **argv) {

    int **array = NULL;
    int size   = argc > 1 ? (int)strtol(argv[1], NULL, 10) : 36;
    int stride = argc > 2 ? (int)strtol(argv[2], NULL, 10) : 6;
    int i,j;

    /* test valid size/stride */
    if (size < stride || size % stride) {
        fprintf (stderr, "error: invalid stride '%d' for %d element array.\n",
                stride, size);
        return 1;
    }

    srand (time(NULL)); /* initialize seed */

    /* alloc array of pointers to array of integers in memory */
    array = xcalloc (size/stride, sizeof *array);

    /* allocate arrays of integers */
    for (i = 0; i < size/stride; i++)
        array[i] = xcalloc (stride, sizeof **array);

    /* fill with random values */
    for (i = 0; i < size/stride; i++)
        for (j = 0; j < stride; j++)
            array[i][j] = rand() % 1000;

    /* printing in simulated 2D format */
    printf ("\n printing (%d x %d) array\n\n",
            size/stride, stride);

    for (i = 0; i < size/stride; i++) {
        for (j = 0; j < stride; j++)
            printf (" %4d", array[i][j]);
        putchar ('\n');
    }

    /* print a particular element array[1][2] */
    if (stride > 1)
        printf ("\n array[1][1] in (%d x %d) array : %d\n\n",
                size/stride, stride, array[1][1]);

    /* free allocated memory */
    for (i = 0; i < size/stride; i++)
        free (array[i]);
    free (array);

    return 0;
}

/** xcalloc allocates memory using calloc and validates the return.
 *  xcalloc allocates memory and reports an error if the value is
 *  null, returning a memory address only if the value is nonzero
 *  freeing the caller of validating within the body of code.
 */
void *xcalloc (size_t n, size_t s)
{
    register void *memptr = calloc (n, s);
    if (memptr == 0)
    {
        fprintf (stderr, "%s() error: virtual memory exhausted.\n", __func__);
        exit (EXIT_FAILURE);
    }

    return memptr;
}

示例使用/输出

$ ./bin/array_stride_2d 12 2

 printing (6 x 2) array

  535   68
   45  815
  348  480
  417  151
  443  789
  267  738

 array[1][1] in (6 x 2) array : 815

$ ./bin/array_stride_2d 12 3

 printing (4 x 3) array

  841  195  147
  870   18  892
  624  516  820
  250  769  532

 array[1][1] in (4 x 3) array : 18

$ ./bin/array_stride_2d 12 4

 printing (3 x 4) array

  116  275  740  510
  625  122  386  623
  624  879  970  396

 array[1][1] in (3 x 4) array : 122

$ ./bin/array_stride_2d 12 6

 printing (2 x 6) array

  543  631  562  504  307  940
  932   75  225  662  181  990

 array[1][1] in (2 x 6) array : 75

示例 - 将线性数组视为2D数组

#include <stdio.h>
#include <stdlib.h>
#include <time.h>

void *xcalloc (size_t n, size_t s);

int main (int argc, char **argv) {

    int *array = NULL;
    int size   = argc > 1 ? (int)strtol(argv[1], NULL, 10) : 36;
    int stride = argc > 2 ? (int)strtol(argv[2], NULL, 10) : 6;
    int i,j;

    /* test valid size/stride */
    if (size < stride || size % stride) {
        fprintf (stderr, "error: invalid stride '%d' for %d element array.\n",
                stride, size);
        return 1;
    }

    srand (time(NULL)); /* initialize seed */

    /* alloc array of size sequential in memory */
    array = xcalloc (size, sizeof *array);

    /* fill with random values */
    for (i = 0; i < size/stride; i++)
        for (j = 0; j < stride; j++)
            array[i * stride + j] = rand() % 1000;

    /* printing in simulated 2D format */
    printf ("\n printing (%d x %d) array\n\n",
            size/stride, stride);

    for (i = 0; i < size/stride; i++) {
        for (j = 0; j < stride; j++)
            printf (" %4d", array[i * stride + j]);
        putchar ('\n');
    }

    /* print a particular element array[1][2] */
    if (stride > 1)
        printf ("\n array[1][1] in (%d x %d) array : %d\n\n",
                size/stride, stride, array[1 * stride + 1]);

    free (array);

    return 0;
}

/** xcalloc allocates memory using calloc and validates the return.
 *  xcalloc allocates memory and reports an error if the value is
 *  null, returning a memory address only if the value is nonzero
 *  freeing the caller of validating within the body of code.
 */
void *xcalloc (size_t n, size_t s)
{
    register void *memptr = calloc (n, s);
    if (memptr == 0)
    {
        fprintf (stderr, "%s() error: virtual memory exhausted.\n", __func__);
        exit (EXIT_FAILURE);
    }

    return memptr;
}

使用/输出

$ ./bin/array_stride_1d 12 2

 printing (6 x 2) array

  220  155
  755   51
  427  270
  691  597
  982  995
    4  444

 array[1][1] in (6 x 2) array : 51

$ ./bin/array_stride_1d 12 3

 printing (4 x 3) array

  990  837  473
  153   10  337
  139  940  444
  768  625  457

 array[1][1] in (4 x 3) array : 10

$ ./bin/array_stride_1d 12 4

 printing (3 x 4) array

  617  943  444  396
   38  357  103  441
  646  416   40  586

 array[1][1] in (3 x 4) array : 357

$ ./bin/array_stride_1d 12 6

 printing (2 x 6) array

  364   61  373  723  994  849
  793  332  913  991  999  373

 array[1][1] in (2 x 6) array : 332

内存错误检查

$ valgrind ./bin/array_stride_1d 12 6
==21560== Memcheck, a memory error detector
==21560== Copyright (C) 2002-2012, and GNU GPL'd, by Julian Seward et al.
==21560== Using Valgrind-3.8.1 and LibVEX; rerun with -h for copyright info
==21560== Command: ./bin/array_stride_1d 12 6
==21560==

 printing (2 x 6) array

  359  841  728  356  563  487
  626   58  823  270  860  896

 array[1][1] in (2 x 6) array : 58

==21560==
==21560== HEAP SUMMARY:
==21560==     in use at exit: 0 bytes in 0 blocks
==21560==   total heap usage: 1 allocs, 1 frees, 48 bytes allocated
==21560==
==21560== All heap blocks were freed -- no leaks are possible
==21560==
==21560== For counts of detected and suppressed errors, rerun with: -v
==21560== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 2 from 2)

希望这对您学习C语言中将数组作为二维数组处理的基本两种方案有所帮助。您甚至可以更有创意地创建超过2D的维度数组,但请注意索引计算很快就会变得更加复杂。即使在2D级别上,您也可以从这些方法中获得更多的收益,如行向量、列向量、上/下三角矩阵、矩阵算术等。如果您有任何问题,请让我知道。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接