【问题标题】:What's the difference between &C::c and &(C::c)?&C::c 和 &(C::c) 有什么区别?
【发布时间】:2018-10-23 02:19:19
【问题描述】:

下面的测试代码,我将输出信息放在注释中。 我使用的是 gcc 4.8.5 和 Centos 7.2。

#include <iostream>
#include <cstdio>

class C 
{
    public:
        void foo() {
            printf("%p, %p\n", &C::c, &(C::c)); // output value is 0x4, 0x7ffc2e7f52e8
            std::cout << &C::c << std::endl;    // output value is 1
        }
        int a;
        int c;
};

int main(void)
{
    C co;

    printf("%p\n", &C::c);              // output value is 0x4
    std::cout << &C::c << std::endl;    // output value is 1

//    printf("%p\n", &(C::c));   // compile error, invalid use of non-static data member 'C::c'

    co.foo();

    return 0;
}
  1. 根据C++ operator Precedence:: 运算符的优先级高于&amp; 运算符。我认为&amp;C::c 等于&amp;(C::c),但输出却不然。为什么它们不同?
  2. &amp;(C::c) 在 main 中导致编译错误,而在 foo 函数中却没有,这是为什么呢?
  3. printfstd::cout&amp;C::c 的值不同,这是为什么呢?

【问题讨论】:

  • 您不能printf 指向成员值的指针。 %p 仅用于常规指针。
  • @n.m.更糟糕的是,%p 仅适用于 void *

标签: c++


【解决方案1】:

