指向类成员的指针(.*)(->*)

  • Post author:
  • Post category:其他




要点

  1. 指针是有类型的,这点和「通常的指针」一样,指向int的指针也只能指向int类型的数据
  2. 相对于「通常的指针」,增加了对指向对象的限制 —— 指向的对象限定为某个class中的成员
  3. 这种指针不可以单独使用,需要配合一个具体存在的对象才有意义
#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)



版权声明:本文为Renekton_bhk原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。