转换和继承,虚函数
Understanding conversions between base and derived classes is essential to
understanding how object-oriented programming works in C++.
理解基类和派生类之间的转换是必不可少的 理解面向对象编程在。
Like built-in pointers, the smart pointer classes (§12.1, p. 450) support the
derived-to-base conversion—we can store a pointer to a derived object in a
smart pointer to the base type.
像内置指针,智能指针类支持 导出到基类的转换可以存储一个指向派生类对象的一个
基类类型的指针。
Static Type and Dynamic Type(静态和动态类型)
class Quote { public: Quote()=default; Quote(const string &book, double sales_price):bookNo(book), price(sales_price) {cout<<"Quote gouzhao function"<<endl;} string isbn() const {return bookNo;} //返回指定数量的项目总销售价格 //派生类将重写应用不同的折扣算法 virtual double net_price(size_t n) const {return n*price;} virtual ~Quote()=default; //动态链接析构函数 private: string bookNo; //这本书的isbn号 protected: double price=0.0; //不打折的价格 }; //继承,如何继承? class Bulk_quote : public Quote { public: Bulk_quote()=default; Bulk_quote(const string & book, double p, size_t qty, double disc) : Quote(book, p), min_qty(qty), discount(disc) {cout<<"Bulk_quote construct function"<<endl;} //重写虚函数 double net_price(size_t n) const override {cout<<"double net_price(size_t)"<<endl; return n*price;} //再次说明,请声明函数后一定要记得定义它,不然我这是出了各种莫名其妙的错误! // ~Bulk_quote2(){cout<<"~Bulk_quote2()"<<endl;} private: size_t min_qty=0; double discount=0.0; };
There Is No Implicit Conversion from Base to Derived ...
void fun1() { Quote base; // Bulk_quote* bulkP=&base; 错误不能把基类转换成派生类 // Bulk_quote& bulkRef=base; 同上 Bulk_quote bulk; Quote *itemP=&bulk; //派生类转换成基类 // Bulk_quote *bulkP=itemP; error:基类到派生类 }
...and No Conversion between Objects
void fun2() { Bulk_quote bulk; //派生类 Quote item(bulk); //调用基类Quote的赋值构造函数 item=bulk; // calls Quote::operator=(const Quote&)拷贝赋值运算符 }
这里当我们的参数对象是基类的时候,转换的时候只有基类部分会被拷贝,而派生的那部分
会直接被忽略
Virtual Functions虚函数
Key Concept: Conversions among Types Related by Inheritance
There are three things that are important to understand about conversions
among classes related by inheritance:
? The conversion from derived to base applies only to pointer or reference
types.
? There is no implicit conversion from the base-class type to the derived
type.
? Like any member, the derived-to-base conversion may be inaccessible due
to access controls.
关键概念:继承关系的类型之间的转换
因继承而相关联的类中转换的重要的三件事 :
?从派生类到基类的转换只适用于指针或引用 类型。
?有从基类到派生类没有隐式转换
?像任何成员,派生类到基类的转换可能无法访问由于 访问控制。
There are three things that are important to understand about conversions
among classes related by inheritance:
? The conversion from derived to base applies only to pointer or reference
types.
? There is no implicit conversion from the base-class type to the derived
type.
? Like any member, the derived-to-base conversion may be inaccessible due
to access controls.
关键概念:继承关系的类型之间的转换
因继承而相关联的类中转换的重要的三件事 :
?从派生类到基类的转换只适用于指针或引用 类型。
?有从基类到派生类没有隐式转换
?像任何成员,派生类到基类的转换可能无法访问由于 访问控制。
动态绑定
double print_total(ostream &os, const Quote &item, size_t n) { //根据不同的对象来绑定到这个参数的类型 //这里引用Quote::net_price 或 Bulk_quote::net_price double ret=item.net_price(n); os<<"ISBN: "<<item.isbn() //调用 Quote::isbn <<" # sold: "<<n<<" total due: "<<ret<<endl; return ret; //这里上面的Quote参数是可以接受Quote或者Bulk_quote类型的 }
Calls to Virtual Functions May Be Resolved at Run Time
调用的虚函数可以在运行时确定
void fun3() { Quote base("0-201-82470-1", 50); print_total(cout, base, 10); //调用Quote的net_price Bulk_quote derived("0-201-82470-1", 50, 5, 0.19); print_total(cout, derived, 10); //调用Bulk_quote的net_price base=derived; //吧quote类型的部分拷贝到base base.net_price(20); }
Virtual Functions in a Derived Class(【派生类中的虚函数)
A function that is virtual in a base class is implicitly virtual in its
derived classes. When a derived class overrides a virtual, the parameters in
the base and derived classes must match exactly.
derived classes. When a derived class overrides a virtual, the parameters in
the base and derived classes must match exactly.
The final and override Specifiers
(1) 重载的几个函数必须在同一个类中;
覆盖的函数必须在有继承关系的不同的类中
(2) 覆盖的几个函数必须函数名、参数、返回值都相同;
重载的函数必须函数名相同,参数不同。参数不同的目的就是为了在函数调用的时候编译器能够通过参数来判断程序是在调用的哪个函数。这也就很自然地解释了为什么函数不能通过返回值不同来重载,因为程序在调用函数时很有可能不关心返回值,编译器就无法从代码中看出程序在调用的是哪个函数了。
(3) 覆盖的函数前必须加关键字Virtual;
重载和Virtual没有任何瓜葛,加不加都不影响重载的运作。
覆盖的函数必须在有继承关系的不同的类中
(2) 覆盖的几个函数必须函数名、参数、返回值都相同;
重载的函数必须函数名相同,参数不同。参数不同的目的就是为了在函数调用的时候编译器能够通过参数来判断程序是在调用的哪个函数。这也就很自然地解释了为什么函数不能通过返回值不同来重载,因为程序在调用函数时很有可能不关心返回值,编译器就无法从代码中看出程序在调用的是哪个函数了。
(3) 覆盖的函数前必须加关键字Virtual;
重载和Virtual没有任何瓜葛,加不加都不影响重载的运作。
struct B { virtual void f1(int) const; virtual void f2(); void f3(); }; struct D1 : B //这是什么继承? { void f1(int) const override; //ok可以覆盖 // void f2(int) override; error没有f2(int)这个虚函数 // void f3() override; //error:f3()不是虚函数 // void f4() override; error:没有f4()这个虚函数 };
final这个关键字
struct D2 : B { //从B继承f2,f3之后我们override f1 void f1(int) const final; //之后派生类无法覆盖f1 }; struct D3 : D2 { void f2(); // void f1(int) const; //注意:这个函数是被final修饰的函数 };
Virtual Functions and Default Arguments(虚函数和默认参数)
具有默认参数的虚函数应该使用相同的参数 在基值和派生类。
Circumventing the Virtual Mechanism
void fun4() { cout<<"there is fun4"<<endl; Quote *baseP; double undiscounted = baseP->Quote::net_price(42); }
Ordinarily, only code inside member functions (or friends) should need to use
the scope operator to circumvent the virtual mechanism
通常只有类里面的成员函数(友元函数)需要使用作用域操作符来规避虚拟机制
15.4. Abstract Base Classes
抽象类就是类里定义了纯虚成员函数的类
为什么要定义抽象基类呢?依我所见主要有以下原因:
1.最重要的原因是,可以将接口与实现分离。接口是软件产品最有价值的资源,
设计接口比实现接口需要耗费更昂贵的成本。因此,要将接口保护起来,
以免在针对客户需求修改实现的时候,程序员不小心把接口破坏掉。
2.引入抽象基类和纯虚函数方便实现C++的多态特性。
可以用抽象基类的指针去调用子类对象的方法。
3.很多时候,许多基类被实例化是不合理的。
例如“形状”这个基类,被实例化之后反而会让人相当费解,
所以干脆将“形状”这个类定义为抽象类,由它派生出正方形,三角形等子类。
为什么要定义抽象基类呢?依我所见主要有以下原因:
1.最重要的原因是,可以将接口与实现分离。接口是软件产品最有价值的资源,
设计接口比实现接口需要耗费更昂贵的成本。因此,要将接口保护起来,
以免在针对客户需求修改实现的时候,程序员不小心把接口破坏掉。
2.引入抽象基类和纯虚函数方便实现C++的多态特性。
可以用抽象基类的指针去调用子类对象的方法。
3.很多时候,许多基类被实例化是不合理的。
例如“形状”这个基类,被实例化之后反而会让人相当费解,
所以干脆将“形状”这个类定义为抽象类,由它派生出正方形,三角形等子类。
纯虚函数
当类声明中包含纯虚函数时,则不能创建该类的对象。
基类的纯虚函数必须有“=0”,但不一定没有函数的实现,只是不能直接内嵌在类中。
基类的纯虚函数必须有“=0”,但不一定没有函数的实现,只是不能直接内嵌在类中。
class Disc_quote : public Quote { public: Disc_quote()=default; Disc_quote(const string & book, double price, size_t qty, double disc): Quote(book, price), quantity(qty), discount(disc) {cout<<"Disc_quote构造函数"<<endl;} double net_price(size_t) const = 0; //纯虚函数 protected: size_t quantity=0; double discount=0.0; };
纯虚函数不能直接在类里面进行定义,要定义就要在外面
virtual void Move(int nx, int ny) = 0;
void BaseEllipse::Move(int nx, int ny) {x = nx; y = ny;}
这样是允许的
含有纯虚函数的类就是抽象类
void fun5() { cout<<"there is fun5"<<endl; // Disc_quote discount; //error:Disc_quote是一个抽象类,含有纯虚函数没法实例化 Bulk_quote bulk; //ok,这个里面没有纯虚函数,不是抽象类 }
A Derived Class Constructor Initializes Its Direct Base Class Only
就是派生类参数列表可以直接初始化基类
class Bulk_quote2 : public Disc_quote { public: Bulk_quote2()=default; //直接初始化 Bulk_quote2(const string& book, double price, size_t qty, double disc): Disc_quote(book, price, qty, disc) {} double net_price(size_t) const override; //覆盖纯虚函数 };
有些人觉得,做人要真,所以说话要直,结果就到处直来直去得罪人。其实大错。中国人写"真"字,是"直"下面两点。也就是说,一些实话、直话,也要保留两点,不要全部说出去。实话实说是真,但实话全说就是蠢。肚子里藏不住话的人,自以为说真话,其实不过是情商不够而已。
不幸的是,作者好像既不真也不直~~~~~