类成员指针和成员函数指针(深入理解)

本文深入解析了C++中类成员指针和成员函数指针的概念,包括它们的工作原理、如何在继承关系中应用以及与普通指针的区别,并通过代码示例进行说明。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

类成员指针和成员函数指针(深入理解

         本想实现一个boost的bind,然而在绑定类成员函数时引出了成员函数指针这么个东西,通过深入的理解之后,又引出了类成员指针这么个东西。通过学习对他们有了深刻的理解,现在做一个总结如下,以示区分。(更多参考可见《c++必知必会》中第15,16节)

1.类成员指针

1)类成员指针到底是神马东东

”指向类成员的指针“这个描述中有“指针”这个术语,它其实既不包含地址,行为也不像指针。怎么理解呢?与常规指针不同,一个指向类成员的指针并不指向一个具体的内存位置,它指向的是一个类的特定成员,而不是指向一个特定对象的特定成员。通常最清楚的说法是:把数据成员的指针看做为一个偏移量,它告诉你, 一个特定成员的位置距离对像的起点有多少个字节。而且在大多数编译器中都把数据成员的指针实现为一个整数,其中包含被指向成员的偏移量,另外在加1(这里是为了让0值也可以表示一个空的数据成员)。

举个例子:

class test_t
{
	public:
		int m_value;
};

int test_t::*mem_ptr;

mem_ptr ptr = &test_t::m_value;
ptr设置成&test_t::m_value时,实际上是将ptr设置成m_value在test_t类上的偏移量。
test_t test;
test.*ptr = 2
当写下test.*ptr时,实际上是请求将test对象的地址加上数据成员的偏移量,为的就是访问这个数据成员。
2)在继承关系中的应用法则

在C++中,存在指向派生类的指针向指向基类的预定义转换,但是在指向成员指针的情况下则恰恰相反:存在指向基类的类成员指针向指向公有派生类的成员指针的转化,反过来则会出错。如果理解成员指针其实是类成员的相对类对象地址的一个偏移量,这样对于上面的法则就会很容易理解。例如:

class test2_t:public test_t
{
	public:
		float m_value2;
}
int test2_t::*mem_ptr = &test_t::m_value;//ok,从基类到派生类的转化
int test_t::*mem_ptr = &test2_t::m_value2;//error,从派生类到基类的转化


因为test2_t对象中包含test_t对象的对象,所以在test_t对象上的任何偏移在test2_t对象上是有效的,
然而反过来就未必是一个有效的偏移量。


2.类成员函数指针

1)类成员函数指针是神马东东

获取非静态成员函数的地址时,得到的不是一个地址,而是一个指向成员函数的指针。怎么理解呢?如果你关注的了《深入C++对象模型》你就会明白,对于类的所有非静态成员函数,在同一个进程内,在代码段中存在且只存在一份,所有类的对象在调用同一成员数据都是调用的这个份,但是必须传入一个隐含的this指针来访问相关数据成员。所以成员函数指针指向的就是这个代码空间。

成员函数指针跟普通的函数指针是有很大区别的,纵然返回值和参数在一样的情况下。一个指向成员函数的指针的实现必须存储一些信息,比如它所指向的成员函数是虚拟的还是非虚拟的,以及到哪里去查找虚函数表,如果涉及数据成员,还要通过this来计算偏移量等等。。总之:成员函数有一个非成员函数不具有的属性---它的类信息。成员函数的指针必须与向其赋值的函数类型匹配,不是两方面,而是三个方面都要匹配:a:参数类型和个数b:返回类型c:它所属的类型(its class)

2)在继承关系中的应用法则

和数据成员指针一样,指向成员函数的指针也表现出一定的逆变性,即存在从指针基类的成员函数指针向指向派生类的成员函数指针的转化,反之则不然。因为基类成员函数会试图通过其this访问基类成员,然而派生类函数可能会试图访问基类中不存在的成员。如:

class B
{
	public:
		void bset(int val_)
		{
			m_bval = val_;
		}
	private:
		int m_bval;
		
}

class D:public B
{
	public:
		void dset(int val_)
		{
			m_dval = val_;
		}
	private:
		int m_dval;
		
}
B b;
D d;
void (B::*f1)(int) = &D::dset;
(b.*f1)(12); // error, 访问不存在的dval成员。
void (D::*f2)(int) = &B::bset;
(d.*f2)(11);//OK,设置继承来的m_bval成员。

最后,根据以上分析写了点测试代码,包含了静态成员函数指针,有几处输出可能不太明白,请高人指点!

#include <iostream>
#include <stdio.h>
using namespace std;

class test_t
{
    public:
        void print1()
        {   
            cout << "hello:"<< endl;
        }   
        static void print2()
        {   
            cout << "hello:"<< endl;
        }   
        int m_value;
        static int g_data;
};

int test_t::g_data = 2;

typedef void (test_t::*func_ptr)();
typedef int test_t::*mem_ptr;

int main()                                                                                                                                                   
{ 
    mem_ptr ptr = &test_t::m_value;
    printf("%p\n", ptr); //nil,这个地方出nil不明白。
    cout << &test_t::m_value<< endl;//1,输出1不明白
    cout << ptr << endl;//1, 输出1不明白
    cout << &ptr << endl;//0x7fffe3376078

    printf("%p\n", &test_t::print1);//0x400ade
    cout << &test_t::print1 << endl;//1,输出1不明白,难道偏移量是1?


    int* tmp_ptr = &test_t::g_data;
    cout << *tmp_ptr << endl;//2
        
    typedef void (*func)();
    func tmp = &test_t::print2;
    printf("%p\n", tmp);//0x400abc
    cout << &tmp << endl;//0x7fffe3376070
}








评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值