C++ 成员指针详解

成员指针是 C++ 中一个相对特殊的概念,它允许我们存储类成员(数据成员或成员函数)的指针。本文将详细介绍成员指针的概念、用法以及实际应用场景。

1. 成员指针的基本概念

成员指针与普通指针不同,它指向的是类的成员,而不是对象。成员指针的类型需要包含类名,因为成员指针需要知道它属于哪个类。

1.1 数据成员指针

cpp 复制代码
class Person {
public:
    std::string name;
    int age;
};

// 声明成员指针
std::string Person::*namePtr = &Person::name;
int Person::*agePtr = &Person::age;

// 使用成员指针
Person p;
p.*namePtr = "张三";  // 等价于 p.name = "张三"
p.*agePtr = 25;      // 等价于 p.age = 25

1.2 成员函数指针

cpp 复制代码
class Calculator {
public:
    int add(int a, int b) { return a + b; }
    int subtract(int a, int b) { return a - b; }
};

// 声明成员函数指针
int (Calculator::*addPtr)(int, int) = &Calculator::add;
int (Calculator::*subtractPtr)(int, int) = &Calculator::subtract;

// 使用成员函数指针
Calculator calc;
Calculator* pCalc = &calc;

int result1 = (calc.*addPtr)(5, 3);        // 使用对象调用
int result2 = (pCalc->*addPtr)(5, 3);      // 使用指针调用

2. 静态成员指针与普通成员指针的对比

2.1 静态成员指针

cpp 复制代码
class MathUtils {
public:
    static int add(int a, int b) { return a + b; }
    static int subtract(int a, int b) { return a - b; }
    static double PI;
};

double MathUtils::PI = 3.14159;

// 静态成员函数指针
int (*staticAddPtr)(int, int) = &MathUtils::add;
int (*staticSubtractPtr)(int, int) = &MathUtils::subtract;

// 静态数据成员指针
double* staticPiPtr = &MathUtils::PI;

// 使用静态成员指针
int result = staticAddPtr(5, 3);  // 直接调用,不需要对象
double pi = *staticPiPtr;         // 直接访问,不需要对象

2.2 主要区别

  1. 语法差异

    • 普通成员指针:ReturnType (ClassName::*ptrName)(Params) = &ClassName::memberName
    • 静态成员指针:ReturnType (*ptrName)(Params) = &ClassName::memberName
  2. 使用方式

    • 普通成员指针:需要通过对象或指针调用(使用 .*->* 操作符)
    • 静态成员指针:可以直接调用,不需要对象实例
  3. 内存模型

    • 普通成员指针:存储的是成员在类中的偏移量
    • 静态成员指针:存储的是实际的函数地址或变量地址
  4. 应用场景

    • 普通成员指针:适用于需要访问特定对象成员的情况
    • 静态成员指针:适用于全局函数或工具类的情况

3. 成员指针的实际应用

3.1 通用访问器

成员指针可以用来创建通用的成员访问器,这在需要统一处理多个成员时特别有用:

cpp 复制代码
class Student {
public:
    std::string name;
    int score;
    std::string address;
};

template<typename T>
void printMember(const Student& s, T Student::*member) {
    std::cout << s.*member << std::endl;
}

// 使用示例
Student s{"李四", 95, "北京"};
printMember(s, &Student::name);
printMember(s, &Student::score);
printMember(s, &Student::address);

3.2 回调函数系统

成员指针在实现回调系统时非常有用:

cpp 复制代码
class Button {
public:
    using ClickHandler = void (Button::*)();
    
    void setClickHandler(ClickHandler handler) {
        clickHandler = handler;
    }
    
    void click() {
        if (clickHandler) {
            (this->*clickHandler)();
        }
    }
    
private:
    ClickHandler clickHandler = nullptr;
    
    void onDefaultClick() {
        std::cout << "默认点击处理" << std::endl;
    }
    
    void onSpecialClick() {
        std::cout << "特殊点击处理" << std::endl;
    }
};

// 使用示例
Button btn;
btn.setClickHandler(&Button::onSpecialClick);
btn.click();  // 输出:特殊点击处理

3.3 使用 std::mem_fn

C++11 引入了 std::mem_fn,它可以将成员函数指针转换为可调用对象:

cpp 复制代码
#include <functional>

class Widget {
public:
    void process() { std::cout << "处理中..." << std::endl; }
};

// 使用 std::mem_fn
auto processFn = std::mem_fn(&Widget::process);
Widget w;
processFn(w);  // 调用 w.process()

4. 注意事项

  1. 成员指针不能指向静态成员
  2. 成员指针的类型必须完全匹配
  3. 使用成员指针时要注意访问权限
  4. 成员指针不能用于虚函数
相关推荐
喜欢吃燃面15 分钟前
深入 C++ STL:从 unordered_set与unordered_map到底层哈希表的原理与实现
数据结构·c++·散列表
FlightYe44 分钟前
音视频修炼之基础理论(五):AAC格式与解析
android·linux·c++·音视频·aac
ShineWinsu2 小时前
对于MySQL:数据库的操作的解析
linux·数据库·c++·mysql·面试·笔试·库的操作
云小逸2 小时前
C++ 第一阶段:对象、内存与生命周期
开发语言·c++
夜航海图2 小时前
S-57 数据解剖:把一个 ENC 文件拆给你看
c++
(Charon)2 小时前
【C++】网络缓冲区设计(三):Chain Buffer链式缓冲区、动态扩容与跨节点读写
服务器·c++
软行4 小时前
LeetCode 每日一题 3876. 构造奇偶一致的数组 II
c++·算法·leetcode
繁星蓝雨4 小时前
C++设计原理———重载(extern “C“的由来、顺序依赖、语义依赖、overload、名称修饰符、对象操作、运算符重载、新增运算符、枚举和布尔类型)
c语言·c++·extern c·overload·重载·语义依赖·顺序依赖
handler014 小时前
【Linux】虚拟地址空间解析
linux·运维·c++·线程·进程·虚拟地址空间·虚拟地址
(Charon)4 小时前
【C++】网络缓冲区设计(二):Ring Buffer环形缓冲区、head/tail与跨界读写
开发语言·c++