【问题标题】:Why we can't use dot for new created pointers to structs [duplicate]为什么我们不能将点用于新创建的指向结构的指针[重复]
【发布时间】:2019-11-08 21:30:25
【问题描述】:

我有点困惑,为什么这段代码有效:

struct product{
    double price;
    int quantity;
    char name[1];
}p2;
printf("%d",p2.quantity);

虽然这不起作用:

struct product *p3=&p2;
printf("%d",p3.quantity);

我的意思是p2p3 都是指向同一个对象的指针,所以为什么我们需要在第二种情况下写printf("%d",p3->quantity); 才能让它工作。

【问题讨论】:

  • p2 不是指针。
  • p2 不是指针,只有&p2 会产生一个。
  • 我认为您需要 C 底漆:. 点代表struct 成员。 -> 箭头用于指针目标。
  • Arrow Operator vs. Dot Operator 的可能重复项:“箭头”运算符是“语法糖”。 bar->member(*bar).member 相同。前者更具可读性。所以你可以使用printf("%d",(*p3).quantity);,如果你愿意的话;)
  • 没有。 “p2” 不是 一个指针。问:“p2 怎么不是指针那么它是什么?” A: p3 ;) 结构是结构,指向结构的指针是指针......并且永远不会相遇 ;)

标签: c pointers struct


【解决方案1】:

回答您对 C“指针”的困惑:

  • C != C#。

  • C 没有 .Net reference objects

  • 虽然您可以将任何 C 对象(char、int、struct 等)的地址转换为指针,但该对象 不存在t 必然本身是一个指针。

看这里:

Difference between variable and data object in C language?

在 C 中,对象是任何占用存储空间的东西。 C 2011 online draft.

左值是一个表达式,它指定一个对象,使得 可以读取或修改该对象的内容(基本上,任何 可以作为赋值目标的表达式是左值)。 虽然 C 标准没有定义术语变量,但您可以 基本上可以将其视为指定对象的任何标识符...

指针是任何表达式,其值为对象的位置。 指针变量是存储指针值的对象。

