试图理解“指向成员的指针”

时间:2013-10-23 12:16:18

标签: c++

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

以下是一个示例类:

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");
}

8 个答案:

答案 0 :(得分:6)

  

为什么“&” “T :: b”需要吗?

因为标准要求它。这是为了区别于访问静态类成员。

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

  

只有在使用显式&并且其操作数为 quali fi ed-id 时才会形成指向成员的指针   在括号中。 [注意:即表达式&(qualified-id),其中包含 quali fi ed-id   括号,不形成“指向成员的指针”类型的表达式。qualified-id也不是,因为   对于非静态成员函数,没有从 quali fi ed-id 到类型“指针”的隐式转换   成员函数“因为从函数类型的左值到类型”指向函数的指针“(4.3)。也不是   &unqualified-id指向成员的指针,即使在 unquali fi ed-id 的类的范围内。 - 结束记录]


  

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

不是真的。在需要时,数组会自动转换为指向第一个元素的指针。数组的名称是数组,句点。


  

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

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


  

对于“标准”指针,函数名称是函数地址的表示,因此我们可以在分配指针时编写& 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.

再次,数组到指针的转换。请注意arrArr的元素是int[10] s。 pAA指向arrArr的第一个元素,该元素是位于&arrArr[0]的10个整数的数组。如果你增加pAA,它将等于&arrArr[1](因此命名为pA会更合适。)

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

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

递增pAA现在将在arrArr结束时超过100英镑。

答案 1 :(得分:3)

我认为最简单的事情就是暂时忘记班级成员,并回顾一下指针和腐烂。

int local;
int array[10];

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

人们倾向于说“衰减指针”与指向数组的指针相同。但arr&arr之间存在重要差异。前者不会腐烂到后者

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

如果执行&arr,则会得到一个指向10-int数组的指针。这与指向9-int数组的指针不同。它与指向int的指针不同。 sizeof(*p_array_standard) == 10 * sizeof(int)

如果你想要一个指向第一个元素的指针,即int指针,sizeof(*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]的指针 - &gt; int*。它不会衰减到指向数组的指针,即int (*p)[10]


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

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

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

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

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

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

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

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

我认为这是你感兴趣的问题。我们已经删除了腐朽,以便专注于真正的问题。这行与非成员对象一起使用,为什么它不能与成员对象一起使用?

简单的答案是该标准不要求它。指针衰变是一段语法糖,他们根本没有说明它必须在这样的情况下起作用。

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

问:现在我有自己的问题。指针衰减可以扩展到像这样的成员数组吗?我认为这是有道理的。我不是唯一一个想到这个的人!有关详情,请参阅this discussion。这是可能的,我想没有什么能阻止编译器将其作为扩展实现。子对象(包括数组成员)与类的开头有一个固定的偏移量,所以这很合乎逻辑。

答案 2 :(得分:2)

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

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

这里有两个问题,或者一个,或者两个以上......首先是子表达式T::bb成员变量不是静态的,无法使用该语法进行访问。对于指向成员的指针,您需要始终使用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;

答案 3 :(得分:1)

  

为什么“&amp;” “T :: b”需要吗?

因为这是指定语言的方式。为了保存单个字符,决定不使用成员到指针转换使语言复杂化,尽管由于历史原因,我们对数组和函数进行了类似的转换。

  

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

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

  

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

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

  

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

同样,函数名不是指针;它只能转换成一个。

  

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

一个是指向数组的指针,另一个是指向成员数组的指针。它们非常相似,所以看起来非常相似,除了表示一个是成员指针而另一个是普通指针的差异。

答案 4 :(得分: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];

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

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"

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

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],尽管它们指向的值是不同的数组。

答案 5 :(得分:0)

因为它上面的T已经具有明确定义的含义: type Class T。因此,T::b等内容在逻辑上用于表示Class T成员。要获取这些成员的地址,我们需要更多语法,即&T::b。这些因素不会与自由函数和数组一起发挥作用。

答案 6 :(得分:0)

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

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

原始坚果壳中需要&来获取成员和对象地址的逻辑。

答案 7 :(得分:-1)

void (*pF)() = fun;        //here also everything is clear

它不起作用,因为功能乐趣未定义

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的变量,因为没有创建任何对象,也没有调用构造函数。