• C++成员指针


    1. 成员指针分为成员函数指针,数据成员指针,注意,成员指针并没有真的指向一个内存,它只是表示表示在当前的类,那个字段的位置而已,比如&X::f表示的只是这个f在X类中的位置。

    2. 你可以理解为是一个类似偏移量的东西。

    3.所以,成员指针也没办法脱离类的实例对象单独使用,不管是非静态数据成员指针还是非静态成员函数指针

    4.静态数据成员和静态成员函数不与类关联,也就不参与这个成员指针的讨论了,切记。

    实际上我之前打算是直接说成员指针不是指针,毕竟std::is_pointer也没把它包含在内,也不同于一般的指针的行为,但是,标准没规定它是什么,所以我也就严谨一点,只说,成员指针不是普通的指针

     成员函数指针

    1. struct X {
    2. void f() { std::cout << "6"; }
    3. };
    4. int main() {
    5. void(X::*p)() = &X::f;
    6. X x;
    7. (x.*p)();//6
    8. }

    这样可能还是不直观,我们用函数传参的方式

    1. struct X {
    2. void f() { std::cout << "6"; }
    3. };
    4. void f2(void(X::* p)(), X& x) {
    5. (x.*p)();
    6. }
    7. int main() {
    8. X x;
    9. f2(&X::f, x);//6
    10. }

    可能这时候你们就会有疑问了,我们好像一直在用.*运算符,为什么不使用->*呢?

    其实只要你函数实例传的是指针,而不是引用就行了,如下

    1. struct X {
    2. void f() { std::cout << "6"; }
    3. };
    4. void f2(void(X::* p)(), X* x) {
    5. (x->*p)();
    6. }
    7. int main() {
    8. X x;
    9. f2(&X::f, &x);//6
    10. }

    应该还是非常好理解的,这属于c++的基本语法,我们常用的std::thread,std::bind,std::invoke都是这样的

    那么问题又来了,如果我们要传递一个非静态成员函数是有重载的,该怎么办呢?

    1. struct X {
    2. void f() { std::cout << "6\n"; }
    3. void f(int) { std::cout << "int\n"; }
    4. };

     如果是这样的话,我们还能直接f2(&X::f, &x,参数);吗?其实是可以的

    1. struct X {
    2. void f() { std::cout << "6\n"; }
    3. void f(int) { std::cout << "int\n"; }
    4. };
    5. void f2(void(X::* p)(), X* x,int n) {
    6. (x->*p)();
    7. }
    8. int main() {
    9. X x;
    10. f2(&X::f, &x,5);//6
    11. }

    这种传参还是足以区分重载的,没什么问题

    但是我们要考虑另一个问题,比如我们使用std::bind,如何确定绑定哪个成员函数呢?

    1. struct Test_bind {
    2. void t(int n) {
    3. for (; n; n--)
    4. std::cout << "t\n";
    5. }
    6. void t() {
    7. std::cout << "重载的t\n";
    8. }
    9. };
    10. int main() {
    11. Test_bind t_b;
    12. auto n = std::bind(static_cast<void(Test_bind::*)(int)>(&Test_bind::t)
    13. , &t_b, 3);
    14. n();
    15. }

    我们使用强制转换,显式的指明到底要绑定哪个成员函数即可,这种情况挺多见的,比如在qt的object::connect啥的上面,一定要记住。

    注意,operator.*不可以重载,但是operator->*可以重载,如下:

    1. struct X {
    2. void f() { std::cout << "6\n"; }
    3. template<typename Ty>
    4. auto operator ->*(Ty v) {
    5. return (this->*v)();
    6. }
    7. };
    8. int main(){
    9. X x;
    10. x->*& X::f;
    11. }

    如果对于std::bind的实现有兴趣可以看看本人上一篇文章

    C++实现std::bind_归故里@的博客-CSDN博客【代码】C++实现std::bind。我们用最简单的方式完成它的功能,即requires和lambda,只需要三个函数重载即可https://blog.csdn.net/a4364634611/article/details/127709198

     讲完了成员函数指针和那几个运算符,那数据成员指针也是非常的简单了

    数据成员指针

    1. struct X {
    2. int x = 1;
    3. };
    4. int main() {
    5. int X::* n = &X::x;
    6. X x;
    7. std::cout << x.x << '\n';//1
    8. int& v = (x.*n);
    9. v = 100;
    10. std::cout << x.x << '\n';//100
    11. }

    和绑定成员函数指针没什么区别,甚至更加方便,我们直接得到了x对象的数据成员x的引用

    同样介绍一下传参的写法

    1. struct X {
    2. int x = 1;
    3. };
    4. void f(int X::* v, X* x) {
    5. (x->*v) = 66;
    6. }
    7. int main() {
    8. X x;
    9. f(&X::x, &x);
    10. std::cout << x.x << '\n';//66
    11. }

    这些一般我们都不会去直接使用,都是间接的,在c++的标准库中有很多好用的包装函数,我们介绍一下c++17的invoke

    1. class Test {
    2. public:
    3. uint16_t num = 0;
    4. void f() {
    5. std::cout << "66\n";
    6. }
    7. };
    8. int main() {
    9. Test c;
    10. uint16_t& i = std::invoke(&Test::num, &c);//绑定数据成员返回引用
    11. i = 9999;
    12. std::cout << c.num << '\n';
    13. std::invoke(&Test::f, &c);//直接调用,无返回值
    14. }

    成员访问运算符

    运算符名语法可重载原型示例(对于 class T )
    类定义内类定义外
    下标a[b]R& T::operator[](S b);

    R& T::operator[](S1 s1, ...);

    (C++23 起)
    N/A
    间接寻址*aR& T::operator*();R& operator*(T a);
    取地址&aR* T::operator&();R* operator&(T a);
    对象的成员a.bN/AN/A
    指针的成员a->bR* T::operator->()N/A
    指向对象的成员的指针a.*bN/AN/A
    指向指针的成员的指针a->*bR& T::operator->*(S b);R& operator->*(T a, S b);

    注解

    • 与大多数用户定义的重载相同,返回类型应当与内建运算符所提供的返回类型相匹配,以便用户定义的运算符可以和内建运算符以相同的方式使用。不过,在用户定义的运算符重载中,任何类型都可以作为它的返回类型(包括 void)。一个例外是 operator->,它必须返回一个指针或者另一个带有重载的 operator-> 的类以使它真正可用。

    解释

    内建的 下标 (subscript) 运算符提供对它的指针数组操作数所指向的对象的访问。

    内建的 间接寻址 (indirection) 运算符提供对它的指针操作数所指向的对象或函数的访问。

    内建的 取地址 (address of) 运算符创建指向它的对象或函数操作数的指针。

    对象的成员 和 指向对象的成员的指针 运算符提供对它的对象操作数的数据成员或成员函数的访问。

    内建的 指针的成员 和 指向指针的成员的指针 运算符提供对它的指针操作数所指向的类的数据成员或成员函数的访问。

    总结

    相信如果学习过模板的开发者们都觉得这样全部写明实在太不优雅了,我们一般写自然也不会写成这样的,使用模板会方便的多,但是为了新鲜的开发者们学习难度,我们使用的最简单直接的方式,全部写明参数来介绍。如果有兴趣的话可以看看本人前面说的bind的实现,是如何不写明类型接收参数的。

    如果还有疑问,可以阅读cppref

    成员访问运算符 - cppreference.comhttps://zh.cppreference.com/w/cpp/language/operator_member_access

  • 相关阅读:
    云尘靶场 Medium_Socnet 内网为docker 无站代理 不存在gcc的提权方式 解决ldd过高无法执行exp 指定so文件
    网络流学习笔记
    springboot毕设项目校园运动会管理系统6wumc(java+VUE+Mybatis+Maven+Mysql)
    功能测试进阶建议,学习思路讲解
    (六)vulhub专栏:Apereo-cas 4.x反序列化漏洞
    FastXML代码编译和调试支持
    Java编程技巧:分类
    C# Onnx PP-HumanSeg 人像分割
    pgpool密码验证失败问题
    图像几何变换
  • 原文地址:https://blog.csdn.net/a4364634611/article/details/127712744