繁体   English   中英

难以理解将多维数组传递给函数时要传递哪些元素

[英]trouble understanding what elements are passed when passing multidimensional arrays to functions

我读到某个地方可以将以下数组以这种方式传递给以下函数,如下所示,但是我不明白该数组中的哪些元素正好传递给该函数。
这些是数组

int array[NROWS][NCOLUMNS];
int **array1;
int **array2;
int *array3;
int (*array4)[NCOLUMNS];

这些是功能:

f1(int a[][NCOLUMNS], int m, int n);
f2(int *aryp, int nrows, int ncolumns);
f3(int **pp, int m, int n);

我从中读取的网站提到我们可以通过以下方式将以下数组传递给以下功能:

f2(&array[0][0], NROWS, NCOLUMNS);
f2(*array2, nrows, ncolumns);
f2(array3, nrows, ncolumns);
f2(*array4, nrows, NCOLUMNS);
f3(array1, nrows, ncolumns);
f3(array2, nrows, ncolumns);

array1array2不是指针数组吗? 那么,当您将它们传递给f3 ,所有指针都传递了吗? 关于传递给f2 array2f2在形式参数中有一个普通的指针,但是array2是一个指针数组,那么当将指针数组传递给f2时如何访问各个行和列呢? 当传递array4到函数f2的一维数组的指针时,如何访问单个行和列?

就语言概念而言,您可以理解为C中的数组并不总是数组。

C是70年代的一种语言。 与更抽象的后代相比,它与机器实现非常接近。 因此,如果您想了解混乱的相似语法元素之外的内容,则必须考虑实现。

指针和数组都可以通过(方括号)表示法访问。

括号表示法无疑是有用的,但就指针和数组之间的混淆而言,括号表示法是万恶之源。

f[i]也将为指针和数组“工作”,尽管如我们所见,底层机制将有所不同。

指针与数组之间的关系

让我们从变量声明开始。

指针

float * f只是告诉编译器,符号f某天将引用未知数量的float。

f未初始化。 由您决定实际数据将在哪里,并设置f指向它们。

指针算术和括号符号

请记住,当您向一个指针添加/减去一个值时,单位是指针类型的大小。

float * f;
float * f3 = f+3; // internal value: f + 3 * sizeof (float)

// these two statements are identical
*f3 = 1;
*(f+3) = 1;

当您要从指针引用连续数据时,由于编写*(f+i)很麻烦,因此可以使用方括号表示法

f[3] = 1; // equivalent to *(f+3) = 1;

不管使用哪种表示法,f [3]的地址都是这样计算的:

@f[ 3 ] = f + 3 * sizeof (float)

您可以在功能上将f视为一个(动态)数组,但是正如C所看到的那样, 它仍然是一个指针 ,通过使它看起来像数组的语法进行引用。

数组

float f[10]仍然告诉编译器f将引用一些float,但是它也

  • 在适当的位置分配请求的浮点数
    • 如果f是自动局部变量,则在堆栈上
    • 如果f是全局变量或静态变量,则在静态数据(也称为BSS)中
  • 将符号f视为指向这些浮点值中第一个的常量指针

即使数组创建语法可能令人困惑,但数组在编译时始终具有已知的固定大小。

例如, float f[] = {2,4,8}声明一个长度为3的数组,等效于float f[ 3 ] = {2,4,8} 为了方便起见,可以省略该维:该长度反映了初始化程序的数量,而不会强迫程序员重复它的明确性。

不幸的是, []表示法还可以在其他一些情况下引用指针 (稍后会详细介绍)。

括号符号和数组

括号表示法是访问数组内容的最自然的方法。

当您引用数组时,编译器会知道它是一个数组。 然后,它可以基于数组的第一个元素访问数据,如下所示:

@f[ 3 ] = f + 3 * sizeof (float)

对于一维数组(但仅在这种情况下!),您可以看到地址计算与指针的计算完全相同。

数组作为指针

由于数组也被认为是(常量)指针,因此可以使用数组来初始化指针,因为相反显然是假的(因为数组是常量指针,因此其值不能更改)。

插图

void test (void)
{
    float* f1;
    float  f2[10];
    float  f3[];         // <-- compiler error : dimension not known
    float  f4[] = {5,7}; // creates float f4[2] with f4[0]=5 and f4[1]=7

    f1[3] = 1234; // <--- write to a random memory location. You're in trouble
    f2[3] = 5678; // write into the space reserved by the compiler

    // obtain 10 floats from the heap and set f1 to point to them
    f1 = (float *) calloc (10, sizeof(float));
    f1[3] = 1234; // write into the space reserved by you

    // make f1 an alias of f2 (f1 will point to the same data as f2)
    f1 = f2;              // f2 is a constant pointer to the array data
    printf ("%g", f1[3]); // will print "5678", as set through f2

    // f2 cannot be changed
    f2 = f1; // <-- compiler error : incompatible types ‘float[10]’ / ‘float *’
}

走向多维

让我们将示例扩展到二维情况:

float    f2[3][10]; // 2d array of floats
float ** f1;        // pointer to pointer to float

f1 = f2; // <-- the compiler should not allow that, but it does!

f2[2][5] = 1234;           // set some array value
printf ("%g\n", f2[2][5]); // no problem accessing it

printf ("%g\n",f1[2][5]);  // bang you're dead

让我们看看这里发生了什么

当您声明float f2[3][10] ,编译器会将30个所需的float分配为一个连续的块。 前10个浮点数代表f [0],接下来的10个f [1]等。

