概述
昨天发现了一个问题,就是使用对类中的非静态成员函数使用std::bind时,不能像普通函数一样直接传递函数名,而是必须显式地调用&(取地址),于是引申出我们今天的问题:非静态类成员函数指针和普通函数指针有什么区别?
一.C++中对函数到指针的隐式转换
以前在C语言程序设计课上,老师都会说:“函数名就是指向这个函数的指针”。实际上通过查阅cppreference中的隐式转换规则,其中有这么一句关键的话道出了玄机:
函数类型
T
的左值能隐式转换成指向该函数的指针纯右值。这不作用于非静态成员函数,因为不存在指代非静态成员函数的左值。
这一句话表明,实际上我们在把普通函数的函数名当作右值使用的过程中,C++隐式地将其转换成了指向该函数的的指针。这也就是我们在使用std::bind的过程中可以直接传递普通函数的函数名的原因,例如:
int foo(int a,int b) { return (a+b); } int main() { auto function = std::bind(foo,std::placeholders::_1,std::placeholders::_2); std::cout << function(3,4) << std::endl; //7 return 0; }
但是,这一条隐式转换规则不作用于非静态成员函数,因为不存在指代非静态成员函数的左值。因此我们能看出,指向非静态成员函数的指针和指向普通函数的指针是有区别的。接下来我们将会讨论类成员函数指针(除非有特别说明,下文中的“类成员”都指代“非静态类成员”)。
二.类成员函数指针的声明和调用
类成员函数指针的声明有些类似普通函数指针。但是区别在于,类成员函数是类成员的一部分,所以需要将类成员函数指针使用 ::* 声明成 成员指针类型。这也表示了类成员函数指针不能单独地被调用,需要 该类型的实例使用.*(对象的成员指针) 或 指向该类型的指针使用->*(指针的成员指针) 进行调用。同时由于上文说到的C++隐式转换的规则,我们需要显示地调用 &(取地址) 获取非静态成员函数指针:
class A { public: void FunA() { std::cout << "Function A" << endl; } }; int main() { void(A::*p1)() = &A::FunA; //p1(); Error A a; A *p2 = &a; (a.*p1)(); // Succ (p2->*p1)(); // Succ return 0; }
三.探究类成员函数指针
我们都知道,普通函数指针实际上指向的是函数代码的起始地址。然而类成员函数指针不仅仅是类成员函数的内存起始地址,它还解决C++中涉及到的多重继承、虚函数等导致的地址偏移问题。因此,普通函数指针的内存大小是普通指针的大小(32位系统4字节,64位系统8字节),但是成员函数指针却不一定。
1.多继承中的使用
现在有三个类A B C,其中类C public继承 了A B两个类。它们的声明和定义如下:
class A { public: int a; void funA() { std::cout << "function A" << std::endl; } }; class B { public: int b; void funB() { std::cout << "function B" << std::endl; } }; class C : public A, public B { public: int c; void funC() { std::cout << "function C" << std::endl; } };
现在我们声明一个函数指针:
int main() { void (C::*mfpC)() = &B::funB; return 0; }
这个指针很特别,它是一个指向C的类成员的指针,但实际上它指向了C的基类B的成员函数B::funB。现在我们编译代码并调用gdb调试查看mfpC的信息:
(gdb) p mfpC $1 = (void (C::*)(C * const)) 0x8000966 <B::funB()>, this adjustment 4 (gdb) p sizeof(mfpC) $2 = 16 (gdb) x/16xb &mfpC 0x7ffffffee830: 0x66 0x09 0x00 0x08 0x00 0x00 0x00 0x00 0x7ffffffee838: 0x04 0x00 0x00 0x00 0x00 0x00 0x00 0x00
可以看见,mfpC实际占用了16个字节(运行环境为64位操作系统),这16个字节中不仅包含了它所指向的函数的起始地址,还包含了this指针的调整值。因为对于多重继承来说,如果类成员函数指针保存的是非最左基类的成员函数地址,根据C++标准,非最左基类实例的开始地址肯定不同于派生类实例的开始地址,所以需要调整this指针,使其指向非最左基类实例。
2.有虚函数的使用
现在修改代码,在类B中定义一个虚函数funC,并在类C中重写它:
class A { public: int a; void funA() { std::cout << "function A" << std::endl; } }; class B { public: int b; void funB() { std::cout << "function B" << std::endl; } virtual void funC() { std::cout << "function C in class B" << std::endl; } }; class C : public A, public B { public: int c; void funC() { std::cout << "function C in class C" << std::endl; } };
然后我们实例化一个类C的对象c和一个指向B::funC的成员函数指针mfpB,最后通过c调用它:
int main() { C c; void (B::*mfpB)() = &B::funC; (c.*mfpB)(); return 0; }
结果显而易见,输出的不是"function C in class B",而是"function C in class C"。
由此可见,成员函数指针虽然看起来和普通的函数指针有些类似,但实际上两者还是有很大的差别的。
转载于:https://www.cnblogs.com/RodChong/p/9890447.html
最后
以上就是听话山水为你收集整理的关于C++中的非静态类成员函数指针的全部内容,希望文章能够帮你解决关于C++中的非静态类成员函数指针所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复