2021-08-20

C++primer第五版第15章笔记

面向对象程序设计

面向对象程序设计主要基于三个方面:数据抽象、继承和动态绑定。本章介绍继承和动态绑定。

15.1oop:概述

使用数据抽象我们可以将接口与实现分离;使用继承,可以定义其相似类型并对相似类型建模;使用动态绑定,可以在一定程度上忽略相似类型区别,使用统一方式使用它们的对象。

15.2定义基类和派生类

首先定义基类

#include <iostream>
#include<string>
using namespace std;
class quote{
public:
	quote()=default;
	quote(const string &book,const double &sales_price):
		bookno(book),price(sales_price){}
	string isbn()const{
		return bookno;
	}
	virtual double net_price(size_t n)const{
		return n*price;
	}
	virtual void debug(ostream &,const quote &);
	virtual ~quote()=default;//对析构函数进行动态绑定,一般基类析构函数都要定义成虚函数
private:
	string bookno;
protected:
	double price=0.0;
};
void quote::debug(ostream &os,const quote &qt){
	os<<qt.bookno<<" "<<qt.price;
}

派生类可以继承其基类的成员,对于虚函数可以定义自己的版本,即覆盖。C++中,如果是我们不想改变功能的函数,则可以通过继承使用,如果是我们需要改变的,需要定义成虚函数。
15.2.2定义派生类

class bulk_quote : public quote
{
public:
	void debug(ostream &,const quote &);
	bulk_quote()=default;
	bulk_quote(const string &,double,size_t,double);
	double net_price(size_t)const override;
private:
	size_t min_qty=0.0;
	double discount=0.0;
};
void bulk_quote::debug(ostream &os,const bulk_quote &qt){
	os<<qt.bookno<<qt.discount<<qt.min_qty<<qt.price;
}
bulk_quote::bulk_quote(const string &book,double p,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;
	else
		return cnt*price;
}

编译器可以隐式的执行派生类到基类的转换,因为派生类里含有基类部分,意味着我们可以使用派生类再基类指针或者引用的地方。
如果基类定义了一个静态成员,则不管有多少个派生类,都只有一个函数实例。
若我们不想将某个基类用作被继承,可以在类名后面加final。
15.2.3类型转换与继承
我们使用基类引用或指针时并不知道绑定的真实类型,可以是派生类的对象或者地址。表达式或变量的静态类型在编译时就是已知的,而动态类型只有运行时才能确定。若表达式为不是指针和引用而是一个对象,则静态类型和动态类型一样,都是基类的类型,即便传入的是派生对象。如果我们使用派生类对象给基类对象赋值,则会切掉派生类部分,只剩下基类部分。

15.3虚函数

当使用基类指针和引用时是动态绑定,直到运行时才知道掉用了虚函数的哪个版本,所有虚函数都必须有定义。
虚函数也可以有默认实参,如果使用默认实参,实参值由静态绑定的默认值确定。某些情况下我们希望调用某个具体的虚函数而不是通过动态绑定,则可以加上类名显示声明使用的是哪个版本的虚函数。

15.4抽象基类

我们可能需要将各个派生类中不变的成分抽象出来,就可以定义一个具有纯虚函数的派生类,再将其他类继承这个派生类。纯虚函数无需定义,也可以在类外定义,书写=0表明函数为纯虚函数。含有纯虚函数的类是抽象基类,不能建立这个类的具体实例对象,派生类构造函数只能初始化它的直接基类。

15.5访问控制与继承

类使用protected关键字来声明那些可以被派生类访问,但不想被其他公共成员访问的资源。对于类的用户来说不可见,对于派生类和友元来说是可以访问的,派生类和友元只能通过派生类对象访问(不能通过基类)。
派生访问符对于派生类访问基类没有影响,控制的是派生类用户(或派生类的派生类)访问权限。
友元关系不可传递也不可继承,基类友元不能访问派生类,派生类的友元也不能访问基类,但基类的友元可以访问派生类的基类部分。
我们可以使用using来改变派生类继承时个别成员的可访问性,但这些成员必须是派生类原本就可以访问的,如:

class base{
public:
  size_t size()const{return n}
protected:
  size_t n;
};
class derived:private base{
public:
  using base::size;
protected:
  using base::n
};
这是私有继承,本来派生类的用户是不能访问base和n的,但是使用了using声明,使用户可以访问size

