关于指向成员的指针的一些问题

时间:2014-06-21 13:55:07

标签: c++ class pointers

我想问一下以下场景中编译器的行为: -

class A 
{
    public:
          int n;
          int *ptrClass;
};

int main()
{
    A a;
    int A::*ptr;

    ptr = &A::n;
    a.ptrClass = &a.n;

    cout << "\nPointer points to the memory address: " << ptr;
    cout << "\nPointer points to the value: " << a.*ptr;

    cout << "\nPointer points to the memory address: " << a.ptrClass;
    cout << "\nPointer points to the value: " << *a.ptrClass;

    getchar();
    return 0;
}

问题1: -

  

为什么ptr可以在没有任何引用的情况下在main中访问   A级,          就像a.ptr一样,我已经将ptr声明为int A :: * ptr;          是不是让ptr成为班级成员?

问题2: -

  

当我运行上面的程序时,我注意到,n的内存地址   班级          当由ptr和a.ptrClass提取时,A是不同的。没得到那个。任何          请解释。

问题可能非常基础,但请提供帮助。

1 个答案:

答案 0 :(得分:4)

  

为什么在没有引用类A的情况下可以在main中访问ptr,类似于a.ptr,我已经将ptr声明为int A :: * ptr;它不能使ptr成为一个类成员吗?

int A::* ptr;

不会将ptr声明为班级A的成员。它宁愿将ptr声明为指向类A的类int的数据成员的指针。例如:

class A {
public:
    int i0;
    int i1;
};

int main() {
    int A::* mptr;
    mptr = &A::i0;
    mptr = &A::i1;
}

指向成员的指针并不指向特定的内存地址,它们非常类似于偏移量。例如:

int main() {
    A a[2] = {{1, 2}, {3, 4}};

    int A::* mptr = &A::i0;
    // print the first data member (i0) of a[0] and a[1]
    for(int i = 0; i < 2; ++i) {
        std::cout << a[i].*mptr << "\n";
    }

    mptr = &A::i1;
    // print the second data member (i1) of a[0] and a[1]
    for(int i = 0; i < 2; ++i) {
        std::cout << a[i].*mptr << "\n";
    }
}

A的对象的可能内存布局:

class A {
public:
    int i0;
    int i1;
};

A a = {42, 21};
+-a--------------+
| +-i0-+  +-i1-+ |
| | 42 |  | 21 | |
| +----+  +----+ |
+----------------+

现在,我们添加指针:

int* pi1 = &a.i1;
int A::* mpi1 = &A::i1;
A* pa = &a;
+-a--------------+
| +-i0-+  +-i1-+ |
| | 42 |  | 21 | |
| +----+  +----+ |
+---------^------+
^         | pi1
| pa

pi1pa都指向特定对象。另一方面,mpi1 (= any)A类型对象的偏移量:

+-A--------------+
| +-i0-+  +-i1-+ |
| |    |  |    | |
| +----+  +----+ |
~~~~~~~~~~^ mpi1 |
+----------------+

我们现在可以将mpi1合并到pa以获取特定对象a.i1

+-a--------------+
| +-i0-+  +-i1-+ |
| | 42 |  | 21 | |
| +----+  +----+ |
+---------^------+
^~~~~~~~~~+ mpi1
| pa

这写成:

pa->*mpi1 // this expression refers to `a.i1` aka `*pi1`

所以我们可以确定:

&(pa->*mpi1) == pi1

如果我们将地址和偏移量解释为整数值,我们可能会得到类似的结果:

pa   == 0x7fff39519328
pi1  == 0x7fff3951932c
mpi1 == 4

因此,我们可以看到8 + 4 == 12 == 0xc,即&#34; pa + mpi1 == pi1&#34;。不要太过于字面意思,C ++中不允许执行此添加:pa + mpi1不是合法的C ++。此外,我们无法保证所有平台都可以添加这两个值。但它解释了x86上的流行实现。


  

当我运行上面的程序时,我注意到,当由ptr和a.ptrClass提取时,类A的n的内存地址是不同的。没有那个。请解释。

cout << "\nPointer points to the memory address: " << ptr;

这一行实际上是

cout << "\nPointer points to the memory address: " << (bool)ptr;

相同
cout << "\nPointer points to the memory address: " << (ptr != nullptr);

要看到这一点,请尝试

cout << boolalpha;

将布尔值打印为字符串 true / false

另一方面,这一行:

cout << "\nPointer points to the memory address: " << a.ptrClass;

转换为void*。这是C ++中的 原始内存指针类型。指向特定对象的每个指针都可以转换为void*,指向相同的内存位置。 cout << some_void_star操作打印此void*指向的原始内存地址。对于许多硬件体系结构,地址是或可以表示为整数。通常为操作cout << some_void_star打印此整数表示。

指向成员的指针不是指向特定对象的指针,因此不允许将其转换为void*(它们不指向特定地址)。将它们的值打印为整数是非常棘手的。你可以尝试:

#include <algorithm>
//...
// this code will probably produce wrong results on big endian architectures
static_assert(sizeof(unsigned long long) >= sizeof(mpi1));
unsigned long long as_integer;
std::copy_n(reinterpret_cast<char const*>(&mpi1), sizeof(mpi1),
            reinterpret_cast<char*>(&as_integer));
std::cout << as_integer;