知乎有很多博主学不会了一言不合就抄书,我觉得这对学习计算机科学是一样的。
这里说一个自己或许也有许多人都会也不太注意的点,派生类向基类转换及其可访问性,抄自《C++ Primer》。
派生类向基类转换(D2B)通常我们想把指针或引用绑定到一个对象上,则引用或指针的类型应该与对象类型一致,或者对象类型含有一个可接受的const类型转换规则,但存在继承关系的类例外:我们可以将基类的指针或引用绑定到派生类对象上。
因为派生类含有与基类对应的组成部分,派生类是基类的超集,它含有基类没有的部分。如何理解超集?比如晚期智人和尼安德特人都是人属的种,所以二者都是人,却都又有其特殊的部分,属于不同的种,存在一定的生殖隔离,但都可以当成人来看;又比如全同粒子,是指具有完全相同内禀属性的粒子,但又因为自旋导致的交换对称性的差异,又能够分为玻色子和费米子。
派生类向基类的转换是否可访问由该代码使用该转换的代码决定。我们可以将基类指针或引用绑定到派生类对象的基类部分:
Base b; //基类对象 Derive d; //派生类对象 Base* p = &b; //p指向基类对象 p=&d; //p指向派生类对象的基类部分,注意p的类型是Base Base &ptr = d; //一个指向基类的指针ptr指向派生类对象的基类部分,注意ptr的类型也是Base
这种转换称之为派生类到基类的类型转换,和其它类型转换一样,编译器隐式执行。这种特性意味着我们能把派生类对象或其引用用在需要基类引用的地方;对于指针,也是一样。
但这种特性有表示了一种重要的含义:当使用基类指针或引用时。实际上我们并不清楚该指针或者引用所绑定对象的真实类型——这个对象的类型可能是基类也可能是派生类。
这就引出了另外一个问题,这涉及到一点点底层的知识,那就是程序中的变量或者表达式的静态类型与动态类型的区分。如果是静态类型,那么代码段在编译时是已知的,它是变量声明时确定类型或者表达式生成的类型,而动态类型则是变量或表达式表示的内存中的对象的类型,直到程序运行时才可知。(这可能对应内存中的不同区域的代码段,比如代码是静态的就在静态的指令或者数据区躺着,如果是动态的就在系统生成的栈或者用户申请的堆区域中。)
不存在从基类向派生类的隐式类型转换(No implicit B2D)之所以能D2B因为派生类对象包含基类部分,而基类的引用或指针能绑定到该基类部分上。基类对象可以作为一个独立的存在,也可以作为派生类对象的一部分存在。如果基类对象不是派生类对象的一部分,则它只有基类定义的成员而没有那些独属于派生类的成员。(超集,显然可能存在 的情况。)
因为上述这种特性,所以不存在从基类到派生类的自动类型转换:
Base b; Derived* dp=&b; //错误,不能将基类转换成派生类 Derived& dref=ba; //错误,不能将基类转换成派生类
但是仍有一种情况比较特别:一个基类指针或引用绑定在派生类对象上,我们也不能执行从基类向派生类的类型转换。
Derive d;
Base* bp=&d; //正确,Derive为动态类型,程序在运行时可知其具体类型,
//基类指针绑定到派生的基类部分
Derive* dp=bp; //错误,不能将基类转换成派生类
在对象之间不存在类型转换
D2B向基类的自动类型的转换只对指针和引用类型有效,在派生类类和基类类之间不存在这样的转换。
当我们初始化或者赋值一个类型的对象时,实际上是在调用各种构造函数与运算符(拷贝调用移动赋值),这些成员通常包含一个参数,其返回类型是类类型的const版本的引用。当这些成员接受参数时,派生类允许我们用D2B将基类的操作传递给派生类对象。这些操作不是虚函数,它们是基类中定义的那些,这些构造函数显然只能处理自己的成员。若我们把一个派生类对象赋值给一个基类的对象,调用的则是基类的赋值运算操作,这种运算符只能处理基类中的成员。
Derive d; Base b(d); //调用运算符的拷贝构造 Base::Base(const Derive&) b = d; //赋值运算符的赋值操作 Base::operator = (const Derive&)
在上述过程中,会忽略掉派生类的部分,所以我们可以说派生类对象的派生类部分被切掉(sliced down)了。
派生类向基类转换的可访问性同时派生类的派生访问说明符
class/struct D: public/private/protected B
也会有影响。假设派生类D继承自基类B:
- Public,用户才能使用派生类向基类的转换;若D继承自B的方式是protected/private的,则用户的代码不能使用D到B的转换。
- 不论D以何种方式继承B,D的成员函数和友元都可以使用派生类D向基类B的转换;派生类向其直接基类的转换对于派生类的成员和友元来说是永远可以访问的。
- 如果D继承B是public/protected的,D的派生类的成员和友元可以使用D向B的类型转换;若private,则不能使用。



