1概述
- 面向对象设计的三个基本概念:数据抽象、继承和动态绑定(封装、继承、多态)。
数据抽象: 将类接口与实现分离。
继承:定义相似的类型,并对其相似关系建模,更容易定义与其他类相似但不完全相同的新类
动态绑定:在一定程度上忽略类的区别,以统一的方式使用它们的对象。使用彼此相似的类编写程序时,在一定程度上忽略掉他们的区别(下一章)。
2继承
2.1常用概念
- 基类:层次关系的根部,负责定义层次关系中所有共同类拥有的成员。
- 派生类:直接间接从基类继承,各自定义特有成员。
- 虚函数:基类会区分类型相关的函数与不做改变直接继承的函数。当基类希望派生类各定义合适自身的版本时,会将这些函数声明成虚函数。
#include<string>
class Quote {
public:
std::string isbn() const;
virtual double net_price(std::size_t n)const;
};
class sub_quote:public Quote{ //:后是派生列表,一个派生类可以有多个基类
public:
//派生类需要在其内部对所有重新定义的虚函数进行声明
double net_price(std::size_t)const override;
};
2.2基类定义
- 基类需要将两种成员函数分开:基类希望派生类直接继承和进行覆盖两类。
- 对于用做基类的类必须已经定义而非仅仅声明。
- 当不希望其他类继承它时,即不想做为基类时,可以在类名后加一个关键字
final
。
2.2.1虚函数
- 对于希望进行覆盖的成员函数部分通常定义为虚函数。
1.虚函数可以是任何构造函数之外的非静态函数。
2.通过在类内声明语句加上关键字"virtual"进行动态绑定(不用于类外部函数定义)。
3.当使用指针或引用调用虚函数时,该调用会被动态绑定,此时动态类型和静态类型可能不同,虚函数的调用在运行过程中被解析,根据绑定对象类型选择对应虚函数(多态思想)。当对于非虚函数或通过非引用指针类型调用虚函数时,在编译时进行解析绑定。
4.基类成员函数是虚函数,对应派生类中隐式的也是虚函数。
5.基类通常会定义一个虚析构函数。
虚函数的实现:虚表
2.2.2protected访问说明符
- "protected"访问说明符:
1.派生类成员和友元可以通过派生类对象访问基类受保护成员,派生类用户禁止访问。
class Quote {
public:
//友元函数
friend void clobber(sub_quote& sq) { sq.price; } //通过sub_quote对象访问Quote中protected对象。
friend void clobber(Quote& sq) { sq.price; }//错误,访问不到Quote(不是友元函数,只是继承关系)
//构造函数
Quote() = default;
Quote(std::string s,double p):bookname(s),price(p){}
//获得书名
std::string getbookname() const{ return bookname; }
//获得价格,定为虚函数,派生类可以覆盖该函数
virtual double net_price(std::size_t n)const { return n * price; }
//析构函数,基类通常需要定义一个虚析构函数
virtual ~Quote() = default;
private:
std::string bookname;
protected://受保护,派生类可以访问该对象
double price = 0.0;
};
class nosub final:Quote{};
2.3派生类定义
2.3.1类派生列表
- 指出派生类从哪些基类继承而来,带有访问说明符(public/private/protected),用来控制派生类从访问对象继承来的成员对其派生类是否可见。
- 派生类声明时不包含派生类列表。
2.3.2派生类中虚函数
- 派生类对于未覆盖的虚函数会直接继承。
- 基类成员函数是虚函数,对应派生类中隐式的也是虚函数。
- 派生类进行函数覆盖时,可以使用virtual关键字指出函数性质(非必须),其形参类型和返回类型需要与基类相匹配。当返回类型是基类本身引用或指针时,对于派生类覆盖的函数可以返回对应派生类的指针或引用(派生类到基类的类型转换可访问)。
- 如果派生类定义了一个函数与基类中虚函数的名字相同但是形参列表不同,被认为与原有基类中函数独立(会把虚函数隐藏)。
- 对于覆盖的,c++11可以通过关键字
"override"
显示覆盖,当没有覆盖时c++会报错。(位置在const
和&
、&&
之后)。可以把函数指定为final
,不允许后续函数进行覆盖。final和override之间没有顺序,需要在const、引用以及尾置返回类型之后。 - 对于拥有默认实参的虚函数:实参值由调用的静态类型决定。静态类型是基类,则使用基类函数定义的默认实参。
- 当希望对虚函数的调用不进行动态绑定,强迫进行某个特定版本时(派生类要调用其基类的虚函数版本),需要说明虚函数的类作用域,此时会在编译时完成解析。
double total1(std::ostream& os, const Quote& item) {
double ret = item.net_price(); //默认20
os << " total " << ret << std::endl;
return ret;
}
double total2(std::ostream& os, const sub_quote& item) {
double ret = item.net_price();//默认100
os << " total " << ret << std::endl;
return ret;
}
total1(std::cout, bulk); //
total2(std::cout, bulk); //
std::cout << bulk.Quote::net_price() << std::endl;
2.3.3派生类对象
- 派生类对象包含两部分:自己定义的(非静态)成员子对象、从基类继承对象。
- 可以把派生类对象当做基类对象使用,因为派生类对象中包含基类对应组成部分,派生类使用基类对象时可以使用public和protected部分。可以把基类指针或引用绑定到派生类对象的基类部分上。
- 对于基类定义的静态成员,整个继承体系只存在该成员唯一定义。如果该静态成员是可以访问的,可以通过声明基类或派生类函数域、对象访问以及this指针访问的形式使用。
- 通过using声明,可以改变派生类中基类个别成员的访问级别,派生类只为可访问的名字提供using声明。这里的访问指的是派生类用户的访问级别。
class Base {
protected:
int prot;
private:
int priv;
};
class Sneaky :private Base{
public:
using Base::priv; //不可访问
using Base::prot; //可以修改为public
};
2.3.4派生类构造函数
- 对于从基类继承的部分,派生类不能直接初始化,需要使用基类的构造函数进行初始化。
sub_quote(std::string s,double p,std::size_t min_q,double disc):
Quote(s,p),min_qty(min_q),discount(disc){}
class sub_quote; //派生类声明
class sub_quote:public Quote{ //:后是派生列表,一个派生类可以有多个基类
public:
//构造函数,对基类部分成员采用基类构造函数初始化
sub_quote() = default;
sub_quote(std::string s,double p,std::size_t min_q,double disc):
Quote(s,p),min_qty(min_q),discount(disc){}
//派生类需要在其内部对所有重新定义的虚函数进行声明
double net_price(std::size_t n)const override {
if (n >= min_qty) {
return n * price * discount; //price为protected访问说明符中成员,派生类可以访问
}
else {
return n * price;
}
}
using Quote::bookname; //将bookname的访问级别调整至public
private:
std::size_t min_qty = 0;
double discount = 0.0;
};
Quote book1("牙医谋杀案",25);
sub_quote bulk("ABC谋杀案", 30, 100, 0.8);
Quote* p = &book1;
std::cout << p->getbookname() << std::endl;
p = &bulk;
std::cout << p->getbookname() << std::endl;
Quote& r = bulk;
std::cout << r.getbookname() << std::endl;
//静态对象调用
Quote::foo();
sub_quote::foo();
p->foo();
//类内函数可以:this->foo();
print_total(std::cout, r, 200);
print_total(std::cout, book1, 5);
2.4基类与派生类之间关系
:
-派生类和基类关系反映的 “是一种”的关系:公有派生类可以用在任何需要基类对象的地方。
-类暗含成员关系反映的是 “有一个”的关系。
2.4.1基类与派生类的转换与使用
使用:
- 把引用或指针绑定到一个对象上,引用或指针的类型应与对象类型一致,或者对象类型含有一个可接受的const类型转换规则。存在继承关系的类是一个例外,基类的指针(包括智能指针)和引用可以绑定到派生类的基类部分上。对于存在继承关系类型的类,要区分其静态类型和动态类型。静态类型相当于形参类型,动态类型依赖于所绑定的实参。
静态类型:编译时已知,变量声明时的类型或表达式生成的类型。
动态类型:变量或表达式表示的运行时内存中的对象的类型
转换:
- 不存在基类到派生类的隐式转换,除非使用强制类型转换dynamic_cast和static_cast。派生类和基类对象之间(除指针和引用)也不存在转换。
- 对于初始化和赋值,实际上也是基类类型const版本的引用。此时派生类部分会被切掉,只留下基类部分。
2.4.2 从基类继承而来的成员
- 对于继承而来的成员访问权限受到基类中该成员访问说明符和派生类中派生列表的访问说明符限制。
- 基类中该成员访问说明符控制派生类成员和友元访问权限。
- 派生类表中的访问说明符控制派生类用户(外部函数以及派生类的派生类)对于基类成员的访问权限。
2.4.3对于派生类向基类的转换
假设是D继承自B
- 如果是
class D:public B{};
,派生类用户(外部函数)可以使用D向B的转换。 - D的成员和友元永远可以使用D向B的转换。
- 如果是
class D:public B{};
或class D:protected B{};
,派生类的派生类可以使用D向B的转换。
对于一个类有三种用户:
- 类的实现者:编写类成员和友元(public、protected、private)
- 派生类:继承基类(public、protected)
- 类的接口调用者:访问类公共接口(public)
2.4.4继承来派生类作用域
-
派生类的作用域嵌套在其基类作用域之内。当派生类的作用域内无法正确解析,则编译器将继续在外层的类作用域寻找改名字的定义。
-
对象的静态类型决定了该对象哪些成员是可见的。
-
当派生类重用定义在其直接基类或间接基类中的名字时(不管形参数目多少,是否一致),派生类名字会将隐藏基类名字。此时需要通过作用运算符调用隐藏的基类成员。
-
函数调用解析过程:
p->mem()
/obj.mem()
1.确定对象静态类型,这里该类型应为类类型。
2.在对应类类型中查找mem(),如果找不到依次在直接基类中不断查找直到达到继承链顶端。如果找不不到报错。
3.如果找到mem,对形参列表进行类型检查。
4.类型检查合法:如果mem是虚函数,依据动态类型判断虚函数版本;佛则进行常规函数调用。
- 成员函数可以被重载,如果派生类希望基类的重载版本对他是可见的,派生类需要覆盖所有的版本,或者一个也不覆盖。可以使用using声明指定一个名字,将所有重载实例添加到派生类作用域中,派生类在重载隐藏特有函数即可。
#include<iostream>
class Base {
protected:
void fcn(int i) { std::cout << "from base input int" << std::endl; }
void fcn() { std::cout << "from base input void" << std::endl; }
void fcn(double) { std::cout << "from base input double" << std::endl; }
};
class sub :public Base {
public:
using Base::fcn;
void fcn(){ std::cout << "from sub input void" << std::endl; }
void ff(){ Base::fcn(); }
};
int main() {
sub a;
a.fcn(); //from sub input void
a.fcn(0.00); //from base input double
a.ff();// from base input void
//a.Base::fcn(); //错误,用户不能调用Base中protected对象
}
2.5 友元与继承
-各个类控制各自访问权限,友元关系不能继承,派生类的友元不能随意访问基类成员。
- 对于声明了A类友元是B类,则A可以访问B对象的成员,包括B派生类C中B的成员。
2.6 拷贝控制与继承
2.6.1基类的拷贝控制
2.6.1.1虚析构函数
- 基类需要析构函数,并且需要将基类的析构函数定义为虚函数,保证delete基类指针时,能选择合适的析构函数版本。此时无法推断基类是否还需要赋值运算符或拷贝构造函数。
- 对于派生类的析构函数来说,析构函数只负责销毁派生类自己分类的资源,他除了销毁自己的成员外,还负责销毁派生类的直接基类部分。
- 定义了析构函数不能拥有合成移动操作。基类没有合成移动操作,则派生类也没有。
- 析构函数执行流程:派生类析构函数首先执行,然后是基类的析构函数。
Base *p = new Base;
delete p;//调用Base类析构函数
p = new sub;
delete p;//调用sub类析构函数
2.6.1.2合成拷贝控制
- 合成的拷贝控制流程:先合成直接基类,再合成拷贝控制中的自定义成员
- 拷贝控制函数能定义的前提是相应的成员可访问并且没有删除:
- 1.基类中默认构造函数或拷贝控制是被删除的(=delete)或不可访问的(private),则派生类中对应成员是被删除的。
2.基类中有一个不可访问或被删除的析构函数,则合成的默认和拷贝构造函数以及移动构造函数被删除。
3.定义了拷贝构造函数,则不会合成移动构造函数。
.编译器不会合成一个删除掉的移动操作,基类被删除,则派生类也没有。
- 合成拷贝控制采用
=default
形式显式使用。 - 对于移动操作:基类需要显示定义移动操作才能执行移动操作。此时,还需要显式的定义拷贝操作。此时,除非派生类中有排斥移动的成员,否则派生类将自动获得合成的移动操作。
2.6.2 派生类的拷贝控制
- 派生类进行初始化、拷贝或移动时,要进行自定义部分成员和基类部分成员的初始化、拷贝或移动。而对于销毁,只销毁自己分配的资源(包括自定义和派生类基类部分)。
- 为派生类定义拷贝或移动构造函数时,应使用对应基类构造函数初始化对象的基类部分:
- 对于派生类赋值运算符,必须显式的为其基类部分赋值:
- 对于派生类析构函数,基类部分会被隐式销毁,析构函数内部只负责销毁派生类自定义成员部分:
class B{
public:
B() = default;
B(B&& b) = default;
B(const B& b) = default;
B& operator=(const B&) = default;
B& operator=(B&&) = default;
virtual ~B() = default;
};
class D:public B {
D(const D& d) :B(d) {}
D(D&& d):B(std::move(d)){}
//这种情况派生类构造函数的基类部分会被赋予默认值,自定义成员会移动赋值
D(D&& d){}
//为派生类对象基类赋值
D& operator=(const D& rhs) {
this->B::operator=(rhs);//说明类作用域,否则使用D::operator=,出现递归错误
return *this;
}
~D() = default;
};
2.7容器与继承
- 使用容器存放继承体系中的元素时,必须采用间接存储的方式,使用基类的指针(最好是智能指针)。派生类的普通指针(智能指针)可以转化为基类的普通指针(智能指针)。
若容器类型为基类对象,如果直接使用派生类对象,则派生类对象自定义部分会被切掉。
- 对于c++编程,无法直接使用对象进行面向对象编程,必须使用引用或指针。
2.6.3 派生类的构造函数
- 一个类只能初始化其直接基类,“继承”其直接基类的构造函数,继承方式是提供一条
using
声明语句。此时对于基类的每个构造函数,编译器会在派生类中生成一个与之对应的构造函数。对于派生类的自定义成员将被默认初始化。
//类内声明
using Base::Base;
//等价于
D(int a,int b):Base(a,b){};
- 不管using声明而位置,基类私有构造函数在派生类仍然是私有构造函数,并继承原有属性(constexpr/explicit)。
- 默认实参不会被继承,而是变为多个构造函数。
- 如果派生类构造函数有和基类构造函数相同的参数列表,则该构造函数不会被继承。
- 默认、拷贝和移动构造函数不会被继承。会按正常规则被合成。
2.7抽象基类
2.7.1目的
负责创建基类下的通用接口,后续派生类可以覆盖该接口。
2.7.2纯虚函数
- 通过定义纯虚函数,实现接口定义,纯虚函数无序定义。如果提供定义,需要定义在类的外部。
- 纯虚函数定义方式:在函数体位置书写
=0
,该语句只能出现在类内部虚函数声明语句中。
//对于基类的派生类
double net_price(std::size_t )const = 0;
2.7.3抽象基类
- 含有/直接继承未经覆盖纯虚函数的类,是抽象基类。
- 不能定义抽象基类的对象,可以定义覆盖了纯虚函数后派生类的对象。
重构:在基类中增加抽象基类是重构。重构负责重新设计类的体系以便将操作数据从一个类移动到另一个类。
#include<string>
#include<iostream>
class Quote {
public:
//构造函数
Quote() = default;
Quote(std::string s, double p) :bookname(s), price(p) {}
//获得书名
std::string getbookname() const { return bookname; }
//获得价格,定为虚函数,派生类可以覆盖该函数
virtual double net_price(std::size_t n )const;
//析构函数,基类通常需要定义一个虚析构函数
virtual ~Quote() = default;
//静态对象
static void foo() {}
private:
std::string bookname;
protected:
double price = 0.0;
};
class Disc_quote :public Quote {
public:
Disc_quote() = default;
Disc_quote(std::string s, double p,std::size_t q,double dis):
Quote(s,q),quantity(q),discount(dis){}
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;
Bulk_quote(std::string s, double p, std::size_t q, double dis):
Disc_quote(s,p,q,dis){}
double net_price(std::size_t) const override;
};
3动态绑定
- 通过使用动态绑定,可以同时处理基类和派生类的对象。函数运行版本由实参决定,又称为运行时绑定。
- 使用基类的引用或指针调用一个虚函数时将发生动态绑定。
//这里的item既可以是基类又可以是派生类
double print_total(std::ostream& os, const Quote& item, std::size_t n) {
double ret = item.net_price(n);
os << "sold" << n << "total" << ret<<std::endl;
return ret;
}
4文本查询程序
4.1需求
- 设计一个文本查询查询程序,满足如下要求:
1.对单词查询,可以查询单词在与不在的行(不在:~);
2.可以匹配两个单词同时出现或任意一个出现的情况(与&或|);
3.运算符可以混合使用,并存在优先级,可以用圆括号表示查询次序。
4.2设计方法
4.2.1 操作界面
- 将几种查询方式建模成相互独立的类,这些类共享一个公共基类:
WordQuary
NotQuary
OrQuary
AndQuary
- 每个类包含两个操作:
eval:接收TextQuary对象,返回一个QuaryResult
rep:
- 这四个类共享同一个接口,可以定义为抽象基类(Quary_base)
- 考虑到“OrQuary ”和“ AndQuary”是二元运算,为他们定义一个抽象基类“BinaryQuary”
4.2.2 接口
为整个继承体系定义一个名为Quary的接口类,保存一个Quary_base指针。
4.3.3代码
#include<vector>
#include<string>
#include<iostream>
#include<fstream>
#include<sstream>
#include<memory>
#include<map>
#include<set>
#include<algorithm>
class QueryResult {
public:
friend std::ostream& print(std::ostream&, QueryResult);
using lineno = std::vector<std::string>::size_type;
QueryResult(std::string s, std::shared_ptr<std::set<lineno>> l, std::shared_ptr<std::vector<std::string>>f)
:sought(s), lines(l), file(f) {};
std::shared_ptr<std::set<lineno>> getlines() { return lines; }
std::shared_ptr<std::vector<std::string>> getfile() { return file; }
private:
std::string sought;
std::shared_ptr<std::set<lineno>>lines;
std::shared_ptr<std::vector<std::string>> file;
};
std::ostream& print(std::ostream& os, QueryResult qr) {
os << qr.sought << " occurs " << qr.lines->size() << " times" << std::endl;
for (auto num : *qr.lines) {
os << "\t(line " << num + 1 << ") " << *(qr.file->begin() + num) << std::endl;
}
return os;
}
class TextQuary {
public:
friend class QueryResult;
using lineno = std::vector<std::string>::size_type;
TextQuary(std::ifstream& ifile);
/*QueryResult query(string s);*/
QueryResult query(std::string s) const{
static std::shared_ptr<std::set<lineno>> nodata(new std::set<lineno>);
auto loc = wm.find(s);
if (loc != wm.end()) {
return QueryResult(s, loc->second, this->file);
}
else {
return QueryResult(s, nodata, this->file);
}
}
private:
std::shared_ptr<std::vector<std::string>>file;
std::map<std::string, std::shared_ptr<std::set<lineno>>> wm;
};
TextQuary::TextQuary(std::ifstream& ifile) :file(new std::vector<std::string>) {
std::string sl;
while (getline(ifile, sl)) {
file->push_back(sl);
int n = file->size() - 1;
std::istringstream line(sl);
std::string word;
while (line >> word) {
auto& lines = wm[word];
if (!lines) { //没有lines指针就创一个
lines.reset(new std::set<lineno>);
}
lines->insert(n);
}
}
}
//QueryResult TextQuary::query(string s) {
// static shared_ptr<set<lineno>> nodata(new set<lineno>);
// auto loc = wm.find(s);
// if (loc != wm.end()) {
// return QueryResult(s, loc->second, this->file);
// }
// else {
// return QueryResult(s, nodata, this->file);
// }
//}
class Base_Quary {
friend class Quary;
friend class QueryResult;
private:
virtual QueryResult eval(const TextQuary&) const = 0;
virtual std::string rep() const = 0;
protected:
using line_no = TextQuary::lineno;
virtual ~Base_Quary() = default;
};
class Quary {
public:
friend Quary operator~(const Quary&);
friend Quary operator&(const Quary& operlhs,const Quary& operrhs);
friend Quary operator|(const Quary& operlhs, const Quary& operrhs);
Quary(const std::string& s);
QueryResult eval(const TextQuary& t) const { return q->eval(t); }
std::string rep()const { return q->rep(); }
private:
Quary(std::shared_ptr<Base_Quary> query) :q(query) {}
std::shared_ptr<Base_Quary> q;
};
std::ostream& operator<<(std::ostream os, const Quary& quary) {
return os << quary.rep();
}
class WordQuary :public Base_Quary{
private:
friend class Quary;
WordQuary(const std::string& s) :quary_word(s){}
QueryResult eval(const TextQuary& t) const override{
return t.query(quary_word);
}
std::string rep()const override { return quary_word; }
std::string quary_word;
};
inline Quary::Quary(const std::string &s):q(new WordQuary(s)){}
class NotQuary :public Base_Quary {
private:
friend Quary operator~(const Quary& operand);
NotQuary(const Quary& s) :query(s) {}
QueryResult eval(const TextQuary& t) const override;
std::string rep()const override { return "~("+query.rep() + ")"; }
Quary query;
};
inline Quary operator~(const Quary& operand) {
return std::shared_ptr<Base_Quary>(new NotQuary(operand));
}
QueryResult NotQuary::eval(const TextQuary& t) const{
auto result = query.eval(t);
auto ret_lines = std::make_shared<std::set<line_no>>();
auto beg = result.getlines()->begin();
auto end = result.getlines()->end();
auto sz = result.getfile()->size();
for (auto n = 0; n < sz; ++n) {
if ((beg == end) || (n != *beg)) {
ret_lines->insert(n);
}
else if (beg != end) {
++beg;
}
}
return QueryResult(rep(), ret_lines, result.getfile());
}
class BinaryQuary :public Base_Quary {
public:
BinaryQuary(const Quary& s1, const Quary& s2,std::string s):querylhs(s1),queryrhs(s2),opsym(s){}
protected:
//QueryResult eval(const TextQuary& t) const = 0; 默认继承纯虚函数
std::string rep()const override { return "("+querylhs.rep() + " " + opsym + " " + queryrhs.rep()+")"; }
Quary querylhs;
Quary queryrhs;
std::string opsym;
};
class AndQuary :public BinaryQuary {
private:
friend Quary operator&(const Quary& operlhs, const Quary& operrhs);
AndQuary(const Quary& s1, const Quary& s2) :BinaryQuary(s1,s2,"&") {}
QueryResult eval(const TextQuary& t) const override;
};
inline Quary operator&(const Quary& operlhs, const Quary& operrhs) {
return std::shared_ptr<Base_Quary>(new AndQuary(operlhs,operrhs));
}
QueryResult AndQuary::eval(const TextQuary& t) const {
auto right = queryrhs.eval(t);
auto left = querylhs.eval(t);
auto ret_lines = std::make_shared<std::set<line_no>>();
std::set_intersection
(left.getlines()->begin(), left.getlines()->end(), right.getlines()->begin(), right.getlines()->end(),
inserter(*ret_lines,ret_lines->begin()));
return QueryResult(rep(), ret_lines, left.getfile());
}
class OrQuary :public BinaryQuary {
public:
friend class QueryResult;
private:
friend Quary operator|(const Quary& operlhs, const Quary& operrhs);
OrQuary(const Quary& s1, const Quary& s2) :BinaryQuary(s1, s2, "|") {}
QueryResult eval(const TextQuary& t) const override;
};
inline Quary operator|(const Quary& operlhs, const Quary& operrhs) {
return std::shared_ptr<Base_Quary>(new OrQuary(operlhs, operrhs));
}
QueryResult OrQuary::eval(const TextQuary& t) const{
auto right = queryrhs.eval(t);
auto left = querylhs.eval(t);
auto ret_lines = std::make_shared<std::set<line_no>>(left.getlines()->begin(),left.getlines()->end());
ret_lines->insert(right.getlines()->begin(), right.getlines()->end());
return QueryResult(rep(), ret_lines, left.getfile());
}
void runQuiries(std::ifstream& ifile) {
TextQuary tq(ifile);
std::string s,s2;
query1:
std::cout << "enter first word you are looking for:";
std::cin >> s;
Quary q1(s);
std::cout << "enter second word you are looking for:";
std::cin >> s2;
Quary q2(s2);
Quary q3 = (~q1)& q2;
print(std::cout, (~q1).eval(tq)) << std::endl;
print(std::cout, q2.eval(tq)) << std::endl;
print(std::cout, q3.eval(tq)) << std::endl;
std::cout << "continue?y/n";
std::cin >> s;
if (s == "y") {
goto query1;
}
}
int main() {
std::ifstream file("text.txt");
runQuiries(file);
std::set<int> num1 = { 1,2,3,4,5 };
std::set<int> num2 = { 1,2 };
std::set<int> num3;
std::set_intersection(num1.begin(), num1.end(), num2.begin(), num2.end(), inserter(num3, num3.begin()));
std::for_each(num3.begin(), num3.end(), [](int a) {std::cout << a; });
std::cout << std::endl;
return 0;
}