一、在c++里,什么是子类型呢? ios
当一个已知类型S,它至少提供了另外一个类型T的行为,他还能够包含本身的行为,这时则称类型S是类型T的子类型。子类型的概念涉及到行为共享,即代码重用的问题,他与继承有着密切的联系。 c++
值得注意的是,共有继承能够实现子类型。为何这么说呢?由于当一个类的继承关系为共有继承时,基类的公有成员和保护成员做为派生类的成员时,他们都保持了原有的访问权限,既,派生类至少提供了基类的行为。因此说共有继承能够实现子类型。 程序员
来一个例子: 函数
class A spa
{ 指针
public: 调试
void print() const 对象
{cout << "A :: print() called.\n" ;} 继承
}; get
class B:public A
{
public:
void f()
{}
};
void f1 (const A &r)
{
r.print();
}
void main ()
{
B b;
f1(b);
}
执行程序会输出结果:
A :: print() called.
这就是子类型的应用,注意:子类型的关系是不可逆的。
二、类型适应
类型适应是指两种类型之间的关系。例如,B类型适应于A类型是指B类型的对象可以用于A类型的对象所可以使用的场合。子类型与类型适应是一致的,A类型是B类型的子类型,那么B类型必将适应于A类型(派生类对象能够用于基类对象所使用的场合,派生类对象的指针和引用也适应于基类对象的指针和引用。)
子类型的重要性在于减轻了程序员编写代码的负担,同时也提升了代码的重用率。由于一个函数能够用于某类型的对象,则它能够用于该类型的子类型的对象,这样就没必要为处理这些子类型的对象去重载该函数
三、赋值兼容规则
一般在共有继承方式下,派生类是基类的子类型。这时派生类对象与基类对象之间的一些关系的规则称为赋值兼容规则。
在公有继承方式下,赋值兼容该规则规定:
(1) 派生类对象能够赋值给基类对象
eg: a = b;
(2) 派生类对象的地址值能够赋值给基类的对象指针
eg: pa = &b;(pa 是对象指针)
(3) 派生类对象能够用来给基类对象引用初始化。
eg: A &a = b;
使用上述规则要注意两点:
(1) 具备派生类公有继承基类的条件
(2) 上述三个规定不可逆
在来一个例子讨论一下:
#include <iostream>
using namespace std;
class A
{
public:
A()
{a = 0;}
A(int i)
{ a = i; }
void print()
{cout << a << endl;}
int geta()
{return a;}
private:
int a;
};
class B:public A
{
public:
B()
{ b = 0;}
B(int i, int j):A(i),b(j)
{}
void print()
{
A::print();
cout << b << endl;
}
private:
int b;
};
void fun(A &d)
{
cout << d.geta() * 10 << endl;
}
void main()
{
B bb(9,5);
A aa(5);
aa = bb;
aa.print();
A *pa = new A(8);
B *pb = new B(1,2);
pa = pb;
pa -> print();
fun(bb);
}
输出结果
9
1
90
关于不可逆的问题咱们不去讨论,下面咱们来改变程序,看看它是怎么运行的
抛出问题,pa = pb 这句话执行后,到底pa 指向的是pb的地址仍是pa 的呢?若是是pb的,pa是否能够调用pb的方法?
咱们来断点调试:
能够看到,执行到这一步时,pb 和pa的地址值是不一样的,下面推动断点
能够看到,pa 的地址值已经和pb的地址值相同了,那么能够能够用pa调用B 类的公有成员呢?
咱们在class B里写一个函数
void abc()
{
cout << "能够越界吗?" << endl ;
}
在main 函数里加上一句话
pa -> abc();
结果编译器提示有错误:
说明abc()不是A类的成员,虽然pa和pb的地址是相同的,可是是不能越界的。咱们在void fun(A &d)里加入一句话,使其变成:
void fun(A &d)
{
cout << d.geta() * 10 << endl;
d.print();
}
从新运行,咱们来看一下结果:
9
1
90
9
结果代表了,它并无调用B类的
void print()
{
A::print();
cout << b << endl;
}
而是调用了A类的void print() 函数,这是为何呢?这里涉及到了静态联编和动态联编的有关知识, 在之后的文章中将会介绍。
抛出问题,根据赋值兼容该规则中的第三条规则:派生类对象能够用来给基类对象引用初始化,那么若是这样行么?
void fun(A d) //将 & 去掉后的结果怎样?
{
cout << d.geta() * 10 << endl;
}
输出结果
9
1
90
说明这么写彻底是能够的,那么程序是怎么调用的呢?难道是运用了拷贝构造函数?咱们在class A的定义里补充以下一个拷贝构造函数来证明咱们的猜测
A(A &aa)
{
a = aa.a;
cout << "A的拷贝" << endl;
}
再次执行程序:
9
1
A的拷贝
90
能够将‘&’在加上,发现没有调用拷贝构造函数,这里更加清晰的知道了’&’的做用,既,它就是变量的另外一个别称,不会调用拷贝构造函数,因此用引用的执行的效率也会加快