CS50 第 4 周过滤器和 "void vs pointers"

CS50 week 4 filter and "void vs pointers"

我正在尝试使用 CS50 制作灰度滤镜。

首先 - 为什么我的代码无法正常工作,而我在 GitHub 上找到的与我的代码非常相似的代码却运行良好。

这是我的代码:

void grayscale(int height, int width, RGBTRIPLE image[height][width])
{
    int i = 0, j = 0;
    float rgbGray;
    while (i < height)
    {
        while (j < width)
        {
            rgbGray = (image[i][j].rgbtBlue + image[i][j].rgbtGreen + image[i][j].rgbtRed) / 3.00;
            rgbGray = round(rgbGray);
            image[i][j].rgbtBlue = rgbGray;
            image[i][j].rgbtGreen = rgbGray;
            image[i][j].rgbtRed = rgbGray;
            j++;
        }
        i++;
    }
    return;
}

这是我在 GitHub

上找到的工作代码
void grayscale(int height, int width, RGBTRIPLE image[height][width])
{
    float rgbGray;

    for (int i = 0; i < height; i++)
    {
        for (int j = 0; j < width; j++) {

            rgbGray = round( (image[i][j].rgbtBlue + image[i][j].rgbtGreen + image[i][j].rgbtRed)/ 3.00);

            image[i][j].rgbtBlue = rgbGray;
            image[i][j].rgbtGreen = rgbGray;
            image[i][j].rgbtRed = rgbGray;

        }
    }
    return;
}

作者:https://gist.github.com/ndiecodes

第二个问题是 - 为什么不需要指针。我的意思是在讲座中有信息,我们需要在我们想要更改元素值的情况下使用指针。此函数不返回任何值 - 它只是 VOID - 所以我很好奇为什么第二个代码可以正常工作

您的代码使用 while 循环,在循环后不会将 j 重置回零。因此,第一行之后的每一行都没有执行任何操作。

其次,这里没有 显式 指针,但是 C 数组被有效地传递,就好像(松散地说)它们是指向数据的指针,所以您对 image 影响调用者看到的数据。

Second question is - why there aren't pointers needed. I mean in lecture there is information, that we need to use pointers in cases where we want to change values of elements.

数组很奇怪。

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

当你以数组表达式作为参数调用函数时,函数实际接收的是指向第一个元素的指针:

int arr[10];
foo( arr );  // equivalent to foo( &arr[0] );
...
void foo( int *a ) { ... }

在你的例子中,当你像这样调用 grayscale 时:

RGBTRIPLE image[rows][cols];
...
grayscale( rows, cols, image );

grayscale 实际收到的是 指针 指向 RGBTRIPLE:

cols 元素数组
void grayscale( int height, int width, RGBTRIPLE (*image)[width] )
{
  ...
}

然而...

在函数参数声明的上下文中,任何类型为 T []T [N] 的参数都将“调整”为类型 T *,因此我可以 声明 foo

中的任何一个
void foo( int a[10] )

void foo( int a[] )

void foo( int *a )

并且所有内容的解释方式相同 - a 指向 int 的指针 ,而不是 int 的数组。这就是为什么您仍然可以将 grayscale 声明为

void grayscale( int height, int width, RGBTRIPLE image[height][width] )

数组下标是根据指针操作定义的。表达式 a[i] 被定义为 *(a + i) - 给定起始地址 a,偏移 i 个对象( 不是字节!)解决并取消引用结果:

a[0] == *(a + 0) == *a
a[1] == *(a + 1)
a[2] == *(a + 2)

等等

数组访问是为什么衰减规则首先存在。 C是从一种更早的语言B派生出来的(B派生自BCPL,BCPL派生自CPL,受Algol等的影响)。在 B 语言中,有一个指向数组第一个元素的显式指针。给定声明

auto vec[10];

你会在记忆中得到这个:

     +---+
vec: |   | ----------+
     +---+           |
      ...            |
     +---+           |
     |   | vec[0] <--+
     +---+
     |   | vec[1] 
     +---+
      ...

所以在 B 中 a[i] == *(a + i) 的等价性是有意义的——你总是有一个指向第一个元素的显式指针。

当他在设计 C 时,Ritchie 想保留 B 的数组行为,但他不想为行为所需的显式指针预留 space - 他只想预留 space 用于数组元素本身。鉴于

int vec[10];

你得到

     +---+
vec: |   | vec[0]
     +---+
     |   | vec[1]
     +---+
      ...

所以他想出了衰减规则 - 任何时候编译器看到一个数组表达式不是 &sizeof(或 _Alignof)的操作数,它用指向数组第一个元素的指针替换该表达式。

这就是为什么在将数组参数传递给 scanf 等函数时 不需要 需要使用 & 的原因,以及为什么不能return 来自函数的数组 - 所有得到 returned 的是指向数组第一个元素的指针,当函数 returns.