简单来说,不同的对象同一行为的结果不同
比如买票这个行为,大学生买票和普通成年人买票就不同,大学生可以买学生票,成年人得买全票
抢红包,每个人抢的金额可能都不一样,但是行为(抢红包)都是一样的
即同一件事给不同人做有不同结果
这里出现了一个新的概念,虚函数
如果存在多态,说明存在虚表,要知道结果调用的父类的函数,还是子类的函数就看具体new的对象是谁
用于类的继承中,函数名,返回值,函数参数的个数以及类型都相同,只有函数体不同的两个函数构成重写,重写也叫做覆盖。
补充:基类的析构函数是虚函数,那基类与派生类的析构函数构成重写,虽然名字不同。
协变:虽然函数不完全一致,但也构成重写(有兴趣可以百度),简单说就是派生类重写基类虚函数时,如果基类虚函数的返回值是一个基类对象的指针,那派生类重写的虚函数返回值可以相同,也可以是一个指向派生类对象的指针。
在一个类里面被关键字virtual修饰的函数
virtual这个关键字也用于虚继承解决菱形继承问题
class Test
{
public:
virtual void func()//virtual修饰的函数
{
;
}
};
将虚函数和虚函数地址映射起来的一张表
简单说就是这张表每个函数对应其自己的地址
类里如果存在虚函数,那就会生成一张虚表,实例化出的对象里面会有一个虚表指针指向虚表
虚函数在运行时是动态绑定的,普通函数是静态绑定的
动态绑定是啥?可以理解为运行时根据具体对象调用相应的函数,换句话说,new的是谁就找谁
静态绑定,把函数调用与响应调用所需的代码结合的过程称之为静态绑定,因为普通函数不存在多态一说,一般编译期间就可确定该对象调用的是哪个函数
随笔记录:设计一个不能被继承的类:把基类的构造函数设为私有
同名函数不是重写就是重定义/隐藏
final:被final修饰的类不能被继承,被final修饰的函数不能被重写
override:检查函数是否是重写,不是的话编译器报错
class Base final
{
};
class Derive :public Base//err
{
};
int main()
{
return 0;
}
class Base
{
public:
virtual void print() final {}
};
class Derive :public Base
{
public:
virtual void print(){}//err
};
int main()
{
return 0;
}
class Base
{
public:
virtual void print() {}
};
class Derive :public Base
{
public:
virtual void print(int) override{}//err
};
int main()
{
return 0;
}
抽象类,也叫接口类,即包含纯虚函数的类
virtual void Drive() =0;//纯虚函数,不需要实现,一般作为基类供子类继承
虚函数的继承是一种接口继承,普通函数的继承是一种实现继承
class Base
{
public:
virtual void print()=0 ;
};
class Derive :public Base
{
public:
virtual void print()
{
;
}
};
int main()
{
//Base b;
Derive d;
return 0;
}
class Base
{
public:
virtual void one(){};
virtual void two(){};
virtual void three(){};
private:
int a;
};
class Derive :public Base
{
public:
};
int main()
{
Base b;
Derive d;
return 0;
}
计算一个类的大小,如果这个类中有虚函数,大小需要加上一个虚表指针(4/8个字节)
虚基表指针与虚表指针不同(虚函数表指针),虚基表是解决菱形继承时的一个东西
虚函数被编译后和普通函数一样放在代码段 只是函数地址被放到虚表中
一个类的所有对象共享一张虚表,不管是否完成重写
class Base
{
public:
virtual void one(){ cout << "Base::one" << endl; }
virtual void two(){ cout << "Base::two" << endl; }
virtual void three(){ cout << "Base::three" << endl; }
private:
int a;
};
class Derive :public Base
{
public:
virtual void one(){ cout << "Derive::one" << endl; }
virtual void two(){ cout << "Derive::two" << endl; }
virtual void three(){ cout << "Derive::three" << endl; }
};
typedef void(*VPTR)();
void Print(VPTR vptr[])
{
for (int i = 0; vptr[i] != nullptr; i++)
{
printf("vptr[%d]:%p -> ", i, vptr[i]);
vptr[i]();
}
cout << endl;
}
int main()
{
Base b;
Print((VPTR*)(*((int*)&b)));
Derive d;
Print((VPTR*)(*((int*)&d)));
return 0;
}
// 这里找到虚表
//&d->(int*)&d->*(int*)&d->(VPTR*)(*(int*)(&d))
class Base1
{
public:
virtual void one(){ cout << "Base1::one()" << endl; }
virtual void two(){ cout << "Base1::two()" << endl; }
private:
int a;
};
class Base2
{
public:
virtual void one(){ cout << "Base2::one()" << endl; }
virtual void two(){ cout << "Base2::two()" << endl; }
};
class Derive:public Base1,public Base2
{
public:
virtual void three(){ cout << "Derive::three()" << endl; }
};
typedef void(*VPTR)();
void Print(VPTR vptr[]);
int main()
{
Base1 b1;
Print((VPTR*)(*((int*)&b1)));
Base2 b2;
Print((VPTR*)(*((int*)&b2)));
Derive d;
Print((VPTR*)(*(((int*)(&d)) + 0)));
//Print((VPTR*)(*(((int*)(&d)) + 1)));
Print((VPTR*)(*(int*)((char*)&d + sizeof(Base1))));
return 0;
}
void Print(VPTR vptr[])
{
for (int i = 0; vptr[i] != nullptr; i++)
{
printf("vptr[%d]:%p -> ", i, vptr[i]);
vptr[i]();
}
cout << endl;
}
由于编译器的一些原因,打印虚表前需要清理解决方案,不然可能导致程序崩溃…
小结
多继承会使派生类中不止一张虚表
派生类未重写的虚函数放在第一张虚表中(这个第一张表的第一是继承的顺序,不是声明)
基类存在相同的函数,调用可能会导致访问不明确
菱形继承 菱形虚拟继承的虚表相关知识这里暂不讨论
如果传对象也可以实现多态 ,那就会发生一种情况,子类切片后赋给父类,改变了父类的虚函数表,那父类都不知道自己调用的虚函数是自己的还是子类的。
后面会记录一篇题目合集