多态:
运行期绑定机制,将函数名绑定到函数具体实现代码。
目的:
接口重用。
多态就是将函数名称动态绑定到函数入口地址(一个函数在内存中起始的地址)的运行期绑定机制。
运行期绑定和编译期绑定
编译时就决定函数调用的代码:编译期绑定
运行时才将函数名称绑定到入口:运行期绑定
如果对函数的绑定发生在运行时而非编译时,就称函数为多态的。
C++中满足特定条件的成员函数才可以是多态的:
存在继承结构、结构中的一些类必须具有同名的virtual成员函数、至少有一个基类类型的指针或基类类型的引用
函数在基类中声明为虚函数,在派生类中即使不注明也自动成为虚函数。如果虚函数在类外定义,关键字virtual仅在类内声明时需要,不需在定义时使用。
C++只能将成员函数定义为虚函数,顶层函数不行。
使用虚成员函数表来实现虚成员函数的运行期绑定(运行时查询)。
构造函数不能是虚成员函数,但是析构函数可以。
只有非静态成员函数才可以是虚成员函数。
基类与派生类的转换:
只有公用派生类才是基类真正的子类型,它完整地继承了基类的功能。基类的公用和保护成员按艳阳保留通过调用基类的公共成员函数访问基类的私有成员。
基类与派生类有“赋值兼容”关系;可以将派生类的值赋给基类对象,在用到基类对象时可以用子类对象代替。
1.派生类对象可以向基类对象赋值
//B是A的派生类
A a1;
B b1;
a1 = b1;
子类型关系是单向的,只能用子类对象对基类对象赋值,因为基类对象不包含派生类成员,无法对派生类成员赋值。同理同一基类的不同派生类成员也不能互相赋值。
2.派生类对象可以替代基类对象向基类对象的引用进行赋值或初始化。
//B是A的派生类
A a1;
B b1;
//*******第一种*******
A& r = a1;
//定义基类A对象的引用变量r并用a1初始化
r = b1;
//派生类对象b1对a1的引用变量r赋值
//*******第二种*******
A& r = b1;
//基类A对象的引用变量r,用B的对象b1初始化
此时的r是b1中基类部分的别名,与b1中基类部分共享存储单元,起始地址相同。
3.函数的参数是基类对象或基类对象的引用,相应的实参可以用子类对象
//B是A的派生类
void func(A& r){
cout << r.num << endl;
}
//调用
func(b1);
//num:基类数据成员
4.派生类对象的地址可以覆盖指向基类对象的指针变量,即指向基类对象的指针变量可以指向派生类对象
第四条必须是用virtual声明虚函数后才有以上作用。
虚函数的意义:
基类中的某些函数不适合继承到派生类。解决方式是使用不同函数名或同名覆盖或声明虚函数(通过指向基类的指针指向同一类族中不同类的对象,从而调用出其中的同名函数)
通过虚函数和指向基类对象的指针变量的配合使用,就能方便地调用同一类族中不同类对象的同名函数,只要先用基类指针指向该对象即可。
在基类中定义的非虚函数在派生类中被重新定义:
用基类指针调用该成员函数,则会调用对象中基类部分的成员函数;用派生类指针调用该成员函数,则会调用派生类对象中的成员函数
声明虚函数的条件:
类的继承结构层次、类的成员函数(不可以是普通函数)。虚函数:允许在派生类中对基类的虚函数重新定义。
类中同时存在两个virtual和非virtual是不可行的。
声明为虚函数的情况:
类是否会作为基类;成员函数在被继承后有无可能改变功能;对成员函数的调用是否通过基类指针或引用(而非通过对象调用)
虚函数定义的规则:
如果虚函数在基类和派生类中仅有名字相同,而形参/返回值类型不同,不会进行动态联编。
只有类成员函数可以声明为虚函数。
静态成员函数不能是虚函数,因为静态成员函数有特点是不受限于某个特定的对象。
内联函数不能是虚函数,因为不能在运行中动态确定位置。(虚函数在类内定义,编译时被看做非内联的)。
构造函数不能是虚函数,因为构造时还没有对象;析构函数可以是虚函数且通常声明为虚函数。
重载overloading
在同一个命名空间(namespace) 中,两个函数同名但参数个数或类型不同。两个函数既可以是虚函数也可以是一般函数。(不同的类 (包括具有继承关系的父子类) 是不同的namespace,所以它们之间没有overload 的关系)
#include<iostream>
using namespace std;
class C {
public:
C() {}
C(int x) {}
};
void f(int d) {}
void f(char c) {}
int main() {
//default
C c1;
//convert
C c2(27);
// f(int d) called
f(3);
// f(char c) called
f('pi');
}
遮蔽/隐藏 hiding:
在继承层次中在派生类中定义一个和基类中函数同名的函数,不论参数是否相同,基类中的函数都会被隐藏,通过指向派生类的指针或引用都不能调用。这种现象与虚函数、函数的参数、函数的存取权限都无关。
覆盖overriding:
特殊的hiding(完全hiding);函数名和函数的参数都相同。
overload重载 | 发生在同一命名空间中 |
hiding遮蔽 | 发生在有继承关系的不同类的成员函数之间 |
多态 与 重载overload/隐藏hiding/覆盖override
名字共享:
共享函数名的情况: |
1.重载函数名的顶层函数; |
2.重载构造函数; |
3.非构造函数是同一个类中名字相同的成员函数; |
4.继承层次中的同名函数(虚函数) |
抽象基类:
公共接口(shared interface):
一个成员函数的集合,任何支持该接口的类必须定义该集合中的所有函数,这些类应以恰当方式定义成员函数。
抽象基类:
确保其派生类必须定义某些指定的函数,否则派生类不能被实例化。
纯虚函数:
只有拥有纯虚成员函数的类才能称为抽象基类。(抽象、不能实例化)
只有虚函数才能声明为纯虚函数。
在一些情况下,在基类中不能为虚函数给出一个有意义的实现,这时可以将它声明为纯虚函数,它的实现留给派生类来做
//纯虚函数的一般形式:
class Class{
virtual (type类型) (func_name函数名) (参数列表) = 0;
virtual void Func() = 0;
};
注意:1.纯虚函数没有函数体; 2.“= 0” 只起到形式作用 3.最后有分号
抽象类:
类带有纯虚函数时,不能声明类的对象,称这个类为抽象类。
派生类不给出纯虚函数的声明的话,这个函数仍然是纯虚函数,派生类是抽象类。
派生类可以声明新的纯虚函数或对基类中声明的纯虚函数再进行纯虚函数声明。
成员函数内可以调用纯虚函数,构造/析构函数中不可以。
抽象类可以用作基类 或者用作 指针或引用的基类型。
运行期类型识别:
运行期检查类型转换操作并确定对象的类型。
关于dynamic_cast操作符:
编译合法的类型转换操作可能会在运行期引发错误,转型涉及对象指针或引用时,易发生错误。dynamic_cast操作符可在运行期对可疑的转型操作进行测试。
一个基类指针不经过明确的转型操作,就能指向基类对象或者派生类对象;反之不同,将一个派生类指针指向基类对象是一种不明智的做法
dynamic_cast: 在运行期检测某个转型动作是否类型安全。仅对多态类型有效。
修改:
#include<iostream>
using namespace std;
class B {
public:
virtual void f() { }
};
class D :public B {
public:
void m() { }
};
int main() {
D* p = dynamic_cast<D*>(new B);
//the cast is safe or not
if (p)
//be safe
p->m();
//be not safe
else
cout << "not safe for p to point to a B*";
}
如果操作:
dynamic_cast<T*>(Ptr)
转型动作安全,则返回Ptr,否则返回false或0
上例中返回值为false,因为试图将派生类指针指向基类B的对象。
dynamic_cast规则:
从派生类D*到基类B*的dynamic_cast可以,向上转型(upcast) |
从基类B*到派生类D*的dynamic_cast不可以,向下转型(downcast)如上例 |
从A*到Z*的dynamic_cast不可以 |
从Z*到A*的dynamic_cast不可以 |
typeid操作符:
用于确定表达式的类型;头文件是<typeinfo>
例:声明 float x 和 long val
typeid返回一个type_info类对象引用,type_info是一个系统类,用来描述类型。
例:Book* bookPtr = new Textbook( “test”, 1 );
—————————————2021-11-28-14:39———————————————