当您编写f2[ 2 ][ 5 ] ,编译器仍然知道f是一个数组 ,因此它可以像下面这样计算所需的float的有效地址:

@f2[ 2 ][ 5 ] = f + ( 2 * 10 + 5 ) * sizeof (float)

您也可以通过多个括号访问指针 ,只要该指针具有适当数量的参考级别即可:

引用指针时,编译器仅简单地连续应用指针算法:

float h = f1[2][5];

等效于:

float * g = f1[2]; // equivalent to g = *(f1+2)
float   h = g[5];  // equivalent to h = *(g +5)

f1[ 2 ][ 5 ]由编译器作为*(*(f1+ 2 )+ 5 ) 最终地址的计算方式如下:

@f1[ 2 ][ 5 ] = *(f + 2 * sizeof (float *)) + 5 * sizeof (float)

你要的就知道了

除了相同的括号符号之外,还有两个非常不同的实现。

显然,当尝试通过f1访问f2数据时,结果将是灾难性的。

编译器将从f2[2]获取第三个浮点数,将其视为指针,将其添加20,然后尝试引用结果地址。

如果您通过这种错误初始化的指针写了一些值,那么如果遇到访问冲突而不是静默破坏一些随机的四个字节的内存,请认为自己很幸运。

不幸的是,即使除非编译器知道f2是数组,否则无法正确访问基础数据结构, f2 仍被 视为常量float**指针

在理想的世界中,它不应该,但是在C语言中(它!)。

这意味着您可以为数组分配指针, 而编译器不会抱怨它 ,即使结果没有意义。

函数调用

数组和指针都可以作为参数传递给函数。

但是,为避免像上一个示例那样发生灾难性的错误解释,必须让编译器知道要传递给该函数的是数组还是指针。

同样,由于编译器将数组视为常量指针,因此您可以做一些愚蠢的事情,例如声明数组并将其传递给指针之类的函数。

空方括号

更糟的是,函数参数声明的语法允许使用方括号,从而使数组和指针之间的混淆更加可能。

void f (float f1[]);

完全按照

void f (float * f1);

即使变量声明

float f1[];

将产生错误,而不是将其视为声明float * f的替代方法。

您可以说[]符号可以指定指针 ,但只能在函数参数中指定

为什么不允许使用它,因为变量声明可能会引起争议(除其他事项外,它与float f[] = { ... }初始化数组声明语法不明确),但最终结果是该函数参数十进制表示法增加了另一层混乱。

例如,可以用任何奇怪的方式声明著名的argv参数:

int main (int argc, char ** argv)
int main (int argc, char * argv[])
int main (int argc, char argv[][])

另一方面,如果您完全了解指针和数组之间的区别,则空括号比指针表示法更方便,尤其是在这种情况下:

void fun (float f[][10]); // pointer to arrays of 10 floats

等效的指针语法会强制您使用方括号:

void fun (float (* f)[10]);

在声明这样的变量时,您将不可避免:

float (* f)[10]; // pointer to array of 10 floats
float f[][10];   // <-- compiler error : array dimension not known

结论

就函数原型而言,您可以在语法变体之间进行选择,但是如果传递给函数的变量与原型不匹配,则所有这些都会以眼泪结束。

float      ** var1;           // pointer to pointer to float
float       * var2[10];       // array of pointers to float
float      (* var3)[10];      // pointer to array of floats (mind the brackets!)
float         var4[10][10];   // array of arrays of floats (2d array of floats)

// using empty brackets notation
void fun1 (float f[  ][  ]);
void fun2 (float f[10][  ]);
void fun3 (float f[  ][10]);
void fun4 (float f[10][10]);

// using same syntax as for variables declaration
void fun1 (float ** f);
void fun2 (float * f[10]);
void fun3 (float (* f)[10]); // <-- [] notation (arguably) easier to read
void fun4 (float f[10][10]); // must always use square brackets in that case

// even more choice for multiple level pointers
void fun1 (float * f[]);

// any funI (varJ) call with I != J will end up in tears

最后的建议

当然,这是个人喜好问题,但是我建议使用typedef作为获得更多抽象的一种方式,并将C语法奇数的使用限制到最小。

// type definition
typedef float (* tWeirdElement)[10];
typedef tWeirdElement (* tWeirdo)[10]; // pointer to arrays of 10 pointers
                                       // to arrays of 10 floats 

// variable declaration
tWeirdo weirdo;

// parameter declaration
void do_some_weird_things (tWeirdo weirdo);

首先清除您对数组和指针的困惑。 永远记住, 数组不是指针
在您所有的声明中,只有两个

int array[NROWS][NCOLUMNS];  
int (*array4)[NCOLUMNS];  

是数组。 其余的是指针,而不是数组。

array1array2不是指针数组吗?

没有永不。 array1array2int **类型,即指针类型是指向integer的指针

那么,当您将它们传递给f3 ,所有指针都传递了吗?

不。我在上面解释说array1array2不是指针数组。

关于传递给f2 array2f2在形式参数中有一个普通的指针,但是array2是一个指针数组,那么当将指针数组传递给f2时如何访问各个行和列呢?

f2期望将指向int的指针作为其第一个参数。 *array2是指向int的指针。 因此在通话中

f2(*array2, nrows, ncolumns);  

指向int的指针作为第一个参数传递给f2 ,而不是指针数组。

当传递array4到函数f2的一维数组的指针时,如何访问单个行和列?

由于您要向f2传递一个指向int类型参数的指针,因此您只能访问row。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM