【问题标题】:Returning a two-dimensional array in C?在C中返回一个二维数组?
【发布时间】:2014-01-27 21:08:01
【问题描述】:

我最近开始编程C 只是为了好玩。我是桌面领域C# .NETJava 的一名非常熟练的程序员,但这对我来说有点太大的挑战。

我正在尝试做一些“简单”的事情,比如从函数返回一个二维数组。我已经尝试在网络上对此进行研究,但我很难找到有效的方法。

这是我目前所拥有的。它并没有完全返回数组,它只是填充一个。但即使这样也无法编译(如果您是熟练的C 程序员,我相信原因对您来说一定很明显)。

void new_array (int x[n][n]) {
  int i,o;

  for (i=0; i<n; i++) {
      for (o=0; o<n; o++) {
        x[i][o]=(rand() % n)-n/2;
      }
  }

  return x;
}

及用法:

int x[n][n];
new_array(x);

我做错了什么?需要指出的是,n 是一个常量,其值为3

编辑:尝试定义常量时出现编译器错误:http://i.imgur.com/sa4JkXs.png

【问题讨论】:

标签: c


【解决方案1】:

C 不像大多数语言那样对待数组;如果您想在 C 中使用数组,则需要了解以下概念。

除非它是 sizeof 或一元 &amp; 运算符的操作数,或者是用于在声明中初始化另一个数组的字符串文字,否则 表达式 类型为 "N- T" 的元素数组将被转换 ("decay") 为类型为 "pointer to T" 的表达式,表达式的值将是数组第一个元素的地址。这个结果不是左值;它不能是赋值的目标,也不能是++-- 运算符的操作数。

这就是为什么你不能定义一个函数来返回一个数组类型;作为return 语句的一部分,数组表达式将被转换为指针类型,此外,无论如何都无法将结果分配给另一个数组表达式。

信不信由你,这是有充分的技术原因的;最初开发 C 时,Dennis Ritchie 从 B 编程语言中借鉴了很多概念。 B 是一种“无类型”语言;一切都存储为无符号字或“单元格”。内存被视为“单元”的线性阵列。当您将数组声明为

auto arr[N];

B 将为数组内容留出 N 个“单元”,以及一个绑定到 arr 的附加单元,以存储第一个元素的偏移量(基本上是一个指针,但没有任何类型语义)。数组访问被定义为*(arr+i);您从存储在a 中的地址偏移i 单元格并取消引用结果。这对 C 非常有用,直到 Ritchie 开始将结构类型添加到语言中。他希望结构的内容不仅能以抽象的术语描述数据,而且能物理地表示位。他使用的例子是这样的

struct {
  int node;
  char name[14];
};

他想为节点留出 2 个字节,紧接着为名称元素留出 14 个字节。他想要一个这样的结构数组布局,这样你有 2 个字节,然后是 14 个字节,然后是 2 个字节,然后是 14 个字节,等等。他想不出一个处理数组指针的好方法,所以他完全摆脱了它。 C 没有为指针留出存储空间,而是简单地从数组表达式本身计算它。这就是为什么您不能为数组表达式分配任何东西的原因;没有什么可以将值分配给

那么,如何从函数中返回二维数组?

你没有。你可以返回一个指针指向一个二维数组,比如:

T (*func1(int rows))[N]
{
  T (*ap)[N] = malloc( sizeof *ap * rows );
  return ap;
}

这种方法的缺点是必须在编译时知道N

如果您使用的是支持可变长度数组的 C99 编译器或 C2011 编译器,则可以执行以下操作:

void func2( size_t rows, size_t cols, int (**app)[cols] ) 
{
  *app = malloc( sizeof **app * rows );
  (*app)[i][j] = ...;                   // the parens are necessary
  ...
 }

如果您没有可用的可变长度数组,那么至少列维度必须是编译时常量:

#define COLS ...
...
void func3( size_t rows, int (**app)[COLS] )
{ 
  *app = malloc( sizeof **app * rows );
  (*app)[i][j] = ...;
}

您可以将内存零碎地分配到像二维数组一样的东西中,但行不一定是连续的:

int **func4( size_t rows, size_t cols )
{
  int **p = malloc( sizeof *p * rows );
  if ( p )
  {
    for ( size_t i = 0; i < rows; i++ )
    {
      p[i] = malloc( sizeof *p[i] * cols );
    }
  }
  return p;
}

p不是一个数组;它指向一系列指向int 的指针。出于所有实际目的,您可以像使用二维数组一样使用它:

 int **arr = foo( rows, cols );
 ...
 arr[i][j] = ...;
 printf( "value = %d\n", arr[k][l] );

请注意,C 没有任何垃圾收集;你有责任清理自己的烂摊子。前三种情况,很简单:

int (*arr1)[N] = func(rows);
// use arr[i][j];
...
free( arr1 );

int (*arr2)[cols];
func2( rows, cols, &arr2 );
...
free( arr2 );

int (*arr3)[N];
func3( rows, &arr3 );
...
free( arr3 );

在最后一种情况下,由于您进行了两步分配,因此您需要进行两步释放:

int **arr4 = func4( rows, cols );
...
for (i = 0; i < rows; i++ )
  free( arr4[i] )
free( arr4)

