1. 成员指针分为成员函数指针,数据成员指针,注意,成员指针并没有真的指向一个内存,它只是表示表示在当前的类,那个字段的位置而已,比如&X::f表示的只是这个f在X类中的位置。
2. 你可以理解为是一个类似偏移量的东西。
3.所以,成员指针也没办法脱离类的实例对象单独使用,不管是非静态数据成员指针还是非静态成员函数指针。
4.静态数据成员和静态成员函数不与类关联,也就不参与这个成员指针的讨论了,切记。
实际上我之前打算是直接说成员指针不是指针,毕竟std::is_pointer也没把它包含在内,也不同于一般的指针的行为,但是,标准没规定它是什么,所以我也就严谨一点,只说,成员指针不是普通的指针。
- struct X {
- void f() { std::cout << "6"; }
- };
- int main() {
- void(X::*p)() = &X::f;
- X x;
- (x.*p)();//6
- }
这样可能还是不直观,我们用函数传参的方式
- struct X {
- void f() { std::cout << "6"; }
- };
-
- void f2(void(X::* p)(), X& x) {
- (x.*p)();
- }
-
- int main() {
- X x;
- f2(&X::f, x);//6
- }
可能这时候你们就会有疑问了,我们好像一直在用.*运算符,为什么不使用->*呢?
其实只要你函数实例传的是指针,而不是引用就行了,如下
- struct X {
- void f() { std::cout << "6"; }
- };
-
- void f2(void(X::* p)(), X* x) {
- (x->*p)();
- }
-
- int main() {
- X x;
- f2(&X::f, &x);//6
- }
应该还是非常好理解的,这属于c++的基本语法,我们常用的std::thread,std::bind,std::invoke都是这样的
那么问题又来了,如果我们要传递一个非静态成员函数是有重载的,该怎么办呢?
- struct X {
- void f() { std::cout << "6\n"; }
- void f(int) { std::cout << "int\n"; }
- };
如果是这样的话,我们还能直接f2(&X::f, &x,参数);吗?其实是可以的
- struct X {
- void f() { std::cout << "6\n"; }
- void f(int) { std::cout << "int\n"; }
- };
-
- void f2(void(X::* p)(), X* x,int n) {
- (x->*p)();
- }
-
- int main() {
- X x;
- f2(&X::f, &x,5);//6
- }
这种传参还是足以区分重载的,没什么问题
但是我们要考虑另一个问题,比如我们使用std::bind,如何确定绑定哪个成员函数呢?
- struct Test_bind {
- void t(int n) {
- for (; n; n--)
- std::cout << "t\n";
- }
- void t() {
- std::cout << "重载的t\n";
- }
- };
-
- int main() {
- Test_bind t_b;
- auto n = std::bind(static_cast<void(Test_bind::*)(int)>(&Test_bind::t)
- , &t_b, 3);
- n();
- }
我们使用强制转换,显式的指明到底要绑定哪个成员函数即可,这种情况挺多见的,比如在qt的object::connect啥的上面,一定要记住。
注意,operator.*不可以重载,但是operator->*可以重载,如下:
- struct X {
- void f() { std::cout << "6\n"; }
- template<typename Ty>
- auto operator ->*(Ty v) {
- return (this->*v)();
- }
- };
-
- int main(){
- X x;
- x->*& X::f;
- }
如果对于std::bind的实现有兴趣可以看看本人上一篇文章
讲完了成员函数指针和那几个运算符,那数据成员指针也是非常的简单了
- struct X {
- int x = 1;
- };
-
- int main() {
- int X::* n = &X::x;
- X x;
- std::cout << x.x << '\n';//1
- int& v = (x.*n);
- v = 100;
- std::cout << x.x << '\n';//100
- }
和绑定成员函数指针没什么区别,甚至更加方便,我们直接得到了x对象的数据成员x的引用
同样介绍一下传参的写法
- struct X {
- int x = 1;
- };
-
- void f(int X::* v, X* x) {
- (x->*v) = 66;
- }
-
- int main() {
- X x;
- f(&X::x, &x);
- std::cout << x.x << '\n';//66
- }
这些一般我们都不会去直接使用,都是间接的,在c++的标准库中有很多好用的包装函数,我们介绍一下c++17的invoke
- class Test {
- public:
- uint16_t num = 0;
- void f() {
- std::cout << "66\n";
- }
- };
-
- int main() {
- Test c;
- uint16_t& i = std::invoke(&Test::num, &c);//绑定数据成员返回引用
- i = 9999;
- std::cout << c.num << '\n';
- std::invoke(&Test::f, &c);//直接调用,无返回值
- }
运算符名 | 语法 | 可重载 | 原型示例(对于 class T ) | |||
---|---|---|---|---|---|---|
类定义内 | 类定义外 | |||||
下标 | a[b] | 是 | R& T::operator[](S b);
| N/A | ||
间接寻址 | *a | 是 | R& T::operator*(); | R& operator*(T a); | ||
取地址 | &a | 是 | R* T::operator&(); | R* operator&(T a); | ||
对象的成员 | a.b | 否 | N/A | N/A | ||
指针的成员 | a->b | 是 | R* T::operator->() | N/A | ||
指向对象的成员的指针 | a.*b | 否 | N/A | N/A | ||
指向指针的成员的指针 | a->*b | 是 | R& T::operator->*(S b); | R& operator->*(T a, S b); | ||
注解
|
内建的 下标 (subscript) 运算符提供对它的指针或数组操作数所指向的对象的访问。
内建的 间接寻址 (indirection) 运算符提供对它的指针操作数所指向的对象或函数的访问。
内建的 取地址 (address of) 运算符创建指向它的对象或函数操作数的指针。
对象的成员 和 指向对象的成员的指针 运算符提供对它的对象操作数的数据成员或成员函数的访问。
内建的 指针的成员 和 指向指针的成员的指针 运算符提供对它的指针操作数所指向的类的数据成员或成员函数的访问。
相信如果学习过模板的开发者们都觉得这样全部写明实在太不优雅了,我们一般写自然也不会写成这样的,使用模板会方便的多,但是为了新鲜的开发者们学习难度,我们使用的最简单直接的方式,全部写明参数来介绍。如果有兴趣的话可以看看本人前面说的bind的实现,是如何不写明类型接收参数的。
如果还有疑问,可以阅读cppref
成员访问运算符 - cppreference.comhttps://zh.cppreference.com/w/cpp/language/operator_member_access