15

我试图了解“指向成员的指针”是如何工作的,但对我来说并非一切都清楚。

这是一个示例类:

class T
{
public:
    int a;
    int b[10]; 
    void fun(){}
};

以下代码说明了问题并包含问题:

void fun(){};

void main()
{
   T obj;                
   int local;
   int arr[10];
   int arrArr[10][10];

   int *p = &local;   // "standard" pointer
   int T::*p = &T::a; // "pointer to member" + "T::" , that is clear

   void (*pF)() = fun;        //here also everything is clear
   void (T::*pF)() = T::fun;  
   //or
   void (T::*pF)() = &T::fun;  

   int *pA = arr; // ok
   int T::*pA = T::b; // error

   int (T::*pA)[10] = T::b; // error
   int (T::*pA)[10] = &T::b; //works;

//1. Why "&" is needed for "T::b" ? For "standard" pointer an array name is the representation of the 
//   address of the first element of the array. 

//2. Why "&" is not needed for the pointer to member function ? For "standard" pointer a function name 
//   is the representation of the function address, so we can write &funName or just funName when assigning to the pointer. 
//   That's rule works there.

//3. Why the above pointer declaration looks like the following pointer declaration ?: 

   int (*pAA)[10] = arrArr; // Here a pointer is set to the array of arrays not to the array. 
   system("pause");
}
4

8 回答 8

6

为什么 "T::b" 需要 "&" ?

因为标准要求它。这是为了将其与访问静态类成员区分开来。

从标准草案n3337第 5.3.1/4 段,强调我的:

&只有在使用显式且其操作数是 未括在括号中的限定 ID时,才会形成指向成员的指针。[注意:也就是说,表达式&(qualified-id),其中qualiified-id括在括号中,不会形成“指向成员的指针”类型的表达式。也没有qualified-id,因为没有从非静态成员函数的限定 ID到类型“指向成员函数”的隐式转换,因为从函数类型的左值到类型“指向函数”(4.3) . 即使在unqualified-id类的范围内,也不是 &unqualified-id指向成员的指针。——尾注]


对于“标准”指针,数组名表示数组第一个元素的地址。

并不真地。如果需要,数组会自动转换为指向第一个元素的指针。数组的名称是一个数组,句号。


为什么指向成员函数的指针不需要“&”?

需要的。如果你的编译器允许它,它就有一个错误。见上面的标准。


对于“标准”指针,函数名称是函数地址的表示,因此我们可以在分配给指针时写 &funName 或只是 funName。

同样的事情也适用于数组。有一个自动转换,但是一个函数有一个函数类型。

考虑:

#include <iostream>

template<typename T, size_t N>
void foo(T (&)[N]) { std::cout << "array\n"; }

template<typename T>
void foo(T*) { std::cout << "pointer\n"; }

int main()
{
    int a[5];
    foo(a);
}

输出是array

同样对于函数指针:

#include <iostream>

template<typename T>
struct X;

template<typename T, typename U>
struct X<T(U)> {
    void foo() { std::cout << "function\n"; }
};

template<typename T, typename U>
struct X<T(*)(U)> {
    void foo() { std::cout << "function pointer\n"; }
};

void bar(int) {}

int main()
{
    X<decltype(bar)> x;
    x.foo();
}

输出是function


并对此进行澄清,因为我不确定您的评论到底是什么意思:

int arrArr[10][10];
int (*pAA)[10] = arrArr; // Here a pointer is set to the array of arrays not to the array.

同样,数组到指针的转换。请注意, 的元素arrArrint[10]s。pAA指向第一个元素,arrArr它是一个由 10 个整数组成的数组,位于&arrArr[0]. 如果你增加pAA它会等于&arrArr[1](所以命名它pA会更合适)。

如果你想要一个arrArr整体的指针,你需要说:

int (*pAA)[10][10] = &arrArr;

增量pAA现在将带您超过arrArr100 个整数的末尾。

于 2013-10-23T12:28:37.363 回答
4

我认为最简单的事情就是暂时忘记类成员,并回顾一下指针和衰减。

int local;
int array[10];

int *p = &local; // "standard" pointer to int

人们倾向于说“衰减指针”与指向数组的指针相同。arr但是和之间有一个重要的区别&arr。前者不衰成后者

