1 类在内存中的存储:
类在内存中首先是其一个虚表(virtual table), 然后是其祖先的数据,其父亲的数据,然后才是自己的数据。
类C 在内存中的存储: {<B> = {<A> = {_vptr.A = 0x8048b40, i = 1}, j = 2}
代码:
class A {
public:
virtual int v1(){return i;}
int i;
};
class B : public A {
int j;
public:
virtual int v2(){return j;}
};
class C : public B {
public:
virtual int v2(){return 1;}
};
2. 重载的问题。
重载只在一个类中存在, 父类和子类之间是不存在重载的。
class parent {
public:
virtual void f(int c){cout<<"parent"<<c<<endl;}
};
class child : public parent{
public:
int f(const char *s, int c) {cout<<"child"<<s<<c<<endl;}
};
pa.f(1);
ch.f(1); // 这条语句是编译不过的, 因为子类中已经有了一个函数f, 所以父类的函数在子类中是看不到的。
3 子类指针强制转为父类指针:
子类指针强制转为父类指针, 其访问的数据是父类的数据, 但是访问的虚函数则是子类override的虚函数。
这是因为父类的数据也在子类的对象中,但是父类的虚函数在虚函数表中的地址却已经被改为子类的虚函数的地址了。
4. 父类指针强制转为子类指针:
如果父类指针指向的是子类对象则没问题, 如果父类对象指针指的是自己的话,则可能发生内存溢出等问题。
这是因为父类对象中并没有子类对象的数据成员, 如果父类对象指针访问这些数据成员,则可能发生空指针,或访问到
其他的物理空间,导致破坏程序的数据。
//ABC类的定义如 1.
char* s = str();
A a;
B b;
C c;
test2((A*)&b); // 调用子类B的虚函数
test3((B*)&a); // 父类访问子类的数据 i, 因为对象A在栈中, 因此内存溢出覆盖了指针 s.
5 char a[] ="string" // a指向的“string“ 存储在栈中
char *a ="string" // a指向的“string“ 存储在静态存储区中。 (全局变量和静态变量也存储在静态存储区中。)
6 为什么构造函数不能是虚函数, 析构函数可以是虚函数。
很简单, 因为这是c++ 规范上规定的, 编译器必须这么实现!!
至于为什么这样规定, 规范上没具体解释。
我的看法是:
- 构造函数是没有名字的, 不能使用name lookup来找到他,也就是说 用户不能显示地调用构造函数。
以上这些都是c++规范上定义的, 无需解释。 因此上如果定义构造函数为虚函数, 那么
用户无法显示的调用它,而它又在对象初始化时已经被调用过了, 所以 构造函数白白的占用了虚表中的一个位置, 浪费了系统空间。
构造函数是用来定义对象类型的, 它只在对象初始化时被调用。 另外如果构造函数是虚函数, 那么只能通过查找虚表,找到构造函数的指针, 然后调用构造函数。 但调 用构造函数时, 对象还没有建立起来,虚表更无从谈起。 这就造成了矛盾。
所以构造函数不可以是虚函数。
- 但问题出来了, 析构函数和构造函数一样不能显示被调用,那为什么析构函数可以是虚函数呢?
我认为,这其实是 c++的一个work around.
因为 众所周知析构函数可以是虚函数是为了解决 upcast问题。
class base { }; class child : public base();
base * poly = new child;
delete poly;
在这里,如果析构函数不是虚函数, 那么由于编译器并不知道poly指针指向的是child对象, 而错误地以为poly指向的是base 对象,
因此只调用 base 的析构函数, 那么child的析构函数就不会被调用,可能引发内存泄露。
所以在这里,必须把析构函数定义为虚函数, 编译器会把这个虚函数指针放到虚表中, 在对象析构时, 调用这个析构函数, 然后再调用其父类的虚函数(不是存在虚表中)。
其实也就是编译器对虚析构函数做了特殊处理, 它的处理方法既不同于普通的析构函数, 也不同于普通的虚函数。
那么为什么, 编译器不默认的把所有的析构函数都放到虚表中呢, 这主要是 怕浪费空间, 因为很多类是没有子类和虚函数的,
如果强行将析构函数放到虚表中,会多出两个指针(一个是虚表的地址, 一个是析构函数在虚表中的地址)。
因此上析构函数默认不是虚函数, 如果我们需要的话, 可以将其设为虚函数。
6 数组的一个误解:
下面程序在vc上是编译不过的, 但是gcc没问题。
void test(int n) {
unsigned int const size1=2;
char str1[size1];
unsigned int temp;
cin>>temp;
char str2[temp]; // 问题在这
}
7 虚拟继承
即使继承基类多次,派生类也只有一份基类的拷贝在派生类对象中。
8 对象数组:
#include <iostream>
using namespace std;
class t {
public:
t(){
std::cout << "t constructor";
}
~t(){
std::cout << "t desconstructor";
}
};
int main(int argc, char*argv[])
{
t* s = new t[20];
//t* s =new t();
delete []s;
return 0;
}