【问题标题】:Pointer arithmetic with 2D arrays in CC中二维数组的指针算法
【发布时间】:2015-11-15 19:48:18
【问题描述】:

我需要帮助了解如何在 C 中对二维数组使用点算术。我使用这个网站 (http://www.geeksforgeeks.org/dynamically-allocate-2d-array-c/) 作为参考(使用示例 1,单个指针)。

int numRows = 2;
int numColumns = 3;

double * arrayMatrix = malloc(numRows * numColumns * sizeof(double));

int row = 0;
int column = 0;

printf("\nPlease enter the elements of your augmented matrix:\n");
for(row = 0; row < numRows; row++)
{
    for(column = 0; column < numColumns; column++)
    {
        printf("A[%d][%d]:", row + 1, column + 1);
        scanf("%lf", &arrayElement);
        printf("\n");

        *(arrayMatrix + row * numColumns + column) = arrayElement;
        //arrayMatrix[row + numColumns + column] = arrayElement;
    }
}

// TEST PRINT
for(row = 0; row < numRows; row++)
{
    for(column = 0; column < numColumns; column++)
    {
        printf("%5.2lf", *(arrayMatrix + row * numColumns + column));
        //printf("%5.2lf",  arrayMatrix[row + numColumns + column]);
    }
    printf("\n");
}

我需要帮助了解这是否是将数据输入二维数组的正确方法,以及它是否也是从二维数组打印数据的正确方法。我将第 1 行的示例数据用作 {1, 2, 3},第 2 行用作 {1, 2, 3};但是,当打印出所有 6 个元素的信息时,我得到的信息都是 0。

我也将此答案用作参考 (How to use pointer expressions to access elements of a two-dimensional array in C?)。具体遵循这一行:

int x = *((int *)y + 2 * NUMBER_OF_COLUMNS + 2); // Right!

但我使用的是双指针而不是整数,但我不知道这是导致我的问题还是其他原因。

编辑 - 稍微更新了代码,但仍然无法正常工作。

编辑 2:这是我一直在尝试使用的代码的最新更新。从数组中输入和打印数据的所有 3 种方式都会产生相同的结果(数组中的所有值都为 0)。

int numRows = 2;
int numColumns = 3;

//double * arrayMatrix = malloc(numRows * numColumns * sizeof(double));
double (*arrayMatrix)[numColumns] = malloc(sizeof(double[numRows][numColumns]));

int row = 0;
int column = 0;

printf("\nPlease enter the elements of your augmented matrix:\n");
for(row = 0; row < numRows; row++)
{
    for(column = 0; column < numColumns; column++)
    {
        printf("A[%d][%d]:", row + 1, column + 1);
        scanf("%lf", &arrayElement);
        printf("\n");

        //*(arrayMatrix + row * numColumns + column) = arrayElement;
        //arrayMatrix[row + numColumns + column] = arrayElement;
        arrayMatrix[row][column] = arrayElement;
    }
}

// TEST PRINT
for(row = 0; row < numRows; row++)
{
    for(column = 0; column < numColumns; column++)
    {
        //printf("%5.2lf", *(arrayMatrix + row * numColumns + column));
        //printf("%5.2lf",  arrayMatrix[row + numColumns + column]);
        printf("%5.2lf", arrayMatrix[row][column]);
    }
    printf("\n");
}

【问题讨论】:

  • 您发布的链接中的可怕示例、不必要的指针类型强制转换、不必要的指针取消引用以及不检查来自malloc() 的返回值表明他们的作者不关心阅读任何关于如何操作的文档malloc() 工作。
  • 1) 不要在 C 中转换 malloc 和朋友的结果。 2) 不要在不同的指针类型之间疯狂地转换。您违反了严格的别名规则。 3)您的代码中没有二维数组。
  • 我已经稍微更新了代码,但是对于数组的所有值,它仍然只显示 0,我不知道为什么。
  • 也很糟糕,因为该链接只有伪二维数组的示例,而不是真实数组。你在那里找到的所有东西都属于历史书籍,不要使用它,不要试图从中学习。只需使用double (*arrayMatrix)[numColumns] = malloc(sizeof(double[numRows][numCollums]));,您就可以省去所有的麻烦。
  • 如何使用该代码访问数组?是“arrayMatrix[row][column] = arrayElement”吗?我正在使用它并且在打印时仍然得到 0 的值(使用相同的变量进行打印:arrayMatrix[row][column])。

标签: c pointers multidimensional-array pointer-arithmetic


【解决方案1】:

我想我明白你想要做什么。在我们讨论代码之前,让我们先回顾一下并学习一些关于在 C 中模拟 2D 数组的不同方法。您有两种基本方法。您可以静态或动态声明一个数组array[row][col] = {{r0c0, r0c1, r0c2, ...}, {r1c0, r1c1, r1c2, ...} ... };,它将创建一个单独的内存块,值顺序存储r0c0, r0c1, r0c2, ..., r1c0, r1c1, r1c2, ....,或者您可以创建row多个指针,每个指针指向@987654324的单个数组@元素。第二种方法(row 的指针数量,每个指针都指向 col 元素的数组)在内存中不需要是连续的。可以,但不要求一定是。

