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] = ...;
...
}
如果您没有可用的可变长度数组,则至少列维度必须是编译时常量:
#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)
n
,以及编译器报错信息是什么? - legends2k