要点
- 指针是有类型的,这点和「通常的指针」一样,指向int的指针也只能指向int类型的数据
- 相对于「通常的指针」,增加了对指向对象的限制 —— 指向的对象限定为某个class中的成员
- 这种指针不可以单独使用,需要配合一个具体存在的对象才有意义
#include <bits/stdc++.h>
using namespace std;
class A
{
public:
int para1;
int para2;
int callback(int n)
{
return 2 * n;
}
void display()
{
cout << para1 << " " << para2 << endl;
}
};
int main()
{
A a;
A *pa = &a;
a.para1 = 1;
a.para2 = 2;
a.display(); /* 输出:1 2 */
/* pInt是指向int类型数据的指针,且仅限于class A中的int成员 */
int A::*pInt;
/* pInt指向class A中的para1成员 */
pInt = &A::para1;
/* 利用专用的运算符「->*」提取对象a中的para1成员, 可以换另一种写法「a.*pInt = 123;」 */
pa->*pInt = 123;
a.display(); /* 输出:123 2 */
/* pInt改为指向class A中的para2成员 */
pInt = &A::para2;
/* 利用专用的运算符「->*」提取对象a中的para2成员, 可以换另一种写法「a.*pInt = 456;」 */
pa->*pInt = 456;
a.display(); /* 输出:123 456 */
/* pfunc是函数指针,函数参数为1个int,返回值为int,该指针仅限指向class A中相应类型的成员 */
int (A::*pfunc)(int n) = &A::callback;
cout << (pa->*pfunc)(7) << endl; /* 输出:14 */
//等同于 cout << (a.*pfunc)(7) << endl;
}
纠结的演化过程
习惯了常规的指针,这种特殊的,指向类成员的指针确实有点诡异。但可以按照下面这样的演化过程来理解这种指针。
step1:指向int型的指针
int *pInt;
step2:指向int型的指针+限制为class A的成员
int A::*pInt;
step3:指向具体的class A的成员
pInt = &A::para1;
如此看,等号右边的
&
已经不同于通常意义的「取地址」符号,毕竟
A::para1
也不是一个具体的数据对象
step4:将指针与具体的class A的对象结合,这样就能干点什么了
pa->*pInt = 123;
a.*pInt = 123;
上述操作也分别等价于
pa->para1 = 123;
a.para1 = 123;
对比上述2种赋值para1的方式,可以有个初步的结论:
*pInt
字面上等价于
para1
此时再回顾step3中莫名其妙的
&
符号,似乎也有了一点点道理,毕竟无论怎样用到
pInt
,前面都必须带个
*
也就是必须以
*pInt
的形式出现,这个解引用的
*
,正好可以和取地址符号
&
相互抵消,换句话说,
*pInt
作为一个整体,成为了「class A中的para1成员」的代名词。
一点疑惑
既然
*pInt
成为了一个整体,似乎其中的
*
有点多余了,直接将
pInt
声明为「class A中的para1成员的代名词」不就可以了?比如说像这样
int A::Represent_para1;
Represent_para1 = A::para1;
a.Represent_para1 = 123;
实践证明这样是不行的,vscode中报错提示:
不允许使用限定名
暂时没有想明白为什么不允许这样,不过直观上看,这样做确实会有点怪怪的——似乎声明了
Represent_para1
是class A的一个成员。
暂且先接受这个设定吧,之后弄明白了再来补充。
实战用途
下面的代码中,class A提供了1个公用的接口GetResult,该接口根据传入的参数不同,控制调用不同的内部函数,并返回相应的数值。
#include <bits/stdc++.h>
using namespace std;
class A
{
private:
int func1(int n) { return 2; }
int func2(int n) { return 4; }
int func3(int n) { return 6; }
/* 关注点1 */
vector<pair<int, int (A::*)(int)>> vecList = {
{1, &func1},
{2, &func2},
{3, &func3},
};
public:
int GetResult(int n)
{
for (auto iter = vecList.begin(); iter != vecList.end(); iter++)
{
if (iter->first == n)
{
return (this->*(iter->second))(n); /* 关注点2 */
}
}
return 0;
}
};
int main()
{
A a;
cout << a.GetResult(0) << endl; /* 输出:0 */
cout << a.GetResult(1) << endl; /* 输出:2 */
cout << a.GetResult(2) << endl; /* 输出:4 */
cout << a.GetResult(3) << endl; /* 输出:6 */
}
需要注意的有两点
关注点1
vecList是元素为pair的数组,其第二个成员类型就是上面提到的「指向类成员的指针」,这里并不能用普通的函数指针代替(类型不匹配),因为func1、func2、func3都是class A的成员函数,这与其它普通的函数身份是不一样的。
仿照前面纠结的演化过程的推导,不难看出
int (A::*)(int)
的“原始形态”就是
int (*)(int)
,即「返回值为int,参数为1个int的函数指针」,只不过加上了限定符
A::
之后,这个指针被限制只能指向class A的成员。
关注点2
在通过「指向类成员的指针」调用相应的「成员函数」时,首先通过
iter->second
提取到了相应的指针。根据前面的讨论可知,这个指针只是表明了「指向class A的具体的某个成员(函数)」,只依据这个指针是无法完成调用的,这个指针必须依附在一个实际存在的class A的实例上才可以使用,显然,最合适的人选就是发起这次调用的
a
,最终的调用形式即为
(this->*(iter->second))(n)
,当然,也可以换个形式:
((*this).*(iter->second))(n)