理解作为参数传递的语法 *y[ ]

Understanding the syntax *y[ ] passed as parameter

我试图理解 C 代码,但我无意中发现了以下函数定义:

int foo(int n, double *y[]);

我无法理解如何处理和解释 *y[]。此语法用于多个函数,但处理方式不同。在这个例子中,foo1()它被用作一个二维数组:

int foo1(int n, double *y[]){
 double var0 = 0;
 double var1 = 0;
 for (i = 0; i < n; i++) {
    var1 += y[i][1] ;
    var0 += y[i][0] ;
 }
 // do stuff
 return 0;
}

和这里的语法一样,只是把*y[]当成了数组,不过这里他们用的是指针*表示法:

int foo2(int n, double *y[]){
 double var0 = 0;
 for (i = 0; i < n; i++) {
    var0 += *y[i] ;
 }
 // do stuff
 return 0;
}

您能否准确解释一下 *y[] 的含义以及为什么它可以如此灵活地用作函数参数?

在函数声明中,数组类型的参数被转换为指针。所以参数 double *y[] 正好等同于 double **y.

关于 *y[i]y[i][0] 的使用,来自数组索引运算符 [] 与指针算术和取消引用的等价性。

表达式 E1[E2] 完全等同于 *((E1)+(E2)) 这意味着 y[i][0]*(y[i]+0)*y[i] 相同。

这个参数声明

double *y[]

声明了一个未知大小的数组,其中的元素类型为 double *。也就是说它是一个 double *.

类型的指针数组

好像是这个函数的第一个参数

int foo(int n, double *y[]);

即变量n指定数组中的元素个数

这个表达式

y[i]

给出数组的 i-th 元素。因为数组的元素是一个指针(例如指向具有两个元素的数组的第一个元素的指针),那么这些表达式

y[i][1] 
y[i][0]

产生尖元素。

至于这个表达

*y[i]

则等同于表达式

y[i][0]

为了更清楚,我将提供一个演示程序,其中使用 char * [].

类型的数组而不是 double * [] 类型的数组

给你。

#include <stdio.h>

void f( size_t n, char *  s[] )
{
    for ( size_t i = 0; i < n; i++ )
    {
        for ( size_t j = 0; s[i][j] != '[=16=]'; j++ )
        {
            putchar( s[i][j] );
        }
        putchar( '\n' );
    }
}

int main(void) 
{
    char * s[] =
    {
        "Hello",
        "World"
    };
    
    f( sizeof( s ) / sizeof( *s ), s );
    
    return 0;
}

程序输出为

Hello
World

根据foo1中的用法,y是指向double1的2元数组的指针数组,有些东西像这样:

   +---+                                                  +---+
y: |   | y[0] ------------------------------------------> |   | y[0][0]
   +---+                                 +---+            +---+
   |   | y[1] -------------------------> |   | y[1][0]    |   | y[0][1]
   +---+                                 +---+            +---+
    ...                                  |   | y[1][1]
   +---+              +---+              +---+
   |   | y[n-1] ----> |   | y[n-1][0]
   +---+              +---+
                      |   | y[n-1][1]
                      +---+

并且可能声明为

double *x[N];  // or some other name, just picking x for convenience

并使用现有数组的地址进行初始化:

double a[2], b[2], c[2], ...;
double *x[N] = { a, b, c, ... };

或者内存是动态分配的:

for ( size_t i = 0; i < N; i++ )
  x[i] = malloc( sizeof *x[i] * 2 );

在调用函数中。

除非它是sizeof或一元&运算符的操作数,或者是用于在声明中初始化字符类型数组的字符串文字,表达式“T 的 N 元素数组”类型的 将被转换或“衰减”为“指向 T 的指针”类型的表达式,并且表达式的值将为数组第一个元素的地址。

当您使用 x 作为参数调用 foo1foo2 时,如

foo1( N, x );
foo2( N, x );

表达式x从类型“指向double的指针的N元素数组”类型转换为类型“指向[=18的指针的指针” =]",第一个元素的地址 (&x[0]) 是实际传递给函数的地址。

在函数参数声明中,T a[N]T a[] 形式的任何参数都被“调整”为 T *a - IOW a 始终被视为指针T,而不是数组。

因此,即使 y 在函数参数列表中被声明为 double *y[],它实际上被视为 double **y

数组下标操作a[i]定义*(a + i)——给定一个起始地址,偏移i个元素(不是字节!)从该地址并取消引用结果。所以 y[i] 等价于 *(y + i)y[i][j] 等价于 *(*(y + i) + j)。这也意味着表达式 *y 等价于 y[0]*y[i] 等价于 y[i][0].

*y    == *(y + 0)    == y[0]
*y[i] == *(y[i] + 0) == y[i][0]

  1. 更准确地说,每个 y[i] 指向 double 的 2 元素数组的第一个元素。