【问题标题】:Use of a function on a 2d array在二维数组上使用函数
【发布时间】:2022-01-08 10:19:15
【问题描述】:

我编写的代码允许我通过传递数组的元素来修改函数内的一维数组的元素:

  • 我打印原始数组
  • 我将数组的每个元素传递给函数
  • 在函数中,我将值 50 添加到数组的每个元素中
  • 然后我调用该函数,并打印出来以筛选修改后的元素值(即每个元素的值+50)

我已经能够为一维数组执行此操作,数组中的示例值为 (10,20,30),修改后打印的值为 (60,70,80)。

我希望做的是调整该代码以适用于 2D 数组,您将在下面看到我这样做的尝试。此代码侧重于 int 的使用,但一旦我了解了如何实现这一点,我希望也能适应 2D 字符串的使用。

使用下面的代码:

我的目标是

  1. 打印以筛选原始二维数组
  2. 将二维数组的每个元素传递给函数
  3. 在函数内将值 50 添加到数组的每个元素
  4. 然后调用该函数,将修改后的元素值打印到屏幕上(预期结果显示在屏幕60、61等。) 到目前为止,我已经能够将原始二维数组打印到屏幕上。这是我认为我搞砸的功能,并希望得到任何建议。谢谢。
#include <stdio.h>
#include <string.h>

#define M 4
#define N 2

int function(int **arr);

int main() {
    int i, a;
    int arr[N][M] = {10, 11, 12, 13, 14, 15, 16, 17};

    // the int array first

    for(i = 0; i < N; i++) {
        for(size_t j = 0; j < M; j++) {
            // Accessing each variable
            printf("value of arr[%d] is %d\n", i, arr[i][j]);
        }
    }

    printf("\n    ***values after modification***\n");

    a = function(&arr[i][0]);

    // int array print results
    for(int i = 0; i < N; i++) {
        for(size_t j = 0; j < M; j++) {
            printf("value of arr %d\n", arr[i][j]);
        }
    }

    return 0;
}

int function(int **arr) {
    int i;
    int j;

    for(int i = 0; i < 3; i++) {
        for(size_t j = 0; j < 5; j++) {
            arr[i][j] = arr[i][j] + 50;
        }
    }
}

对于愚蠢的错误,我提前道歉我对 C 很陌生。 提前谢谢你。

【问题讨论】:

  • 作为一般建议,我们要求发布者 (1) 描述他们想要做什么(检查,或多或少); (2)发布他们试图实现的代码(检查); (3) 描述程序的预期输出(缺失); (4) 描述实际结果或编译错误,以及它与预期结果有何不同(如果不明显(缺失))。如果您提供我们理解您的问题所需的全部信息,您将更有机会获得有意义的答案。
  • 道歉@Peter-ReinstateMonica。我将尝试编辑问题以澄清。谢谢。
  • 不用担心。该帖子已经比许多帖子好,否则我不会费心写请求;-)。来吧。
  • 当你调用function i 等于N
  • 我认为您没有告诉我们代码无法编译以及错误消息。也就是说,第 (4) 点仍然缺失。 ;-)。

标签: c multidimensional-array


【解决方案1】:
  • 函数int function(int **arr) 不返回int,所以改为void
  • 当你调用它时,a = function(&amp;arr[i][0]);,你不要在赋值后使用a。我建议您将 a 从程序中完全删除,因为它没有在任何地方使用。
  • 对函数function(&amp;arr[i][0]); 的调用应该只是function(arr);
  • 函数签名需要包含除最外层之外的所有维度:
    void function(int arr[][M])
    
  • 在函数内部,您使用35 而不是NM。这会越界访问数组。
  • function 中,您在函数开头声明的ij 未使用。删除它们。
  • arr[i][j] = arr[i][j] + 50; 最好写成arr[i][j] += 50;
  • 初始化多维数组时,使用大括号使代码更容易阅读:
    int arr[N][M] = {{10, 11, 12, 13}, {14, 15, 16, 17}};
    
  • main 中,您将intsize_t 混合用于索引变量。我建议你满足于一种类型。
  • 删除未使用的头文件 (string.h)

例子:

#include <stdio.h>

#define N 2
#define M 4

void function(int arr[][M]) {
    for(int i = 0; i < N; i++) {
        for(size_t j = 0; j < M; j++) {
            arr[i][j] += 50;
        }
    }
}

int main() {
    int arr[N][M] = {{10, 11, 12, 13}, {14, 15, 16, 17}};

    for(size_t i = 0; i < N; i++) {
        for(size_t j = 0; j < M; j++) {
            printf("value of arr[%zu][%zu] is %d\n", i, j, arr[i][j]);
        }
    }

    printf("\n    ***values after modification***\n");

    function(arr);

    // int array print results
    for(size_t i = 0; i < N; i++) {
        for(size_t j = 0; j < M; j++) {
            printf("value of arr[%zu][%zu] is %d\n", i, j, arr[i][j]);
        }
    }
}

