数据抽象是一种依赖于接口和实现分离的编程技术。继承和动态绑定对程序的编号有两方面的影响:一是我们可以更容易地定义与其它类相似但不完全相同的类;二是在使用这些彼此相似的类编写程序时,我们可以在一定程度上忽略掉它们的区别。
定义鸡肋:
#include<bits/stdc++.h>
using namespace std;
class Quote{
public:
Quote()=default;
Quote(string s,double t):bookNo(s),price(t){};
~Quote()=default;//都需要一个虚函数,
string isbn(){
return bookNo;
}
virtual double net_price(size_t n){return n*price;}//总价函数,定义成虚函数,所以可以动态绑定
private:
string bookNo;
protected :
double price=0.0;
};
注意:基类通常都应该定义一个虚析构函数,即使该函数不执行任何实际操作
基类通过在其成员函数的声明之前加上关键字 virtual 使得该函数执行动态绑定。**任何构造函数之外的非静态函数都可以是虚函数。**如果基类把一个函数声明成虚函数,则该函数在派生类中隐式地也是虚函数
派生类可以继承定义在基类中的成员,但是派生类不一定有权访问从基类继承而来的成员。派生类只能访问公有成员和受保护的成员,不能访问私有成员
#include<bits/stdc++.h>
using namespace std;
class Quote{
public:
Quote()=default;
Quote(string s,double t):bookNo(s),price(t){};
~Quote()=default;//都需要一个虚函数,
string isbn(){
return bookNo;
}
virtual double net_price(size_t n){return n*price;}//总价函数,定义成虚函数,所以可以动态绑定
private:
string bookNo;
protected :
double price=0.0;
};
class Bulk_quote:public Quote{
public :
~Bulk_quote(){};
Bulk_quote(const string&,double ,size_t,double );
double net_price(size_t n)const override;
void net_price(size_t n)const override;//让编译器显式的检查有没有覆盖成功
};
int main(){
return 0;
}
虚函数:基类希望它各自的派生类定义适合自己的版本,这就是虚函数
动态绑定:
#include<bits/stdc++.h>
using namespace std;
class Quote{
public:
Quote()=default;
Quote(string s,double t):bookNo(s),price(t){};
~Quote()=default;//都需要一个虚函数,
string isbn(){
return bookNo;
}
virtual double net_price(size_t n){return n*price;}//总价函数,定义成虚函数,所以可以动态绑定
private:
string bookNo;
protected :
double price=0.0;
};
class Bulk_quote:public Quote{
public :
~Bulk_quote(){};
Bulk_quote(const string&,double ,size_t,double );
double net_price(size_t n)const override;
void net_price(size_t n)const override;//让编译器显式的检查有没有覆盖成功
};
double print_total(ostream&os,const Quote&item,size_t n){
double ret=item.net_price(n);//根据item指向的对象不桶,那么这个函数的行为也不桶,这就是动态绑定
}
int main(){
return 0;
}
当我们使用基类的指针或者引用,调用一个虚函数的时候会发生动态绑定。因为这是在函数运行的时候确定调用那个,这又被称为运行时绑定。
派生类使用基类的成员:
double Bulk_quote::net_price(size_t cnt){//派生类可以直接使用基类的protected和public成员,但是不能访问私有成员
cout<<bookNo<<endl;//私有不可访问。报错
if(cnt>price) return price;//公有可以访问
else return cnt;
}
继承与静态成员:
#include<bits/stdc++.h>
using namespace std;
class Base{
public:
static void statmem();
};
//一定要记得初始化
void Base::statmenm(){};
class Derived:public Base{
public:
void f(const Derived&);
};
void Derived(const Derived&obj){
Base::statmem();
Derived::statmem();//再派生类中可以通过多种方式调用这部分
obj.statmem();
statmem();
}
int main(){
return 0;
}
不论派生出多少类,静态成员是唯一的。
假如某个静态成员是可以访问的,那么我们可以通过基类,也可以通过派生类访问它。
派生类的声明
//class Derived;//声明是正确的
//class Derived:public :Base;
int main(){
return 0;
}
派生类包含类名但是不包含继承列表
被用作基类的类
#include<bits/stdc++.h>
using namespace std;
class Quote;
class Derived :public Quote{};//错误,
int main(){//如果某个类被用作基类,那么一定是被定义,而非被简单声明(防止了自己继承自己的情况)
}
防止继承的发生:
在类名后面跟一个关键字 final 能防止继承发生:
#include<bits/stdc++.h>
using namespace std;
class Quote final{};
class Derived :public Quote{};//错误,final类无法被继承
类型转换与继承:
可以将基类的指针或引用绑定到派生类对象上,当使用基类的引用或指针时,实际上我们并不清楚该引用或指针所绑定对象的真实类型。该对象可能是基类的对象,也可能是派生类的对象
和内置指针一样,只有指针类也支持派生类向基类的类型转换,这意味着我们可以将一个派生类对象的指针存储在一个基类的智能指针内
静态类型与动态类型:
当使用存在继承关系的类型时,必须将一个变量或其它表达式的静态类型与该表达式表示对象的动态类型区分开来。表达式的静态类型在编译时总是已知的,它是变量声明时类型或表达式生成的类型;动态类型则是变量或表达式的内存中对象的类型。动态类型直到运行时才可知。
**如果达式既不是引用也不是指针,则它的动态类型永远与静态类型一致。**而基类的指针或引用的静态类型可能与其动态类型不一致。如:
#include<bits/stdc++.h>
using namespace std;
class Quote{};
class Derived:public Quote{};
int main(){
Quote item;
Derived der;
Quote *p=&item;//正确
//对于rp来讲:静态类型是Quote,动态类型是Derived
p=&der;//p指向der的Quote部分
Quote &r=der;//r指向der的Quote部分,被覆盖的指向覆盖后的版本,没有被覆盖的指向基类版本
return 0;
}
不存在从基类向派生类的隐式类型转换:
之所以存在派生类向基类的类型转换是因为每个派生类对象都包含一个基类部分,而基类的引用或指针可以绑定到该基类部分上。但基类不一定包含派生类(派生类可能定义了新的成员),所以如果我们将一个基类的对象向派生类的类型转换,则我们有可能会使用基类中没有的成员,所以不存在从基类向派生类的自动类型转换:
#include<bits/stdc++.h>
using namespace std;
class Quote{};
class Derived:public Quote{};
int main(){
Quote item;
Derived der;
Derived *d=&item;//不存在基类向派生类的隐式类型转换
Derived &r=item;//不存在基类向派生类的隐式类型转换
//因为基类只是派生类的一部分
Quote *p=&der;
Derived *d1=p;//注意:即使一个基类指针或引用绑定在一个派生类对象上,也不能执行从基类向派生类的转换
return 0;
}
我们可以通过 dynamic_cast 或 static_cast 显式地将一个基类对象转换为派生类类型
在对象之间不存在类型转换:
派生类向基类的自动类型转换只对指针或引用类型有效,在派生类类型和基类类型之间不存在这样的转换。但我们可以通过拷贝构造函数、移动构造函数、拷贝赋值运算符或移动赋值运算符将一个派生类类型转换成基类类型(发生了切掉!!),因为这些拷贝控制成员中通常包含一个本类类型的 const 引用或右值引用:
#include <iostream>
using namespace std;
class Quote {
public:
Quote() = default;
Quote(const std::string &book, double sales_price) :
bookNo(book), price(sales_price) {}
std::string isbn() const {
return bookNo;
}
virtual double net_price(std::size_t n) const {//定义成虚函数,运行2时进行动态绑定
return n * price;
}
virtual ~Quote() = default;//基类通常都应该定义一个虚析构函数,即使该函数不执行任何实际操作
private:
std::string bookNo;//书籍的isbn编号
protected://可被派生类访问
double price = 0.0;//代表普通状态下不打折的价格
};
class Bulk_quote : public Quote {
public:
Bulk_quote() = default;
Bulk_quote(const std::string&, double, std::size_t, double);
double net_price(std::size_t) const override;//override显式注明该成员函数覆盖它继承的虚函数
// ~Bulk_quote();
private:
std::size_t min_qty = 0;//适用折扣政策的最低购买量
double discount = 0.0;//以小数表示的折扣额
};
Bulk_quote::Bulk_quote(const std::string &book, double p, std::size_t qty, double disc) :
Quote(book, p), min_qty(qty), discount(disc) {}
double Bulk_quote::net_price(size_t cnt) const {
if(cnt >= min_qty) return cnt * (1 - discount) * price;
return cnt * price;
}
int main(void){
Bulk_quote bulk;//派生类对象
Quote item(bulk);//使用合成拷贝构造函数Quote::Quote(const Quote&)
item = bulk;//使用合成拷贝赋值运算符Quote& Quote::operator=(const Quote&)
//显然我们不能将基类类型通过拷贝控制成员转换成派生类对象
// Quote cnt;
// Bulk_quote gg(cnt);
return 0;
}