C++ 与&类&有关的注意事项总结(五):指向类成员的指针 - charley_yang - 博客园
移动开发(Android、iPhone、Windows Mobile)
| J***A | C | C++ | .net | Objective C | 微软企业开发技术 | 嵌入式系统设计与开发
posts - 96, comments - 131, trackbacks - 0, articles - 0
&&&& 假定 Screen类定义了四个新成员函数----forward() back() up()和down() 它们分别向右、向左、向上和向下移动光标。首先,我们在类体中声明这些新的成员函数 : class Screen { public: inline Screen& forward(); inline Screen& back(); inline Screen& end(); inline Screen& up(); inline Screen& down(); // 其他成员函数同前 private: inline int row(); // 其他私有成员同前 };
& &&& Screen类的用户要求一个函数 repeat(),它执行用户指定的操作 n 次,它的非通用的实现如下:&
Screen &repeat( char op, int times ) { switch( op ) { case DOWN: // 调用 Screen::down() n 次 & case UP: // 调用 Screen::up() n 次 & // ... } }
& &&&& 虽然这种实现能够工作,但是它有许多缺点 一个问题是,它依赖于 Screen的成员函数保持不变,每次增加或删减一个成员函数,都必须更新 repeat(); 第二个问题是它的大小,由于必须测试每个可能的成员函数,所以repeat()的完整列表非常大而且不必要地复杂 。 &&&& 替换的办法是一种更通用的实现。用Screen成员函数的指针类型的参数取代 OP,repeat() 不需要再确定用户期望什么样的操作,整个switch 语句也可以被去掉。 & 一、类成员的类型
& &&&& 函数指针不能被赋值为成员函数的地址,即使返回类型和参数表完全匹配。例如 ,下面的 pfi 是一个函数指针,该函数没有参数,返回类型为 int:&
int (*pfi) ();
&&&& 给出两个全局函数 HeightIs()和 WidthIs() :
int HeightIs(); int WidthIs();
&&&& 把 HeightIs()和WidthIs()中的任何一个或两个赋值给指针 pfi 都是合法且正确的:&
pfi = HeightIs; pfi = WidthIs;
&&&& 类 Screen也定义了两个访问函数----height()和 width()----它们也没有参数,返回类型也 是 int : inline int Screen::height() { return _ } inline int Screen::width() { return _ }
&&&& 但是把 height()或 width()任何一个赋给指针 pfi 都是类型违例,都将导致编译时刻错误: & // 非法赋值: 类型违例 pfi = &Screen::
&&&& 为什么会出现类型违例呢?成员函数有一个非成员函数不具有的属性----它的类 (its class), 指向成员函数的指针必须与向其赋值的函数类型匹配,不是两个而是三个方面都要匹配 :(1) 参数的类型和个数 (2) 返回类型 (3) 它所属的类类型 。 &&&&& 成员函数指针首先必须被绑定在一个对象或者一个指针上,才能得到被调用对象的 this指针,然后才调用指针所指的成员函数。虽然普通函数指针和成员函数指针都被称作指针,但是它们是不同的事物。&
&&&& 成员函数指针的声明要求扩展的语法,它要考虑类的类型,对指向类数据成员的指针也是这样。考虑 Screen类的成员 height 的类型,它的完整类型是“ short 型的 Screen类的成 员“,指向_height 的指针的完整类型是 ”指向short 型的 Screen类的成员“的指针,这可以写为 : short Screen::* &&&&& 指向 short 型的 Screen类的成员的指针的定义如下 : & short Screen::*ps_S &&&& ps_Screen可以用_height 的地址初始化,如下: &short Screen::*ps_Screen = &Screen::_ &&&&& 类似地,它也可以用_width的地址赋值 如下&
ps_Screen = &Screen::_
& &&&& 定义一个成员函数指针需要指定函数返回类型、参数表和类,例如,指向Screen成员函数并且能够引用成员函数 height()和 width()的指针类型如下: & int (Screen::*) ()
&&&& 这种类型指定了一个指向类 Screen的成员函数的指针,它没有参数,返回值类型为int 。指向成员函数的指针可被声明、初始化及赋值如下: & // 所有指向类成员的指针都可以用 0 赋值 int (Screen::*pmf1)() = 0; int (Screen::*pmf2)() = &Screen:: &pmf1 = pmf2; pmf2 = &Screen::
&&&& 使用 typedef可以使成员指针的语法更易读,例如,下面的类型定义: & Screen& ( Screen::* ) ()
&&&& 也就是指向 Screen类成员函数的一个指针,该函数没有参数,返回Screen类对象的引用。它可以被下列 typedef定义 Action所取代: &typedef Screen& (Screen::*Action)(); &Action default = &Screen:: Action next = &Screen::
&&&& 指向成员函数类型的指针可以被用来声明函数参数和函数返回类型 ,我们也可以为成员函数类型的参数指定缺省实参,例如: & Screen& action( Screen&, Action );
&&& action()被声明为有两个参数 ,一个 Screen类对象的引用 和一个指向类 Screen的成员函数的指针,该函数没有参数,返回Screen类对象的引用。 action()可以以下列任意方式被调用: &Screen myS typedef Screen& (Screen::*Action)(); Action default = &Screen:: &extern Screen& action( Screen&, Action = &Screen::display ); &void ff() {
action( myScreen ); action( myScreen, default ); action( myScreen, &Screen::end ); }
& 二、使用指向类成员的指针 & &&&& 类成员的指针必须总是通过特定的对象或指向该类类型的对象的指针来访问 ,我们通过使用两个指向成员操作符的指针(针对类对象和引用的.* 以及针对指向类对象的指针的-&*)来做到这一点,例如,如下所示, 我们通过成员函数的指针调用成员函数: & int (Screen::*pmfi)() = &Screen:: Screen& (Screen::*pmfS)( const Screen& ) = &Screen:: &Screen myScreen, *bufS &// 直接调用成员函数 if ( myScreen.height() == bufScreen-&height() ) bufScreen-©( myScreen ); &// 通过成员指针的等价调用 if ( (myScreen.*pmfi)() == (bufScreen-&*pmfi)() ) (bufScreen-&*pmfS)( myScreen );
& &&&& 如下调用: & (myScreen.*pmfi)() (bufScreen-&*pmfi)()
&&& 要求有括号 因为调用操作符----()----的优先级高于成员操作符指针的优先级,没有括号&
myScreen.*pmfi() &&&& 将会被解释为& myScreen.*(pmfi())
&&&& 它会先调用函数 pmfi() 把它的返回值与成员对象操作符的指针 .* 绑定,当然 pmfi的类型不支持这种用法,会产生一个编译时刻错误。&
& &&&& 类似地 指向数据成员的指针可以按下列方式被访问: typedef short Screen::*ps_S &Screen myScreen, *tmpScreen = new Screen( 10, 10 ); &ps_Screen pH = &Screen::_ ps_Screen pW = &Screen::_ &tmpScreen-&*pH = myScreen.*pH; tmpScreen-&*pW = myScreen.*pW;
& &&&& 下面是在开始时讨论的成员函数 repeat()的实现,它被修改为用一个成员函数的指针作为参数: & typedef Screen& (Screen::*Action)(); &Screen& Screen::repeat( Action op, int time s ) { for ( int i = 0; i & ++i ) & (this-&*op)(); &return * }
& &&&& 参数 op 是一个成员函数的指针 它指向要被调用times 次的成员函数,若要为 repeat() 的参数提供缺省实参,则声明如下: &class Screen { public: Screen &repeat( Action = &Screen::forward, int = 1 ); // ... };
&&& repeat()的调用如下 : Screen myS myScreen.repeat(); // repeat( &Screen::forward, 1 ); myScreen.repeat( &Screen::down, 20 );
& 三、静态类成员的指针 & &&&& 在非静态类成员的指针和静态类成员的指引之间有一个区别,指向类成员的指针语法不能被用来引用类的静态成员,静态类成员是属于该类的全局对象和函数,它们的指针是普通指针。(请记住静态成员函数没有 this指针) & &&&& 指向静态类成员的指针的声明看起来与非类成员的指针相同,解引用该指针不需要类对象,例如 我们来看一下类 Account:&
& class Account { public: static void raiseInterest( double incr ); static double interest() { return _interestR } double amount() { return _ } private: static double _interestR double _ string _ }; &inline void Account::raiseInterest( double incr ) { _interestRate += }
&&& &_interestRate的类型是double* 而不是 : // 不是 &_interestRate 的类型 double Account::*
&&&& 指向_interestRate的指针定义如下:& // OK: 是 double*, 而不是 double Account::* double *pd = &Account::_interestR&&&& 它被解引用的方式与普通指针一样,不需要相关的类对象,例如:A
// 用普通的解引用操作符 double daily = *pd /365 * unit._
& &&&& 但是,因为_interestRate和_amount 都是私有成员,所以我们需要使用静态成员函数interest()和非静态成员函数amount()。&&&& 指向interest()的指针的类型是一个普通函数指针& // 正确 double (*) ()
&&&& 而不是类 Account 的成员函数的指针& // 不正确 double (Account::*) ()
&&&& 这个指针的定义和对 interest()的间接调用处理方式与非类的指针相同: & // ok: douple(*pf) () 不是 double(Account::*pf)() double (*pf)() = &Account::
double daily = pf () / 365 * unit.amount();& & & & &在C++ 中,当定义了一个类,类的成员中有指针的时候,需要注意:如果将类的对象A赋值给B的时候,两个对象的指针是指向同一个地址的,其余的变量都是相等的。在图像处理中,比如定义了一个图像类,类成员中有具体的图像数据,如果将两个对象直接相等,会使两个对象的数据指针指向同一个地址。如果要使两个对象的指向不同的地址可以为类定义一个函数,用于两个对象之间的直接赋值运算。下面以例子说明。
& & & & 声明了一个类:
class CImgData
int *pimgdata = new int [10];
int main()
CImgData A;
A.width = 2;
A.height = 3;
for (int i = 0; i & 10; i++)
A.pimgdata[i] = i * 7 + 5;
cout && &A 的地址是: & &&
cout && A.pimgdata &&
CImgData B;
//ACImgDataToBCImgData(A, B);
cout && &B 的地址是: & &&
cout && B.pimgdata &&
for (int m = 0; m & 10; m++)
cout && B.pimgdata[m] &&
cout && &B.width= & && B.width &&
cout && &B.height=& && B.height &&
cout && &completed& &&
& & & 运行结果:
& & & &可以看出:A和B的数据指针的地址相同,相关的变量被复制。
& & & & &改进:为类添加对象赋值函数
void ACImgDataToBCImgData(CImgData
A, CImgData
B.height = A.
B.width = A.
for (int i = 0; i & 10; i++)
B.pimgdata[i] = A.pimgdata[i];
int main()
CImgData A;
A.width = 2;
A.height = 3;
for (int i = 0; i & 10; i++)
A.pimgdata[i] = i * 7 + 5;
cout && &A 的地址是: & &&
cout && A.pimgdata &&
CImgData B;
ACImgDataToBCImgData(A, B);
cout && &B 的地址是: & &&
cout && B.pimgdata &&
for (int m = 0; m & 10; m++)
cout && B.pimgdata[m] &&
cout && &B.width= & && B.width &&
cout && &B.height=& && B.height &&
cout && &completed& && endl;
& & & & 运行结果如下:
& & & &结果显示:两个对象的指针地址不一样,因此连个对象的没有联系,只是单纯将相关的数据复制传递。但是,注意到这个时候width和height两个成员没有传递,是因为,数据变量作为函数的形参传递的时候,使用的拷贝的临时变量。
& & & &改进:使用引用传递。
& & & &将函数改为如下,其余的部分不变:
void ACImgDataToBCImgData(CImgData
&A, CImgData
B.height = A.
B.width = A.
for (int i = 0; i & 10; i++)
B.pimgdata[i] = A.pimgdata[i];
& & & &结果如下:
& & & &结果显示:两个对象的地址不同,同时相关变量的数据值成功传递。
参考知识库
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
访问:338次
排名:千里之外
原创:11篇指向类成员函数的指针其实并非指针
字体:[ ] 类型:转载 时间:
对于指向类成员的指针,必须紧记,指向类成员(非static)的指针并非指针
1、与常规指针不同,一个指向成员的指针并不指向一个具体的内存位置,它指向的是一个类的特定成员,而不是指向特定对象里的特定成员。通常最清晰的做法,是将指向数据成员的指针看作为一个偏移量。
这个偏移量告诉你,一个特定成员的位置距离对象的起点有多少个字节。
2、给定一个成员在类内的偏移量,为了访问位于那个偏移量的数据成员,我们需要该类的一个对象的地址。这时候就需要 .*和-&*的操作。pC-&*pimC,请求将pC内的地址加上pimC内的偏移量,为的是访问pC所指向的C对象中适当的数据成员。aC.*pimC,请求aC的地址加上pimC中的偏离量,也是为了访问pC所指向的C对象中适当的数据成员。Ps:*成员指针解引用操作符(.*)从对象或引用获取成员*成员指针箭头操作符(-&*)通过对象的指针获取成员
3、获取非静态成员函数的地址时,得到的不是一个地址,而是一个指向成员函数的指针。
4、为了对一个指向成员函数的指针进行解引用,需要一个对象或一个指向对象的指针。对于指向数据成员的指针的情形,为了访问该成员,需要将对象的地址和成员的偏移量相加。对于指向成员函数的指针的情形,需要将对象的地址用作this指针的值,进行函数调用,以及作为其他用途。
5、一个指向成员函数的指针的实现自身必须存储一些信息,诸如它所指向的成员函数是虚拟的还是非虚拟的,到哪里支找到的适当的虚函数表指针等等。6、另外补充一点关于指向内联函数的指针的问题。一个函数指针指向内联函数是合法的。然而,通过函数指针调用内联函数将不会导致内联式的函数调用,因为编译器无法在编译期精确地确定将会调用什么函数。因此在调用点,编译器别无他法,只好生成间接、非内联的函数调用代码。
您可能感兴趣的文章:
大家感兴趣的内容
12345678910
最近更新的内容
常用在线小工具自身类的对象不可以作为某类的成员吗,还是自身类的指针,自身类的引用_百度知道3527人阅读
笔试面试(12)
昨天去面试一家公司,面试题中有一个题,自己没弄清楚,先记录如下:
void printA()
cout&&&printA&&&
virtual void printB()
cout&&&printB&&&
main函数调用:
D *d=NULL;
d-&printA();
d-&printB();
输出结果是?
当时想的是对象d直接初始化为NULL,非虚的成员函数没有地址,应该找不到,而virtual成员函数,由于对象会有指向虚拟函数表的指针-vptr,指向virtual函数列表的虚拟函数表,这样应该能够取到地址(实际上,这个virtual函数的printB最应该想到是直接崩溃,因为d指向NULL,即地址为0x,再去找虚地址指针,肯定是不允许的)。
下面具体分析一下吧
先看一下类的成员函数的情况,
virtual ~C(){}
类A、B、C三个类,一个是什么都没有的真的空类,一个是有成员函数的类,最后一个是带有虚函数的类。
那他们分别咱的内存大小是多少呢?
cout&&&A=&&&sizeof(A)&&
cout&&&B=&&&sizeof(B)&&
cout&&&C=&&&sizeof(C)&&
32位windows xp机器上测试结果:
从A和B的比较可以看出成员函数是不占用类空间的,再具体一个例子:
sizeof(E)在32位机器上输出结果,如果不考虑对齐 为5,考虑则为8,可见和上面B类的预期一致。
&& &我们可以说,静态数据成员和静态成员函数时类的一部分,而不是对象的一部分(谭老师说的)。
当我们实例化一个对象的时候,因为这个对象是用类定义的,那么它理所当然拥有了这个类的数据和函数。但是,一般情况下,不同的对象,他们的的数据值不同,但是函数的代码都相同。所以,为了节约存储空间(想象一下我们如果定义了100个对象,那么用100段内存空间存储相同的代码,岂不是很浪费?),我们让成员函数的代码共享。
&& & &我们把成员函数的代码存储在对象空间之外。换句话说,成员函数的代码,都不占据对象的存储空间。它会被存在其他地方。
&& & &所以类的成员函数,对于类来讲。一方面是逻辑上的“属于”,一方面是物理上的“不依赖“。
&& & &回到思考题上来,对于非静态成员函数来说,它当然是对象的一部分。(只是因为存储方式上的特殊性,容易让人误解!)
回答开头问题:
类中包括成员变量和成员函数
new出来的只是成员变量,成员函数始终存在
所以如果成员函数未使用任何成员变量的话,不管是不是static的,都能正常工作
一个对象的空间=所有成员变量的大小
如果这个对象的类有虚函数的话,还可能多一个指向虚表的指针
所有函数存放在独立于对象的存储空间内
对象调用函数时,对静态成员函数直接调用不存在问题,对成员函数需要把自己以this指针传给函数以指明以哪个对象调用
所以用未初始化的指针调用静态成员函数、或者调用未使用任何成员变量的成员函数(即未用到this指针)
从理论上都是可行的,至于具体支不支持看各个编译器吧
main函数中,如果
&& &d.printA();
&& &d.printB();
都能正常输出,d在栈上。。。
参考知识库
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
访问:696849次
积分:7657
积分:7657
排名:第1848名
原创:147篇
转载:19篇
评论:92条
(1)(1)(1)(1)(1)(1)(1)(2)(1)(3)(1)(2)(1)(3)(3)(2)(7)(3)(12)(14)(15)(3)(2)(5)(1)(7)(1)(2)(1)(1)(1)(1)(1)(3)(6)(3)(16)(9)(12)(19)(5)