【问题标题】:Pointers and Arrays -- Learn C the hard way指针和数组——艰难地学习 C
【发布时间】:2013-06-18 01:20:26
【问题描述】:

这个问题来自 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;
}

指令是给“rewrite all the array usage in this program so that it's pointers.”的意思是不是做类似的事情?

int *ptr;
ptr = &ages[0]

【问题讨论】:

  • 这也可能意味着使用malloc动态分配数组。关于您的ptr 分配ptr = ages; 应该足够了。
  • 指针和数组在很多情况下是可以互换的。例如,在顶部,*argv[] 可以替换为 **argv。您可以通过简单地删除所有方括号并在每个数组的前面放置一个 * 来完成分配。
  • 你能提供一个链接到有问题的确切练习吗?
  • BTW - 如果代码有注释“安全地获取年龄大小”,这是错误的;如果有人更改了年龄的类型,则会给出错误的值。获取年龄大小的更安全方法是sizeof(ages) / sizeof(*ages)

标签: c arrays pointers


【解决方案1】:

让我先说点题外话:

  • 我不认为这是一本很好的书。我认为它混淆了一些主题,使它们看起来比实际更难。对于更好的高级 C 书,我会推荐 Deep C Secrets by Peter van der Linden,对于初学者的书,我会推荐原版 K & R

无论如何,您似乎正在查看来自this chapter 的额外学分练习。

  • 另一个问题-我不认为这是一个特别明智的学习练习(另一个答案指出这个问题没有意义),所以这个讨论会变得有点复杂。我会推荐K & R第5章的练习。

首先我们需要了解pointers are not the same as arrays。我在另一个答案here 中对此进行了扩展,我将从C FAQ 借用相同的图表。以下是我们声明数组或指针时内存中发生的情况:

 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};

我们得到:

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

出于解释的目的,我将使用非法声明 - 如果我们可以说过:

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++) {
    

    (这就是字符串的工作方式,使用特殊的 NUL 值 '\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[]

说“有一个指向字符的指针数组,称为 argv”。但是,编译器将函数参数中的数组视为指向数组第一个元素的指针,所以如果你这样写:

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

编译器实际上会看到:

int main(int argc, char **argv)

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

【讨论】:

  • 非常感谢!这为我清理了很多东西。
【解决方案2】:

这是一种在不使用动态分配的情况下将 agesnames 更改为指针的方法。

  // create two arrays we care about
  const char *ages = "\x17\x2b\x0c\x59\x02";
  const char (*names)[6] = (void *)
      "Alan\0\0" "Frank\0" "Mary\0\0" "John\0\0" "Lisa\0\0";

  // safely get the size of ages
  int count = strlen(ages);

  //...

  // setup the pointers to the start of the arrays
  const char *cur_age = ages;
  const char (*cur_name)[6] = names;

namescur_name 都是指针类型,尽管它们确实指向数组。

【讨论】:

  • 幸运的是,人们不会活多个世纪(现在?还没有?)。因此,人们的年龄适合一个字节。 Proof that it works.
【解决方案3】:

“使用指针而不是数组使用重写代码”的任务并没有足够清晰地表述以使其有意义。在 C 语言中,99.9%(只是一个非正式的数字)的数组功能基于隐式数组到指针的转换,这意味着几乎每次使用数组时,您也使用指针。没有办法解决。

换句话说,形式上真的不需要重写任何东西。

如果你重写你的代码

int *ptr = &ages[0];

并使用ptr 代替ages,您将简单地显式地显式地显式地显示您的代码中已经存在的内容。如果这就是该任务的真正含义,那么您当然可以这样做。但我认为这种多余的练习没有多大意义。

【讨论】:

  • 这是一个冗长的“不要打扰”,而不是答案。
  • @Jake Sellers:不,这不是“不要打扰”。这是“需要更多信息”和“如果他们不提供更多信息,他们就不知道自己在问什么”。
  • 评论是为了获取更多信息,而不是答案。
  • @Jake Sellers:我不是在询问更多信息。我不是被分配有问题的人。我只是告诉 OP 在我看来 在这种情况下应该采取什么行动。此外,如果我在他的位置上,我会将其视为一个技巧问题,“它没有意义”实际上是正确且最终的答案。如果我是问这个问题的人,这正是我希望从知道他们在说什么的人那里得到的答案。
  • @Jake Sellers:学习?你正在向合唱团布道。我的回答的重点是理解问题的隐含含义(即使问题的作者不是有意的)将教给初学者的不仅仅是为了重写而“用指针”盲目重写代码它“带有指针”。
【解决方案4】:

我猜这意味着

  1. 使用malloc()为每个数组分配内存,使用free()释放内存。

  2. 在所有 for 循环中使用指针算法。

【讨论】:

    【解决方案5】:

    这可能意味着像你建议的那样,是的。

    但请记住,ages 已经是一个 int 指针 (int *) —— 在 C 语言中,数组只是内存中彼此相邻的一堆东西。表示该数组的变量只是指向该数组中第一个元素的指针,[] 运算符是取消引用。

    你可以这样想:

    在您的程序运行时某处有一块内存包含
    |...| 23 | 43 | 12 | 89 | 2 |...|
    每个盒子代表足够的空间来容纳一个int
    那么,程序中的变量ages 只是一个指针,它保存了该块中第一个元素的地址。它“指向”23,类型为int*。如果取消引用,您会发现 *ages 的计算结果为 23。同样,如果您使用该地址并“跳过”一个 int-size 前进,您将获得 43。在代码中,这看起来像
    *(ages + 1 * sizeof(int))
    您可以在其中将1 替换为您想跳过的许多元素。因为这真的很丑陋和令人困惑,所以 C 为您提供了一种很好的方式来做完全相同的事情:[] 运算符。一般来说,

    some_array[n] == *(some_array + n * sizeof(array_element_type))

    希望对您有所帮助,祝您学习 C 好运!确保你花时间真正理解数组和指针的相等性;如果你不这样做,以后很多事情都会变得更加困难。

    【讨论】:

    • 不,它希望他了解指针和数组之间的异同,如果它想教他关于取消引用指针的知识,它会教他关于取消引用指针的知识。您可以阅读它们如何经常互换但又不同here 问题 6.2。
    猜你喜欢
    • 1970-01-01
    • 2016-05-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-04-06
    • 2011-12-04
    • 2016-06-28
    相关资源
    最近更新 更多