重新学习《C++Primer5》第15章-面向对象程序设计
2016-03-16 12:44
381 查看
15.1 OOP:概述
1.继承派生列表:类定义后面
2.动态绑定
动态绑定:使用基类引用(或指针)调用一个虚函数时。
15.2 定义基类和派生类
15.2.1 定义基类#include<iostream> #include<string> using namespace std; class Quota{ public: Quota() = default; Quota(const string &book, double sales_price) : bookNo(book), price(sales_price){} virtual double net_price(size_t n)const { return n*price; } virtual ~Quota() = default;//基类通常都应该定义一个虚析构函数,即使该函数不执行任何实际操作也是如此 private: string bookNo; protected: double price = 0.0; };
1. 成员函数与继承:
基类必须将它的两种成员函书区分开来:
一种是基类希望派生类进行覆盖的函数,这时基类通常将其定义为虚函数,当使用引用或者指针时,根据绑定对象的不同调用不同版本。
一种是基类希望派生类直接基础而不要改变的函数。
vitual关键字:非静态函数都可以使用,只能出现在类内部声明
2.访问控制与继承
如果基类希望派生类能够访问,定义成protected
15.2.2 定义派生类
class Bulk_quota :public Quota{ public: Bulk_quota() = default; Bulk_quota(const string&, double, size_t, double); double net_price(size_t)const override; private: size_t min_qty = 0; double discount = 0.0; };
派生类经常(但不总是)覆盖它继承的虚函数
派生类到基类的类型转换
派生类构造函数:首先基类进行构造,然后依次初始化派生类
直接基类和间接基类
防止基础的发生:在类名后面跟一个关键字:final
15.2.3 类型转换与继承
1.可以将基类的指针或引用绑定到派生类对象上;
2.不存在从基类想派生类的自动类型转换;
总结:从派生类想基类转换只针对指针或引用类型有效,当我们从派生类拷贝或赋值到基类是只处理派生类中所属基类的部分。
15.3 虚函数
1.只有当我们使用基类的引用或指针调用一个虚函数时会执行动态绑定,动态绑定的对象依赖于绑定的对象;2.派生类可省略virtual;
3.如何处理我们原本希望派生类能覆盖基类的中虚函数,但把形参表弄错了(正常不会报错,或变成两个独立的函数)?
答:C++11 的override关键字
定
struct B{ virtual void f1(int) const; virtual void f2(); void f3(); }; struct D2 :B{ void f1(int) const override; void f2(int) override;//错误:B没有形如f(int)的函数 void f3() override;//错误:f4不是虚函数 void f4() override;//错误:B中没有名为f4的函数 };
final关键字:定义了final关键字的函数,之后任何尝试覆盖该函数的操作都会引发错误
struct D2 :B{ void f1(int) const final; }; struct D3 :D2{ void f2();//正确:从B继承而来 void f2(int) const;//错误:D2已经将f2声明为final };
4.回避函数机制
double undiscounted=baseP->Quote::net_price(42);
15.4 抽象基类
1.纯虚函数定义:函数后=0,只能出现在类内部,一般无须定义,如果要定义可以定义在类的外部;
含有纯虚函数的类是抽象基类,抽象基类负责定义接口,其它类可以覆盖该接口,不能创建一个抽象基类;
派生类构造函数只初始化它的直接基类;
重构:在基类的体系中增加一个抽象基
15.5 访问控制与继承
1.受保护的成员受保护的成员对于类的用户来说不可访问(类似私有成员);
受保护的成员对于派生类的成员和友元是可访问的(类似共有成员);
派生类的成员或友元只能通过派生类对象来访问基类的受保护成员;
派生类成员和友元只能访问派生类对象中的基类部分的受保护成员;
2.继承:public、private、protected
…. | public: | protected: | private: |
---|---|---|---|
public继承 | public | protected | 不可用 |
protected继承 | protected | protected | 不可用 |
private继承 | private | private | 不可用 |
假设D继承自B:
只有当D公有地继承B时,用户代码才能使用派生类像基类的转换;
不论D以什么方式继承B,D的成员函数和友元都能使用派生类想基类的转换;
如果D继承B的方式是公有或者受保护的,则D的派生类的成员和友元可以使用D向B的类型转换;
总结:
类的设计应该考虑,首先一个类有两种不同的用户:普通用户和类的实现者,普通用户使用对象,访问公有,而实现者负责编写类的成员和友元的代码,成员和友元既能访问公有,也能访问私有。
第三种用户,派生类,基类把它希望派生类能够使用的部分声明为受保护的,普通用户不能方位。
基类的设计:考虑一为可供派生类访问,二为只能有基类及基类的友元访问,前者声明为protected,后者声明为private。
4.友元和继承
友元类:声明为友元类可以访问 ,友元不能继承。
使用using改变成员可访问性:可以将该类的直接或间接基类中的任何可访问成员标记出来,访问权限由using声明语句之前的访问说明符决定。
默认的继承保护级别:使用class默认是私有继承,使用struct默认是公有继承。
习题代码
//考察前面笔记上的派生类向基类转换的三条性质
#include<iostream> class Base{ public: void pub_mem(); void memfcn(Base &b){ b = *this; } protected: int prot_mem; private: char priv_mem; }; struct Pub_Derv :public Base{ int f(){ return prot_mem; } void memfcn(Base &b){ b = *this; std::cout << "Pub_Derv" << std::endl; } //char g(){ return priv_mem; } }; struct Priv_Derv :private Base{ int f1(){ return prot_mem; } void memfcn(Base &b){ b = *this; std::cout << "Priv_Derv" << std::endl; } }; struct Prot_Derv :protected Base{ int f2(){ return prot_mem; } void memfcn(Base &b){ b = *this; std::cout << "Prot_Derv" << std::endl; } }; struct Derived_from_Public :public Pub_Derv{ int use_base(){ return prot_mem; } void memfcn(Base &b){ b = *this; std::cout << "Derived_from_Public" << std::endl; } }; struct Derived_from_Protected :protected Prot_Derv{ int use_base(){ return prot_mem; } void memfcn(Base &b){ b = *this; std::cout << "Derived_from_Protected" << std::endl; } }; int main() { Pub_Derv d1; Priv_Derv d2; Prot_Derv d3; Derived_from_Public dd2; Derived_from_Protected dd3; Base base; Base *p = new Base; p = &d1; //p = &d2; //p = &d3; p = &dd2; //p = &dd3; d1.memfcn(base); d2.memfcn(base); d3.memfcn(base); dd2.memfcn(base); dd3.memfcn(base); return 0; }
15.6继承中的类作用域
1.派生类的位于基类之内在编译时先查找自己类内部,找不到再找外层的类
2.派生类的成员将隐藏同名的基类成员,但是可以通过作用域运算符来使用隐藏的成员:return Base::mem;
3.关于查找总结:假设调用p->men();首先确定p的静态类型,然后在p的静态类型中查找,找不到再往外层查找,直到继承的顶端,还找不到则会报错,如果找到了,检查是否合法,然后在根据是否是虚函数,如果是则要根据动态绑定对象确定到底运行哪个版本;反正直接调用。
4.在派生类中的函数即使参数与基类不一样,也会将其隐藏;所以基类和派生类必须有相同的形参;
5.使用using声明语句可以将该函数所以重载实例添加到派生类作用域中。
15.7 构造函数与拷贝控制
15.7.1 虚析构函数
1.虚析构函数执行动态分配对象时,确保正确执行对应的析构函数;2.虚析构函数也会被继承;
3.一个基类总是需要定义析构函数,而且总是定义成虚析构
15.7.2 合成拷贝控制与继承
1.无论是构造、拷贝还是析构都是从本身开始,然后往上执行;2.如果基类中的默认构造、拷贝构造、拷贝赋值运算符或析构函数是被删除或者不可访问,则派生类对应的成员将是被删除的;如果基类中有一个不可访问或删除的析构函数,则派生类中合成的默认和拷贝构造函数将是被删除的;
15.7.3 派生类的拷贝控制成员
1.当派生类定义拷贝或移动,该操作负责拷贝或移动基类部分成员在内的整个对象;2.在默认情况下,基类默认构造函数初始化派生类对象的基类部分,如果想拷贝(或移动)基类部分,则必须在函数初始值中显示地使用基类的构造函数;
3.派生类赋值运算符也必须显示地为基类部分的赋值;
D& D::operator=(const D &rhs) { Base::operator=(rhs); return *this; }
4.如果构造函数或析构函数调用了某个虚函数,则我们应该执行与构造函数或析构函数所属类型相对应的虚函数版本。
15.7.4 继承的构造函数
两种情况派生类不会继承基类构造函数:第一是如果派生类的构造函数与基类的构造函数具有相同的参数列表,则该构造函数不会被继承;第二是默认、拷贝和移动构造函数不会被继承。15.8 容器与继承
1.我们不能把具有继承关系的多种类型的对象直接存放在容器中,因为如果定义关于基类的vector,则如果插入派生类对象,会把派生类部分忽略掉,而如果定义关于派生类的vector,则如果插入基类对象,不能将基类转化为派生类。2.希望在容器中存放具有继承关系的对象时,实际上存放的通常是基类的指针(更好的选择是智能指针)。
vector<shared<Quote>> basket; basket.push_back(make_shared<Quote>("",500)); basket.push_back(make_shared<Bulk_quote>());
15.8.1 编写Basket类
class Quote{ }; class Basket{ public: void add_item(const shared_ptr<Quote> &sale){ items.insert(sale); } double total_receipt(ostream&) const; private: static bool compare(const shared_ptr<Quote> &lhs, const shared_ptr<Quote> &rhs) { return lhs->isbn() < rhs->isbn(); } multiset<shared_ptr<Quote>, decltype(compare*)> items(compare); };
15.9 文本查询程序
15.9.1 面向对象解决方案
1.继承体系的设计:当令一个类共有地继承另一个类时,派生类与基类反映的“是一种(IsA)”关系。另一种关系是“有一个(Has A)”关系,暗含成员的意思。#include<iostream> #include<istream> #include<set> #include<map> #include<string> #include<memory> #include<algorithm> #include"QueryResult.h" #include"TextQuery.h" using namespace std; class Query_base { friend class Query; protected: using line_no = TextQuery::line_no; virtual ~Query_base(); private: virtual QueryResult eval(const TextQuery&)const = 0; virtual string rep() const = 0; }; class Query{ friend Query operator~(const Query&); friend Query operator|(const Query&, const Query&); friend Query operator&(const Query&, const Query&); friend ostream& operator<<(ostream&, const Query&); public: Query(string &s) :q(make_shared<Query_base>(s)){} QueryResult eval(const TextQuery &t)const{ return q->eval(t); } string rep()const{ return q->rep(); } private: Query(shared_ptr<Query_base> query) :q(query){} shared_ptr<Query_base> q; }; ostream& operator<<(ostream &os, const Query& query) { return os << query.rep(); } class WordQuery :public Query_base{ friend class Query; WordQuery(const string &s) :query_word(s){} QueryResult eval(const TextQuery &t)const { return const_cast<TextQuery&>(t).query(query_word); } string rep(){ return query_word; } string query_word; }; class NotQuery :public Query_base{ friend Query operator~(const Query&); NotQuery(const Query &q) :query(q){} string rep() const{ return "~(" + query.rep() + ")"; } QueryResult eval(const TextQuery&)const; Query query; }; inline Query operator~(const Query &operand) { return shared_ptr<Query_base>(new NotQuery(operand)); } class BinaryQuery :public Query_base{ protected: BinaryQuery(const Query &l, const Query &r, string s) :lhs(l), rhs(r), opSym(s){} string rep()const{ return "(" + lhs.rep() + " " + opSym + " " + rhs.rep() + ")"; } Query lhs, rhs; string opSym; }; class AndQuery :public BinaryQuery{ friend Query operator&(const Query&, const Query&); AndQuery(const Query &left, const Query &right) :BinaryQuery(left, right, "&"){} QueryResult eval(const TextQuery&)const; }; inline Query operator&(const Query &lhs, const Query &rhs) { return shared_ptr<Query_base>(new AndQuery(lhs, rhs)); } class OrQuery :public BinaryQuery{ friend Query operator|(const Query&, const Query&); OrQuery(const Query &left, const Query &right) :BinaryQuery(left, right, "|"){} QueryResult eval(const TextQuery&)const; }; inline Query operator|(const Query &lhs, const Query &rhs) { return shared_ptr<Query_base>(new OrQuery(lhs, rhs)); } QueryResult OrQuery::eval(const TextQuery &text)const { auto right = rhs.eval(text), left = lhs.eval(text); auto ret_lines = make_shared<set<line_no>>( left.begin(), left.end()); ret_lines->insert(right.begin(), right.end()); return QueryResult(rep(), ret_lines, left.get_file()); } QueryResult AndQuery::eval(const TextQuery &text)const { auto left = lhs.eval(text), right = rhs.eval(text); auto ret_lines = make_shared<set<line_no>>(); set_intersection(left.begin(), left.end(), right.begin(), right.end(), inserter(*ret_lines, ret_lines->begin())); return QueryResult(rep(), ret_lines, left.get_file()); } QueryResult NotQuery::eval(const TextQuery &text)const { auto result = query.eval(text); auto ret_lines = make_shared<set<line_no>>(); auto beg = result.begin(), end = result.end(); auto sz = result.get_file().size(); for (size_t n = 0; n != sz; ++n) { if (beg == end || *beg != n) ret_lines->insert(n); else if (beg != end) ++beg; } return QueryResult(rep(), ret_lines, result.get_file()); } int main() { ifstream infile("text"); TextQuery file(infile); while (true) { string sought1, sought2, sought3; cin >> sought1 >> sought2 >> sought3; Query q = Query(sought1)&Query(sought2) | Query(sought3); cout << q<<endl; const auto results = q.eval(file); print(cout, results); } return 0; }
相关文章推荐
- 你好,C++(12)怎样管理多个类型同样性质同样的数据?3.6 数组
- C语言之动态分配内存
- C/C++ debug(三)
- C++ 的引用
- 十六进制转换为浮点数
- 实例讲解C++设计模式编程中State状态模式的运用场景
- 转换到COFF期间失败:文件无效或损坏
- 【C++】容器适配器实现队列Queue的各种功能(入队、出队、判空、大小、访问所有元素等)
- C++的继承与派生
- 解析C++编程中如何使用设计模式中的状态模式结构
- 二分查找(返回目标元素的第一个位置、最后一个位置)
- C++模板
- 详解C++设计模式编程中对访问者模式的运用
- C++中的extern "C"声明简介
- C++虚函数及虚函数表解析
- C++:类与对象
- C/C++的条件编译
- 深入解析C++设计模式编程中解释器模式的运用
- 线性表链式存储的C语言实现(含源码)
- 策略模式加工厂模式实现商场促销 C++