由于您多次打印数组,您还可以添加一个函数来这样做,而不必在 main 中重复该代码:

void print(int arr[][M]) {
    for(size_t i = 0; i < N; i++) {
        for(size_t j = 0; j < M; j++) {
            printf("value of arr[%zu][%zu] is %d\n", i, j, arr[i][j]);
        }
    }
}

【讨论】:

  • 非常感谢@Ted Lyngmo,我稍后会尝试并回复您。非常感谢您花时间解释。
  • @Nolan22 不客气!我希望它会有所帮助!
  • 太棒了,谢谢你解决了我的问题。如果我要说有一个字符数组而不是一个 int 数组(例如 char arr [N][M] 由日期 22/04/1991 组成)并且想将该 char 数组输入到函数中,请问您的建议,将字符串更改为 long int(使用 strtol),然后打印出那些修改后的值。怎么做最好?我需要包含 char[][M] 作为我假设的参数,而不是 int [][M],并且有 long int 函数,而不是 void 函数,对吗?请告诉我是否更可以接受我的尝试。
  • @Nolan22 不客气!对于char arr[N][M];,如果有意义的话,您可以将其视为固定宽度字符串的一维数组吗?我不确定 long int 而不是 void 是什么意思,所以如果您在尝试实施时为此打开一个新问题,它可能会更容易回答。
  • 任何建议将不胜感激stackoverflow.com/questions/70632390/…
【解决方案2】:

C(和 C++)中的二维数组实际上是一维数组,其元素是一维数组。索引运算符 [] 具有从左到右的语义,因此对于类型 arr[N][M],首先评估第一个索引(具有 N 个元素)。结果表达式,例如arr[0]arr 中的第一个元素,是一个有 M 个元素的一维数组。当然,该数组可以再次索引,例如arr[0][1],导致第一个子数组中的第二个 int。

C 语言的一个怪癖是,如果您使用数组作为函数参数,则函数看到 是指向第一个元素的指针。用作参数的数组“衰减”,或者如标准所说,以这种方式“调整”。这与二维数组没有什么不同,只是二维数组的元素本身就是数组。因此,接收函数得到的是一个指向int arr[M]的指针。

考虑:如果你想传递一个简单的整数数组,比如intArr[3],给一个函数,函数看到的是一个指向第一个元素的指针。这样的函数声明可能看起来像void f(int *intPtr),对于这个例子来说,只是用f(intArr) 调用。另一种编写方式是void f(int intPtr[])。它的含义完全相同:参数是指向 int 的指针,而不是数组。它指向一连串整数中的第一个(甚至可能是唯一一个)元素。

二维数组的逻辑是完全相同的——除了所讨论的元素具有“M ints 数组”类型,例如int subArr[M]。这种类型的指针参数可以用两种方式编写,比如简单的 int 数组:作为指针,如 void f(int (*subArrPtr)[M]) 或以数组表示法,顶层元素的数量未知,如 void f(int arr[][M])。与简单的 int 数组一样,这两个参数符号完全等效且可互换。两者实际上都声明了一个指针,所以(*subArrPtr)[M] 可以这么说更切中要害(呃),但可能更晦涩。

(*subArrPtr) 中有趣的括号的原因是我们必须首先取消引用指针才能获得实际的数组,然后才能索引它。如果没有括号,索引运算符[] 将具有优先权。您可以在this table 中查找优先级。 [] 在第 1 组中具有最高优先级,而取消引用运算符 *(不是乘法!)在第 2 组中。如果没有括号,我们将首先索引,然后才取消引用数组元素(因此它必须是一个指针),也就是说,我们将声明一个指针数组,而不是一个指向数组的指针。

因此,您的函数的两个可能的、可互换的签名是

void function( int (*arrArg)[M] ); // pointer notation
void function( int arrArg[][M]  ); // "array" notation (but actually a pointer)

整个程序,也纠正了 Ted 提到的问题,并且没有打印原始值(毕竟我们知道它们),如下所示。我还调整了二维数组的初始化,以便子数组变得可见。 C 对初始化结构和数组非常宽松;它只是让您编写连续的值并随时间填充嵌套子对象的元素。但我认为显示结构有助于理解代码并揭示错误,例如子数组中的元素数量错误。我以一种方式声明了该函数,并以另一种方式定义了它,以表明函数签名是等效的。我还更改了定义和函数的名称以赋予它们更多含义。

#include<stdio.h>

#define NUM_ELEMS_SUBARRAY 4
#define NUM_ELEMS_ARRAY 2

