从汇编看c++中指向成员变量的指针(一)中讨论的情形没有虚拟继承,下面来看看,当加入了虚拟继承的时候,指向成员变量的指针有什么变化。

下面是c++源码:

#include <iostream>
#include <cstdio>
using namespace std; class Top {
public:
int _top;
};
class Left : public virtual Top {
public:
int _left;
}; class Right : public virtual Top {
public:
int _right;
}; class Bottom : public Left, public Right {
public:
int _bottom; }; int main() {
Bottom b;
Bottom* bp = &b;
Top* tp = bp;
Left* lp = bp;
Right* rp = bp; //虚基类Top中的成员变量指针
int Top::*tmp1 = &Top::_top;
//Left中的成员变量指针
int Left::*lmp1 = &Left::_top;
int Left::*lmp2 = &Left::_left;
//Right中的成员变量指针
int Right::*rmp1 = &Right::_top;
int Right::*rmp2 = &Right::_right;
//Bottom中的成员变量指针
int Bottom::*bmp1 = &Bottom::_top;
int Bottom::*bmp2 = &Bottom::_left;
int Bottom::*bmp3 = &Bottom::_right;
int Bottom::*bmp4 = &Bottom::_bottom; //输出各成员变量指针的大小
cout << "各成员变量指针的大小" << endl;
cout << "sizeof(tmp1) = " << sizeof(tmp1) << endl;
cout << "sizeof(lmp1) = " << sizeof(lmp1) << endl;
cout << "sizeof(lmp2) = " << sizeof(lmp2) << endl;
cout << "sizeof(rmp1) = " << sizeof(rmp1) << endl;
cout << "sizeof(rmp2) = " << sizeof(rmp2) << endl;
cout << "sizeof(bmp1) = " << sizeof(bmp1) << endl;
cout << "sizeof(bmp2) = " << sizeof(bmp2) << endl;
cout << "sizeof(bmp3) = " << sizeof(bmp3) << endl;
cout << "sizeof(bmp4) = " << sizeof(bmp4) << endl; //输出个成员变量指针的值
cout << "各成员变量指针的值" << endl;
printf("&Top::_top = %d\n", &Top::_top);
printf("tmp1 = %d\n", tmp1);
cout << endl;
printf("&Left::_top = %d\n", &Left::_top);
printf("lmp1 = %d\n", lmp1);
printf("&Left::_left = %d\n", &Left::_left);
printf("lmp2 = %d\n", lmp2);
cout << endl;
printf("&Right::_top = %d\n", &Right::_top);
printf("rmp1 = %d\n", rmp1);
printf("&Right::_right = %d\n", &Right::_right);
printf("rmp2 = %d\n", rmp2);
cout << endl;
printf("&Bottom::_top = %d\n", &Bottom::_top);
printf("bmp1 = %d\n", bmp1);
printf("&Bottom::_left = %d\n", &Bottom::_left);
printf("bmp2 = %d\n", bmp2);
printf("&Bottom::_right = %d\n", &Bottom::_right);
printf("bmp3 = %d\n", bmp3);
printf("&Bottom::_bottom = %d\n", &Bottom::_bottom);
printf("bmp4 = %d\n", bmp4); bp->*bmp1 = ;
bp->*bmp2 = ;
bp->*bmp3 = ;
bp->*bmp4 = ; bmp1 = tmp1;
bmp2 = lmp2;
bmp3 = rmp2; }

下面是程序运行的结果:

通过程序运行结果,我们可以得到2种信息:

1 包含虚拟继承的时候,成员变量指针仍然指向的不是成员变量在内存中的真正地址(bmp3和&Bttom::_right的值仍然不一样)

2 包含虚拟继承的时候,成员变量指针都为8字节,而不是4字节,比如c++代码中除了tmp1成员变量指针之外的其他成员变量指针的大小

那么,包含虚拟继承的时候,成员变量指针存储的值是什么,为什么会是8字节?下面通过分析来分析类Bottom中成员变量指针定义的汇编代码:

