using namespace std; class A { };
class B : public virtual A { };
class C : public virtual B { }; int main() {
cout << sizeof(A);//8,由于有一个虚函数,那么必须有一个对应的虚函数表来记录对应的函数入口地址。每个地址需标有一个虚指针,指针的大小为4,。类中还有一个char k[3],每一个char值所占的位置为1,所以char k[3]所占的大小是3。做一次数据对齐后,其大小变为4,加上4,则为8.
cout << sizeof(B);//16。由于虚继承了class A,同时还拥有自己的虚函数,B类中首先拥有一个虚表指针,指向自己的虚函数表。虚继承是如何实现的呢?首先通过加入一个虚类指针来指向父类,然后还要包含父类的所有内容。B类的大小等于自身的大小加上父类的大小。
cout << sizeof(C);//24. C类的大小等于自身的大小加上父类的大小。 }
析构函数可以为virtual型,为什么构造函数不能为虚的呢?
虚函数采用一种虚调用的办法。虚调用是一种可以在只有部分信息的情况下工作的规则,特别允许我们调用一个只知道接口而不知道其准确对象类型的函数。但是如果要创建一个对象,你势必要知道对象的准确类型,因此构造函数不能为虚。关键字virtual告诉编译器它不应当完成早绑定,应当自动安装实现晚绑定。
关键字static的作用?完整答案
1、 函数体内static变量的作用范围为该函数体,不同于auto变量,该变量的内存只被分配一次,因此
其值在下次调用时继续维持上次的值。
2、 在模块内的static全局变量可以被模块内所有的函数访问,但不能被模块外其他函数访问。 3、 在模块内的static函数只可被这一模块内的其它函数调用,这个函数的适用范围被限制在声明它的模
char i[3]; public:
virtual void cc() {} char j[3]; public:
virtual void bb() {} char k[3]; public:
virtual void aa() {}
块内。
4、 在类中的static成员变量属于整个类所拥有,对类的所有对象只有一份拷贝。
5、 在类中的static成员函数属于整个类所拥有,这个函数不能接受this指针,因而只能访问类的static
成员变量。
printf(“%f”, 5);//是0.00000,正确的是printf(“%f”, 5.0); printf(“%d”, 5.01);//是一个大数
虚函数的入口地址和普通函数有什么不同?
每个虚函数都在vtable中占了一个表项,保存着一条跳转到它的入口地址的指令(实际上就是保存了它的入口地址)。当一个包含虚函数的对象(不是对象的指针)被创建的时候,它在头部附加上了一个指针,指向vtable中相应的位置。调用虚函数的时候,不管你用什么指针调用,他先根据vtable找到入口地址在执行,从而实现了“动态联编”。而不像普通函数那样简单的跳转到了一个固定的地址。
将一个类拥有一个或多个纯虚函数或者将该类的构造函数定义成一个私有成员就可以阻止该类实例化。
为什么将父类的析构函数修饰为virtual型?
将父类的析构函数设为virtual型,则所有父类的派生类的析构函数将自动变为virtual型,这保证在任何情况下,不会出现由于析构函数未被调用而导致的内存泄露。这才是父类的设为virtual型的真正原因。
什么是多态?
多态性是允许你将父对象设置成为和它的一个或更多的子对象相等的技术,赋值之后,父对象就可以根据当前赋值给它的子对象的特性以不同的方式运作。简单地说,允许将子类类型的指针赋值给父类,通过父类调用来实现。如果不是晚绑定,就不是多态。重载只是一种语言特性,与多态无关,与面向对象无关。
#include
public:
void virtual() {cout<< “B” < void virtual f() {cout<< “A”< int main() { A *pa = new A(); pa->f(); B *pb = (B*)pa; pb->f(); delete pa,pb; pa = new B(); pa->f(); pb = (B*)pa; pb->f(); }//输出结果为AABB。 //A类里的f函数是一个虚函数,虚函数是被子类同名函数所覆盖的,而B类里的f函数也是一个虚函数,它覆盖A类f函数的同时,也会被它的子类覆盖。但是在B *pb = (B*)pa;里面,该语句的意思是转换pa为B类型并新建一个指针pb,将pa复制到pb。但是pa的指针始终没有发生变化,所以pb也指向pa的f函数。这个不是覆盖问题。delete pa, pb;删除了pa和pb所指向的地址,但pa、pb指针并没有删除。现在重新给pa指向新地址,所指向的位置是B类的,而pa指针类型是A类的,所以就产生了一个覆盖。最后的pb = (B*)pa;由于pa本来指向B类的,所以输出的值是B。 #include class B : public A { }; class C : public B { protected: int m_data; public: protected: int m_data; B(int data = 1){m_data = data;} int doGetData(){return m_data;} public: protected: int m_data; A(int data = 0){m_data = data;} int GetData(){return doGetData();} virtual int doGetData(){return m_data;} public: }; C(int data = 2){m_data = data;} int main() { C c(10); cout< 也未定义,故调用A中的GetData(),因为A中的doGetData()是虚函数,所以调用B类中的doGetData(),而B类的doGetData()返回B::m_data,故输出1. cout< cout< cout< B中也未定义,故调用A中的GetData(),因为A中的doGetData()是虚函数,所以调用B类中的doGetData(),而B类的doGetData()返回B::m_data,故输出1. cout< cout< cout< 解答:构造函数从最初始的基类开始构造,各个类的同名变量没有形成覆盖,都是单独的变量。 如果虚函数是非常有效的,为什么不把每个函数都声明为虚函数?:析构函数可以是内联函数。 不行,这是因为虚函数是有代价的,由于每个虚函数的对象都必须维护一个v表,因此在使用虚函数的时候都会产生一个系统开销。如果仅是一个很小的类,并且不想派生其他类,那么根本没必要使用虚函数。 class A {//错误 }; class A { }; class A { }; int *p; *p = 8;//错误,整数指针p并没有指向实际的地址,在这种情况下就给它赋值是错误的,因为赋 A() {const int Size = 0;}//正确 static const int size = 0;//正确的 const int size = 0;//常量必须在构造函数的初始化列表中初始化或者将其设置成static 而B类的doGetData()返回B::m_data,故输出1. 的值不知道该放到哪里去,从而造成错误。 const double di;//错误,const常量赋值时,必须同时初始化。 int a[3]; a[0] = 0; a[1] = 1; a[2] = 2; int *p, *q; p = a; q = &a[2]; cout<< a[q - p]; q的实际地址是0x22ff70,p的实际的地址是0x22ff68.两者相差是8.但q-p的实际运算是(q的实际地址 – p的实际地址)/ sizeof(int)为2. #include void main() { } B类只有一个元素是int m_c,但是A类的内存空间中存放第一个元素的位置是m_a,pb指向的是对象的内存首地址,比如0x22ff58,当pb->fun()调用的是B::fun()来打印m_c时,编译器对m_c对它的认识就是m_c距离对象的偏移量0,于是打印了对象a首地址的偏移量0x22ff58+0变量值。 在主函数main中可以不写return语句,因为编译器会隐式返回0,但是在其它的函数是不行的。 函数的形参在函数未调用时预分配存储空间是错误的,调用到实参才会分配空间。 A a; B *pb = (*B)(&a); pb->fun();//1 public: A() {m_c =3;} ~A(){} void fun(){printf(“%d”, m_c);} int m_c; public: A() {m_a =1; m_b = 2;} ~A(){} void fun(){printf(“%d%d”, m_a, m_b);} int m_a; int m_b; private: private: