在C语言中如何返回一个二维数组?

7

最近我为了好玩开始学习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


1
我总是很喜欢听熟练的非C程序员说C语言对他们来说是一种挑战。 :) - this
1
“常量”是什么意思? - Carl Norum
1
顺便说一句,包括编译器错误总是有帮助的。 - Carl Norum
1
请问您能否展示一下如何声明 n,以及编译器报错信息是什么? - legends2k
1
可能是数组指针衰减和将多维数组传递给函数的重复问题。 - legends2k
显示剩余4条评论
7个回答

7

C语言中对待数组的方式与大多数其他语言不同;如果你想在C语言中使用数组,需要理解以下概念。

除非它是sizeof或一元运算符&的操作数,或者是一个字符串字面值用于在声明中初始化另一个数组时,类型为“N元素数组 of T”的表达式将被转换(“衰减”)为类型为“指向T的指针”的表达式,表达式的值将是数组的第一个元素的地址。这个结果不是lvalue;它不能成为赋值的目标,也不能成为++--运算符的操作数。

这就是为什么你不能定义一个返回数组类型的函数;数组表达式将在return语句中转换为指针类型,而且无论如何都没有办法将结果分配给另一个数组表达式。

信不信由你,这背后有一个坚实的技术原因;当Dennis Ritchie最初开发C语言时,他从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 的指针。就实际目的而言,您可以像使用 2D 数组一样使用它:

 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)

4

您的函数返回值为void,因此return x;一行是多余的。除此之外,您的代码看起来很好。这是指假设您已经有#define n 3而不是像const int n = 3;那样。


除了返回类型之外,代码不太好。问题表明 OP 想要返回一个数组,而不仅仅是填充一个数组。这意味着代码偏离了其规范(它没有返回一个数组,但是期望返回一个数组),这就是 bug 的定义。 - Eric Postpischil
我已将其定义为“const int n = 3”。有什么问题,这两种常量之间有什么区别? - Mathias Lykkegaard Lorenzen
你所说的“kind”根本不是常量。const并不意味着“常量”,而是表示“无法更改此变量”。我确信C语言FAQ在这方面提供了很多信息。而使用#define定义变量只是进行简单的文本替换,因此你所有的n都将被直接替换为字面值3 - Carl Norum
我尝试使用define n 3,但它不起作用。我的GCC编译器(MinGW)报错了。http://i.imgur.com/sa4JkXs.png - Mathias Lykkegaard Lorenzen
这看起来好像你没有删除另一个声明。 - Carl Norum

4

在C语言中,无论是多维还是其他形式,都不能返回数组。

这主要是因为语言规定了不允许这样做。另一个原因是通常局部数组是在堆栈上分配的,在函数返回时被释放,所以将它们返回没有意义。

将指向数组的指针传入并对其进行修改通常是更好的选择。


3

要返回(指向)在编译时已知维度的新创建的数组,您可以执行以下操作:

#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。 - chux - Reinstate Monica

2
在 C 语言中,只有按值传递和返回(没有按引用传递)。因此,传递数组(按值)的唯一方法是将其地址传递给函数,使其可以通过指针进行操作。
然而,返回一个数组的地址是不可能的,因为当控制权到达调用者时,函数就会超出作用域,它的自动变量也会随之消失。因此,如果你真的需要返回数组,你可以动态分配数组、填充并返回它,但更好的方法是传递数组,并将维护数组的责任留给调用者。
至于错误,我在 GCC 中得到的唯一警告是“warning: 'return' with a value, in function returning void”,这意味着你不应该从一个 void 函数中返回任何东西。
“void new_array (int x[n][n]);” 这里实际上是将一个 n*n 的整型数组的指针作为参数传入;经过衰减后的类型是“int (*x)[n]”。这是因为通常情况下,数组会衰减为指针。如果你在编译时知道了“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);

2

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

#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;
}

1
你可能已经将 n 声明为一个常量整数:
const int n = 3;

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

#define n 3

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