; 40   :   //Bottom中的成员变量指针
; 41 : int Bottom::*bmp1 = &Bottom::_top; mov DWORD PTR $T24720[ebp], ;将0写入临时对象ST24720的首地址处内存
mov DWORD PTR $T24720[ebp+], ;将4写入偏移临时对象ST24720首地址4byte处内存
mov ecx, DWORD PTR $T24720[ebp];将临时对象ST24720首地址处内存内容给寄存器ecx
mov DWORD PTR _bmp1$[ebp], ecx;将寄存器ecx的值写入bmp1首地址处内存
mov edx, DWORD PTR $T24720[ebp+];将偏移临时对象ST24720首地址4byte处内存内容给寄存器edx
mov DWORD PTR _bmp1$[ebp+], edx;将寄存器edx的内容给偏移bmp1首地址4byte处内存 ; 42 : int Bottom::*bmp2 = &Bottom::_left;
;过程同bmp1,只是存储的值不同 mov DWORD PTR $T24721[ebp],
mov DWORD PTR $T24721[ebp+],
mov eax, DWORD PTR $T24721[ebp]
mov DWORD PTR _bmp2$[ebp], eax
mov ecx, DWORD PTR $T24721[ebp+]
mov DWORD PTR _bmp2$[ebp+], ecx ; 43 : int Bottom::*bmp3 = &Bottom::_right;
;和bmp1有点不一样 这里仅就汇编程序的实际执行流程来分析,其它的忽略
xor edx, edx;将寄存器edx里面的内容异或运算,这时,不管edx里面是什么,此时存的值一定是0
cmp edx, -;将edx的值同-1比较
jne SHORT $LN9@main;根据比较结果,如果edx的值不是-1,就跳到标号$LN9@main处执执行,这里显然是要跳转执行
mov DWORD PTR $T24722[ebp],
mov DWORD PTR $T24722[ebp+], -
mov eax, DWORD PTR $T24722[ebp]
mov DWORD PTR $T24726[ebp], eax
mov ecx, DWORD PTR $T24722[ebp+]
mov DWORD PTR $T24726[ebp+], ecx
jmp SHORT $LN10@main
$LN9@main:
xor edx, edx;将edx寄存器里面的内容异或运算,这时,不管edx里面是什么,此时存的值一定是0
jne SHORT $LN7@main;如果异或的结果不为零,就跳转到标号$LN7@main处执行,否则,顺序执行,这里显然是顺序执行
mov DWORD PTR tv89[ebp], ;将8给临时变量tv89(8刚好是sizof(Left))
jmp SHORT $LN8@main;跳转到标号$LN8@main处执行
$LN7@main:
mov DWORD PTR tv89[ebp],
$LN8@main:
mov eax, DWORD PTR tv89[ebp];将tv89的值给寄存器eax
add eax, ;将寄存器eax里面的值加上4 (4刚好是父类Right子对象中vbtable指针的大小)
mov DWORD PTR $T24723[ebp], eax;将寄存器eax的值写入临时对象ST24723首地址处内存
mov DWORD PTR $T24723[ebp+], ;将0写入偏移临时对象首地址4byte处内存
mov ecx, DWORD PTR $T24723[ebp];将临时对象ST24723首地址处内容给寄存器ecx
mov DWORD PTR $T24726[ebp], ecx;将寄存器ecx的值写入临时对象ST24726的首地址处内存
mov edx, DWORD PTR $T24723[ebp+];将偏移临时对象ST24723首地址4byte处内存内容给寄存器edx
mov DWORD PTR $T24726[ebp+], edx;将寄存器edx的内容给偏移临时对象ST24726首地址4byte处内存
$LN10@main:
mov eax, DWORD PTR $T24726[ebp];将临时对象ST24726首地址处内存给寄存器eax
mov DWORD PTR _bmp3$[ebp], eax;将寄存器eax的内容给bmp3首地址处内存
mov ecx, DWORD PTR $T24726[ebp+];将偏移临时对象ST24726首地址4byte处内存内容给寄存器ecx
mov DWORD PTR _bmp3$[ebp+], ecx;将寄存器ecx的内容给偏移bmp3首地址4byte处内存内容 ; 44 : int Bottom::*bmp4 = &Bottom::_bottom;
;过程同bmp1,只是存储的值不同 mov DWORD PTR $T24729[ebp], ; 00000010H
mov DWORD PTR $T24729[ebp+],
mov edx, DWORD PTR $T24729[ebp]
mov DWORD PTR _bmp4$[ebp], edx
mov eax, DWORD PTR $T24729[ebp+]
mov DWORD PTR _bmp4$[ebp+], eax

可以看到,bmp1~bmp4被当成了对象看待,其里面存储的值如下:

        