struct与class区别在于,struct继承和创建成员时默认是公有的,而class是私有的。

15.6继承中的类作用域

派生类的作用域嵌套在基类的作用域中,若名字在派生类中无法正确解析,则会继续在基类中寻找。
对象、指针或引用决定了对象的哪些成员可见,即便动态和静态类型不一致,还是由静态类型决定(这跟虚函数重载无关),如:

class disc_quote:public quote{
public:
  pair<size_t,double> discount_policy(){
     return {quantity,discount};
  }
};
disc_quote bulk;
disc_quote *bulkp=&bulk;
quote *iteam=&bulk;
bulk->discount_policy();//正确
iteam->discount_policy();//错误,这里虽然动态类型与静态类型不一样,但是由于是一个quote指针,所以在quote类中寻找函数,找不到将报错

派生类可以允许重定义直接或间接基类中的名字,此时内层作用域将隐藏外层作用域的名字,出非用类名::指定。
若派生类中函数与基类中函数重名,则派生类将会隐藏基类函数(注意不是重载),也可以通过类名::方式访问。

15.7构造函数和拷贝控制

位于继承体系的类也需要赋值、拷贝、移动和销毁操作,我们若没定义,编译器也会合成一个版本,这些也可被定义成删除的合成函数。
15.7.1虚析构函数
我们需要定义虚析构函数,这样救能在继承体系下使用动态分配内存了,若不定义成虚析构函数,则可能会出现指针的静态类型和动态类型不符合的情况,调用错析构函数。如果一个类定义了析构函数,则编译器不会为这个类合成移动操作。
15.7.2合成拷贝控制与继承
基类和派生类的合成拷贝控制成员依次对类本身的成员进行初始化、赋值或销毁的操作,同时合成的成员还负责使用其直接基类的操作使对象的直接基类部分初始化、赋值和销毁操作。但需要注意的一点是相应的成员应该可访问并且不能是一个被删除的函数。
某些基类的方式可能导致派生类成员被定义成删除的函数,如基类缺少拷贝操作,若派生类使用拷贝操作,则基类部分将缺少拷贝。、
15.7.3派生类的拷贝控制成员
派生类的拷贝和移动构造函数在拷贝和移动自身成员的同时,也要拷贝和移动基类成员。但析构函数不同,只负责销毁自身成员。

class D:public base{
public:
  D(const &d):base(d){}
  D(D&& d)base(std::move(d)){}
};
在定义派生类的拷贝构造函数时,需要使用基类的拷贝函数,并赋予初值派生类对象d。
如果未使用基类拷贝函数,将使用基类的默认初始值
D(const& d){}
这样拷贝的D对象将会很奇怪,它的基类部分是默认初始值,而D的派生类部分则是拷贝而来的

与拷贝函数一样,派生类的赋值运算符也必须显示使用基类的赋值运算符赋值基类部分。如:

base::operator=(const base&)//不会被自动调用
D& D::operator=(const D &rhs){
  base::operator=(rhs);//显示使用基类的赋值运算符
  赋值派生类对象部分
  return *this;
} 

我们最好不要在构造函数中调用虚函数,因为这可能使对象处于一种未完成状态从而导致程序崩溃。
15.7.4继承的构造函数
在派生类中允许我们使用直接基类的构造函数,我们可以使用

using base::base;
这条语句将使用基类构造函数构造基类部分,并将派生类部分使用默认初始值进行初始化。

using声明语句不能是explicit或constexpr,派生类继承的构造函数的属性由直接基类的原本属性决定。
默认、移动和拷贝构造函数不会被继承,这些构造函数将按照默认规则合成,继承的构造函数将不会被当作用户定义的构造函数来使用,因此如果一个类只有继承的构造函数,编译器还是会合成一个默认构造函数。

15.8容器和继承

使用容器存放继承体系的对象时,因为容器不允许存储不同类型的元素,所以需要间接存储元素。
我们不能存储派生类对象,因为基类不能转化为派生类,也不能存储基类对象,因为派生类转化时只会保留基类部分,应该存储一个基类指针,最好是智能指针,如:

vector<shared_ptr<quote>>basket;
basket.push_back(make_shared<quote>("0-201-87",50));
basket.push_back(make_shared<bulk_quote>("0-201",50,10,.25));
这两个存储时虽然make_ptr里类型不同,但存储时都是quote指针

15.8.1编写basket类

#include<iostream>
#include<memory>
#include<fstream>
#include<sstream>
#include<map>
#include<set>
#include<vector>
using namespace std;
class queryresult;
class tectquery {
public:
	tectquery(ifstream &infile) {
		string text;
		int i = 0;
		line = make_shared<vector<string>>();
		while (getline(infile, text)) {
			line->push_back(text);
			istringstream ss(text);
			string temp;
			while (ss >> temp) {
				auto &lines = hash[temp];
				if (!lines) {
					lines.reset(new set<int>());
				}
				lines->insert(i);
			}
			i++;
		}
	}
	queryresult query(const string &word);
private:
	shared_ptr<vector<string>> line;
	map<string, shared_ptr<set<int>>> hash;
};
class queryresult {
	friend void runqueries(ifstream &infile); 
public:
	queryresult(string o, shared_ptr<set<int>> p, shared_ptr<vector<string>> q) :
			s(o), wm(p), line(q) {}
	string s;
	shared_ptr<set<int>> wm;
	shared_ptr<vector<string>> line;
};
queryresult tectquery::query(const string &word) {
	shared_ptr<set<int>> l(new set<int>());
	auto iter = hash.find(word);
	if (iter == hash.end()) {
		queryresult result(word, l, line);
		return result;
	} else {
		queryresult result(word, iter->second, line);
		return result;
	}
}
ostream &print(ostream& os, const queryresult &result) {
	os << result.s << "times:" << result.wm->size() << endl;
	for (auto num : *result.wm) {
		cout << num << " " << *(result.line->begin() + num) << endl;
	}
	return os;
}
void runqueries(ifstream &infile) {
	tectquery tq(infile);
	while (true) {
		cout << "enter your word";
		string s;
		if (!(cin >> s) || s == "q")
			break;
		print(cout, tq.query(s)) << endl;
	}
}
class query_base{
	friend class query;
public:
	using line_no = tectquery::line;
	virtual ~query_base()=default;
private:
	virtual queryresult eval(tectquery&)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 &);
public:
	query(const string &);
	queryresult eval(const tectquery &t)const
	{
		return p->eval(t);
	}
	string rep(){
		return p->rep();
	}
private:
	query(shared_ptr<query_base>query):p(query){}
	shared_ptr<query_base>p;
};
class wordquery:public query_base{
	friend class query;
	wordquery(const string &s):query_word(s){}
	queryresult eval(const tectquery &t)const{
		return t.query(query_word);
	}
	string rep()const{
		return query_word;
	}
	string query_word;
};
query::query(const string &s):q(new wordquery(s)){};
class notquery:public query_base{
	friend query operator~(const query &);
	notquery(const query &q):que(q){}
	string rep()const {
		return "~("+query.rep()+")";
	}
	queryresult eval(const tectquery &);
	query que;
};
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 tectquery &);
};
inline query operator&(const query &lhs,const query &rhs){
	return shared_ptr<query_base>(new andquery(lhs,rhs));
} 
class orquery{
	friend query operator|(const query &,const query &);
	orquery(const query &left,const query &right):
		binaryquery(left,right,"|"){}
	queryresult eval(const tectquery &);
};
inline query operator|(const query &left,const query &right){
	return shared_ptr<query_base>(new orquery(left,right)); 
}
queryresult orquery::eval(const tectquery &tect){
	auto left=lhs.eval(tect),right=rhs.eval(tect);
	auto ret_lines=make_shared<set<int>>(left.begin(),left.end());
	ret_lines->insert(right.begin(),right.end());
	return queryresult(rep(),ret_lines,left.get_file());
} 
queryresult andquery::eval(const tectquery &tect){
	auto left=lhs.eval(tect),right=rhs.eval(tect);
	auto ret_lines=make_shared<set<int>>();
	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 tectquery &tect){
	auto result=que.eval(tect);
	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(end!=beg){
			++beg;
		}
	}
	return queryresult(rep(),ret_lines,result.get_file());
}

int main() {
	ifstream text("/home/10306785@zte.intra/Desktop/test.txt");
	runqueries(text);
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值