4.4 指向成员函数的指针

本文详细探讨了非静态成员函数地址的概念,包括非虚函数与虚函数的不同表现形式,以及如何通过成员函数指针调用这些函数。同时,文中还讨论了在多重继承和虚拟继承情况下成员函数指针的实现细节。

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

Q1:非静态成员函数的地址

• 非静态成员函数地址值:

○ 如果该函数是非虚函数,则得到的结果是其在内存中真正的地址

○ 如果该函数是虚函数,则得到的结果是其在虚函数表中的索引值



• 地址不完全性(数据成员的地址与函数成员的地址):

○ 非静态数据成员的地址是其在类的布局中的位置(加1),是一个不完整的值,需要将其绑定在一个类对象上,才能进行存取

○ 非静态函数成员(非虚)的地址是其在内存中的真正地址,但仍是一个不完全地址,需要被绑定在一个类对象上,才能进行调用(因为类成员函数需要使用 this 指针作为传递通道)



• 成员函数指针的定义与使用:

Eg:

        class X
        {
        public:
            void func(){};

        };

        int main()
        {
            void (X::*pfunc)() = &X::func;         //定义并初始化指针
            X x;
            (x.*pfunc)();      //绑定对象并调用函数指针所指函数。指向“member selection”运算符(.*   ->*)
            (&x->*pfunc)();
            return 0;
        }

可以看出,指向成员函数的指针的声明语法,以及指向 “member selection运算符”的指针,其作用均是作为 this 指针的空间保留者。因此,可以理解静态成员的类类型是函数指针,而不是指向成员函数的指针(因为静态成员函数不能绑定某个对象,且其调用不需要 this 指针)




Q2:支持“指向虚拟成员函数”的指针



• 虚拟成员函数的地址值是该函数在虚函数表中的索引值,而普通的成员函数的地址值是该函数在内存中的真正的地址值,当虚拟成员函数与普通成员函数有相同的函数原型时,将带来复杂性,即成员函数指针需要满足以下两个条件
1. 能持有两种数据(索引值与地址值)

2. 数值可以被区分出是内存地址函数还是索引值

Eg:

class X
{
public:
    int normfunc();
    virtual int virfunc();    //普通成员函数 normfunc() 与虚拟成员函数 virfunc() 有相同的函数原型
};

//函数指针 pfunc 既可以指向普通成员函数(内存地址),也可以指向虚拟成员函数(索引值) 
int(X::*pfunc)() = &X::normfunc;
int(X::*pfunc)() = &X::virfunc; 


带有限制的解决方法:这两种值被内含在一个普通的指针内,其识别使用以下技巧:(对于调用方式 (ptr->pfunc())(); 进行讨论;其中X x; X ptr = &x; )
(((int)pfunc) & ~127) ? (*pfunc)(ptr) : (*ptr->vptr[(int)pfunc])(ptr);

这种区别方法含有缺陷:继承体系中最多只有128个虚函数,对于 pfunc 中值小于128 的认为是索引值,否则认为是地址值




Q3:在多重继承下, 支持成员函数的指针



• 定义以下结构体来实现让多重继承与虚拟继承支持成员函数的指针:
        struct __mptr
        {
            int delta;                  // this 指针的 offset 值
            int index;                  // 虚函数表的索引(当 index 不指向虚函数时,会被设为-1)
            union
            {
                ptrtofunc   faddr;      //非虚成员函数的内存地址       
                int     v_offset;       //一个虚基类的 vptr 的位置
            };
        };

此时调用操作会发生如下变化:

(ptr->*pfunc)();

//转换为如下形式的调用:

(pfunc.index < 0) ? (*pfunc.faddr)(ptr) : (*ptr->vptr[pfunc.index](ptr));

此方法缺点:每一个调用操作都要付出成本,检查其是否为虚函数或非虚函数。



• 微软导入 vcall thunk,在此策略下,避免了上述检查操作,faddr 被指定的要不就是真正的成员函数地址,要不就是 vcall thunk 地址。


• 每个编译器在自身内部根据不同的类特性提供多种指向成员函数的指针形式。如微软:
1. 一个单一继承实例(其中持有 vcall thunk 地址或是函数地址,仅需要 faddr 成员)
2. 一个多重继承实例(其中持有 faddr, delta 两个成员) // delta 提供 this 指针的偏移值
3. 一个虚拟继承实例(其中持有四个成员)
### 使用 `new` 操作符创建数组指针并关联类名的实例 在 C++ 中,可以使用 `new` 操作符来动态分配内存空间给数组,并将其与特定的类名关联起来。以下是详细的说明以及实现方法。 #### 动态创建对象数组 当需要创建一个由多个类的对象组成的数组时,可以利用 `new` 来完成这一任务。语法如下: ```cpp ClassName* ptr = new ClassName[arraySize]; ``` 其中: - `ClassName` 是要创建的对象所属的类; - `ptr` 是指向该类类型指针; - `arraySize` 表示希望创建的对象数量。 通过这种方式,程序会在堆上为指定大小的对象数组分配连续的存储区域[^1]。 #### 初始化对象数组中的元素 如果类具有默认构造函数,则每个对象都会被自动初始化。如果没有提供无参构造函数或者想要调用带参数的构造函数,则需采用另一种方式——即逐个显式地设置这些对象的状态。例如: ```cpp class Example { public: int value; Example(int v):value(v){} }; Example* examples = new Example[5]{1, 2, 3, 4, 5}; // 或者单独赋值 for(int i=0;i<5;i++) {examples[i].value=i;} ``` 这里展示了两种不同的初始化策略:一种是在声明的同时给予初始值列表;另一种则是后续遍历整个集合逐一设定属性[^3]。 #### 删除动态分配的对象数组 一旦不再需要这个数组及其所含有的任何资源时,应该及时释放它占用的空间以免造成泄漏。这可通过下面这条语句达成目的: ```cpp delete[] ptr; ``` 注意这里的方括号表明我们正在处理的是一个多维结构而非单个实体[^2]。 #### 成员访问与迭代器模式应用 对于已经建立好的此类容器来说,通常还需要考虑如何有效地对其进行读取/更新操作等问题。借助循环控制机制配合下标运算符([]),我们可以轻松获取任意位置上的项目信息。另外值得注意的是,由于是指向实际数据区段的第一项地址的一个简单整数偏移量计算过程而已,所以效率非常高[^4]。 下面是完整的例子演示以上概念的应用场景之一: ```cpp #include <iostream> using namespace std; class Test{ private: double num; public: Test(double n){ this->num=n; } void show(){ cout<<this->num<<"\n"; } }; int main() { const unsigned short SIZE=7; // 创建Test类型的新数组 Test *tests=new Test[SIZE]{1.1,2.2,3.3,4.4,5.5,6.6,7.7}; // 遍历打印每一个测试案例的结果 for(unsigned short index=0;index<SIZE;++index){ tests[index].show(); } delete [] tests;// 清理工作完成后记得销毁之 return EXIT_SUCCESS; } ``` 此代码片段首先定义了一个简单的类'Test'用于封装浮点数值,并提供了相应的显示功能。接着,在主函数内部按照前述规则构建了一组这样的单元格序列并通过标准输出设备展示出来最后妥善回收了之前请求得到的那一片宝贵的土地! ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值