里面存储的值知道了,但是到底都有什么意义呢?下面我们就来看用着4个成员变量指针操作相应成员变量的汇编码:

   :   bp->*bmp1 = ;

    mov    ecx, DWORD PTR _bp$[ebp];将对象b的首地址给寄存器ecx
mov edx, DWORD PTR [ecx];将对象b首地址处内容(即vbtable的首地址)给寄存器edx
mov eax, DWORD PTR _bmp1$[ebp+];将偏移bmp1首地址4byte处内容(即4)给寄存器eax
mov ecx, DWORD PTR _bp$[ebp];将对象b首地址给寄存器ecx
add ecx, DWORD PTR [edx+eax];寄存器edx里面存储vbtable首地址,而eax存储的是4
;因此edx+eax仍然是偏移vbtable首地址4byte处内存地址,所以这条指令是
;获取偏移vbtable首地址4byte处内存内容(存储的是vbtable指针偏移虚基类Top子对象首地址的偏移量,为20)
;与寄存器ecx内容相加,结果保存到寄存器ecx
;所以,ecx里面保存的是虚基类Top子对象的首地址
mov edx, DWORD PTR _bmp1$[ebp];将bmp1首地址处内存内容(即0)给寄存器edx
mov DWORD PTR [ecx+edx], ;寄存器ecx保存虚基类Top子对象首地址,寄存器edx里面内容为0 所以ecx+edx仍然是虚基类
;Top子对象首地址,这里将1写入虚基类首地址处内存,即给对象b成员变量_top赋值 ; 83 : bp->*bmp2 = 2; mov eax, DWORD PTR _bp$[ebp];将对象b的首地址给寄存器eax
mov ecx, DWORD PTR [eax];获取对象b首地址内容(即vbtable的首地址)给寄存器ecx
mov edx, DWORD PTR _bmp2$[ebp+];将偏移bmp2首地址4byte处内存内容(即0)给寄存器edx
mov eax, DWORD PTR _bp$[ebp];将对象b首地址给寄存器eax
add eax, DWORD PTR [ecx+edx];寄存器ecx里面存的是vbtable首地址,edx里面存的是0 所以ecx+edx
;仍然是vbtable首地址,这里获取的是vbtable首地址处内容(即vbtable指针偏移对象b首地址的偏移量,为0)
;所以,这里取vbtable首地址处内存内容,在和寄存器eax里面内容相加,结果保存到eax里面
;此时eax保存的是对象b的首地址
mov ecx, DWORD PTR _bmp2$[ebp];将bmp2首地址处的内容(即4)给寄存器ecx
mov DWORD PTR [eax+ecx], ;eax保存对象b的首地址 ecx内容为4 eax+ecx为对象b成员变量_left实际的内存地址
;因此这里是给对象b的成员变量_left赋值 ; 84 : bp->*bmp3 = 3;
;和bp->*bmp3 = 2的操作相似,只是数据不同 mov edx, DWORD PTR _bp$[ebp]
mov eax, DWORD PTR [edx]
mov ecx, DWORD PTR _bmp3$[ebp+]
mov edx, DWORD PTR _bp$[ebp]
add edx, DWORD PTR [eax+ecx]
mov eax, DWORD PTR _bmp3$[ebp]
mov DWORD PTR [edx+eax], ; 85 : bp->*bmp4 = 4;
;和bp->*bmp2 = 2的操作相似,只是数据不同 mov ecx, DWORD PTR _bp$[ebp]
mov edx, DWORD PTR [ecx]
mov eax, DWORD PTR _bmp4$[ebp+]
mov ecx, DWORD PTR _bp$[ebp]
add ecx, DWORD PTR [edx+eax]
mov edx, DWORD PTR _bmp4$[ebp]
mov DWORD PTR [ecx+edx],

通过汇编码我们可以发现,bmp1~bmp4中所存储的值的意义,第一项仍然是偏移成员变量所属类对象首地址的偏移量(但是虚基类成员变量指针有点不一样,比如bmp1第一项存储的是0,是对象b的_top成员变量相对于虚基类Top子对象首地址的偏移量,而不是相对于对象b的首地址偏移量);而第二项是偏移vtable首地址的偏移量。成员变量指针通过这两个数据,以及绑定的对象或者对象指针(仍然相当于this指针容器)来计算出成员变量在内存中的真正地址。至于lmp1~lmp2 rmp1~rmp2所存储的值,和bmp1~bmp2类似。下面是Top Left Right Bottom的内存布局:

     

之所以包含虚拟继承的成员变量指针需要额外的字节来存储信息,就是因为当存在虚基类的时候,虚基类的位置是不固定的。比如,如果Bottom又派生了一个SubBottom子类(非虚拟继承),且该子类引入了一个新的成员变量int  _subBottom,那么SubBottom的内存布局中,_subBottom就会加到_bottom的后面,_top的前面。这样,_top在类Bottom中距离首地址是20,在SubBottom中就会成为24,而_left和_right位置不会变,在SubBottom中距离其首地址仍然是4和12。类似的,如果SubBottom也派生了一个子类(非虚拟继承)SubSubBotom,且该子类也引入了一个成员变量int _subSubBottom,类SubSubBottom的内存布局中,subSubBottom就会加到subBottom后面,_top前面,这样,_top距离SubSubBottom的首地址偏移量变成了28,而_left _right仍然是4和12.但是,其在虚基类Top里面的偏移量是固定的,总是0.所以通过虚基类成员变量指针,比如bmp1来操作虚基类成员变量,总是要先定位相应的虚基类首地址,然后通过虚基类成员变量(_top)相对于虚基类(Top)首地址的偏移量来得出虚基类成员变量(_top)在内存中的真正地址。而要定位虚基类(Top)首地址,就要有相应的vbtable信息,这就是成员变量指针中的额外字节存储的信息。

从汇编看c++中指向成员变量的指针(一)中一样,基类成员变量指针可以绑定到派生类对象或者对象指针上面,同时也可以绑定到由派生类对象指针向上转型到基类的指针上面,编译器内部做和从汇编看c++中指向成员变量的指针(一)中一样的转化。

成员变量指针之间的转换

这种情况之下,基类成员变量指针也可以转换成派生类成员变量指针,因为基类中的成员变量一定存在于派生类中,但是,派生类成员变量指针无法转换成基类成员变量指针,因为派生类中存在的成员变量,基类中不一定存在。和从汇编看c++中指向成员变量的指针(一)中讲的一样,对于bmp1(8byte) = tmp1(4byte) bmp2 = lmp2 bmp3 = rmp3的转化,并不是将后者的值赋给前者,而是编译器内部进行转化,下面给出汇编码:

   :   bmp1 = tmp1;

    cmp    DWORD PTR _tmp1$[ebp], -;比较tmp1的值和-1的大小 如果tmp1等于-1 那么说明tmp1还为指向任何成员变量
