【问题标题】:K&R: array of character pointersK&R:字符指针数组
【发布时间】:2009-05-08 02:52:24
【问题描述】:

在第 页。 109 的 K&R,我们看到:

void writelines(char *lineptr[], int nlines)
{
  while (nlines -- > 0) printf("%s\n", *lineptr++);
}

我对 *lineptr++ 究竟做了什么感到困惑。据我了解,printf 需要一个 char 指针,因此我们使用 *lineptr 来提供它。然后我们将 lineptr 增加到数组中的下一个 char 指针?这不违法吗?

在第 99 页,K&R 写道:“数组名称不是变量;像 a=pa [其中 a 是数组,pa 是指向数组的指针] 和 a++ 这样的结构是非法的。”

【问题讨论】:

    标签: c arrays pointers kernighan-and-ritchie


    【解决方案1】:

    继续阅读!在 p 的最底部。 99

    作为函数定义中的形参,

     char s[];
    

     char *s;
    

    是等价的;我们更喜欢后者,因为它更明确地表明参数是一个指针。

    即您永远不能将数组(不是变量)传递给函数。如果你声明一个看起来像接受数组的函数,它实际上接受一个指针(一个变量)。这是有道理的。函数参数不是变量会很奇怪——每次调用函数时它都可以有不同的值,所以它肯定不是常量!

    【讨论】:

    • 好吧,您可以将数组名称传递给函数,只是有一些幕后操作使它看起来好像您真的传入了一个指针。这似乎更像是应对而不是有意义。
    • 哦,对了,我现在想起来了。该数组不会被复制到函数的堆栈帧中,只是一个指向该数组的指针。谢谢提醒!
    【解决方案2】:

    lineptr 并不是真正的数组;它是指向指针的指针。请注意,后自增运算符++ 的优先级高于解引用运算符*,所以lineptr++ 中发生的情况是这样的:

    1. lineptr 递增以指向数组中的下一个元素
    2. lineptr++的结果是lineptr的旧值,即指向数组中当前元素的指针
    3. *lineptr++ 取消引用,所以它是数组中当前元素的值

    因此,while 循环遍历数组中lineptr 指向的所有元素(每个元素都是char*)并将它们打印出来。

    【讨论】:

    • 啊,这很有道理。谢谢大家回答我的问题!
    【解决方案3】:

    *lineptr++ 想象成以下内容可能更容易:

    *(lineptr++)
    

    这里,指向char*s的数组的指针被移动到数组的下一个元素,也就是从lineptr[0],我们移动到lineptr[1]。 (由于指针的后缀增量,指针的增量发生在取消引用之后。)

    所以基本上,会发生以下事情:

    1. lineptr[0]char* 类型)通过延迟检索。
    2. 指针递增到数组的下一个元素。 (通过指针lineptr 的后缀增量。)
    3. 指针现在指向lineptr[1],再次从步骤 1 重复该过程。

    【讨论】:

    • 我认为 (lineptr++) 在这里不是很有帮助。这意味着 lineptr 在被取消引用之前 *before 递增。真正发生的更像是两个独立的任务:*lineptr、++lineptr。
    • 如果你使用 (*lineptr)++ 你将取消引用然后递增。 lineptr 是指向您通过取消引用选择第一行的数组的指针,然后您增加指针以指向下一个字符。因此,如果行指针是 3 行数组,则括号将从第一个、第二个和第三个字符开始打印第一行 3 次。 *(lineptr++) 将打印三行中的每一行。
    • *(lineptr++) 仍然是一个语句,因此指针的取消引用将在后增量发生之前发生。添加括号只会更改运算符的优先级。在这种情况下,正如 Adam Rosenfield 的回答所指出的那样,++ 的优先级高于 *,因此括号仅作为该单个语句中发生的两件事的视觉指示。
    【解决方案4】:

    亚当·罗森菲尔德选择的答案是错误的。 coobird的答案也是如此。出于这个原因,我对这两个答案都投了反对票。

    Adam Markowitz 对*lineptr++ 的解释是正确的,但他没有回答主要问题这是否是合法的 C99 代码。只有 Tom Future 能做到;不幸的是,他没有解释*lineptr++。我给他们每人一分。

    所以简而言之,lineptr 是一个变量,可以作为指针进行操作。因此,增加指针是合法的。

    lineptr 是指向字符序列指针序列的指针。换句话说,它是指向字符串数组的第一个字符串的指针。根据代码,我们可以假设字符串是空('\0')终止的字符序列。 nlines 是数组中的字符串数。

    while 测试表达式是nlines-- > 0nlines-- 是一个后减量(因为-- 在变量的右侧)。因此,它在执行测试之后执行,并且不管测试结果如何,所以无论如何。

    因此,如果作为参数给出的nlines 值为0,则首先执行测试并返回false;循环中的指令不被执行。请注意,由于nlines 无论如何都会递减,所以while 循环之后nlines 的值将是-1

    如果nlines == 1,测试将返回truenlines将递减;循环中的指令将执行一次。请注意,在执行这些指令时,nlines 的值是0。当再次进行测试时,我们又回到了nlines == 0 时的情况。

    printf 指令使用*lineptr++ 表达式。它是指针的后增量(++ 在变量的右侧)。这意味着首先对表达式求值,然后使用后执行增量。所以在第一次执行时printf 接收到字符串数组第一个元素的副本,它是指向字符串第一个字符的指针。 lineptr 仅在此之后递增。下次要执行 printf 时,lineptr 指向第二个元素,并且在打印第二个字符串时将移动到第三个元素。这是有道理的,因为我们显然想要打印第一个字符串。如果 Adam Rosenfield 是对的,第一个字符串会被跳过,最后我们会尝试打印超出最后一个字符串的字符串,这显然是一件坏事。

    所以,printf 指令是以下两条指令的简明形式

    printf("%s\n", *lineptr);
    ++lineptr; // or lineptr++, which is equivalent but not as good. lineptr += 1; is ok too.
    

    请注意,根据经验,当前置增量和后置增量的作用相同时,出于性能原因,前置增量更可取。编译器会小心为您切换它。嗯,大多数时候。只要有可能,对预操作员自己最好,所以总是使用它。一旦您在 C++ 中实现了后自增和预自增,原因就会变得更加明确。

    【讨论】:

    • 感谢您的澄清...我想知道我是不是疯了 :)
    【解决方案5】:

    我认为上面的答案都没有回答这个问题,也就是说,为什么lineptr,一个数组的名称而不是一个变量,可以像在 K&R 中一样递增,甚至只有 chmike 最赞成的答案说了一些关于运算符的关联性,但仍然没有抓住问题的重点。

    我认为这个问题的答案在于这个定义:

    对出现在表达式中的 T 数组类型对象的引用衰减(除了三个例外)成指向其第一个元素的指针;结果指针的类型是指向 T 的指针。

    也就是说,这里的lineptr已经不是数组lineptr的名字了,已经衰减成一个指针,应该被当做指针处理。

    参考:

    1. http://c-faq.com/aryptr/aryptrequiv.html
    2. Passing an Array by reference in C

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-09-10
      • 1970-01-01
      • 1970-01-01
      • 2021-11-26
      • 2018-08-15
      相关资源
      最近更新 更多