C++ 区分&amp; 运算符的两种形式的操作数,一般是左值,特别是(限定)标识符。在&amp;C::c 中,&amp; 的操作数是限定标识符(即只是一个名称),而在&amp;(C::c) 中,操作数是一个通用表达式(因为( 不能是名称的一部分)。

限定标识符形式有一个特殊情况:如果它引用一个类的非静态成员(如您的C::c),&amp; 返回一个称为“指向 C 成员的指针”的特殊值。见here for more information about member pointers

&amp;(C::c) 中没有特殊情况。 C::c 正常解析并失败,因为没有对象可以获取 c 成员。至少 main 是这样的;在C 的方法中(比如你的foo)有一个隐含的this 对象,所以C::c 实际上意味着this-&gt;c 那里。

至于为什么printfcout 的输出不同:当您尝试使用&lt;&lt; 打印成员指针时,它会隐式转换为bool,如果它是a,则产生false空指针和true 否则。 false 打印为0true 打印为 1。你的成员指针不为空,所以你得到1。这与普通指针不同,后者隐式转换为void * 并打印为地址,但成员指针不能转换为void *,因此operator&lt;&lt; 的唯一适用重载是bool 的重载。见https://en.cppreference.com/w/cpp/io/basic_ostream/operator_ltlt#Notes

请注意,从技术上讲,您的 printf 调用具有未定义的行为。 %p 接受 void * 并且您正在向它传递不同类型的指针。在正常的函数调用中,从T *void * 的自动转换会起作用,但printf 是一个变量参数函数,它不为其参数列表提供类型上下文,因此您需要手动转换:

printf("%p\n", static_cast<void *>(&(C::c)));

标准的相关部分是[expr.unary.op],说:

一元&amp; 运算符的结果是指向其操作数的指针。 操作数应为左值或qualified-id。如果操作数是一个 qualified-id 命名某个类 C 的非静态或变体成员 m,类型为 T,则结果类型为“指向类成员的指针 @987654366 @ 类型为 T”,是指定 C​::​m 的纯右值。 否则,如果表达式的类型是T,则结果的类型为“指向T”的指针[...]

【讨论】:

  • @M.M 如果您在foo 中这样做,则不会。请参阅我的其余答案。
  • “尝试打印带有&lt;&lt;的指针,它被隐式转换为布尔值”。我尝试int b = 1; std::cout &lt;&lt; &amp;b &lt;&lt; std::endl;,它输出的地址不只是1。
  • @M.M main 中的那个被注释掉了……或者至少是在你编辑 OP 的代码之前。现在它不再符合问题。叹息。
  • @M.M 这是第二个问题。第一个问题是为什么&amp;C::c&amp;(C::c) 的输出不同。 (第三个问题是为什么printfcout 的输出不同。)
【解决方案2】:

虽然表达式 &amp;C::c 产生指向成员 c 的指针,但表达式 &amp;(C::c) 产生成员变量 c 的地址。您在输出中看到的不同之处在于 std::cout 涉及一个 bool 隐式转换,它告诉您指针是否为空。

由于&amp;C::c 实际上不是null,因此隐式转换为bool,其值为true1

【讨论】:

    【解决方案3】:

    Q1:&amp; 后面跟一个不带括号的qualified-id 的语法有特殊含义。这意味着形成一个指向成员的指针。此外,没有其他方法可以形成指向成员的指针。 C++17 [expr.unary.op]/4 涵盖了这一点:

    只有在使用显式&amp; 并且它的操作数是一个没有括在括号中的qualified-id 时,才会形成指向成员的指针。 [注意:也就是说,表达式&amp;(qualified-id),其中qualified-id 括在括号中,不会形成“指向成员的指针”类型的表达式。 qualified-id [...]

    也没有

    Q3:在您编写printf("%p\n", &amp;C::c); 的两种情况下,&amp;C::c 都是指向成员的指针。 %p 格式说明符仅适用于 void *,因此这会导致未定义的行为,并且程序输出毫无意义。

    代码cout &lt;&lt; &amp;C::c; 通过operator&lt;&lt;(bool val) 输出指向成员的指针,因为存在从指向成员的指针到bool 的隐式转换(在所有情况下都产生true),请参阅[conv.布尔]/1.

    如需进一步讨论如何打印指向成员的指针,请see this answer


    Q2: 代码&amp;(C::c) 没有形成如上所述的指向成员的指针。

    现在,代码C::c 属于语法类别id-expression。 (即 qualified-idunqualified-id)。 id-expression 对其使用有一些限制,[expr.prim.id]/1:

    只能使用表示类的非静态数据成员或非静态成员函数的 id-expression:

    • 作为类成员访问的一部分,其中对象表达式引用成员的类或从该类派生的类,或
    • 形成指向成员的指针 (7.6.2.1),或
    • 如果该 id 表达式表示非静态数据成员并且它出现在未计算的操作数中。

    当我们在 C::foo 函数中时,第一个要点适用。代码与&amp;c 相同,但有不必要的限定。这有类型int *。您可以使用std::cout &lt;&lt; &amp;(C::c); 输出它,这将显示内存地址,即this-&gt;c 的地址。

    当我们在 main 函数中时,三个要点都不适用,因此 &amp;(C::c) 格式不正确。

    【讨论】:

    • Re 这种情况似乎是“另有说明的”: n4659 草案在关于一元运算符的部分中明确说明,8.3.1/4:仅当使用显式 & 并且其操作数是限定 ID 不括在括号中时,才会形成指向成员的指针。 [注意:即表达式 &(qualified-id),其中qualified-id 括在括号中,不形成“指向成员的指针”类型的表达式。 ...]
    • @PeterA.Schneider 谢谢,我忽略了那段。现已更新
    【解决方案4】:

    首先,您不能在课堂外使用&amp;(C::c) 访问int c&amp;(C::c) 表示实例C 的“c”的内存地址,这里有点&amp;(this-&gt;c)。但是,您的 c 不是 C 类的静态成员,也没有 C 实例。你也不能在外面访问int x = C::c

    所以你会看到一个错误:

    //    printf("%p\n", &(C::c));   // compile error, invalid use of non-static data member 'C::c'
    

    如果你有static int c,那么类外的C::c就可以了,因为这里不需要实例。

    让我们运行

    #include <iostream>
    #include <cstdio>
    
    class C
    {
        public:
            void foo() {
                printf("%p, %p, this=%p\n", &C::c, &(C::c), this);
            }
            int a;
            int c;
    };
    
    int main(void)
    {
        C co;
        co.foo();
        return 0;
    }
    

    输出是:

    0x4, 0x7ffee78e47f4, this=0x7ffee78e47f0
    // 0x4 + this == 0x7ffee78e47f4
    // see reference
    

    对于std::out&lt;&lt; &amp;C::c 被隐式转换为bool,所以true1 你看到的。您可以将&amp;C::c 视为an offset of c in C,没有C 实例将无法使用。

    就是这样。

    一些参考:C++: Pointer to class data member "::*"

    完整描述:https://en.cppreference.com/w/cpp/language/pointer

    【讨论】:

    • C::c 中,C 只能是类或命名空间名称(不能是实例)
    猜你喜欢
    • 2016-06-22
    • 2011-04-05
    • 1970-01-01
    • 2010-09-09
    • 2013-06-16
    • 2021-08-31
    • 2014-03-21
    • 2019-09-13
    • 1970-01-01
    相关资源
    最近更新 更多