11

我有一个基类 A 和派生类 B

B 类从 A 派生为 public

如果A 是类 a 是成员变量,我想访问成员变量的地址

当我使用受保护和公共访问说明符时,我观察到不同的行为。

在这种情况下,当 A 类的成员 a受到保护时,我得到:

cout<<&A::a << endl;给我一个编译器错误..

cout<<&(A::a)<<endl;有效并给我正确的结果。

在这种情况下,当 A 类的成员 a 是公开的时,我得到:

为什么会有这种行为?

这是完整的代码:

#include <iostream>
using namespace std;

class A
{
    protected:
    int a;
    void fun() 
    {
    cout<<"A's fun"<<endl;
    }

public:
    A(int Aarg):a(Aarg)
    {
        cout << "A's construction done" <<endl;
    }
};


class B: public A
{
protected:
int b;
public:
void fun()
{
cout << "B's fun"<<endl;
}

B(int As, int Bs):A(As), b(Bs)
{
std::cout<<"B's Construction Done" <<std::endl;
}

void show()
{
A::fun();
//cout << a << endl;
cout<<&A::a << endl; //compilation error
}
};

int main()
{
    B(10,20).show();
    return 0;
}

现在我可以观察到一个未定义的行为:

如果我将 A 类中的成员变量 a 设为 public,则不会出现任何编译错误,但输出为 1,我不知道为什么..

class A{
public:
int a
....
....

输出:

A的建设完成

B的建设完成

A很有趣

0027F91C

1(为什么是 1)并且当我尝试访问受保护的成员时没有出现错误?

4

3 回答 3

4

您在语法中遇到了一个小怪癖(并不是说 C++ 很少......)。访问成员变量的默认方式是直接使用名称或通过this->. 也就是说,您的 show 函数的更简单拼写是:

void B::show() {
   std::cout << a << std::endl;     // alternatively this->a
   std::cout << &a << std::endl;    //               &(this->a)
}

它具有简单且一致的语法。现在,该语言允许您在访问成员时添加额外的限定符来访问基础成员:

std::cout << A::a << std::endl;     // Extra qualification

这实际上等同于this->A::a并且额外限定的主要用途是消除歧义(如果两个基础有一个 member a,则选择一个 in A)以及在虚拟函数禁用动态调度的情况下。

现在您可以对指针执行相同的操作,如 in &this->A::a,它将获取当前对象a的子对象中成员的地址。A这种情况下的问题是您不能删除this->限定符,因为&A::a保留了语法以获取指向成员的指针,尽管通过添加一组额外的括号,因为在&(A::a)解析器中不能再将其解释为获取指向成员的指针,但是而不是获取由 表示的对象的地址,A::a如前所述,它是基数中的成员。

于 2013-01-18T15:33:05.810 回答
3

简短的回答:不涉及未定义的行为。您看到的行为是:

  • 该表达式&A::a试图获取指向 A 类成员 a 的成员的指针。如果 a 在 A 中受保护,则此表达式仅通过 A 类(或 A 的朋友)内的访问检查。在从 A 派生的类 B 中,您只能通过表达式获得指向成员的相同指针&B::a(请注意,此表达式的类型仍然是int A::*)。所以:
    • 如果在 A 中受保护,则派生类 B 的成员函数中不允许A::a该表达式。这是您的编译器错误。&A::a
    • 如果A::a在 A 中是公共的,则此表达式有效,产生指向成员的指针。
  • 将指向成员的指针流式传输到ostream,例如使用cout << &A::awill print 1。这是调用ostream::operator << (bool). 您可以使用 boolalpha 操纵器来查看这确实是选择的重载:cout << boolalpha << &A::a将打印true
  • 如果使用修改后的表达式 &(A::a) 或简单的 &a,则不会形成指向成员的指针。这里取的是当前对象的成员a的地址(即同&(this->a)),是一个普通的int指针。这种对基类子对象的受保护成员的访问*this是有效的,因此即使 a 在 A 中受保护,也可以使用该变体。

更长的解释:

该标准说(5.3.1/3):

一元 & 运算符的结果是指向其操作数的指针。操作数应为左值或限定 ID。如果操作数是一个限定标识符,命名某个类 C 的类型为 T 的非静态成员 m,则结果类型为“指向类型为 T 的类 C 的成员的指针”,并且是一个指定 C::m 的纯右值。[...]

因此,表达式&A::a试图获得指向 A 类成员 a 的成员指针。

在下一段(5.3.1/4)中,详细说明了只有 &X::m 语法产生指向成员的指针 - 既不&(X::m),也不&m或普通X::m

仅当使用显式 & 并且其操作数是未包含在括号中的限定 ID 时,才会形成指向成员的指针。

但只有在允许访问的情况下,这样的表达式才有效。如果是受保护成员 (11.4/1),则适用:

当非静态数据成员或非静态成员函数是其命名类 (11.2) 的受保护成员时,将应用第 11 节中所述之外的附加访问检查 如前所述,授予对受保护成员的访问权限是因为引用发生在某个类 C 的朋友或成员中。如果访问要形成指向成员 (5.3.1) 的指针,则嵌套名称说明符应表示 C 或从 C 派生的类。 [...]

在您的情况下,将授予对受保护成员 a 的访问权限,因为对 a 的引用发生在从 A 派生的类 B 的成员中。当表达式试图形成指向成员的指针时,嵌套名称说明符(部分在最后的 "::a") 之前必须表示 B。因此最简单的允许形式是&B::a. 该表格&A::a只允许在 A 类本身的成员或朋友中使用。

指向成员的指针没有格式化输出运算符(既不是 istream 成员也不是自由运算符函数),因此编译器将查看可以使用标准转换(序列)调用的重载。从指向成员的指针到其他东西的唯一标准转换在 4.12/1 中描述:

[...] 指向成员类型的指针的纯右值可以转换为布尔类型的纯右值。[...] 空成员指针值被转换为 false;任何其他值都将转换为 true。[...]

无需额外转换即可使用此转换来调用basic_ostream<charT,traits>& basic_ostream<charT,traits>::operator<<(bool n). 其他重载需要更长的转换序列,因此重载是最佳匹配。

由于&A::a获取某个成员的地址,它不是空成员指针值。因此它将转换为true,打印为“1”(noboolalpha)或“true”(boolalpha)。

最后,表达式&(A::a)在 B 的成员中有效,即使 a 在 A 中受到保护。根据上述规则,该表达式不会形成指向成员的指针,因此上面引用的特殊访问规则不适用。对于这种情况,11.4/1 继续:

所有其他访问都涉及(可能是隐式的)对象表达式(5.2.5)。在这种情况下,对象表达式的类应为 C 或从 C 派生的类。

这里的客体印象是隐含的(*this),即A::a与 的意思相同(*this).A::a。的类型(*this)显然与发生访问的类(B)相同,因此允许访问。[注:int x = A(42).a不允许在 B 内使用。]

因此&(A::a),within 的B::show()含义与&(this->a)并且是指向 int 的普通指针相同。

于 2013-01-27T00:32:12.570 回答
-5

它是一个语法问题。& 表示你在右边询问元素的地址。

如果你写:

&A::a

就好像你会写

(&A)::a

这意味着您要求从“&A”访问“a”,这是不对的。

& 不仅仅适用于变量,它也可以用于函数,参见:http ://www.cprogramming.com/tutorial/function-pointers.html

于 2013-01-18T14:05:28.253 回答