文章目录
1.类型相容
- 类、类型
- 类型相容:
- 一个类型是另一个类型的子类型(int -> long int)时,类型是相容的
- 赋值相容:对于类型相容的变量才有
- 如果类型相同可以直接赋值
- 子类型可以赋值给父类型
- 问题:a和b都是类,a、b什么类型时,a = b合法(赋值相容)?B是A的子类型的时候
A a; B b; class B: public A; a = b;
- A与B都在栈上,把B内存中的内容拷贝到A内存中,但A与B大小可能不同,派生类的新增内容就要丢掉
- 对象切片
- 对象的身份发生变化,B类型对象变为了A类型的对象
- 属于派生类的属性已不存在
A a = b
:调用A拷贝构造函数- 属于B的部分不会被拷贝
- 使用引用或指针
- B* pb; A* pa = pb; class B: public A
- B b; A& a = b; class B: public A
- 因为是赋值相容的,所以可以指针赋值
- 这种情况类似Java
- B的身份没有发生变化
- 把派生类对象赋值给基类对象,基类的引用或指针可以引用或指向派生类对象,不严谨的说,可以说让父类指向子类
- 传参的时候尽量不要拷贝传参(存在对象切片问题),而是使用引用传参。
//测试切片调用
class A {
int x, y;
public:
void f();
};
class B: public A {
int z;
public:
void f();
void g();
};
//把派生类对象赋值给基类对象
A a;
B b;
a = b;//OK,
b = a;//Error
a.f();//A::f()
//基类的引用或指针可以引用或指向派生类对象
A& r_a = b;//OK
A* p_a = &b;//OK
B& r_b = a;//Error
B* p_b = &a;//Error
//以下两个部分基本是一致的
func1(A& a) {a.f();}
func2(A* pa) {pa->f();}
func1(b);//A::f
func2(&b);//A::f
2.绑定时间
- 前期绑定/静态绑定(Early Binding)
- 编译时刻确定调用哪一个方法
- 依据对象的静态类型
- 效率高、灵活性差
- 静态绑定根据形参决定
- 后期绑定/动态绑定(Late Binding)
- 运行时刻确定
- 依据对象的实际类型(动态)
- 灵活性高、效率低
- C++默认静态绑定,注重效率
- 后期绑定需显式指出:virtual
3.虚函数
- 定义:
- virtual
- 动态绑定
- 根据实际引用和指向的对象类型
- 方法重定义
- 如基类中被定义为虚成员函数,则派生类中对其重定义的成员函数均为虚函数(也就是派生类中的对应函数可以不加virtual)
- 限制
- 类的成员函数才可以是虚函数
- 全局函数不可以是虚函数
- 静态成员函数不能是虚函数
- 静态的成员函数属于类,并不属于一个对象,它是在编译时确定的
- 内联成员函数不能是虚函数
- 编译时确定
- 构造函数不能是虚函数
- 析构函数可以(而且往往)是虚函数
- 类的成员函数才可以是虚函数
3.1后期绑定的实现
class A {
int x, y;
public:
virtual f();
virtual g();
h();
};
class B: public A {
int z;
public:
f();
h();
};
A a; B b;
A* p;
p = &b;
p->f();//B::f()
p->g();//A::g(),B中没有实现
-
p->f():需要寻找a和b中的f()函数地址
-
虚函数表(索引表,vtable):大小可变
- 虚函数表是在构造函数中完成的
- 首先构造基类的虚函数表
- 然后查找派生类中的函数,如果查找了,则会覆盖对应函数来生成虚函数表
-
对象内存空间中含有指针指向虚函数表
-
(**((char *)pb-4)) (pb)
- 两次解引用
- 相当于&A::f (pb)
- f的函数调用(从虚函数表拿数据),pb是this指针
-
空间上和时间上都付出了代价
- 空间:存储虚函数表指针和虚函数表
- 时间:需要通过虚函数表查找对应函数地址,多调用
class A {
public:
A() {f();}
virtual void f();
void g();
void h() {
f();
g();
}
};
class B: public A {
public:
void f();
void g();
};
//直到构造函数返回之后,对象方可正常使用
B b;//A::A(),A::f, B::B()
//为什么调用A的f而不是B的?因为A在构造时B还没有构造,此时虚函数表中还是A的f
A* p = &b;
p->f();//B::f,f是动态绑定
p->g();//A::g,g是静态绑定
p->h();//A::h,B::f,A::g
//非虚接口,普通函数调用虚函数让普通函数呈现虚函数的特性
class A {
public:
virtual void f();
void g();
};
class B: public A {
public:
void f() {g();}
void g();
};
B b;
A* p = &b;
p->f();//B::f,B::g
//f动态绑定,而g静态绑定是按声明的来
//在编译时就确定调用B的f必然调用B的g
//B的f中相当于B* const this; this->g();
4.final,override
- final
- 禁止该函数从基类继承;禁止该函数的重载
- override
- 指明该函数是重载的基类中的一个函数
struct B {
virtual void f1(int) const;
virtual void f2();
void f3();
virtual void f5(int) final;
};
struct D: B {//继承
void f1(int) const override;//正确:f1与基类中的f1匹配;const不能去,否则类型不相同
void f2(int) override;//错误:B没有形如f2(int)的函数;去掉override可以编译,但会名隐藏
void f3() override;//错误:f3不是虚函数
void f4() override;//错误:B没有名为f4的函数
void f5(int);//错误:B已经将f5声明成final
};
5.纯虚函数和抽象类
5.1纯虚函数(同Java中的接口)
- 声明时在函数原型后面加上 = 0:
virtual int f() = 0;
- Means not there
- 往往只给出函数声明,不给出实现
- 可以给出实现,在定义函数实现
- 但是不能通过基类对象来直接调用(查不到)
5.2抽象类
- 至少包含一个纯虚函数
- 不能用于创建对象:抽象类类似一个接口,提供一个框架
- 为派生类提供框架,派生类提供抽象基类的所有成员函数的实现
- 有纯虚函数为什么需要抽象类?
- 不能创建实例
- 只能通过引用或指针来传参数
- 所以能防止对象切片问题
class AbstractClass {
public:
virtual int f() = 0;
};
5.3应用一
Figure *a[100];//Figure基类,不会被创建出来
//virtual display() = 0;
a[0] = new Rectangle();
a[1] = new Ellipse();
a[2] = new Line();
for (int i = 0; i < num_of_figures; i++){
a[i]->display();
}
//基于接口的复用,而不是基于实现的复用
5.3应用二:抽象工厂
- Step1:提供Windows GUI类库:WinButton
WinButton* pb = new WinButton();
pb->SetStyle();
WinLabel* pl = new WinLabel();
pl->SetText();
- Step2:增加对Mac的支持:MacButton,MacLabel
MacButton* pb = new MacButton();
pb->SetStyle();
MacLabel* pl = new MacLabel();
pl->SetText();
- 增加对用户跨平台设计的支持
Button* pb = new MacButton();
pb->SetStyle();
Label* pl = new MacLabel();
pl->SetText();
//创建工厂
class AbstractFactory {
public:
virtual Button* CreateButton() = 0;
virtual Label* CreateLabel() =0;
};
class MacFactory: public AbstractFactory {
//连new操作也抽象
public:
MacButton* CreateButton() {return new MacButton;}
MacLabel* CreateLabel() {return new MacLabel;}
};
class WinFactory: public AbstractFactory {
public:
WinButton* CreateButton() {return new WinButton;}
WinLabel* CreateLabel() {return new WinLabel;}
};
class Button;//Abstract Class
class MacButton: public Button {};
class WinButton: public Button {};
class Label;//Abstract Class
class MacLabel: public Label {};
class WinLabel: public Label {};
AbstractFactory* fac;
switch (style) {
case MAC:
fac = new MacFactory;
break;
case WIN:
fac = new WinFactory;
break;
}
Button* button = fac->CreateButton();
Label* Label = fac->CreateLabel();