jne SHORT $LN11@main;如果不想等,就跳转到标号$LN11@main处执行 否则,顺序执行 这里顺序执行
mov DWORD PTR $T24736[ebp], ;将0写入临时对象ST24736的首地址处内存
mov DWORD PTR $T24736[ebp+], -;将-1写入偏移临时对象ST24736首地址4byte处内存
mov eax, DWORD PTR $T24736[ebp];将临时对象ST24736首地址处内存内容给寄存器eax
mov DWORD PTR $T24738[ebp], eax;将寄存器eax的值给临时对象ST24738首地址处内存
mov ecx, DWORD PTR $T24736[ebp+];将偏移临时对象首地址4byte处内存内容给寄存器ecx
mov DWORD PTR $T24738[ebp+], ecx;将ecx的值写入偏移临时对象ST24738首地址4byte处内存
jmp SHORT $LN12@main;跳转到标号$LN12@main处执行
$LN11@main:
mov edx, DWORD PTR _tmp1$[ebp];将tmp1的值给寄存器edx
mov DWORD PTR $T24737[ebp], edx;将寄存器edx的值给临时对象ST24737的首地址处内存
mov DWORD PTR $T24737[ebp+], ;将4偏移临时对象ST24737首地址4byte处内存
mov eax, DWORD PTR $T24737[ebp];将临时对象ST24737首地址处内存内容给寄存器eax
mov DWORD PTR $T24738[ebp], eax;将寄存器eax的值写入临时对象ST24738首地址处内存
mov ecx, DWORD PTR $T24737[ebp+];将偏移临时对象ST24737首地址4byte处内存内容给寄存器ecx
mov DWORD PTR $T24738[ebp+], ecx;将寄存器ecx的内容给偏移临时对象ST42738首地址4byte处内存
$LN12@main:
mov edx, DWORD PTR $T24738[ebp];将临时对象ST24738首地址处内存内容给寄存器edx
mov DWORD PTR _bmp1$[ebp], edx;将edx的内容给bmp1首地址处内存
mov eax, DWORD PTR $T24738[ebp+];将偏移临时对象ST24738首地址4byte处内存内容给寄存器eax
mov DWORD PTR _bmp1$[ebp+], eax;将eax的内容给偏移bmp1首地址4byte处内存 ; 88 : bmp2 = lmp2; mov ecx, DWORD PTR _lmp2$[ebp];将lmp2首地址处内存内容给寄存器ecx
mov DWORD PTR _bmp2$[ebp], ecx;将ecx的内容给bmp2首地址处的内存
mov edx, DWORD PTR _lmp2$[ebp+];将偏移lmp2首地址4byte处内存内容给寄存器edx
mov DWORD PTR _bmp2$[ebp+], edx;将edx的内容给偏移bmp2首地址4byte处内存 ; 89 : bmp3 = rmp2; cmp DWORD PTR _rmp2$[ebp+], -;将偏移rmp2首地址4byte处内存内容与-1比较,如果等于-1 说明rmp2没有指向任何成员变量
jne SHORT $LN13@main;如果比较结果相等,顺序执行,否则,跳转到标号$LN13@main处执行,这里跳转到标号执行
mov DWORD PTR $T24741[ebp], ;将0写入临时对象ST24741首地址处内存
mov DWORD PTR $T24741[ebp+], -;将-1写入偏移临时对象ST24741首地址4byte处内存
mov eax, DWORD PTR $T24741[ebp];将临时对象ST24741首地址处内存内容给寄存器eax
mov DWORD PTR $T24743[ebp], eax;将寄存器eax内容给临时对象ST24743首地址处内存
mov ecx, DWORD PTR $T24741[ebp+];将偏移临时对象ST24741首地址4byte处内存内容给寄存器ecx
mov DWORD PTR $T24743[ebp+], ecx;将ecx的内容给偏移临时对象ST24743首地址4byte处内存内容
jmp SHORT $LN14@main;跳转到标号$LN14@main处执行
$LN13@main:
mov edx, DWORD PTR _rmp2$[ebp+];将偏移rmp2首地址4byte处内存内容给寄存器edx
neg edx;这条指令的作用是求edx里面内容的补码,即0-edx内容 并且影响标志寄存器里面的标志位CF 结果存于edx
sbb edx, edx;这条指令是带标志位减法,即被减数-减数-标志位CF,结果存于edx
and edx, - ; 将edx里面内容和-8相与,结果存于edx
add edx, ;将edx里面的内容和8相加,结果存于edx
add edx, DWORD PTR _rmp2$[ebp];将edx里面的内容和rmp2首地址处内容相加,结果存于edx
mov DWORD PTR $T24742[ebp], edx;将edx的值给临时对象ST24742首地址处内存
mov eax, DWORD PTR _rmp2$[ebp+];将偏移rmp2首地址4byte处内存内容给寄存器eax
mov DWORD PTR $T24742[ebp+], eax;将eax的值给偏移临时丢向ST24742首地址4byte处内存内容
mov ecx, DWORD PTR $T24742[ebp];将临时对象ST24742首地址处内存内容给寄存器ecx
mov DWORD PTR $T24743[ebp], ecx;将ecx的值给临时对象ST24743的首地址处内存
mov edx, DWORD PTR $T24742[ebp+];将偏移临时对象ST24743首地址4byte处内存内容给寄存器edx
mov DWORD PTR $T24743[ebp+], edx;将edx的值给偏移临时对象ST24743首地址4byte处内存
$LN14@main:
mov eax, DWORD PTR $T24743[ebp];将临时对象ST24743首地址处内存内容给寄存器eax
mov DWORD PTR _bmp3$[ebp], eax;将eax的值给bmp3首地址处内存
mov ecx, DWORD PTR $T24743[ebp+];将偏移临时对象ST24743首地址4byte处内存内容给寄存器ecx
mov DWORD PTR _bmp3$[ebp+], ecx;将ecx的值给偏移bmp3首地址4byte处内存

通过汇编码可以看到,虽然bmp1为8字节,tmp1为4字节,但是编译器内部仍然能够执行正确的转换操作。而且转换后bmp1 bmp2 bmp3的值和直接让bmp1 = &Bottom::_top bmp2 = &Bottom::_left bmp3 = &Bottom::_right效果一样。

