转载自:http://blog.csdn.net/cxsjabcabc/article/details/7623630
源代码下载地址:
http://www.rayfile.com/zh-cn/files/c5d15e54-c18e-11e1-a565-0015c55db73d/
如下代码:
#include <iostream> using namespace std; #define COUT_ENDL(str) std::cout << #str << " is " << (str) << std::endl; class Test { public: Test() // 构造函数不占类大小 { std::cout << "Test is called..." << std::endl; } ~Test() // 析构函数不占类大小 { std::cout << "~Test is called..." << std::endl; } void show() const { } // 非虚函数不管有几个,都不占用类大小 void show1(){} void show2(){} void show3(){} //virtual void virtual_show() { }// 只要出现一个虚函数,不管有几个,开始占用4个字节 //virtual void virtual_show1() { } //virtual void virtual_show2() { } }; int main (int argc, const char * argv[]) { COUT_ENDL(sizeof(Test)) system("pause"); return 0; }
运行结果:
A: 根据上面sizeof得到的值,它应该是不占用的。从实际上说,从代码段的角度,它们和c函数中的全局函数没什么不同;只不过如何访问它们可能有限制而已。经过测试,发现如果想直接获取Test类构造函数的地址,赋值给c风格函数指针,编译器总是报错,必须采用指向类成员函数指针的方式才能使用,所以这里无法给出一个很好证明它类似全局函数的例子。
A: 通过地址来修改数据,c++无法阻止这样的操作,只要它不越界访问。如下代码:
#include <iostream> using namespace std; #define COUT_ENDL(str) std::cout << #str << " is " << (str) << std::endl; class A { public: A(int value_one, int value_two):_value_one(value_one), _value_two(value_two) { } int value_one() const { return _value_one; } int value_two() const { return _value_two; } private: int _value_one; int _value_two; }; int main (int argc, const char * argv[]) { A a(10, 100); COUT_ENDL(a.value_one()) COUT_ENDL(a.value_two()) A *pa = &a; *(int *)pa = 11; // modify a's _value_one *((int *)pa + 1) = 111; // modify a's _value_two COUT_ENDL(a.value_one()) COUT_ENDL(a.value_two()) system("pause"); return 0; }
运行结果:
a.value_one() is 10 a.value_two() is 100 a.value_one() is 11 a.value_two() is 111
显然,此方法有效,而且根本不用担心所操作对应的成员是否是protected或者private的。
而且可以确定的是,对象a中成员_value_one地址为a首地址;_value_two的地址紧挨着成员_value_one.
A: 如下示例:
#include <iostream> using namespace std; #define COUT_ENDL(str) std::cout << #str << " is " << (str) << std::endl; class A { public: A(int value_one, int value_two):_value_one(value_one), _value_two(value_two) { } int value_one() const { return _value_one; } int value_two() const { return _value_two; } private: int _value_one; int _value_two; }; class A_Ex : public A { public: A_Ex(int one, int two, int three):A(one, two), _value_three(three) { } int value_three() const { return _value_three; } private: int _value_three; }; int main (int argc, const char * argv[]) { A_Ex a(10, 100, 1000); A *pa = &a; COUT_ENDL(*(int *)pa) COUT_ENDL(*((int *)pa + 1)) COUT_ENDL(*((int *)pa + 2)) system("pause"); return 0; }
*(int *)pa is 10 *((int *)pa + 1) is 100 *((int *)pa + 2) is 1000
可以看出,A_Ex类继承A类,它的对象首先存放基类成员信息,接着存放本类成员信息,很简单,很直接。
A: 如下代码:
#include <iostream> using namespace std; #define COUT_ENDL(str) std::cout << #str << " is " << (str) << std::endl; class A { public: A(int value_one, int value_two):_value_one(value_one), _value_two(value_two) { } int value_one() const { return _value_one; } int value_two() const { return _value_two; } virtual void show() const { } private: int _value_one; int _value_two; }; class A_Ex : public A { public: A_Ex(int one, int two, int three):A(one, two), _value_three(three) { } int value_three() const { return _value_three; } private: int _value_three; }; int main (int argc, const char * argv[]) { A a(10, 100); A_Ex a_ex(97, 98, 99); COUT_ENDL(sizeof(A)) COUT_ENDL(sizeof(A_Ex)) system("pause"); return 0; }
如果在函数结束前加上断点,并打印变量a和a_ex内部数据,得到如下:
很明显,可以看到,虚表指针占用了对象最初的部分,后面接着是成员变量。
A: 根据上面的输出数据,虚表指针保存在对象首地址处。下面将来证明它的存在:
对象a虚表指针以及虚表内部函数指针的关系简图如下:
由上图,可知,show_func将被赋值为A类中的show函数。执行结果如下:
同理,可以对a_ex对象进行剖析,找出它的虚表和虚表函数。
其实,从上面的分析,也能间接证明类中的成员函数也可以看成全局函数的特殊形式,只是访问方式有所限制。
A: 如果非要将一个较大的对象赋值给较小的对象,而且没有按照特定的转换方式,终究可能会发生数据丢失的问题。如下例子:
在两个断点处打印*pa的数据:
可以看出
(1)*pa数据确实被对象a_ex的数据替换了
(2)不过也可以看出对象a的虚表指针依然没变,导致了调用show函数没有改变。
A: 当然可以。如下代码:
#include <iostream> using namespace std; #define COUT_ENDL(str) std::cout << #str << " is " << (str) << std::endl; class A { public: A(int value_one, int value_two):_value_one(value_one), _value_two(value_two) { } int value_one() const { return _value_one; } int value_two() const { return _value_two; } virtual void show() const { std::cout << "show A..." << std::endl; } // 类A中增加了重载赋值运算符的函数,且内部将对象首地址的虚表指针更改 A & operator=(const A& a) { if(this != &a) { *(int *)this = *(int *)&a; _value_one = a._value_one; _value_two = a._value_two; } return *this; } private: int _value_one; int _value_two; }; class A_Ex : public A { public: A_Ex(int one, int two, int three):A(one, two), _value_three(three) { } int value_three() const { return _value_three; } virtual void show() const { std::cout << "show A_Ex..." << std::endl; } private: int _value_three; }; int main (int argc, const char * argv[]) { A *pa = new A(10, 100); A_Ex *pa_ex = new A_Ex(97, 98, 99); pa->show(); pa_ex->show(); *pa = *pa_ex; // cause slice pa->show(); pa_ex->show(); delete pa_ex; delete pa; system("pause"); return 0; }
可以看出把派生类的空间内容拷到基类空间中(*pa = *pa_ex; // cause slice),即使相应的成员是虚函数,但还是无法把派生类的此成员函数拷贝过去。
请注意,以上 把派生类的空间内容拷到基类空间 与 本文中第9点例子中讲到的基类指针指向派生类指针 是两种不同的操作,如果是进行基类指针指向派生类指针操作,
且是非虚函数,那么即使重载了赋值运行符也是不能把内部圣像首地址虚表指针更改的。(请参照第9点)
运行结果:
show A... show A_Ex... show A_Ex... show A_Ex...
//=============== 9、 当基类指针指向继承类引起的virtual地址替换?========================================== #include <iostream> using namespace std; #define COUT_ENDL(str) std::cout << #str << " is " << (str) << std::endl; class A { public: A(int value_one, int value_two):_value_one(value_one), _value_two(value_two) { } int value_one() const { return _value_one; } int value_two() const { return _value_two; } void show() const { std::cout << "show A..." << std::endl; } private: int _value_one; int _value_two; }; class A_Ex : public A { public: A_Ex(int one, int two, int three):A(one, two), _value_three(three) { } int value_three() const { return _value_three; } void show() const { std::cout << "show A_Ex..." << std::endl; } private: int _value_three; }; int main (int argc, const char * argv[]) { A *pa_Degret = NULL; A_Ex a_Ex(97, 98, 99); a_Ex.show(); // show A_Ex... // !!!!!!!!!!特别注意!!!!!!!!!!!!!!!!!!! // 当是同名函数, 但此函数不是虚的,继承类中的此函数也能覆盖基类函数 pa_Degret = &a_Ex; // 它不调用赋值函数,只是指针的变化 pa_Degret->show(); // show A... // !!!!!!!!!!特别注意!!!!!!!!!!!!!!!!!!! // 当是同名函数, 但此函数不是虚的,继承类中的此函数也能覆盖基类函数; // 但也保存了基类的此函数的指针; // 当强转为基类指针时,发生了指针回到基类的情况。 system("pause"); return 0; }
运行结果是:
可以看出:
(1) 因为是虚函数,所以继承类中只存了此同名函数中自己的地址,基类地址被抛.
(2)/当是同名函数, 但此函数不是虚的,继承类中的此函数也能覆盖基类函数;
但也保存了基类的此函数的指针;
当强转为基类指针时,发生了指针回到基类的情况。
//=============== 10、有两个无关的类,但它们的类中有同名的成员函数。 //====================当一个类的指针指向另一类引起的virtual地址替换? #include <iostream> using namespace std; #define COUT_ENDL(str) std::cout << #str << " is " << (str) << std::endl; class A { public: virtual void show() const { std::cout << "show A..." << std::endl; } void show_noVirtual() const { std::cout << "show_noVirtual A..." << std::endl; } }; class B { public: virtual void show() const { std::cout << "show B..." << std::endl; } void show_noVirtual() const { std::cout << "show_noVirtual B..." << std::endl; } }; int main (int argc, const char * argv[]) { A *pa_Degret = NULL; B b; pa_Degret = (A*)&b; // 总结: // 可以看出与第9点有继承关系的类发生的情况是一致的: // 1、当是同名函数, 但此函数是虚的,B类中的此函数能覆盖A函数; // 但没保存A类的此函数的指针; // 当强转为A类指针时,发生了指针回不到A类的情况 // 2、当是同名函数, 但此函数不是虚的,B类中的此函数也能覆盖A函数; // 但也保存了A类的此函数的指针; // 当强转为A类指针时,发生了指针回到A类的情况 pa_Degret->show(); pa_Degret->show_noVirtual(); system("pause"); return 0; }
运行结果:
可以看出:
(1)、当是同名函数, 但此函数是虚的,B类中的此函数能覆盖A函数; 但没保存A类的此函数的指针;当强转为A类指针时,发生了指针回不到A类的情况
(2)、当是同名函数, 但此函数不是虚的,B类中的此函数也能覆盖A函数;但也保存了A类的此函数的指针;当强转为A类指针时,发生了指针回到A类的情况
//=============== 10、如果以一个基类指针指向一个派生对象,那么经由此指针, // 只能调用基础类别所定义的函数=========================== #include <iostream> using namespace std; #define COUT_ENDL(str) std::cout << #str << " is " << (str) << std::endl; class A { public: void show_A() const { std::cout << "show_A A..." << std::endl; } // 类A中增加了重载赋值运算符的函数,且内部将对象首地址的虚表指针更改 A & operator=(const A& a) { if(this != &a) { *(int *)this = *(int *)&a; } return *this; } }; class A_Ex : public A { public: void show_A_Ex() const { std::cout << "show_A_Ex A_Ex..." << std::endl; } }; int main (int argc, const char * argv[]) { A *pa_Degret = NULL; A_Ex *pa_Ex_Degret = NULL; A_Ex a_Ex; pa_Degret = (A*)&a_Ex; pa_Ex_Degret = (A_Ex *)&a_Ex; // 情况一 //pa_Degret->show_A_Ex(); // 编译不通过 pa_Ex_Degret->show_A_Ex(); // 正确,调用A_Ex::show_A_Ex 函数。 system("pause"); return 0; }
运行结果是:
可以看出: