我们知道一般的赋值兼容,如double d, int i, int *ip; d = i 可以直接赋值, i = d 可以舍弃小数赋值,而 ip = 1024 则出现错误,要添加强制转换,如 ip = (int *) 1024,则不算赋值兼容。
不同数据之间的自动转换和赋值,称为赋值兼容。
1. 那么基类对象和派生类对象是否存在赋值兼容关系?
存在,但是是单向的【从派生类赋值给基类,舍弃其它部分】,而且只能赋值数据成员【为什么?因为赋值函数成员会导致混乱】!只有公有继承才能赋值【为什么?因为私有继承和保护继承的基类成员在类的外部是不可见的,防止意外操作】!
2. 如何赋值?什么情况下系统会自动转换?
A 派生类可以向基类对象赋值,
【假如 B 继承于 A 】A a1; B b1; a1 = b1;在赋值的时候舍弃派生类自己的成员,“大材小用”。
一个例子【注意是公有继承】:
#include <iostream> #include <string> using namespace std; class A{ public: A(int i) : a(i){}//构造函数 void COUT() {cout << a <<endl;} private: int a; }; class B : public A { public: B(int i) : A(i) {b = 10;} int b; }; int main() { A a1(0); B b1(20); a1 = b1; //即使是私有成员a,也将被赋值 a1.COUT(); return 0; }
B 派生类可以向基类对象的引用进行赋值和初始化。【派生类的基类部分和引用具有相同地址】
#include <iostream> #include <string> using namespace std; class A{ public: A(int i) : a(i){}//构造函数 private: int a; }; class B : public A { public: B(int i) : A(i) {b = 10;} int b; }; int main() { A a1(10); B b1(20); A &r = a1; r = b1; //只是一般的赋值操作,引用是不能重定向的 A &r2 = b1; //r和a1具体相同地址,b1的基类部分地址和r2具有相同地址 cout << &r <<endl; cout << &a1 <<endl; cout << &b1 <<endl; cout << &r2 <<endl; cout << &b1 <<endl; return 0; }
C 如果函数的参数是基类对象或基类对象的引用,实参也可以是子对象【子对象:基类经过公有继承后的子类的对象】,赋值过程和上面的类似。
void fun(A &r) {cout << r.num <<endl; } fun( b1 )
D 派生类的对象的地址可以赋给指向基类对象的指针变量,依然是指向派生类继承于该基类的数据部分的起始地址。
3.一个有趣的实例
#include <iostream> #include <string> using namespace std; class Base{ int base; }; class A{ public: A(int i) : a(i){}//构造函数 private: int a; }; class B : public Base,public A { public: B(int i) : A(i) {b = 10;} int b; }; int main() { A a1(10); B b1(20); A *ap = &b1; cout << (int *)ap <<endl; cout << (int *)&b1 <<endl; return 0; }请问上面的ap和&b1值相同吗,答案是不同的。因为B类继承了两个父类,一个是Base【这个是B类对象的地址真正指向的类,因为它是第一个父类】,一个是A,而ap是指向B类对象中A类的部分的地址,所以不同。【假如是私有继承和保护继承,那么A *ap = &b1; 操作将产生错误,因为B类中继承于A类的数据在其它地方是不可见的】
4.另一个有趣的问题【假如A和B类都有display()成员函数,且B继承于A】
A *p = b1;则p->display()调用的是A的display函数还是B的display函数呢,答案是A的display()函数。