一、父类指针指向子类对象
先上代码:
class Base
{
public:
void base_fun1() {}
virtual void base_fun2()
{
cout << "this is Base fun2()" << endl;
}
public:
int a_a;
protected:
int a_b;
private:
int a_c;
};
class Son :public Base
{
public:
void son_fun1() {}
void base_fun2()//重写父类虚函数
{
cout << "this is Son fun2()" << endl;
}
public:
int b_a;
protected:
int b_b;
private:
int b_c;
};
int main()
{
Base* ba = new Son();//父类指针指向子类
//父类指针只能访问从父类继承而来的成员变量和成员函数
//只能访问从父类继承而来的成员变量
ba->a_a = 10;
//只能访问从父类继承过来的成员函数
ba->base_fun1();
//由于子类对象用的是从父类继承而来的虚函数表,所以父类指针可以通过查找父类虚函数表的方式,调用被子类重写后的函数
ba->base_fun2();
//子类指针对所有的成员变量和成员函数随意访问(受访问权限限制)
Son* so = new Son();//子类指针指向子类
so->a_a = 10;
so->b_a = 10;
so->base_fun1();
so->son_fun1();
so->base_fun2();
so->Base::base_fun2();
return 0;
}
so是子类指针,指向子类对象,可以通过so访问任何子类的东西。前提是在遵守访问权限限制的情况下。
ba是父类指针,指向子类对象,但是只能通过ba访问继承过来的成员变量和成员函数。也就是说,不能通过指向子类对象的的父类指针访问子类对象的本身的成员变量和成员函数。
ba可以访问子类对父类虚函数重写的函数。因为子类和父类共用一个虚函数表,子类用的是从父类继承过来的虚函数表(虚函数表问题点击此处),父类指针也只是通过父类的虚函数表查找函数地址,然后调用,之所以可以通过父类指针访问子类重写父类虚函数后的函数,根本上还是因为子类用的还是父类的虚函数表。
通过让父类指针和子类指针指向同一个子类对象,看一下这俩指针到底指向了哪里。我们都知道子类对象是个大空间,里面包括子类本身的成员变量,还包含一个小空间,小空间是父类的无名对象,被子类对象所拥有。如果没有虚继承的话,小空间的首地址和大空间的首地址是一起的,导致无法查看父类指针和子类指针的指向区别,如下图所示(对于继承和虚继承博客,点击此处):
但是虚继承就不一样了,虚继承的话,子类的本身变量成员在小空间上方。如下虚继承代码和子类对象空间结构图:
代码:
class Base
{
public:
void base_fun1() {}
virtual void base_fun2()
{
cout << "this is Base fun2()" << endl;
}
public:
int a_a;
protected:
int a_b;
private:
int a_c;
};
class Son :virtual public Base
{
public:
void son_fun1() {}
void base_fun2()//重写父类虚函数
{
cout << "this is Son fun2()" << endl;
}
public:
int b_a;
protected:
int b_b;
private:
int b_c;
};
int main()
{
Son so;
Base* baptr = &so;
Son* soptr = &so;
cout << "父类指针指向的空间首地址:" << (int)baptr << endl;
cout << "子类指针指向的空间首地址:" << (int)soptr << endl;
cout << "a_a的地址:" << (int)&so.a_a << " " << "b_a的地址:" << (int)&so.b_a << endl;
return 0;
}
运行结果:
子类对象空间结构图 :
所有的东西都在上面的图中。(可以通过vs的开发者工具查看类的对象的空间结构图)
上面的图展示了子类对象的空间结构图,含有指向子类对象的父类指针指向的空间,和指向子类对象的子类指针指向的空间。所以两个指针指向的空间的首地址不一样,指向子类对象的子类指针指向的空间包含指向子类对象的父类指针指向的空间。而父类指针指向的空间里面的东西,就是从父类继承而来的东西,所以只能通过父类指针访问从父类继承过来的成员变量,而能访问子类对象重写的函数,是因为子类用的是从父类继承过来的虚函数指针,用的虚函数表也是从父类继承而来的,父类指针可以通过父类的虚函数指针访问虚函数表,进而调用子类重写的函数。
当子类用保护继承或者私有继承的方式继承父类时,编译器就不允许父类指针指向子类对象了。因为父类指针本质上是指向子类对象空间里的从父类继承而来的那部分内容空间,而这部分内容用的是私有或者包含继承的方式,不允许随意访问,自然也不允许外部指针随意指向。但是在子类类内(成员函数内)可以,因为类内不受访问权限的限制,可以随意访问这些东西。
总结:
如果父类指针指向子类对象,那么只可以通过父类指针访问子类继承的父类的成员函数和成员变量。(遵守访问权限限制是前提,其次,由于子类继承了父类的虚函数指针,且子类用的也是父类的虚函数指针和虚函数表,所以父类指针可以通过查找父类虚函数表的方式调用被子类重写后的函数)。
二、父类对象和子类对象之间的问题
子类对象可以直接赋值给父类对象
子类对象可以直接初始化父类对象
但是反过来不行。
代码:
class Base
{
public:
void base_fun1() {}
virtual void base_fun2()
{
cout << "this is Base fun2()" << endl;
}
public:
int a_a;
protected:
int a_b;
private:
int a_c;
};
class Son :virtual public Base
{
public:
void son_fun1()
{
Son so;
Base* baptr = &so;
}
void base_fun2()//重写父类虚函数
{
cout << "this is Son fun2()" << endl;
}
public:
int b_a;
protected:
int b_b;
private:
int b_c;
};
int main()
{
Base a;
Son b;
a = b;
b = a;//error
return 0;
}
编译器会自动生成赋值函数,这个赋值函数和缺省拷贝构造函数可以说是做法一模一样。都是值拷贝(字节拷贝)。所以如果父类有指针而对象用继承的指针指向了堆空间的话,一定小心这个赋值语句,会造成两个指针指向一个堆空间的问题,这个时候要小心析构函数对一个堆空间进行两次释放的问题。这里不对这个问题再次进行讨论了。在构造函数的博客以及多态的博客中都有讲述产生的问题和解决办法。
为什么子类对象可以赋值给父类对象,而父类对象不能赋值给子类对象呢?这个和本博客的第一大点也有关系,因为可以把子类对象强制转换成父类类型,而这个代价就是,只能访问子类从父类继承的东西,即子类对象会退化成父类对象。换句话说,就是子类对象的大空间中包含的小空间(存放着从父类继承的成员变量)供你强制转换后使用。
而父类对象不能被强制转换成子类类型,所以自然也不能将父类对象赋值给子类对象,也不能通过父类对象对子类对象进行初始化,或者拷贝构造。因为这些函数都有this指针,要通过的第一关就是必须得允许this指针指向你,也就是将你强制转换成等号左边的类型。第一关就通不过,所以肯定不能这样操作。