【问题标题】:Cannot understand why that behavior无法理解为什么这种行为
【发布时间】:2012-08-31 11:51:04
【问题描述】:

我有这个代码:


     char *name[] = { "a1", "b2", "c3", "d4" };
     printf("%s\n", *name); //the critical line

critical line相关:

在这种形式中,输出很简单:a1。 如果我将critical line 替换为:

printf("%s\n", ++*name);

那么输出是1。我认为到目前为止一切都很好。

考虑到name 是指向第一个字符串的指针,分别是"a1",我将critical line 替换为:

printf("%s\n", ++name);

希望我能得到"b2" 结果作为输出。但我得到这个错误:

../src/test.c:32: error: lvalue required as increment operand.

问题:我不明白为什么++*name 是合法的——name 是指向第一个字符串的指针——而++name 不是。在我看来,++name 应该将name 移动到下一个字符串。谁能解释一下我的理解不足在哪里?

【问题讨论】:

    标签: c arrays pointers char


    【解决方案1】:

    当您编写++name 时,数组 name 被转换为指向数组第一个元素的指针。这个转换的结果不是左值,不能用++或其他方式修改。你可以改为写name+1,它会打印正确的东西。当name 是一个数组时,无法修改它以引用除该数组[*] 之外的任何内容。

    同时考虑:

    char **p = name;   // OK, `name' converts to pointer
    ++p;               // OK, `p' is an lvalue
    ++(p+1);           // not OK, `p+1' is not an lvalue
    ++((char**)p);     // not OK, `(char**)p' is not an lvalue
    
    ++*name;           // OK, `*name' is an lvalue
    

    粗略地说,“左值”是一个引用对象的表达式,而“非左值”是一个有值的表达式。对象和值之间的区别在于,对象是存储值的地方(嗯,一次一个值)。值永远不能被修改,对象有时可以。

    只要您有一个左值子表达式,但需要其当前值,该对象就会被读取/加载/无论您想调用什么。在 C++ 中,这被称为“左值到右值的转换”,我不记得它在 C 中是否被称为“评估子表达式”。

    [*] 您可以在内部范围内使用另一个变量 name 对其进行隐藏,该变量引用其他内容。但这仍然没有修改外部的name,只是暂时隐藏了它。

    【讨论】:

    • 查看我对@Kerrek 回复的评论。
    【解决方案2】:

    name 是一个数组,因此,除非您将其用作sizeof& 运算符的操作数,否则它将被评估为指向数组对象初始成员的指针,并且 不是一个左值

    因此,您不能直接使用++ 等运算符修改name(请记住,后缀自增运算符需要一个可修改的左值作为操作数)。否则,您可以使用临时指针(以下示例中为p)。

    #include <stdio.h>
    
    const char *name[] = { "a1", "b2", "c3", "d4" };
    const char **p = name;
    printf("%s\n", *p); /* Output: a1 */
    *++p; /* or *p++ */
    printf("%s\n", *p); /* Output: b2 */
    

    【讨论】:

    • 是的,我完全同意。我只是想强调i 需要是可修改的左值才能执行此操作。注意:我已将其删除。
    【解决方案3】:

    虽然 name 指向第一个元素的地址,但 name 不是type char *,而是char *[4]。因此,sizof(name) == sizeof(char *)*4

    增加一个指针总是意味着增加它所指向的数据的大小。因此,在递增之后,它指向整个数组的后面。就像你有

        int i, a;
        int *p = &i;
        p++;
    

    p 现在将指向后面 i。如果编译器决定在 i 后面加上 a,它将指向 a。

    另请注意,您的数组仅包含 4 个指针,而不是 4 个字符串。像上面一样,这些字符串的实际位置是编译器的选择。因此,第一个字符串的结尾不一定紧挨第二个字符串的开头。特别是如果您稍后将其他值(字符串地址)分配给 name[1]。因此,您可以将名称转换为char **,但不应转换为char *。增加前者将指向第二个元素(第二个 char* 指针)。

    【讨论】:

      【解决方案4】:

      首先,请确保您对以下事实完全满意和自信:数组不是指针。

      其次,name 中有什么内容?数组衰减为指向第一个元素的指针。衰减后,表达式name 的类型为char **(指向char* 数组的第一个元素的指针。但是,衰减后的表达式是一个右值。你不能修改它!自然如此,因为修改指向固定数组的指针是没有意义的。

      因此,你不能直接增加指针,这是衰减name的结果,只能说++5++foo() (其中foo按值返回原始类型)[哎呀,这是对 C++ 的让步]

      可以说的是:

      char ** np = name;
      ++np;
      printf("%s", *np);
      

      这与打印name[1] 的效果相同,但现在您还有一个变量,其中包含指向第二个数组元素的指针。

      【讨论】:

      • “衰减的表达式是一个右值”——或者在 C 标准术语中,它是“表达式的结果”。愚蠢的 C 标准术语。同样,在 C 中,函数调用 foo() 不能返回左值。
      • 更奇怪的是,在我执行你的代码后,我得到了二进制字符串,就像这样:$�'�*�@�� 而不是 "b2"。有什么解释?
      • @artaxerxe:我无法重现:ideone.com/S63K7。您运行的代码可能还有其他问题。
      • @SteveJessop:哦,哎呀,这是 C 而不是 C++ :-S 感谢您的更正!
      • @Kerrek:我的经验法则是,如果提到char* 的问题超过10 秒,并且没有cmets 说“改用string”,那么我应该仔细检查标签 ;-)
      猜你喜欢
      • 2020-08-17
      • 2022-12-12
      • 2016-07-29
      • 2013-08-07
      • 2014-07-11
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多