指针和数组——艰难地学习 C

2024-01-17

这个问题来自 Zed Shaw 的《Learn C the Hard Way》。这是关于指针和数组的。我们在这里给出了一些代码:

#include <stdio.h>

int main(int argc, char *argv[])
{
  // create two arrays we care about
  int ages[] = {23, 43, 12, 89, 2};
  char *names[] = {
      "Alan", "Frank",
      "Mary", "John", "Lisa"
  };

  // safely get the size of ages
  int count = sizeof(ages) / sizeof(int);
  int i = 0;

  // first way using indexing
  for(i = 0; i < count; i++) {
      printf("%s has %d years alive.\n",
              names[i], ages[i]);
  }

  printf("---\n");

  // setup the pointers to the start of the arrays
  int *cur_age = ages;
  char **cur_name = names;

  // second way using pointers
  for(i = 0; i < count; i++) {
      printf("%s is %d years old.\n",
            *(cur_name+i), *(cur_age+i));
  }

  printf("---\n");

  // third way, pointers are just arrays
  for(i = 0; i < count; i++) {
      printf("%s is %d years old again.\n",
              cur_name[i], cur_age[i]);
  }

  printf("---\n");

  // fourth way with pointers in a stupid complex way
  for(cur_name = names, cur_age = ages;
          (cur_age - ages) < count;
          cur_name++, cur_age++)
  {
      printf("%s lived %d years so far.\n",
              *cur_name, *cur_age);
  }

  return 0;
}

该指令是“重写该程序中的所有数组用法,使其成为指针。 http://c.learncodethehardway.org/book/ex15.html#extra-credit“这是否意味着要做类似的事情?

int *ptr;
ptr = &ages[0]

让我先说一些题外话:

  • 我不认为这是一本很好的书。我认为它混淆了一些主题,使它们看起来比实际上更难。如果想要一本更好的高级 C 书,我会推荐Peter van der Linden 的《深 C 秘密》 https://rads.stackoverflow.com/amzn/click/com/0131774298,对于初学者的书,我推荐原版凯瑞 https://rads.stackoverflow.com/amzn/click/com/0131103628

不管怎样,看起来你正在看来自的额外学分练习本章 http://c.learncodethehardway.org/book/ex15.html.

  • 另外,我不认为这对于学习来说是一个特别明智的练习(另一个答案指出这个问题的形成没有意义),所以这个讨论会变得有点复杂。相反,我会推荐第 5 章中的练习。凯瑞 https://rads.stackoverflow.com/amzn/click/com/0131103628.

首先我们需要明白指针与数组不同 http://c-faq.com/aryptr/aryptr2.html。我在另一个答案中对此进行了扩展here https://stackoverflow.com/a/9628309/790070,我将从中借用相同的图表C FAQ http://c-faq.com/aryptr/aryptr2.html。以下是当我们声明数组或指针时内存中发生的情况:

 char a[] = "hello";  // array

   +---+---+---+---+---+---+
a: | h | e | l | l | o |\0 |
   +---+---+---+---+---+---+

 char *p = "world"; // pointer

   +-----+     +---+---+---+---+---+---+
p: |  *======> | w | o | r | l | d |\0 |
   +-----+     +---+---+---+---+---+---+

因此,在书中的代码中,当我们说:

int ages[] = {23, 43, 12, 89, 2};

We get:

      +----+----+----+----+---+
ages: | 23 | 43 | 12 | 89 | 2 |
      +----+----+----+----+---+

我将使用非法声明来进行解释 - 如果我们could讲过了:

int *ages = {23, 43, 12, 89, 2}; // The C grammar prohibits initialised array
                                 // declarations being assigned to pointers, 
                                 // but I'll get to that

这会导致:

      +---+     +----+----+----+----+---+
ages: | *=====> | 23 | 43 | 12 | 89 | 2 |
      +---+     +----+----+----+----+---+