【讨论】:

    【解决方案2】:

    C 具有值类型,包括 struct 聚合。具有某种 struct 类型的表达式不是对结构的引用,而是对结构本身的引用。

    在您的程序中,p2 不是指针,而是直接保存整个结构的变量(存储位置)的名称。 p2 的值就是该结构本身。

    p3 持有指向结构的指针; p3的值是一种数据类型,表示值在内存中的位置。

    C 语言使用不同的运算符来通过指向 struct/union 的指针而不是直接引用成员。

    不一定是这样。编译器有足够的信息,p2.quantityp3.quantity 可以同样工作。然而,事实并非如此。几十年前,Dennis Ritchie 设计的东西,如果p 是指向结构的指针,那么访问成员需要先将p 解引用为*p 以获得p 指向的结构值,然后使用member对该值的选择:(*p).member。这需要括号,因为后缀 . 运算符的优先级高于一元 *。因为那很冗长,所以 Ritchie 或者其他人发明了一种速记符号:-> 运算符。运算符是语法糖:p->member 表示相同的东西(*p).member。该人要么没有意识到 p.member 可以正常工作(应用于 struct/union 指针的点可以选择成员),或者更有可能拒绝该设计(可能是由于“指针取消引用应该是在代码中清晰可见”)。

    通常当我们在 C 程序中看到 z = x.y; 时,我们并不担心内存安全,但 z = x->y 会引发一个危险信号:x 这里是一个有效的指针吗?因此,设计师有一个观点。 -> 运算符突出显示指针正在被取消引用,这在像 C 这样具有手动内存管理的语言中是危险的。

    该系列中的一些其他语言具有显式取消引用。在 Pascal 中,如果 ptr 是指向记录的指针,则获取成员的语法是 ptr^.memb。你不能只使用ptr.memb。但是,请注意在 Pascal 中,指针取消引用 ptr^ 是一个后缀运算符,因此这里没有优先级问题; (ptr^).memb 中不需要括号。

    【讨论】:

      【解决方案3】:

      对于什么是指针,什么不是,这里显然存在混淆。我将稍微重写代码以突出差异 -

      struct product{
          double price;
          int quantity;
          char name[1];
      };
      
      struct product p2;
      struct product *p3;
      
      p3 = &p2;
      

      此代码等同于您拥有的代码。现在注意p3 声明为*(类型为struct product *)。这意味着p3 是一个指针。

      p2 声明为struct product 类型(不是指针)。它是一个结构。

      你已经知道我们在使用来自结构体的成员时使用.,在从指针中获取成员时使用->

      来到&p2& 运算符返回指向和对象的指针。所以&p2struct product*(指针)类型,因此可以分配给p3,它也是一个指针。

      因此,您也可以执行(&p2)->quantity(注意->),因为(&p2) 是一个指针。

      最后,就像& 运算符一样,我们也有*,它从指针返回对象。

      所以你可以使用(*p3).quantity(注意.而不是->)。这里p3 是一个指针,但* 运算符返回一个结构,现在您可以使用.

      【讨论】:

      【解决方案4】:

      我有点困惑,为什么这段代码有效:

      struct product{
          double price;
          int quantity;
          char name[1];
      }p2;
      printf("%d",p2.quantity);
      

      虽然这不起作用:

      struct product *p3=&p2;
      printf("%d",p3.quantity);
      

      p2 被声明为struct product。那是一个结构对象本身,而不是一个指针。一个使用. 操作符来访问它的成员,就像第一个sn-p 中的p2.quantity 一样。

      另一方面,p3 被声明为 指向 类型struct product 的指针,也就是说struct product *。它被初始化为指向p2所标识的对象——这就是&p2,一个代表p2地址的指针值。 . 不适用于指针,因此您的第二个 sn-p 是错误的,但您可以使用带有指向结构或联合的指针的 -> 运算符来访问指向对象的成员。

      我的意思是 p2 和 p3 都是指向同一个对象的指针

      他们绝对不是。初始化p3 = &p2p3 设置为指向 p2,不等于p2。这就是p2的类型struct productp3的类型struct product *的本质区别。

      【讨论】:

      • 是的,我刚刚意识到这一点。所以我删除了评论。没关系。
      【解决方案5】:

      . 成员选择运算符要求其左操作数具有structunion 类型。表达式p2 的类型为struct product,因此它是. 运算符的有效操作数。表达式p3 的类型为struct product *指针 指向struct product),因此它不是. 运算符的有效操作数。

      -> 成员选择运算符要求其左操作数具有指向structunion 类型的指针。表达式p3 的类型为struct product *,因此它是-> 运算符的有效操作数。 -> 执行指针的隐式遵从 - a->b 等同于编写 (*a).b

      表达式p2 不是指针 - 它没有指针类型,因此它不是-> 运算符的有效操作数。 p2 没有指向任何东西,它的东西。

      这是我写的一些代码来说明p2p3 之间的区别 - dumper 是我编写的一个小实用程序,用于将对象的内容转储到内存中:

      #include <stdio.h>
      #include "dumper.h"
      #include <stdint.h>
      
      int main( void )
      {
        struct product
        {
          double price;
          int quantity;
          char name[1];
        };
      
        struct product p2 = {1.00, 1, .name[0] = 'A'}; 
        struct product *p3 = &p2;
      
        char *names[] = {"p2", "p3"};
        void *addrs[] = {&p2, &p3};
        size_t sizes[] = { sizeof p2, sizeof p3};
      
        dumper( names, addrs, sizes, 2, stdout );
      
        return 0;
      }
      

      这是输出:

             Item         Address   00   01   02   03
             ----         -------   --   --   --   --
               p2  0x7ffeec515a68   00   00   00   00    ....
                   0x7ffeec515a6c   00   00   f0   3f    ...?
                   0x7ffeec515a70   01   00   00   00    ....
                   0x7ffeec515a74   41   00   00   00    A...
      
               p3  0x7ffeec515a60   68   5a   51   ec    hZQ.
                   0x7ffeec515a64   fe   7f   00   00    ....
      

      struct 对象p2 位于地址0x7ffeec515a68 并包含一个double 对象(0x3ff0000000000000,它是浮点值1.0 的二进制表示,从地址0x7ffeec515a68 开始) ,后跟一个int 对象(0x00000001,从地址0x7ffeec515a70 开始)后跟一个char 对象(0x41,这是'A' 的字符代码,从地址0x7ffeec515a74 开始) .

      指针对象p3 位于地址0x7ffeec515a60 并包含p2 (0x7ffeec515a68) 的地址。

      【讨论】:

        【解决方案6】:

        在 1974 年 C 参考手册描述的语言中,-&gt; 运算符会将左操作数的 作为字节地址,加上右操作数命名的字段的偏移量, 并将结果地址中的任何内容视为字段类型的对象”,而“.”运算符也会这样做,但使用左操作数的 address。尽管 C 编译器几十年来一直需要. 的左操作数是包含命名字段的结构或联合,-&gt; 的左操作数是指向包含命名字段的结构或联合的指针,1974 C 参考手册没有强加这样的要求,而是以不依赖于左操作数类型的方式定义这些运算符的行为. 根据该手册, 如果存在一些结构类型 S 具有名为 foo 的字段 [注意多个结构类型只能有字段如果它们是 Common Init 的一部分,则具有匹配的名称ial Sequence] 那么1234-&gt;foo 将以等同于((S volatile *)1234)-&gt;foo 的方式处理。

        请注意,在 1974 年的语言中,foo.barfoo-&gt;bar 可能具有不同的有效含义。在人为的情况之外,两者都很少有用,但是如果两个操作都使用相同的运算符,编译器并不总是可以选择有用的。

        【讨论】:

          猜你喜欢
          • 2021-03-17
          • 1970-01-01
          • 2014-05-30
          • 2014-02-16
          • 1970-01-01
          • 2018-11-03
          • 1970-01-01
          • 2018-01-22
          • 2021-12-04
          相关资源
          最近更新 更多