/// @arrArg Is a pointer to the first in a row of one-dimensional
/// arrays with NUM_ELEMS_SUBARRAY ints each.
void add50ToElems(int arrArg[][NUM_ELEMS_SUBARRAY]);

int main()
{
    // Show the nested structure of the 2-dimensional array.
    int arr[NUM_ELEMS_ARRAY][NUM_ELEMS_SUBARRAY] = 
    { 
        {10, 11, 12, 13}, 
        {14, 15, 16, 17} 
    };
                    
    // Modify the array             
    add50ToElems(arr);

    // print results
    for (int i = 0; i < NUM_ELEMS_ARRAY; i++) {
        for (int j = 0; j < NUM_ELEMS_SUBARRAY; j++)
        {
            printf("value of arr[%d][%d]: %d\n", i, j, arr[i][j]);
        }
    }

    return 0;
}

// Equivalent to declaration above
void add50ToElems(int (*arrArg)[NUM_ELEMS_SUBARRAY])
{
    for (int i = 0; i < NUM_ELEMS_ARRAY; i++)
    {
        for (size_t j = 0; j < NUM_ELEMS_SUBARRAY; j++)
        {
            //arrArg[i][j] = arrArg[i][j] + 50;
            arrArg[i][j] += 50;  // more idiomatic
        }
    }
}

为什么将二维数组传递给期望指针指向的函数是错误的?让我们考虑一下void f(int *p) 的含义。它接收一个指向 int 的指针,该指针通常是数组的开头,即在内存中一个接一个地放置的一系列 int 的开头。例如

void f(int *p) { for(int i=0; i<3; ++i) { printf("%d ", p[i]); }

可以用指向数组第一个元素的指针调用:

static int arr[3];
void g() { f(arr); }

当然,这个最小的例子是不安全的(f 怎么知道有三个整数?)但它可以达到目的。

那么void f(int **p); 是什么意思?类似地,它是一个指针,指向一系列指针中的第一个,这些指针一个接一个地位于内存中。我们已经知道为什么如果我们传递二维数组的地址会造成灾难:那里的对象不是指针,而是所有整数!考虑一下:

int arr1[2] = { 1,2 }; 
int arr2[2] = { 2,3 }; 
int arr3[2] = { 3,4 }; 

// This array contains addresses which point 
// to the first element in each of the above arrays.
int  *arrOfPtrToStartOfArrays[3] // The array of pointers
        = { arr1, arr2, arr3 }; // arrays decay to pointers
        
int **ptrToArrOfPtrs = arrOfPtrToStartOfArrays;

void f(int **pp)
{
    for(int pi=0; pi<3; pi++) // iterate the pointers in the array
    {
        int *p = pp[pi]; // pp element is a pointer
        
        // iterate through the ints starting at each address 
        // pointed to by pp[pi]
        for(int i=0; i<2; i++) // two ints in each arr
        {
            printf("%d ", pp[pi][i]); // show double indexing of array of pointers
            // Since pp[pi] is now p, we can also say:
            printf("%d\n", p[i]); // index int pointer
        }
    }
}

int main()
{
    f(ptrToArrOfPtrs);

}

f 遍历指针数组。 它认为那个地址上的,以及随后的地址,都是指针!这就是声明int **pp 的含义。

现在,如果我们改为传递一个充满整数的数组的地址,f 仍然会认为那里的内存充满了指针。像上面的 int *p = pp[i]; 这样的表达式将读取一个整数(例如 1)并认为它是一个地址。然后 printf 调用中的p[i] 将尝试访问地址 1 处的内存。

让我们最后讨论一下为什么应该将二维数组作为指向指针的指针传递的想法如此普遍。一个原因是,虽然将二维数组参数声明为 <strike>void f(int **arr);</strike> 是完全错误的,但您可以访问它的第一个(但只能是第一个)元素,例如int i = **arr。这样做的原因是第一个解引用为您提供了第一个子数组,您可以依次应用解引用运算符,产生它的第一个元素。但是,如果将数组作为参数传递给函数,它不会衰减为指向指针的指针,而是如所讨论的那样衰减为指向其第一个元素的指针。

第二个混淆来源是访问指针数组中的元素使用与访问真正二维数组中的元素相同的双索引:pp[pi][i]arr[i][j]但是,这些表达式生成的代码完全不同,如果传递了错误的类型,就会引发灾难。顺便说一下,你的编译器会警告这一点。

【讨论】:

    猜你喜欢
    • 2013-02-16
    • 2015-03-19
    • 1970-01-01
    • 1970-01-01
    • 2017-11-26
    • 2019-09-04
    • 2016-01-05
    • 2018-01-16
    • 1970-01-01
    相关资源
    最近更新 更多