下面来看进行bmp2 = rmp2转换时的一段汇编码:

mov    edx, DWORD PTR _rmp2$[ebp+];将偏移rmp2首地址4byte处内存内容给寄存器edx
neg edx;这条指令的作用是求edx里面内容的补码,即0-edx内容 并且影响标志寄存器里面的标志位CF 结果存于edx
sbb edx, edx;这条指令是带标志位减法,即被减数-减数-标志位CF,结果存于edx
and edx, - ; 将edx里面内容和-8相与,结果存于edx
add edx, ;将edx里面的内容和8相加,结果存于edx
add edx, DWORD PTR _rmp2$[ebp];将edx里面的内容和rmp2首地址处内容相加,结果存于edx

rmp2是类Right的成员变量指针,而一个类的成员变量指针可以指向该类的任何成员变量,因此rmp2既可能指向类Right中的_right(本例中的情况),也可能指向的是类Right中的_top成员变量,而指向虚基类成员变量的指针存储的值的意义和非虚基类成员变量指针存储的值的意义是不一样的,编译器并不清楚rmp2到底指向的是谁,因此要做一些处理。

如果rmp2指向的是类Right中的_right成员变量(非虚基类成员变量),当执行上面第1行汇编码的时候,edx的值为0;当执行第2行汇编码的时候,0-0 = 0,不产生借位,因此CF置0,edx = 0;当执行第3行汇编的时候 0 - 0 - 0 = 0,因此edx = 0;当执行地4行汇编码的时候,0 & -8 = 0,因此edx = 0;当执行第5行汇编码的时候,0 + 8 = 8,因此edx = 8;当执行第6行汇编码的时候,8 + 4 = 12,因此edx = 12,转化的时候原来的偏移量增加了8(类Left的大小)这个值刚好是类Bottom中_right成员变量相对于对象b首地址的偏移量。

如果rmp2指向的是类Rihgt的_top成员变量(x虚基类成员变量),当执行第一行汇编码时,edx = 4;当执行第二行汇编码时,0 - 4 = -4,产生了借位,因此CF被置1,edx = fffffff4(-4的补码);当执行第3行汇编码时 fffffff4 - fffffff4 - 1 = -1,因此edx = ffffffff(-1的补码);当执行第4行汇编码时 ffffffff & -8 = -8 因此edx = fffffff8(-8的补码);当执行第5行汇编码时,-8 + 8 = 0,因此edx = 0;当执行第6行汇编码时 0 + 0 = 0,因此edx = 0,转换的时候原来存储的偏移量未变。其值刚好是类Bottom中的_top成员变量相对于虚基类子对象Top首地址的偏移量。

也就是说,不管rmp2指向的到底是哪个成员变量,编译器都能保证正确的转换。

bmp2 = lmp2没有这个过程的原因是,父类Left子对象和对象b拥有相同的首地址,因此,不管lmp2指向的是_left,还是_top,原来的偏移量都不需要改变。

附 tmp1 lmp1 lmp2 rmp1 rmp2成员变量指针存储的值

            