稍后可以以相同的方式访问这两个元素 - 第一个元素“23”可以通过以下方式访问ages[0],无论它是数组还是指针。到目前为止,一切都很好。

然而,当我们想要获得计数时,我们遇到了问题。 C 不知道数组有多大——它只知道它所知道的变量有多大(以字节为单位)。这意味着,对于数组,您可以通过以下方式计算出大小:

int count = sizeof(ages) / sizeof(int);

或者,更安全的是:

int count = sizeof(ages) / sizeof(ages[0]);

在数组的情况下,这表示:

int count = the number of bytes in (an array of 6 integers) / 
                 the number of bytes in (an integer)

它正确给出了数组的长度。然而,对于指针的情况,它将显示为:

int count = the number of bytes in (**a pointer**) /
                 the number of bytes in (an integer)

这几乎肯定与数组的长度不同。当使用指向数组的指针时,我们需要使用另一种方法来计算数组的长度。在 C 语言中,以下任一情况都是正常的:

  • 记住有多少个元素:

    int *ages = {23, 43, 12, 89, 2}; // Remember you can't actually
                                     // assign like this, see below
    int ages_length = 5;
    for (i = 0 ; i < ages_length; i++) {
    
  • 或者,保留一个标记值(该值永远不会作为数组中的实际值出现)来指示数组的结尾:

    int *ages = {23, 43, 12, 89, 2, -1}; // Remember you can't actually
                                         // assign like this, see below
    for (i = 0; ages[i] != -1; i++) {
    

    (这就是字符串的工作方式,使用特殊的 NULL 值 '\0' 来指示字符串的结尾)


现在,请记住我说过你实际上不能写:

    int *ages = {23, 43, 12, 89, 2, -1}; // Illegal

这是因为编译器不允许您将隐式数组分配给指针。如果你真的想,你可以写:

    int *ages = (int *) (int []) {23, 43, 12, 89, 2, -1}; // Horrible style 

但不要这样做,因为读起来非常不愉快。出于本练习的目的,我可能会写:

    int ages_array[] = {23, 43, 12, 89, 2, -1};
    int *ages_pointer = ages_array;

请注意,编译器正在将数组名称“衰减”为指向其第一个元素的指针 - 就好像您编写了:

    int ages_array[] = {23, 43, 12, 89, 2, -1};
    int *ages_pointer = &(ages_array[0]);

但是 - 您也可以动态分配数组。对于这个示例代码,它会变得相当冗长,但我们可以将其作为学习练习。而不是写:

int ages[] = {23, 43, 12, 89, 2};

我们可以使用 malloc 分配内存:

int *ages = malloc(sizeof(int) * 5); // create enough space for 5 integers
if (ages == NULL) { 
   /* we're out of memory, print an error and exit */ 
}
ages[0] = 23;
ages[1] = 43;
ages[2] = 12;
ages[3] = 89;
ages[4] = 2;

请注意,我们然后需要释放ages当我们完成记忆后:

free(ages); 

另请注意,有几种编写 malloc 调用的方法:

 int *ages = malloc(sizeof(int) * 5);

这对于初学者来说读起来更清晰,但通常被认为是不好的风格,因为如果更改类型,则需要更改两个地方ages。相反,您可以编写以下任一内容:

 int *ages = malloc(sizeof(ages[0]) * 5);
 int *ages = malloc(sizeof(*ages) * 5);

这些陈述是等效的 - 您选择哪一个取决于个人风格。我更喜欢第一个。


最后一件事 - 如果我们将代码更改为使用数组,您可能会考虑更改此内容:

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

但是,你不需要。原因有点微妙。首先,声明如下:

char *argv[]

说“有一个指向 char 的指针数组,称为 argv”。但是,编译器将函数参数中的数组视为指向数组第一个元素的指针,因此如果您编写:

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

编译器实际上会看到:

int main(int argc, char **argv)

这也是您可以省略用作函数参数的多维数组的第一维长度的原因 - 编译器不会看到它。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

指针和数组——艰难地学习 C 的相关文章

随机推荐