virtual的作用是让实际指向派生类的基类指针能动态调用函数的派生类版本而不是基类,不加virtual关键字调用的就是基类的版本。
其中的原理是指针是静态类型在编译时确定,指向的对象是动态类型在运行时确定,故编译器编译时只能识别到指针的类型是基类,而无法识别到指针指向的实际类型是个派生类,加了virtual声明就能确保识别出动态类型。
c++规定,当一个成员函数被声明为虚函数后,其派生类中的同名函数都自动成为虚函数。
另外可以在virtual方法后面加=0
使其变为纯虚函数,声明了纯虚函数的类无法直接定义使用,且其派生类必须实现纯虚函数的方法(跟Java的abstract很像)
struct A{
virtual void f1(int) const = 0;
};
struct B : A{
void f1(int) const override{};
};
int main(){
//A a1; //forbidden
B b1;
A *pa1 = &b1; //ok
}
override声明可用于检查函数是否重载了基类的同名函数(方法名和参数列表要相同), 主要是让编译器检查方法的,但为了可读性建议最好加上。
如果不希望某个类再被继承或者方法再被重载,可用final声明(这不废话吗?)
struct B : A{
void f1(int) const final; //不允许后继的其他类覆盖f1
}
C++相对于Java独特的一点是能使用派生访问继承符来控制继承自派生类的新类对基类的访问权限:
对于继承自基类的类本身及类的使用者,派生访问继承符并不影响其对基类访问权限,类对基类成员的访问权限仍旧看基类的访问说明符。
public继承自基类的类,对于其子类来说基类的访问属性不变(能访问基类所有非private成员),对于子类用户来说可访问public成员,private或protected的基类部分无法访问;
private继承自基类的类,对于其子类来说基类所有属性都是private,无法访问,子类的用户访问权限同public继承;
protected继承自基类的类,对于其子类来说只能访问基类的public部分,子类的用户访问权限同public继承;
演示:
class Base{
friend class Pal;
public:
void pub_mem(){};
protected:
int prot_mem; // protected 成员
private:
char priv_mem = 'B'; // private 成员
};
struct Pub_Derv : public Base {
void f() {
Base::pub_mem();
Base::prot_mem;
// Base::priv_mem; //private成员对派生类不可访问
}
};
struct Priv_Derv : private Base {
void f1() {
Base::pub_mem();
Base::prot_mem;
// Base::priv_mem; //private成员对派生类不可访问
}
};
struct Prot_Derv : protected Base {
void f2() {
Base::pub_mem();
Base::prot_mem;
// Base::priv_mem; //private成员对派生类不可访问
}
};
struct Derived_from_Public : public Pub_Derv {
void use_base() {
Base::pub_mem();
Base::prot_mem;
// Base::priv_mem; //private成员对派生类不可访问
}
};
struct Derived_from_Private : public Priv_Derv {
void use_base() {
// Base::pub_mem(); //从新类来说继承的基类部分全是private
// Base::prot_mem;
// Base::priv_mem;
}
};
struct Derived_from_Protected : public Prot_Derv {
void use_base() {
Base::pub_mem(); //继承的基类部分全是protected,只有基类和派生类能访问
Base::prot_mem;
// Base::priv_mem;
}
};
int main(){
Pub_Derv d1;
Priv_Derv d2;
Prot_Derv d3;
Base *p = &d1;
// p = &d2; // 无法指向私有继承的派生类,要是能的话就能借此修改基类数据了,破坏封装性
// p = &d3; // 无法将保护继承派生类向基类转换,原因同上
d1.f();
d1.pub_mem();
// d1.prot_mem; //inaccesiable
// d1.priv_mem; //inaccesiable
Derived_from_Public dd1;
Derived_from_Private dd2;
Derived_from_Protected dd3;
p = &dd1;
// p = &dd2;
// p = &dd3;
dd1.f();
dd1.pub_mem();
dd1.use_base();
// dd1.prot_mem; //inaccesiable
// dd1.priv_mem; //inaccesiable
/*
总结:派生访问控制符用来控制后继类对基类部分的访问权限;
派生类私有或受保护的基类部分不能通过转换为基类的方法修改'
*/
}
C++中派生类并不是一个独立对象,而是派生类部分和基类部分的混合,构造派生类时总是先构造基类部分,再构造派生类部分,自上而下; 构析时则相反,先销毁派生类部分,再构造销毁部分,自下而上。
示例:
#include
using namespace std;
class Quote {
public:
Quote() = default;
Quote(const Quote &) = default; //复制拷贝
Quote(Quote &&) = default; //移动拷贝
Quote& operator=(const Quote&) = default; //拷贝赋值
Quote& operator=(Quote &&) = default; //移动赋值
Quote(const std::string &book,double sales_price):bookName(book),price(sales_price){ cout << "Quote contructed with 4 args " << endl; }
std::string isbn() const {return bookName;}
//返回给定数量的书籍的销售总额
//派生类负责改写并使用不同的折扣计算算法
virtual double net_price(std::size_t n) const { return n * price; };
~Quote() { cout << "Quote destroyed " << endl; };
private:
std::string bookName;
protected:
double price = 0.0;
};
class Disc_quote : public Quote {
public:
Disc_quote() = default;
Disc_quote(const std::string& book,double price,std::size_t qty,double disc): Quote(book,price), quantity(qty),discount(disc){ cout << "Disc_quote contructed with 4 args " << endl; }
~Disc_quote(){ cout << "Disc_quote destroyed " << endl; }
double net_price(std::size_t);
std::pair discount_policy() const { return {quantity,discount}; }
protected:
std::size_t quantity = 0; //折扣适用的购买量
double discount = 0.0; // 表示折扣的小数值
};
// 以Disc_quote这个中间类重构Bulk_quote
class Bulk_quote : public Disc_quote {
public:
Bulk_quote() = default;
Bulk_quote(const std::string& book, double price, std::size_t qty, double disc):Disc_quote(book,price,qty,disc){ cout << "Bulk_quote contructed with 4 args " << endl;}
double net_price(std::size_t) const override;
~Bulk_quote(){ cout << "Bulk_quote destroyed " << endl; }
};
double Bulk_quote::net_price(std::size_t n) const{
int total = price * n;
if(n < quantity)
return total * (1 - discount);
else
return total;
}
int main(){
Bulk_quote bulk("Flower for Algernon", 50.00, 5,0.5); //先自下而上调用,后自上而下构建
cout << bulk.isbn() << " " << bulk.net_price(10) << endl;
Bulk_quote *bulkP = &bulk;
Quote *itemP = &bulk;
bulkP->discount_policy();
}
运行将出现如下结果:
Quote contructed with 4 args
Disc_quote contructed with 4 args
Bulk_quote contructed with 4 args
Flower for Algernon 500
Bulk_quote destroyed
Disc_quote destroyed
Quote destroyed
可以将派生类向基类隐式装换,但不允许反过来:
base *bp1,b1;
derived *dp1,d1;
bp1 = &d1; // right
dp1 = &b1; // wrong
此外,在建立有多级派生类的OO体系时最好将基类的构造函数和构析函数声明为虚函数,以便编译器能够正确地构造和析构对象类型。(尤其是当你需要用智能指针处理类的传递时)
完成 15.21 的自定义类练习
扩展该章的TextQuery体系 (15.36,可结合正则表达式)
理解public基础,private基础和protected继承
在C++中管理多级派生类的访问权限,运用三五法则,内存管理(项目实战)
15.8.1 为什么不能直接使用对象进程而要用指针,直接用对象会有什么后果?
回答:
其一,如果直接用对象的话没法解决派生类的兼容问题 比如给vector
传Derived类会导致Derived类被隐式转为Base类,其Base部分被切掉,这样想在Derived类做的一些处理就无效了
其二,避免不必要的拷贝,节省内存空间,加快运行效率。
扩展参考:https://stackoverflow.com/questions/22146094/why-should-i-use-a-pointer-rather-than-the-object-itself
对比Java中的abstract与C++中的virtual