数组索引符号array[i][j] 将负责处理连续内存块中的偏移量,以提供对任何单个元素的访问。这同样适用于访问由array[i] 指向的单个col 大小数组中的任何元素。但实际发生了什么?

让我们看看一个简单的 4 元素数组的表示法,比如array[4]。要访问任何元素,您可以请求任何元素 array[0]array[3] 访问所有 4 个元素。 array[2] 到底是什么?你知道array 也是一个指针。您知道要访问指针所持有地址处的值,您需要取消对指针的引用。要访问array[0],您可以简单地编写*array,但是如何使用指针符号访问第二个元素?

正如我们之前所讨论的,我们在此示例中声明的数组中的所有元素都按顺序存储在内存中。因此,要访问数组中的任何元素,您只需要 距数组开头的偏移量。因为你知道数组的起始地址只是array,如果你想要第二个元素,你需要从开头访问元素偏移量1,或者*(array + 1)——试试看。实际上,您可以从 *(array + i) 开头的偏移量 0-3 访问所有元素,其中 i0-3 中的一个数字。

回顾一下,这也解释了为什么您可以通过简单地使用*array 来访问数组中的第一个元素。如果您为第一个元素编写了完整的语法,您将拥有*(array + 0)——并且您知道+ 0 什么都不做,这就是为什么您可以使用*array 访问第一个元素,因为*(array + 0) = *array