从汇编看c++中指向成员变量的指针(二)的更多相关文章

  1. 从汇编看c++中指向成员变量的指针(一)

    在c++中,指向类成员变量的指针存储的并不是该成员变量所在内存的地址,而仅仅是该成员变量在该类对象中相对于对象首地址的偏移量.因此,它必须绑定到某一个对象或者对象指针上面,这里的对象和对象指针,就相当 ...

  2. 为什么c++中返回成员变量的指针,会破坏了封装?

    上述代码中,get()函数返回的是类成员变量的name的地址,这是很危险的,name是私有的,意味这不想被客户访问,但是如果返回name的地址,那么外部函数就可以修改name,这就破坏了封装性. 为什 ...

  3. 从汇编看c++中的多态

    http://www.cnblogs.com/chaoguo1234/archive/2013/05/19/3079078.html 在c++中,当一个类含有虚函数的时候,类就具有了多态性.构造函数的 ...

  4. 从汇编看c++中临时对象的析构时机

    http://www.cnblogs.com/chaoguo1234/archive/2013/05/12/3074425.html c++中,临时对象一旦不需要,就会调用析构函数,释放其占有的资源: ...

  5. C++中使用初始化列表比在构造函数中对成员变量赋值更高效

    这是在面试中遇到的一个问题,没有答出来,后来上网上查了一些资料,终于弄明白了: 一.首先c++标准规定成员变量必须在调用构造函数前进行初始化(这一点很重要) 二.如果我们在构造函数中对成员变量进行初始 ...

  6. C++中,如何定义和使用指向成员函数的指针

    /*** 定义指向成员函数的指针变量的形式 : 成员函数返回类型 (类名∷*指针变量名)(参数列表)* 成员函数指针变量值的形式 : &类名∷成员函数名;* 成员函数指针变量使用形式 : (对 ...

  7. 继承的基本概念: (1)Java不支持多继承,也就是说子类至多只能有一个父类。 (2)子类继承了其父类中不是私有的成员变量和成员方法,作为自己的成员变量和方法。 (3)子类中定义的成员变量和父类中定义的成员变量相同时,则父类中的成员变量不能被继承。 (4)子类中定义的成员方法,并且这个方法的名字返回类型,以及参数个数和类型与父类的某个成员方法完全相同,则父类的成员方法不能被继承。 分析以上程

    继承的基本概念: (1)Java不支持多继承,也就是说子类至多只能有一个父类. (2)子类继承了其父类中不是私有的成员变量和成员方法,作为自己的成员变量和方法.(3)子类中定义的成员变量和父类中定义的 ...

  8. Java学习笔记十五:Java中的成员变量和局部变量

    Java中的成员变量和局部变量 一:成员变量: 成员变量在类中定义,用来描述对象将要有什么 成员变量可以被本类的方法使用,也可以被其他类的方法使用,成员变量的作用域在整个类内部都是可见的 二:局部变量 ...

  9. Java接口中的成员变量为什么必须声明为public static final?

    我想对于每个Java程序员来说,接口都不陌生,接口中的方法也经常使用.而接口中的成员变量,就显得用得少一点,而对于成员变量为什么必须声明为public static final,可能就更不清楚了,而且 ...

随机推荐

  1. $(document).ready(function(){})和window.onload=function(){}的比较

    这两个函数想必每个前端开发者都不会很陌生,但是很了解用法的人估计就比较少了,博主也是最近才开始注意到这两个函数的区别. 首先$(document).ready(function(){})等同于$(). ...

  2. 控制点:ControlPoint

    位于control:Points面板下,kitControl面板的ControlPallette中也存在控制点. 控制点是什么呢?一个数据值.一个传感器的值.比如,温度值,风速值,压力值,光照值,开关 ...

  3. javascript写的新闻滚动代码

    在企业站中,我们会看到很多新闻列表很平滑的滚动,但是这种功能自己写太浪费时间,下面是我整理好的一组很常用的新闻列表滚动,有上下分页哦! 1.body里面 <div class="tz_ ...

  4. 轻松实现HTML5时钟(分享下自己对canvas的理解,原来没你想像的那么难哦)

    Hey,guys! 让我们一起用HTML5实现一下简易时钟吧! 接触canvas时, 我突然有一种非常熟悉的感觉------canvas的部分的功能其实和Photoshop中的 钢笔工具 是一样的.所 ...

  5. 利用fiddler录制脚本

    特性说明: 版本:V4.4 用途:将fiddler抓取的请求,导出为jmx格式,方便jmeter直接调用 新增功能: 1.在测试计划下,新增[HTTP请求默认值],内容为空,后续需将站点的IP和端口填 ...

  6. mysql 执行reset master 风险

    reset master 会把mysql实例上的所以二进制日志删除,并且日志序列从1开始:这样会引起两个问题. 001.问题一 slave 由于找不到下一个要执行的事件所以会报错.进一步master- ...

  7. C# 语法技巧_三目运算_switch_case

    一.三目运算符 三目运算符的一种简便写法: bool b = str == "abc" ? false : true; 当是自身时,实际上别吝啬那一个括号,有一个括号,实际上更容易 ...

  8. PKU 3667 Hotel(线段树)

    Hotel The cows are journeying north to Thunder Bay in Canada to gain cultural enrichment and enjoy a ...

  9. 【LeetCode练习题】Minimum Path Sum

    Minimum Path Sum Given a m x n grid filled with non-negative numbers, find a path from top left to b ...

  10. wxPython Major类

    转载自:http://www.yiibai.com/wxpython/wxpython_major_classes.html   原始的 wxWidgets(用C++编写)是一个巨大的类库.GUI类从 ...