【讨论】:

    【解决方案2】:

    您的函数返回void,因此return x; 行是多余的。除此之外,您的代码看起来还不错。也就是说,假设你在某个地方有#define n 3,而不是const int n = 3;

    【讨论】:

    • 除了返回类型之外,代码并不好。该问题表明OP想要返回一个数组,而不仅仅是填充一个。这意味着代码偏离了它的规范(它不返回一个数组,而是希望返回),这就是一个错误的定义。
    • 我将其定义为“const int n = 3”。这有什么问题,这两种常量之间有什么区别?
    • 你的种类根本不是一个常数。 const 不是“恒定”的意思,而是“你不能改变这个变量”。我确信 C 常见问题解答有很多关于这个主题的内容。 #define 变体只是做一个简单的文本替换,所以你所有的 ns 都将被替换为文字 3s。
    • 我试过define n 3,但它不起作用。我的 GCC 编译器(MinGW)抱怨。 i.imgur.com/sa4JkXs.png
    • 看来您没有删除其他声明。
    【解决方案3】:

    您不能在 C 语言中返回多维或其他数组。

    主要原因是语言说你不能。另一个原因是,通常本地数组是在堆栈上分配的,因此在函数返回时会被释放,因此返回它们是没有意义的。

    传递一个指向数组的指针并修改它通常是要走的路。

    【讨论】:

      【解决方案4】:

      要返回(指向)在编译时已知的新创建的维度数组,您可以这样做:

      #define n 10 // Or other size.
      
      int (*new_array(void))[n]
      {
          int (*x)[n] = malloc(n * sizeof *x);
          if (!result)
              HandleErrorHere;
      
          for (int i = 0; i < n; ++i)
              for (int o = 0; i < n; ++o)
                  x[i][o] = InitialValues;
      
          return x;
      }
      
      …
      // In the calling function:
      int (*x)[n] = new_array();
      
      …
      // When done with the array:
      free(x);
      

      如果在编译时大小未知,您甚至无法返回指向数组的指针。 C 确实支持变长数组,但不支持函数的返回类型。您可以改为通过参数返回指向可变长度数组的指针。这需要使用一个参数,该参数是指向可变长度数组的指针的指针,因此会有些混乱。

      此外,在调用者中动态分配数组、在调用者中自动分配数组、在被调用函数中动态分配数组以及使用可变长度数组或固定长度数组甚至一维数组之间的首选选择手动索引取决于上下文,包括数组的大小、它的寿命以及您打算将其用于什么操作。因此,在提出具体建议之前,您需要提供额外的指导。

      【讨论】:

      • 不幸的是,OP 要求提供 nn 数组而不是 ab 数组,因此您的答案中的 n 是否与 a 或 b 匹配不清楚。无论如何 +1。
      【解决方案5】:

      在 C 中,只有按值传递/返回(没有按引用传递)。因此,传递数组(按值)的唯一方法是将其地址传递给函数,以便它可以通过指针对其进行操作。

      但是,通过值返回数组的地址是不可能的,因为当控制到达调用者时,函数超出范围并且它的自动变量也随之下降。因此,如果确实需要,您可以动态分配数组、填充并返回它,但首选方法是传递数组并将维护数组的责任留给调用者。

      至于错误,我在 GCC 中得到的唯一警告是 warning: 'return' with a value, in function returning void,这只是意味着您不应该从 void 函数返回任何内容。

      void new_array (int x[n][n]); 你在这里真正做的是获取一个指向 n 个整数数组的指针;衰减的类型是int (*x)[n]。发生这种情况是因为arrays decay into pointers 通常。如果你在编译时知道n,或许最好的通过方式是:

      #define n 3
      void new_array (int (*x)[n][n]) {
        int i,o;
      
        for (i=0; i<n; i++) {
          for (o=0; o<n; o++) {
            x[i][o]=(rand() % n)-n/2;
          }
        }
      }
      

      并将其称为

      int arr[n][n];
      new_array(&arr);
      

      【讨论】:

        【解决方案6】:

        如果你将它们包装在一个结构中,你可以像任何其他变量一样传递任意维度的数组:

        #include <stdio.h>
        
        #define n 3
        
        struct S {
          int a[n][n];
        };
        
        
        static struct S make_s(void)
        {
          struct S s;
        
          int i, j;
          for (i = 0; i < n; i++) {
            for (j = 0; j < n; j++)
              s.a[i][j] = i + j;
          }
        
          return s;
        }
        
        static void print_s(struct S s)
        {
          int i, j;
          for (i = 0; i < n; i++) {
            for (j = 0; j < n; j++)
              printf(" %d", s.a[i][j]);
            printf("\n");
          }
        }
        
        int main(void) {
          struct S s;
        
          s = make_s();
          print_s(s);
        
          return 0;
        }
        

        【讨论】:

          【解决方案7】:

          您可能将n 声明为一个常量整数:

          const int n = 3;
          

          相反,您应该将n 定义为预处理器定义:

          #define n 3
          

          【讨论】:

            猜你喜欢
            • 2018-01-17
            • 2011-12-30
            • 2021-12-24
            • 2014-12-22
            • 2016-10-21
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2021-03-16
            相关资源
            最近更新 更多