好的,那么 2D 机箱呢?如果array[x]*(array + x),那么array[x][y] 是什么?把它分解。你知道你可以把array[x]写成*(array + x),所以array[x][y]可以写成*(array + x)[y](如果我们暂时用stuff代替*(array + x),我们可以写成stuff[y]。我们知道如何用指针表示法写成:*(stuff + y),对吗?现在只需用*(array + x) 替换stuff,你就会得到*(*(array + x) + y)。这是你在模拟二维数组中访问连续内存块中任何元素的完整指针表示法这就是你写array[x][y]时幕后发生的事情。

现在让我们谈谈指针算法。当你声明一个指针时,你是在声明一个指向特定type 的指针(void 的情况除外)。 type 告诉编译器如何使用该指针处理算术运算。例如,如果您声明:

char array[8] = { 1, 2, 3, 4, 5, 6, 7, 8 };
char *p = array;

编译器知道每个char占用1-byte的内存,所以当你写*(p + 1)时,你是从array的开头请求char1-byte。如果你写p++; 然后*p 也是如此。但是会发生什么:

int array[8] = { 1, 2, 3, 4, 5, 6, 7, 8 };
int *p = array;

由于知道typeint 并且int4-bytes(取决于平台),当您编写*(p + 1)p++; *p; 时,您将获得数组中的第二个元素,但值是从数组开头的4-bytestype 告诉编译器如何处理任何给定值的指针算术(即 offset)。

您可以通过一个简单的示例自己巩固这一点:

#include <stdio.h>

#define ROWS 2
#define COLS 2

int main (void) {

    int a[ROWS][COLS] = {{ 1, 2 }, { 3, 4 }};
    int *p = *a;
    int i, j;

    for (i = 0; i < ROWS; i++) {
        for (j = 0; j < COLS; j++)
            printf (" %2d", a[i][j]);
        putchar ('\n');
    }

    /* using a pointer to access the values */
    for (i = 0; i < ROWS * COLS; p++, i++)
        printf (" %2d", *p);
    putchar ('\n');

    return 0;
}

输出

$ ./bin/array_min
  1  2
  3  4
  1  2  3  4

现在谈谈您实际询问的内容。鉴于我们已经讨论过的所有内容,您在声明时要声明什么:

double (*arrayMatrix)[NCOLS] = calloc (NROWS, NCOLS * sizeof **arrayMatrix);

您正在为指向什么的指针声明和分配空间?指向带有NCOLS 元素的doubles 数组的指针。你需要多少个来保存完整的数组?您将需要 NROWS 指向每个包含 NCOLS 元素的数组的指针数。请注意上面使用calloc 而不是malloc。在语法和它们的作用上都有一个重要但微妙的区别。 malloc 将为您分配内存,但该内存未初始化并且可以包含各种 stuffcalloc 也分配,但随后将内存初始化为零。在处理数字数组时,这有助于防止意外访问未初始化的元素(导致未定义的行为)。因此,malloccalloc 之间存在微小的速度差异,但您将很难在少于几百万个分配中找到可测量的差异。

在此背景下,再次考虑您要做什么。通过很少的调整,如果您执行以下操作会更有意义:

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

#define NROWS 2
#define NCOLS 3

int main (void) {

    size_t row = 0;
    size_t col = 0;

    /* allocate NROWS (array of pointers to NCOLS doubles). using 
    * calloc will allocate and initialize all elements to zero.
    */
    double (*arrayMatrix)[NCOLS] = calloc (NROWS, NCOLS * sizeof **arrayMatrix);

    /* prompt user for input, validate a proper value is entered */
    printf("\nPlease enter the elements of your augmented matrix:\n");
    for(row = 0; row < NROWS; row++)
    {
        for(col = 0; col < NCOLS; col++)
        {
            while (printf(" A[%zu][%zu]: ", row + 1, col + 1) && 
                scanf("%lf", &arrayMatrix[row][col]) != 1)
                printf("\n");
        }
    }

    /* printf the array of pointers */
    printf ("\n The matrix entered was:\n\n");
    for(row = 0; row < NROWS; row++)
    {
        for(col = 0; col < NCOLS; col++)
        {
            printf(" %5.2lf", arrayMatrix[row][col]);
        }
        printf("\n");
    }

    free (arrayMatrix);

    return 0;
}

输出

$ ./bin/arraymatrix

Please enter the elements of your augmented matrix:
 A[1][1]: 1
 A[1][2]: 2
 A[1][3]: 3
 A[2][1]: 4
 A[2][2]: 5
 A[2][3]: 6

 The matrix entered was:

  1.00  2.00  3.00
  4.00  5.00  6.00

内存错误/泄漏检查

在您编写的任何动态分配内存的代码中,对于分配的任何内存块,您有两个责任:(1) 始终保留指向内存块起始地址的指针,因此,(2) 它可以在以下情况下被释放它不再需要。您必须使用内存错误检查程序来确保您没有写入超出/超出分配的内存块,并确认您已释放所有已分配的内存。对于 Linux,valgrind 是正常的选择。滥用内存块的方式有很多,可能会导致真正的问题,没有理由不这样做。每个平台都有类似的内存检查器。它们都易于使用。只需通过它运行您的程序即可。

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

Please enter the elements of your augmented matrix:
 A[1][1]: 1
 A[1][2]: 2
 A[1][3]: 3
 A[2][1]: 4
 A[2][2]: 5
 A[2][3]: 6

 The matrix entered was:

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

如果您还有其他问题,请告诉我。我的手指很累。这最终比我预期的要长得多......

【讨论】:

    【解决方案2】:

    这是一个分配连续二维双精度数组的函数:

    double* alloc_array_2d(int Width, int Height){
      return (double*)malloc(Width * Height * sizeof(double));
    }
    

    这是一个函数,它将根据 x 和 y 坐标获取指向数组元素的指针:

    double* get_element_2d(double* Array, int Width, int X, int Y){
      int index = (Width * Y) + X;
      return Array[index];
    }
    

    使用这种方法,可以想象一个 5 x 3 的数组如下所示:

    0  1  2  3  4
    5  6  7  8  9
    10 11 12 13 14
    

    在计算机内存中它实际上是这样的:

    0 1 2 3 4   5 6 7 8 9   10 11 12 13 14
    

    基本思想是一行值的开头位于前一行值的末尾旁边,因此通过将数组的宽度乘以垂直坐标,您可以将索引偏移到正确的行中。例如要访问第二行 (7) 中的第三个对象,我们调用

    get_element_2d(myArray, 5, 2, 1); // 1 * 5 + 2 = 7
    

    请注意,每个坐标都从零开始,就像所有指针算术一样,因此要访问第一行中的元素,您将使用 get_element_2d(Array, Width, X, 0)

    【讨论】:

    • "请注意,每个坐标都比坐标名称所暗示的要小一,这对于 C 中的所有指针算术都是如此" -- 我认为一种更好的方法说明“数组索引在C中为零”,意思是myarray[0]是数组中的第一个元素。它与 pointers 本身无关。
    • 好吧,如果我理解正确的话,它是数组[(列数 * 你想要的列)+ 你想要的行]。我正在使用double * arrayMatrix = malloc(numRows * numColumns * sizeof(double));arrayMatrix[(numColumns * column) + row] = arrayElement; 并打印出printf("%5.2lf", arrayMatrix[(numColumns * column) + row]);,它仍然会导致所有值都为0。看起来它应该可以工作,但它只是没有......
    • 应该是arrayMatrix[(numColumns * row) + column]
    【解决方案3】:

    如果您之前有 double arrayElement;,那么您的第一个代码示例看起来是正确的。

    但是,如果您将它作为 int arrayElement; 或其他某种数据类型,它会解释您所看到的,因为 %lf 只能与 double 一起使用。

    现代编译器能够针对此问题发出警告,因此您可以调查您正在使用哪些编译器开关,以确保您获得最大可能的警告级别。

    为避免此错误,您可以直接扫描到数组中:

    scanf("%lf", &arrayMatrix[row * numColumns + column]);
    

    注意。您似乎已经获得了访问数组的每一行的替代变体,您可以在其中从指针表示法更改为索引表示法。一般来说,索引符号被认为更容易阅读,因此更可取。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2021-11-13
      • 1970-01-01
      • 2017-04-26
      • 1970-01-01
      • 2017-06-11
      • 1970-01-01
      • 2013-01-26
      相关资源
      最近更新 更多