我试图理解"指向成员的指针"是如何工作的,但对我来说并非一切都清楚.
这是一个示例类:
class T
{
public:
int a;
int b[10];
void fun(){}
};
Run Code Online (Sandbox Code Playgroud)
以下代码说明问题并包含问题:
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");
}
Run Code Online (Sandbox Code Playgroud)
为什么"T :: b"需要"&"?
因为标准需要它.这是为了区别于访问静态类成员.
根据标准草案n3337第5.3.1/4段,强调我的:
仅当使用explicit
&并且其操作数是未括在括号中的 quali-ed-id时,才会形成指向成员的指针.[ 注:即,表达&(qualified-id),其中,所述合格音响ED-ID被用括号括起来,不形成类型的表达式"指针构件."无论确实qualified-id,因为从没有隐式转换合格音响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);
}
Run Code Online (Sandbox Code Playgroud)
输出是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();
}
Run Code Online (Sandbox Code Playgroud)
输出是function.
并澄清了这一点,因为我不确定你的评论究竟是什么意思:
int arrArr[10][10];
int (*pAA)[10] = arrArr; // Here a pointer is set to the array of arrays not to the array.
Run Code Online (Sandbox Code Playgroud)
同样,数组到指针的转换.请注意,元素arrArr是int[10]s.pAA指向其中第一个元素arrArr是位于的10个整数的数组&arrArr[0].如果你增加pAA它将等于&arrArr[1](所以命名它pA会更合适).
如果你想要一个arrArr整体的指针,你需要说:
int (*pAA)[10][10] = &arrArr;
Run Code Online (Sandbox Code Playgroud)
增量pAA现在将超过你arrArr的100英尺远的结束.
我认为最简单的事情就是暂时忘记类成员,并回顾一下指针和衰减。
int local;
int array[10];
int *p = &local; // "standard" pointer to int
Run Code Online (Sandbox Code Playgroud)
人们倾向于说“腐烂的指针”与指向数组的指针相同。arr但和之间有一个重要的区别&arr。前者不会衰退为后者
int (*p_array_standard)[10] = &arr;
Run Code Online (Sandbox Code Playgroud)
如果这样做&arr,您将获得一个指向 10 个整数数组的指针。这与指向 9 个整数数组的指针不同。它与指向 int 的指针不同。 sizeof(*p_array_standard) == 10 * sizeof(int)。
如果你想要一个指向第一个元素的指针,即指向int, with 的指针sizeof(*p) == sizeof(int)),那么你可以这样做:
int *p_standard = &(arr[0);
Run Code Online (Sandbox Code Playgroud)
到目前为止,一切都基于标准/显式指针。
C 中有一个特殊规则,允许您替换&(arr[0])为arr。您可以初始化int*with&(arr[0])或 with 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.
Run Code Online (Sandbox Code Playgroud)
当数组衰减时,它衰减为指向单个元素的指针int [10]-> int*。它不会衰减为指向数组的指针,即int (*p)[10].
现在,我们可以从你的问题中看看这一行:
int (T::*pA3)[10] = T::b; // error
Run Code Online (Sandbox Code Playgroud)
同样,班级成员与理解失败的原因无关。左边的类型是指向整数数组的指针,而不是指向整数的指针。因此,正如我们之前所说,衰减无关紧要,您需要&获取指向整数数组的指针类型。
更好的问题是问为什么这不起作用(更新:我现在看到你的问题中确实有这个。)
int T::*pA3 = T::b;
Run Code Online (Sandbox Code Playgroud)
右侧看起来像一个数组,左侧是一个指向单个元素的指针int *,因此您可以合理地问:为什么衰减在这里不起作用?
为了理解为什么衰减在这里很困难,让我们“撤消”语法糖,并替换T::b为&(T::b[0])。
int T::*pA3 = &(T::b[0]);
Run Code Online (Sandbox Code Playgroud)
我认为这是您感兴趣的问题。我们删除了腐烂的内容,以便专注于真正的问题。该行适用于非成员对象,为什么它不适用于成员对象?
简单的答案是标准不需要它。指针衰减是一种语法糖,他们只是没有指定它必须在这种情况下起作用。
指向成员的指针基本上比其他指针要复杂一些。它们必须直接指向对象中出现的“原始”实体。(抱歉,我的意思是它应该通过编码类的开头和该成员的位置之间的偏移量来引用(间接)。但我不太擅长解释这一点。)它们不能指向子对象,例如数组的第一个元素,或者实际上是数组的第二个元素。
问:现在我有一个自己的问题。指针衰减可以扩展到像这样的成员数组吗?我认为这是有一定道理的。 我不是唯一一个有这个想法的人!有关更多信息,请参阅此讨论。这是可能的,而且我想没有什么可以阻止编译器将其实现为扩展。子对象(包括数组成员)距离类的开头有固定的偏移量,因此这是非常合乎逻辑的。