面向对象程序设计OOP
OPP概述
- 核心思想
- 数据抽象:将类的接口和实现分离
- 继承:定义相似的类型并对其相似关系建模
- 动态绑定:在一定程度上忽略相似类型的区别,以统一的方式使用其对象
继承
- 通过继承联系在一起的类构成一种层次关系,通常在层次关系的根部有一个基类,其他类则直接或间接地从基类继承而来,这些继承得到的类称为派生类,基类定义在层次关系中所有类共同拥有的成员,而每个派生类定义各自持有的成员
- 某些函数,基类希望其派生类各自定义适合自身的版本,此时基类就将这些函数声明成虚函数
- 允许派生类显式注明其将使用哪个成员函数改写基类的虚函数,在该成员函数形参列表之后增加一个override关键字
//基类
class Quote{
public:
std::string isbn() const;
virtual double net_price(std::size_t n) const;//虚函数
};
//派生类
class Bulk_quote : public Quote {
public:
double net_price(std::size_t n) const override;
};
动态绑定(运行时绑定)
- 可用同一段代码分别处理Quote和Bulk_quote的对象
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<<"ISNB:"<<item.isbn()<<"# sold:"<<n<<"total due:"<<ret<<endl;
return ret;
}
print_total(cout,basic,20);
print_total(cout,bulk,20);
定义基类和派生类
定义基类
- 作为继承关系中根节点的类通常会定义一个虚析构函数
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 {
return n * price;
}
virtual ~Quote()=deafult;
private:
std::string bookNo;
protected:
double price = 0.0;
};
成员函数与继承
- 派生类需要对虚函数提供自己的新定义以覆盖从基类继承而来的旧定义
- 任何构造函数之外的非静态函数都可以是虚函数
- 关键字virtual只能在类内部的声明语句之前而不能用于类外部的函数定义
定义派生类
- 如果派生类没有覆盖其基类中的某个虚函数,则该虚函数的行为类似于其他的成员,派生类会直接继承其在基类中的版本
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;
private:
std::size_t min_qty=0;
double discount = 0.0;
};
派生类对象及派生类向基类的类型转换
Quote item;//基类对象
Bulk_quote bulk;//派生类对象
Quote *p = &item;//p指向Quote对象
p = &bulk;//p指向bulk的Quote部分
QUote &r = bulk;//r绑定到bulk的Quote部分
派生类构造函数
- 尽管在派生类对象中含有从基类继承而来的成员,但是派生类并不能直接初始化这些成员,和其他创建了基类对象的代码一样,派生类必须使用基类的构造函数来初始化其基类部分
- 先初始化基类部分,然后按照声明的顺序依次初始化派生类的成员
继承与静态成员
- 如果基类定义了一个静态成员,则在整个继承体系中只存在该成员的唯一定义
派生类的声明
- 派生类的声明包含类名但不包含派生列表
class Bulk_quote;
//class Bulk_quote : public Quote;
被用作基类的类
- 使用某个类作为基类,则该类必须已经定义而非仅声明
class Base{/*...*/};
class D1 : public Base(/*...*/);
class D2 : public D1{/*...*/};
//Base是D1的直接基类,是D2的间接基类
防止继承的发生
- 在类后加关键字final
类型转换与继承
- 当使用一个基类的指针或引用时,不清楚其绑定的对象是基类还是派生类
静态类型与动态类型
-
静态类型:在编译时总是已知的,是在变量声明时的类型或表达式生成的类型
-
动态类型:变量或表达式表示的内存中的对象的类型,直到运行时才可知
-
如果表达式既不是引用也不是指针,则其动态类型永远与静态类型一致
-
不存在从基类向派生类的隐式类型转换
-
从派生类向基类的类型转换只对指针或引用类型有效
虚函数
派生类中的虚函数
- 派生类中的虚函数的返回类型、形参必须与基类一致
- 当类的虚函数的返回类型是类本身的指针或引用时,上述规则无效
final与override说明符
- 如果使用override标记了某个函数,但该函数并没有覆盖已存在的虚函数,则编译器将报错
- 如果将某个函数指定为final,则任何尝试覆盖该函数的操作将引发错误
回避虚函数的机制
- 在某些情况下,希望对虚函数的调用不要进行动态绑定,而是强迫其执行虚函数的某个特定版本,可以使用作用域运算符
double undiscounted=baseP->Quote::net_price(42);
- 如果一个派生类虚函数需要调用其基类版本,但是没有使用作用域运算符,则在运行时该调用将被解析为对派生类版本自身的调用,从而导致无限递归
抽象基类
纯虚函数
- 无需定义,在函数体的位置(声明语句的分号之前)书写=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){}
double net_price(std::size_t)const = 0;;
protected:
std::size_t quantity = 0;
double discount = 0.0;
};
含有纯虚函数的类时抽象基类
- 抽象基类负责定义接口,而后续的其他类可以覆盖该接口
- 不能直接创建一个抽象基类的对象
派生类构造函数只初始化其直接基类
class Bulk_quote : public Disc_quote{
public:
Bulk_quote()=default;
Buld_quote(const std::string& book,double price,std::size_t qty,double disc):Disc_quote(book,price,qty,disc){}
double net_price(std::size_t) const override;
};
访问控制与继承
受保护的成员
- protected
- 对于类的用户不可访问,对于派生类的成员和友元可以访问
- 派生类的成员或友元只能通过派生类对象来访问基类的受保护成员,基类对于一个基类对象中的受保护成员没有任何访问特权
class Base{
protected:
int prot_mem;
};
class Sneaky:public Base{
friend void clobber(Sneaky&);//能访问Sneaky::prot_mem;
//friend void clobber(Base&);//不能访问Base::prot_mem;
int j;
};
- 不能继承友元关系,每个类负责控制各自成员的访问权限
改变个别成员的可访问性
class Base{
public:
std::size_t size() const {return n;}
protected:
std::size_t n;
};
class Derived : private Base{
public:
using Base::size;
protected:
using Base::n;
};
默认的继承保护级别
- class定义的派生类时私有继承
- struct定义的派生类时公有继承
构造函数与拷贝控制
虚析构函数
Quote *item = new Quote;
delete item;//调用Quote的析构函数
item = new Bulk_quote;
delete item;//调用Buld_quote的析构函数
虚析构函数将阻止合成移动操作
- 如果一个类定义了析构函数,即使通过=default的形式使用了合成的版本,编译器也不会为这个类合成移动操作