int (*p_array_standard)[10] = &arr;

如果你这样做&arr,你会得到一个指向 10 个整数数组的指针。这与指向 9 个整数数组的指针不同。它与指向 int 的指针不同。 sizeof(*p_array_standard) == 10 * sizeof(int).

如果你想要一个指向第一个元素的指针,即指向 an 的指针intsizeof(*p) == sizeof(int))那么你可以这样做:

int *p_standard = &(arr[0);

到目前为止,一切都基于标准/显式指针。

C 中有一个特殊规则允许您替换&(arr[0])arr. int*您可以使用&(arr[0])或初始化arr。但是如果你真的想要一个指向数组的指针,你必须这样做int (*p_array_standard)[10] = &arr;

我认为衰减几乎可以被视为句法糖。衰减不会改变任何现有代码的含义。它只是让原本非法的代码变得合法。

int *p = arr; // assigning a pointer with an array.  Why should that work?
                  // It works, but only because of a special dispensation.

当数组衰减时,它衰减为指向单个元素的指针int [10]-> int*。它不会衰减为指向数组的指针,即int (*p)[10].


现在,我们可以从您的问题中查看这一行:

int (T::*pA3)[10] = T::b; // error

同样,类成员与理解失败的原因无关。左边的类型是指向整数数组的指针,而不是指向整数的指针。因此,正如我们之前所说,衰减无关紧要,您需要&获取指向整数数组的指针类型。

一个更好的问题是问为什么这不起作用(更新:我现在看到你的问题确实有这个。

int T::*pA3 = T::b;

右手边看起来像一个数组,而左手边是指向单个元素的指针int *,因此您可以合理地问:为什么衰变在这里不起作用?

要理解为什么衰减在这里很困难,让我们“撤消”语法糖,并T::b&(T::b[0]).

int T::*pA3 = &(T::b[0]);

我认为这是您感兴趣的问题。我们已经删除了衰减,以便专注于真正的问题。此行适用于非成员对象,为什么它不适用于成员对象?

简单的答案是标准不需要它。指针衰减是一种语法糖,他们只是没有指定它必须在这种情况下工作。

指向成员的指针基本上比其他指针要麻烦一些。它们必须直接指向对象中出现的“原始”实体。(对不起,我的意思是它应该(间接地)通过编码类的开始和这个成员的位置之间的偏移量来引用。但我不太擅长解释这一点。)他们不能指向子对象,例如数组的第一个元素,或者实际上是数组的第二个元素。

问:现在我有一个自己的问题。指针衰减可以扩展到像这样的成员数组上吗?我觉得有点道理。 我不是唯一一个想到这个的人!有关更多信息,请参阅此讨论。这是可能的,我想没有什么能阻止编译器将它作为扩展来实现。子对象,包括数组成员,与类的开头有一个固定的偏移量,所以这是非常合乎逻辑的。

于 2013-10-23T14:12:29.823 回答
2

首先要注意的是数组衰减为指向第一个元素的指针。

int T::*pA = T::b;

这里有两个问题,或者一个,或者两个以上……第一个是 subexpression T::b。成员变量不是静态的b,不能使用该语法访问。对于指向成员的指针,您需要始终使用 address-of 运算符:

int T::*pa = &T::b;  // still wrong

现在的问题是右侧的类型int (T::*)[10]与左侧不匹配,并且将无法编译。如果你修复左边的类型,你会得到:

int (T::*pa)[10] = &T::b;

哪个是对的。数组倾向于衰减到第一个元素这一事实可能会引起混淆,因此问题可能出在前面的表达式上:int *p = a;编译器将其转换为更显式的int *p = &a[0];. 数组和函数有衰减的趋势,但语言中没有其他元素会衰减。并且T::b不是数组


编辑:我跳过了关于功能的部分......

   void (*pF)() = fun;        //here also everything is clear
   void (T::*pF)() = T::fun; 
   //or
   void (T::*pF)() = &T::fun;

它可能不像看起来那么清楚。该语句void (T::*pf)() = T::fun;在 C++ 中是非法的,您使用的编译器无缘无故地接受它。正确的代码是最后一个:void (T::*pf)() = &T::fun;.

于 2013-10-23T12:31:34.750 回答
1

为什么 "T::b" 需要 "&" ?

因为这就是指定语言的方式。尽管出于历史原因,我们对数组和函数有类似的转换,但决定不要仅仅为了保存单个字符而使语言与成员到指针的转换复杂化。

对于“标准”指针,数组名表示数组第一个元素的地址。

不,不是;由于从 C 继承的神秘转换规则,它可以转换为指向其第一个元素的指针。不幸的是,这引起了普遍(和错误)的信念,即数组是指针。这种混淆可能是没有为成员指针引入类似奇异转换的部分原因。

为什么指向成员函数的指针不需要“&”?

这是。但是,您的编译器接受不正确的void main(),因此它可能会接受其他损坏的代码。

对于“标准”指针,函数名称是函数地址的表示,因此我们可以在分配给指针时写 &funName 或只是 funName。

同样,函数名不是指针;它只是可以转换为一个。

为什么上面的指针声明看起来像下面的指针声明?

一个是指向数组的指针,另一个是指向成员数组的指针。它们非常相似,因此看起来也非常相似,除了表明一个是成员指针而另一个是普通指针的区别。

于 2013-10-23T12:36:48.567 回答
1
int (T::*pA)[10] = &T::b; //works;

3.Why the above pointer declaration looks like the following pointer declaration ?

int (*pAA)[10] = arrArr;

要理解这一点,我们不必将自己与成员数组混淆,简单的数组就足够了。说我们两个

int a[5];
int a_of_a[10][5];

当我们只使用数组的名称时,数组的第一个(最左边的)维度会衰减,我们会得到一个指向数组第一个元素的指针。例如

int *pa = a;                 // first element is an int for "a"
int (*pa_of_a)[5] = a_of_a;  // first element is an array of 5 ints for "a_of_a"

因此,如果不对数组使用&运算符,当我们将其名称分配给指针,或将其作为参数传递给函数时,它会按照说明衰减并给出指向其第一个元素的指针。但是,当我们使用&运算符时,不会发生衰减,因为我们要求的是数组的地址,而不是按原样使用数组名称。因此,我们得到的指针将指向数组的实际类型,没有任何衰减。例如

int (*paa) [5] = &a;                   // note the '&'
int (*paa_of_a) [10][5] = &a_of_a;

现在在您的问题中,上层声明是指向没有衰减的数组地址的指针(一维保持一维),而下层声明是指向具有衰减的数组名称的指针(二维变成一维)。因此,两个指针都指向相同一维的数组并且看起来相同。在我们的例子中

int (*pa_of_a)[5]
int (*paa) [5]

请注意,这些指针的类型是相同的,int (*) [5]尽管它们指向的值是不同的数组。

于 2013-10-23T13:00:11.137 回答
0

因为T它本身已经有一个明确定义的含义:类型 Class T。所以类似的东西在逻辑上T::b用来表示. Class T要获取这些成员的地址,我们需要更多语法,即&T::b. 这些因素不适用于自由函数和数组。

于 2013-10-23T12:24:19.537 回答
0

指向类或结构类型的指针指向内存中的对象。
指向类类型成员的指针实际上指向距对象开头的偏移量。
您可以将这类指针视为指向内存块的指针。这些需要一个实际的地址和偏移量,因此&.

指向函数的指针指向汇编代码中函数的访问点。成员方法通常与将this指针作为第一个参数传递的函数相同。

简而言之,这就是需要 a&来获取成员地址和对象地址的逻辑。

于 2013-10-23T12:33:03.863 回答
-1
void (*pF)() = fun;        //here also everything is clear

它不起作用,因为函数 fun 未定义

int T::*pA = T::b; // error

什么是 T::b?T::b 不是静态成员。所以你需要特定的对象。而是写

int *pA = &obj.b[0]; 

相似地,

int (T::*pA)[10] = &T::b; //works;

它可以编译。但它不会像你预期的那样工作。将 b 设为静态或调用 obj.b 以访问已定义对象的已定义成员。我们可以很容易地检查这一点。为您的 T 类创建构造函数

class T
{
public:
    T() {
        a = 444;
    }
    int a;
    int b[10]; 
    void fun(){}
};

pA 在什么值点上?

 int T::*pA = &T::a; 

*pA 没有指向值为 444 的变量,因为没有创建对象,也没有调用构造函数。

于 2013-10-23T12:32:56.923 回答