文章目录
OPP概述
#include <iostream>
#include <cmath>
// 抽象基类 Shape
class Shape {
public:
// 纯虚函数,使Shape成为抽象类
virtual double area() const = 0;
// 虚析构函数确保正确释放派生类对象
virtual ~Shape() = default;
};
// 派生类 Circle
class Circle : public Shape {
private:
double radius;
public:
Circle(double r) : radius(r) {}
// 实现纯虚函数
double area() const override {
return M_PI * radius * radius;
}
};
// 派生类 Rectangle
class Rectangle : public Shape {
private:
double width, height;
public:
Rectangle(double w, double h) : width(w), height(h) {}
// 实现纯虚函数
double area() const override {
return width * height;
}
};
int main() {
// 通过基类指针实现多态
Shape* shapes[2];
shapes[0] = new Circle(5.0);
shapes[1] = new Rectangle(4.0, 6.0);
// 动态绑定:根据实际对象类型调用对应函数
for (int i = 0; i < 2; ++i) {
std::cout << "Area: " << shapes[i]->area() << std::endl;
delete shapes[i]; // 释放内存
}
// 通过引用实现多态
Circle c(3.0);
Shape& ref = c;
std::cout << "Circle area via reference: " << ref.area() << std::endl;
return 0;
}
继承
定义基类和派生类
定义基类
基类通常要定义一个虚析构函数,即使该函数不执行任何实际操作也是如此
#include <iostream>
#include <string>
// 定义基类Quote
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() = default; // 析构函数动态绑定
private:
std::string bookNo; // 书籍的ISBN编号
protected:
double price = 0.0; // 代表普通状态下不打折的价格
};
// 定义派生类Bulk_quote,用于批量购买有折扣的情况
class Bulk_quote : public Quote {
public:
Bulk_quote(const std::string &book, double p, std::size_t qty, double disc) :
Quote(book, p), quantity(qty), discount(disc) {}
// 重写net_price函数实现批量折扣计算
double net_price(std::size_t n) const override {
if (n >= quantity) {
return n * price * (1 - discount);
} else {
return n * price;
}
}
private:
std::size_t quantity; // 达到折扣的购买数量阈值
double discount; // 折扣率
};
int main() {
Quote basic("12345", 20.0);
Bulk_quote bulk("67890", 30.0, 10, 0.2);
std::size_t num_books = 15;
// 调用基类对象的net_price函数
std::cout << "Basic quote total price for " << num_books << " books: $" << basic.net_price(num_books) << std::endl;
// 调用派生类对象的net_price函数
std::cout << "Bulk quote total price for " << num_books << " books: $" << bulk.net_price(num_books) << std::endl;
return 0;
}
访问控制和继承
定义派生类
派生类对象以及派生类向基类的类型转换
派生类构造函数
首先初始化基类的部分,然后按照声明的顺序依次初始化派生类的成员。
派生类使用基类的成员
派生类可以访问基类的公共成员和受保护成员
继承和静态成员
派生类的声明
被用作基类的类
防止继承发生 Final
类型转换和继承
理解基类和派生类之间的类型转换时l理解c++语言面向对象都关键所在
通常情况下:如果我呢想把引用或者指针绑定到一个对象上,则引用或指针的类型应与对象都类型一致,或者对象的类型或者有一个可接受的const类型转换规则,存在继承关系类是一个例外,我们可以将基类的指针或者引用绑定到派生类对象上。
可以将基类的指针或引用绑定 到派生类对象上有一层极为重要的含义:当使用基类的引用或者指针时,实际上我们并不清楚引用或者指针所绑定的对象的真实类型。该对象可能是基类对象,也可以是派生类的对象。
下面为你详细阐述C++中基类和派生类之间的类型转换情况,并结合具体例子进行说明。
1. 派生类到基类的隐式转换(向上转型)
这是C++面向对象编程的一个重要特性,在存在继承关系的类中,我们能够把派生类的指针或引用赋值给基类的指针或引用。
示例代码
#include <iostream>
using namespace std;
class Base {
public:
void show() { cout << "Base::show()" << endl; }
};
class Derived : public Base {
public:
void show() { cout << "Derived::show()" << endl; }
void derived_only() { cout << "Derived::derived_only()" << endl; }
};
int main() {
Derived d;
// 派生类对象转换为基类引用
Base& b_ref = d;
b_ref.show(); // 输出: Base::show()
// 派生类对象转换为基类指针
Base* b_ptr = &d;
b_ptr->show(); // 输出: Base::show()
// 注意:通过基类指针/引用无法调用派生类特有的方法
// b_ptr->derived_only(); // 编译错误
return 0;
}
详细说明
- 转换可行性:由于派生类对象中包含基类的部分,所以这种转换是安全的,无需进行显式的类型转换。
- 访问限制:当使用基类指针或引用来调用方法时,只能访问基类中定义的成员。就像上面的例子,虽然
Derived
类重写了show()
方法,但通过基类引用或指针调用时,执行的是基类的show()
方法。如果要调用派生类的重写方法,show()
需要被声明为虚函数。 - 切片问题:若直接将派生类对象赋值给基类对象(而不是指针或引用),派生类特有的部分就会被切掉,只保留基类部分。
2. 基类到派生类的显式转换(向下转型)
这种转换存在风险,需要程序员确保转换的安全性,一般借助static_cast
或dynamic_cast
来实现。
示例代码(使用static_cast)
#include <iostream>
using namespace std;
class Base {
public:
virtual void show() { cout << "Base::show()" << endl; }
};
class Derived : public Base {
public:
void show() override { cout << "Derived::show()" << endl; }
void derived_only() { cout << "Derived::derived_only()" << endl; }
};
int main() {
Base* b_ptr = new Derived(); // 向上转型,安全
b_ptr->show(); // 输出: Derived::show()(因为show是虚函数)
// 向下转型:从基类指针转为派生类指针
Derived* d_ptr = static_cast<Derived*>(b_ptr);
if (d_ptr) {
d_ptr->show(); // 输出: Derived::show()
d_ptr->derived_only(); // 现在可以调用派生类特有的方法
}
Base b;
Base* b_ptr2 = &b;
// 下面的转换不安全,因为b实际上是Base对象
Derived* d_ptr2 = static_cast<Derived*>(b_ptr2);
// d_ptr2->derived_only(); // 未定义行为,可能会崩溃
delete b_ptr;
return 0;
}
示例代码(使用dynamic_cast)
#include <iostream>
using namespace std;
class Base {
public:
virtual ~Base() {} // 必须有虚函数才能使用dynamic_cast
virtual void show() { cout << "Base::show()" << endl; }
};
class Derived : public Base {
public:
void show() override { cout << "Derived::show()" << endl; }
};
int main() {
Base* b_ptr = new Derived(); // 向上转型
// 使用dynamic_cast进行安全的向下转型
Derived* d_ptr = dynamic_cast<Derived*>(b_ptr);
if (d_ptr) {
cout << "转换成功" << endl;
d_ptr->show(); // 输出: Derived::show()
} else {
cout << "转换失败" << endl;
}
Base* b_ptr2 = new Base(); // 指向基类对象
Derived* d_ptr2 = dynamic_cast<Derived*>(b_ptr2);
if (d_ptr2 == nullptr) {
cout << "安全检测:无法从Base转换为Derived" << endl;
}
delete b_ptr;
delete b_ptr2;
return 0;
}
详细说明
- static_cast:这种转换方式不会进行运行时类型检查,所以当基类指针实际并不指向派生类对象时,就可能会导致未定义行为。
- dynamic_cast:该转换方式依赖于运行时类型信息(RTTI),在转换时会检查对象的实际类型。如果转换不安全,对于指针会返回
nullptr
,对于引用会抛出std::bad_cast
异常。要使用dynamic_cast
,基类必须至少包含一个虚函数。
3. 多重继承与类型转换
当存在多重继承时,类型转换会变得更加复杂,因为派生类对象中可能包含多个基类的子对象。
示例代码
#include <iostream>
using namespace std;
class Base1 { public: virtual void f() {} };
class Base2 { public: virtual void g() {} };
class Derived : public Base1, public Base2 {};
int main() {
Derived d;
Base1* b1_ptr = &d; // 指向第一个基类
Base2* b2_ptr = &d; // 指向第二个基类
cout << "b1_ptr: " << b1_ptr << endl;
cout << "b2_ptr: " << b2_ptr << endl; // 可能与b1_ptr的值不同
// 从Base2*转回Derived*
Derived* d_ptr = dynamic_cast<Derived*>(b2_ptr);
cout << "d_ptr: " << d_ptr << endl; // 必须调整指针值以指向整个对象
return 0;
}
详细说明
- 指针调整:在多重继承的情况下,派生类对象里的不同基类子对象的地址可能不一样。编译器会自动对指针进行调整,以确保基类指针能够正确指向对应的基类子对象。
- dynamic_cast的作用:当从第二个基类指针转回派生类指针时,
dynamic_cast
会正确调整指针值,使其指向整个派生类对象。
4. 引用类型转换
引用类型的转换规则与指针类似,但引用一旦初始化就不能改变其绑定关系,所以引用转换不会有空引用的情况。
示例代码
#include <iostream>
using namespace std;
class Base { public: virtual void show() {} };
class Derived : public Base {};
int main() {
Derived d;
Base& b_ref = d; // 向上转型,安全
try {
// 安全的向下转型
Derived& d_ref = dynamic_cast<Derived&>(b_ref);
cout << "引用转换成功" << endl;
} catch (const bad_cast& e) {
cout << e.what() << endl;
}
Base b;
Base& b_ref2 = b;
try {
// 这里会抛出std::bad_cast异常
Derived& d_ref2 = dynamic_cast<Derived&>(b_ref2);
} catch (const bad_cast& e) {
cout << "异常处理:" << e.what() << endl;
}
return 0;
}
总结
转换方向 | 转换方式 | 是否安全 | 所需条件 |
---|---|---|---|
派生类→基类(向上转型) | 隐式转换 | 安全 | 继承关系 |
基类→派生类(向下转型) | static_cast | 不安全 | 程序员需确保正确性 |
基类→派生类(向下转型) | dynamic_cast | 安全 | 基类有虚函数,RTTI开启 |
关键注意事项
- 要避免进行不安全的向下转型,建议优先使用
dynamic_cast
。 - 基类的析构函数应该声明为虚函数,这样在通过基类指针删除派生类对象时,能确保正确调用派生类的析构函数。
- 虽然C++允许通过
reinterpret_cast
强制转换指针类型,但这种做法极其危险,应当避免使用。
静态类型与动态类型
不存在基类向派生类的隐式类型转换
在对象之间不存在类型转换
虚函数
对虚函数的调用可能在运行时才被解析
派生类中的虚函数
final和override说明符
虚函数与默认实参
回避虚函数机制
- 虚函数机制与作用域运算符:在 C++ 中,虚函数是实现多态的重要手段。通过虚函数,在运行时会根据对象的实际类型来决定调用哪个函数版本。而作用域运算符 :: 通常用于明确指定调用基类的成员函数版本。一般只有在成员函数(或友元函数)里,为了避免虚函数动态绑定机制带来的影响,才会使用作用域运算符。
- 何时回避虚函数默认机制:当派生类的虚函数需要调用它所覆盖的基类虚函数版本时,就需要回避虚函数的默认机制。因为基类虚函数可能实现了一些通用的、继承层次中所有类型都要执行的操作,派生类在这些通用操作基础上,再添加与自身相关的操作。
- 不使用作用域运算符的风险:如果派生类虚函数要调用基类版本却没使用作用域运算符,由于虚函数的动态绑定特性,运行时会把这个调用解析为对派生类自身虚函数的调用,从而陷入无限递归循环,导致程序崩溃。
- #include <iostream>
using namespace std;
class Base {
public:
virtual void func() {
cout << "Base::func()" << endl;
}
};
class Derived : public Base {
public:
void func() override {
// 错误示范:不使用作用域运算符,会导致无限递归
// func(); // 这行会引发无限递归,因为它会不断调用派生类自身的func函数
// 正确示范:使用作用域运算符调用基类版本
Base::func();
cout << "Derived::func() additional operations" << endl;
}
};
int main() {
Derived d;
d.func();
return 0;
}
15.4 抽象类
纯虚函数
含有纯虚函数的类是抽象基类
我们不能创建抽象基类的对象
派生类构造函数只初始化它的直接基类
访问控制与继承
受保护的对象
公有、私有和受保护继承
派生类向基类转换可访问性
- 公有继承与类型转换访问权限
#include <iostream>
using namespace std;
class B {};
// 公有继承
class D1 : public B {};
// 私有继承
class D2 : private B {};
// 受保护继承
class D3 : protected B {};
int main() {
D1 d1;
B* b1 = &d1; // 合法,D1公有继承B,用户代码可进行派生类向基类的转换
D2 d2;
// B* b2 = &d2; // 非法,D2私有继承B,用户代码不能进行此转换
D3 d3;
// B* b3 = &d3; // 非法,D3受保护继承B,用户代码不能进行此转换
return 0;
}
- 派生类成员函数和友元对类型转换的访问权限
#include <iostream>
using namespace std;
class B {};
class D : private B {
public:
void member_func() {
D d;
B* b = &d; // 合法,在派生类成员函数内,即使是私有继承,也能进行派生类向基类的转换
}
friend void friend_func(D& d);
};
void friend_func(D& d) {
B* b = &d; // 合法,在友元函数内,即使是私有继承,也能进行派生类向基类的转换
}
- 派生类的派生类对类型转换的访问权限
#include <iostream>
using namespace std;
class B {};
// 公有继承
class D1 : public B {};
class DD1 : public D1 {
public:
void member_func() {
DD1 dd1;
B* b = &dd1; // 合法,D1公有继承B,DD1的成员函数可进行DD1向B的转换
}
friend void friend_func(DD1& dd1);
};
void friend_func(DD1& dd1) {
B* b = &dd1; // 合法,D1公有继承B,DD1的友元函数可进行DD1向B的转换
}
// 私有继承
class D2 : private B {};
class DD2 : public D2 {
public:
void member_func() {
DD2 dd2;
// B* b = &dd2; // 非法,D2私有继承B,DD2的成员函数不能进行DD2向B的转换
}
friend void friend_func(DD2& dd2);
};
void friend_func(DD2& dd2) {
// B* b = &dd2; // 非法,D2私有继承B,DD2的友元函数不能进行DD2向B的转换
}
友元和继承
友元关系不能传递,并且不能被继承,不能继承友元关系,每个类负责控制各自成员的访问权限。
改变个别成员的可访问性
默认的继承保护级别
继承中的类作用域
在编译时进行名字查找
一个对象、引用或者指针的静态类型决定哪些成员是可见的,即使静态类型和动态类型可能不一致(当使用基类的引用或指针时会发生在这种情况),但我们能使用哪些成员仍然是静态类型决定的。
在C++中,对象、引用或指针的静态类型决定了编译时可见的成员,而动态类型(运行时实际指向的对象类型)决定了虚函数的调用版本。以下是详细示例说明:
1. 静态类型决定可见成员
即使实际对象是派生类,但通过基类指针/引用访问时,只能调用基类中定义的成员。
#include <iostream>
using namespace std;
class Base {
public:
void baseFunc() { cout << "Base::baseFunc()" << endl; }
};
class Derived : public Base {
public:
void derivedFunc() { cout << "Derived::derivedFunc()" << endl; }
};
int main() {
Derived d;
Base* ptr = &d; // 静态类型是Base*,动态类型是Derived*
ptr->baseFunc(); // 合法:Base类中定义了baseFunc
// ptr->derivedFunc(); // 编译错误:静态类型Base*不可见derivedFunc
}
关键点:
ptr
的静态类型是Base*
,因此只能访问Base
类中声明的成员。- 尽管
ptr
实际指向Derived
对象,但编译时无法访问Derived
的特有成员。
2. 虚函数的动态绑定
虚函数的调用由对象的动态类型决定,但可见性仍由静态类型控制。
class Base {
public:
virtual void virtualFunc() { cout << "Base::virtualFunc()" << endl; }
};
class Derived : public Base {
public:
void virtualFunc() override { cout << "Derived::virtualFunc()" << endl; }
void derivedOnly() { cout << "Derived::derivedOnly()" << endl; }
};
int main() {
Derived d;
Base* ptr = &d; // 静态类型Base*,动态类型Derived*
ptr->virtualFunc(); // 运行时调用Derived::virtualFunc()(动态绑定)
// ptr->derivedOnly(); // 编译错误:静态类型Base*不可见derivedOnly
}
关键点:
virtualFunc()
是虚函数,实际调用Derived
版本(动态绑定)。- 但
derivedOnly()
仍不可见,因为静态类型是Base*
。
3. 多重继承下的静态类型限制
在多重继承中,静态类型决定了可访问的基类子对象。
class Base1 {
public:
void func1() { cout << "Base1::func1()" << endl; }
};
class Base2 {
public:
void func2() { cout << "Base2::func2()" << endl; }
};
class Derived : public Base1, public Base2 {};
int main() {
Derived d;
Base1* ptr1 = &d;
Base2* ptr2 = &d;
ptr1->func1(); // 合法
// ptr1->func2(); // 编译错误:静态类型Base1*不可见func2
ptr2->func2(); // 合法
// ptr2->func1(); // 编译错误:静态类型Base2*不可见func1
}
关键点:
ptr1
和ptr2
的静态类型不同,分别只能访问Base1
和Base2
的成员。- 尽管它们指向同一个
Derived
对象,但编译时可见成员由静态类型决定。
4. 静态类型与强制类型转换
通过 static_cast
可以临时改变静态类型,但需确保安全性。
class Base {
public:
virtual void func() { cout << "Base::func()" << endl; }
};
class Derived : public Base {
public:
void func() override { cout << "Derived::func()" << endl; }
void derivedOnly() { cout << "Derived::derivedOnly()" << endl; }
};
int main() {
Derived d;
Base* ptr = &d;
ptr->func(); // 调用Derived::func()
// 强制转换为Derived*以访问派生类特有成员
Derived* derivedPtr = static_cast<Derived*>(ptr);
derivedPtr->derivedOnly(); // 合法:静态类型现为Derived*
}
关键点:
static_cast
改变了指针的静态类型,使derivedOnly()
变得可见。- 这种转换必须确保安全(如本例中
ptr
确实指向Derived
对象),否则会导致未定义行为。
总结
特性 | 由静态类型决定 | 由动态类型决定 |
---|---|---|
可见成员 | ✅(编译时检查) | ❌ |
虚函数调用 | ❌(仅决定函数签名是否可见) | ✅(运行时根据实际对象类型分派) |
非虚函数调用 | ✅(直接调用静态类型的函数) | ❌ |
理解静态类型与动态类型的区别是掌握C++多态机制的关键。静态类型控制了编译时的接口可见性,而动态类型通过虚函数实现运行时多态。
名字冲突与继承
派生类的成员将隐藏同名的基类成员。
通过作用域运算符来使用隐藏成员
一如往常,名字查找先于类型检查
#include <iostream>
using namespace std;
class Base {
public:
void func(int i) {
cout << "Base::func(int i) : " << i << endl;
}
};
class Derived : public Base {
public:
// 与基类func同名,但形参列表不同,此时基类的func被隐藏
void func(double d) {
cout << "Derived::func(double d) : " << d << endl;
}
};
int main() {
Derived d;
d.func(1.5); // 调用派生类的func(double d)
// d.func(1); // 编译错误,基类的func(int i)在派生类作用域内被隐藏
d.Base::func(1); // 通过作用域运算符访问基类被隐藏的func(int i)
return 0;
}
虚函数和作用域
通过基类调用隐藏的虚函数
覆盖重载的函数
C++ 派生类的拷贝和移动操作实现
#include <iostream>
#include <string>
class Base {
public:
virtual ~Base() = default; // 虚析构函数
};
class Derived : public Base {
std::string data;
public:
Derived(const std::string& str) : data(str) {}
~Derived() = default; // 定义了析构函数,即使是默认形式
// 此时编译器不会合成移动操作
// 如果需要移动语义,需手动定义移动构造和移动赋值
// 例如手动定义移动构造函数
Derived(Derived&& other) noexcept : data(std::move(other.data)) {}
// 手动定义移动赋值运算符
Derived& operator=(Derived&& other) noexcept {
if (this != &other) {
data = std::move(other.data);
}
return *this;
}
};
在 C++ 中,派生类的拷贝和移动操作需要考虑基类部分和派生类成员的处理。以下是详细的实现方法:
1. 拷贝操作实现
拷贝构造函数
class Base {
public:
Base(int x) : x(x) {}
Base(const Base& other) : x(other.x) {
std::cout << "Base copy constructor\n";
}
protected:
int x;
};
class Derived : public Base {
public:
Derived(int x, int y) : Base(x), y(y) {}
// 派生类拷贝构造函数
Derived(const Derived& other)
: Base(other), // 调用基类拷贝构造函数
y(other.y) { // 拷贝派生类成员
std::cout << "Derived copy constructor\n";
}
private:
int y;
};
拷贝赋值运算符
class Derived : public Base {
public:
// 拷贝赋值运算符
Derived& operator=(const Derived& other) {
if (this != &other) {
Base::operator=(other); // 调用基类拷贝赋值
y = other.y; // 拷贝派生类成员
std::cout << "Derived copy assignment\n";
}
return *this;
}
};
2. 移动操作实现
移动构造函数
class Base {
public:
Base(Base&& other) noexcept : x(std::move(other.x)) {
std::cout << "Base move constructor\n";
}
};
class Derived : public Base {
public:
Derived(Derived&& other) noexcept
: Base(std::move(other)), // 调用基类移动构造函数
y(std::move(other.y)) { // 移动派生类成员
std::cout << "Derived move constructor\n";
}
};
移动赋值运算符
class Derived : public Base {
public:
// 移动赋值运算符
Derived& operator=(Derived&& other) noexcept {
if (this != &other) {
Base::operator=(std::move(other)); // 调用基类移动赋值
y = std::move(other.y); // 移动派生类成员
std::cout << "Derived move assignment\n";
}
return *this;
}
};
3. 综合实现示例
#include <iostream>
#include <utility>
class Base {
public:
Base(int x) : x(x) {}
// 拷贝构造函数
Base(const Base& other) : x(other.x) {
std::cout << "Base copy constructor\n";
}
// 拷贝赋值运算符
Base& operator=(const Base& other) {
if (this != &other) {
x = other.x;
std::cout << "Base copy assignment\n";
}
return *this;
}
// 移动构造函数
Base(Base&& other) noexcept : x(std::move(other.x)) {
std::cout << "Base move constructor\n";
}
// 移动赋值运算符
Base& operator=(Base&& other) noexcept {
if (this != &other) {
x = std::move(other.x);
std::cout << "Base move assignment\n";
}
return *this;
}
protected:
int x;
};
class Derived : public Base {
public:
Derived(int x, int y) : Base(x), y(y) {}
// 拷贝构造函数
Derived(const Derived& other)
: Base(other), y(other.y) {
std::cout << "Derived copy constructor\n";
}
// 拷贝赋值运算符
Derived& operator=(const Derived& other) {
if (this != &other) {
Base::operator=(other);
y = other.y;
std::cout << "Derived copy assignment\n";
}
return *this;
}
// 移动构造函数
Derived(Derived&& other) noexcept
: Base(std::move(other)), y(std::move(other.y)) {
std::cout << "Derived move constructor\n";
}
// 移动赋值运算符
Derived& operator=(Derived&& other) noexcept {
if (this != &other) {
Base::operator=(std::move(other));
y = std::move(other.y);
std::cout << "Derived move assignment\n";
}
return *this;
}
private:
int y;
};
int main() {
Derived d1(1, 2);
Derived d2 = d1; // 调用拷贝构造函数
Derived d3(3, 4);
d3 = d2; // 调用拷贝赋值运算符
Derived d4 = std::move(d1); // 调用移动构造函数
d3 = std::move(d4); // 调用移动赋值运算符
return 0;
}
4. 重要注意事项
-
noexcept:移动操作应该标记为
noexcept
,这对标准库容器很重要 -
自赋值检查:赋值运算符中必须检查自赋值情况
-
基类部分处理:
- 在拷贝操作中,显式调用基类的拷贝操作
- 在移动操作中,使用
std::move
转换基类部分
-
Rule of Five:
- 如果定义了任何一个拷贝构造函数、拷贝赋值运算符、移动构造函数、移动赋值运算符或析构函数,最好全部定义
-
派生类成员移动:
- 对派生类成员使用
std::move
进行移动操作 - 基本类型(如int)移动和拷贝效果相同
- 对派生类成员使用
-
异常安全:
- 确保在异常发生时不会泄漏资源
- 移动操作通常不应抛出异常
派生类析构函数
在构造函数和析构函数中调用虚函数
#include <iostream>
#include <string>
// 基类
class Base {
public:
Base(const std::string& name) : baseName(name) {
std::cout << "[Base] 构造函数开始: " << baseName << std::endl;
init(); // 间接调用虚函数(危险!)
directVirtualCall(); // 直接调用虚函数
std::cout << "[Base] 构造函数结束" << std::endl;
}
virtual ~Base() {
std::cout << "[Base] 析构函数开始" << std::endl;
cleanup(); // 间接调用虚函数(危险!)
directVirtualCall(); // 直接调用虚函数
std::cout << "[Base] 析构函数结束" << std::endl;
}
// 初始化函数(间接调用虚函数)
void init() {
setupResources(); // 调用虚函数
}
// 清理函数(间接调用虚函数)
void cleanup() {
releaseResources(); // 调用虚函数
}
// 直接调用虚函数
void directVirtualCall() {
printState();
}
// 虚函数:资源设置
virtual void setupResources() {
std::cout << "[Base] 设置资源" << std::endl;
}
// 虚函数:资源释放
virtual void releaseResources() {
std::cout << "[Base] 释放资源" << std::endl;
}
// 虚函数:打印状态
virtual void printState() {
std::cout << "[Base] 当前状态: " << baseName << std::endl;
}
protected:
std::string baseName;
};
// 中间层类
class Middle : public Base {
public:
Middle(const std::string& name) : Base(name), middleData(nullptr) {
std::cout << "[Middle] 构造函数开始" << std::endl;
// 初始化中间层数据
middleData = new int[10];
for (int i = 0; i < 10; ++i) {
middleData[i] = i;
}
std::cout << "[Middle] 构造函数结束" << std::endl;
}
~Middle() override {
std::cout << "[Middle] 析构函数开始" << std::endl;
// 释放中间层数据
delete[] middleData;
middleData = nullptr;
std::cout << "[Middle] 析构函数结束" << std::endl;
}
void setupResources() override {
std::cout << "[Middle] 设置资源 (数据大小: " << (middleData ? "有效" : "无效") << ")" << std::endl;
}
void releaseResources() override {
std::cout << "[Middle] 释放资源 (数据大小: " << (middleData ? "有效" : "无效") << ")" << std::endl;
}
void printState() override {
std::cout << "[Middle] 当前状态: " << baseName
<< ", 数据: " << (middleData ? "有效" : "无效") << std::endl;
}
private:
int* middleData;
};
// 派生类
class Derived : public Middle {
public:
Derived(const std::string& name) : Middle(name), derivedValue(42) {
std::cout << "[Derived] 构造函数开始" << std::endl;
// 初始化派生类资源
derivedResource = new std::string("资源数据");
std::cout << "[Derived] 构造函数结束" << std::endl;
}
~Derived() override {
std::cout << "[Derived] 析构函数开始" << std::endl;
// 释放派生类资源
delete derivedResource;
derivedResource = nullptr;
std::cout << "[Derived] 析构函数结束" << std::endl;
}
void setupResources() override {
std::cout << "[Derived] 设置资源 (值: " << derivedValue
<< ", 资源: " << (derivedResource ? *derivedResource : "无效") << ")" << std::endl;
}
void releaseResources() override {
std::cout << "[Derived] 释放资源 (值: " << derivedValue
<< ", 资源: " << (derivedResource ? *derivedResource : "无效") << ")" << std::endl;
}
void printState() override {
std::cout << "[Derived] 当前状态: " << baseName
<< ", 值: " << derivedValue
<< ", 资源: " << (derivedResource ? *derivedResource : "无效") << std::endl;
}
private:
int derivedValue;
std::string* derivedResource;
};
int main() {
std::cout << "=== 创建 Derived 对象 ===" << std::endl;
{
Derived d("测试对象");
std::cout << "\n=== 对象完全构造后 ===" << std::endl;
d.directVirtualCall(); // 正常多态调用
d.init(); // 正常调用
}
std::cout << "\n=== 对象销毁后 ===" << std::endl;
return 0;
}
继承的构造函数
在C++中,explicit
和 constexpr
是两个重要的关键字,分别用于控制构造函数的隐式转换和编译期常量计算。以下是对它们的详细解释:
1. explicit
关键字
作用
- 禁止隐式类型转换:用于修饰单参数构造函数(或除第一个参数外其余参数都有默认值的构造函数),防止编译器自动进行类型转换。
- 仅允许显式初始化:必须通过直接构造或显式转换调用构造函数。
示例
class MyClass {
public:
explicit MyClass(int value) : data(value) {} // explicit构造函数
int data;
};
void func(MyClass obj) { /* ... */ }
int main() {
// 错误:无法进行隐式转换
// func(42); // 编译错误
// 正确:显式构造
func(MyClass(42)); // 直接构造
func(static_cast<MyClass>(42)); // 显式转换
}
应用场景
- 单参数构造函数:防止意外的类型转换(如
int
转MyClass
)。 - 安全的类型封装:例如智能指针
std::unique_ptr
的构造函数是explicit
的,避免隐式转换导致内存泄漏。
2. constexpr
关键字
作用
- 编译期常量表达式:声明一个函数或变量可以在编译期计算,用于生成编译期常量。
- 提高性能:将计算移到编译期,减少运行时开销。
示例
// 编译期常量函数
constexpr int factorial(int n) {
return (n <= 1) ? 1 : n * factorial(n - 1);
}
// 编译期常量变量
constexpr int value = factorial(5); // 编译期计算为120
// 运行时使用
int runtimeValue = 10;
int result = factorial(runtimeValue); // 运行时计算
应用场景
- 模板元编程:提供编译期参数。
- 数组大小:
constexpr int size = 10; int arr[size]; // 合法,size是编译期常量
- 数学计算:如编译期平方根、幂运算等。
3. explicit constexpr
组合使用
作用
- 编译期构造函数:禁止隐式转换,且允许在编译期创建对象。
示例
class Point {
public:
explicit constexpr Point(int x, int y) : x(x), y(y) {}
constexpr int getX() const { return x; }
constexpr int getY() const { return y; }
private:
int x, y;
};
// 编译期常量对象
constexpr Point p(10, 20); // 编译期构造
// 编译期计算
constexpr int sum = p.getX() + p.getY(); // 编译期求值
对比总结
特性 | explicit | constexpr |
---|---|---|
核心作用 | 禁止隐式类型转换 | 编译期常量计算 |
修饰对象 | 构造函数 | 函数、变量、对象 |
编译期行为 | 无特殊编译期优化 | 强制编译期计算(若参数为常量) |
运行时行为 | 仅影响构造方式 | 允许运行时调用(参数为变量时) |
典型场景 | 防止意外类型转换 | 提高性能、模板元编程 |
注意事项
-
constexpr
函数限制:- 函数体必须足够简单(如单一
return
语句)。 - C++11/14与C++17/20的规则略有不同(如C++17允许
if
语句)。
- 函数体必须足够简单(如单一
-
explicit
与转换运算符:explicit
也可用于转换运算符(如explicit operator bool()
),防止隐式转换。
-
组合使用:
explicit constexpr
常用于需要编译期构造且禁止隐式转换的场景(如安全的编译期工厂函数)。
通过合理使用explicit
和constexpr
,可以提高代码的安全性、可读性和性能。
以下通过代码示例来详细说明文中内容:
1. 基类构造函数含默认实参时派生类构造函数的情况
#include <iostream>
class Base {
public:
Base(int a, int b = 10) : m_a(a), m_b(b) {}
void print() {
std::cout << "Base: a = " << m_a << ", b = " << m_b << std::endl;
}
private:
int m_a;
int m_b;
};
class Derived : public Base {
public:
using Base::Base; // 继承基类构造函数
};
int main() {
// 派生类获得两个构造函数
Derived d1(1, 2); // 调用接受两个形参的构造函数
d1.print();
Derived d2(3); // 调用接受一个形参的构造函数
d2.print();
return 0;
}
在上述代码中,Base
类的构造函数 Base(int a, int b = 10)
第二个形参有默认实参。Derived
类通过 using Base::Base;
继承基类构造函数后,获得了两个构造函数:一个接受两个形参(没有默认实参),用于像 Derived d1(1, 2);
这样的调用;另一个接受一个形参,对应基类中最左侧没有默认值的形参,用于像 Derived d2(3);
这样的调用。
2. 基类含多个构造函数时派生类的继承情况
#include <iostream>
class Base {
public:
Base(int a) : m_a(a) {}
Base(int a, int b) : m_a(a), m_b(b) {}
void print() {
std::cout << "Base: a = " << m_a;
if (m_b.has_value()) {
std::cout << ", b = " << m_b.value();
}
std::cout << std::endl;
}
private:
int m_a;
std::optional<int> m_b;
};
class Derived : public Base {
public:
// 继承部分构造函数
using Base::Base;
// 为基类部分构造函数定义自己的版本
Derived(int a, int b, int c) : Base(a, b), m_c(c) {}
private:
int m_c;
};
int main() {
Derived d1(1); // 调用继承来的接受一个形参的构造函数
d1.print();
Derived d2(2, 3); // 调用继承来的接受两个形参的构造函数
d2.print();
Derived d3(4, 5, 6); // 调用派生类自己定义的构造函数
d3.print();
return 0;
}
这里 Base
类有两个构造函数。Derived
类通过 using Base::Base;
继承了部分基类构造函数,同时又为接受三个参数的情况定义了自己的构造函数 Derived(int a, int b, int c)
。当派生类定义的构造函数与基类构造函数参数列表不同时,两者共存;若参数列表相同,派生类定义的构造函数会替换继承而来的构造函数。
3. 默认、拷贝和移动构造函数不被继承的情况
#include <iostream>
class Base {
public:
Base(int a) : m_a(a) {}
Base(const Base& other) : m_a(other.m_a) {
std::cout << "Base copy constructor" << std::endl;
}
Base(Base&& other) noexcept : m_a(other.m_a) {
std::cout << "Base move constructor" << std::endl;
}
private:
int m_a;
};
class Derived : public Base {
public:
using Base::Base;
};
int main() {
Base b(1);
Derived d1(2);
// 这里会调用合成的拷贝构造函数(不是继承的)
Derived d2 = d1;
// 这里会调用合成的移动构造函数(不是继承的)
Derived d3 = std::move(d2);
return 0;
}
在这个例子中,Base
类定义了拷贝构造函数和移动构造函数。Derived
类虽然继承了基类其他构造函数(通过 using Base::Base;
),但默认、拷贝和移动构造函数不会被继承,而是按照正常规则被合成。当进行对象拷贝或移动操作时,调用的是合成的构造函数,而不是继承自基类的。
通过这些例子,可以更好地理解文中关于基类构造函数继承以及派生类构造函数相关规则。
15.8 容器与继承
在容器中放置(智能)指针而非对象
编写Basket类
#include <iostream>
#include <memory>
#include <set>
#include <string>
// 基类Quote,表示商品报价
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() = default;
private:
std::string bookNo; // 商品编号
protected:
double price = 0.0; // 商品单价
};
// 派生类Bulk_quote,继承自Quote,用于表示批量折扣商品
class Bulk_quote : public Quote {
public:
Bulk_quote() = default;
Bulk_quote(const std::string& book, double p, std::size_t qty, double disc)
: Quote(book, p), quantity(qty), discount(disc) {}
// 重写net_price函数,实现批量折扣计算
double net_price(std::size_t n) const override {
if (n >= quantity) {
return n * price * (1 - discount);
}
return n * price;
}
private:
std::size_t quantity; // 享受折扣的最低购买数量
double discount; // 折扣率
};
// Basket类,用于管理购物篮中的商品
class Basket {
public:
// Basket使用合成的默认构造函数和拷贝控制成员
void add_item(const std::shared_ptr<Quote>& sale) {
items.insert(sale);
}
// 打印每本书的总价和购物篮中所有书的总价
double total_receipt(std::ostream& os) const {
double sum = 0.0;
// 遍历购物篮中的每个商品项
for (auto iter = items.cbegin(); iter != items.cend();
iter = items.upper_bound(*iter)) {
// 获取当前商品项的数量
std::size_t n = items.count(*iter);
// 计算当前商品项的总价
sum += (*iter)->net_price(n);
// 打印当前商品项的信息和总价
print_total(os, **iter, n);
}
os << "Total Sale: " << sum << std::endl;
return sum;
}
private:
// 该函数用于比较shared_ptr,multiset成员会用到它
static bool compare(const std::shared_ptr<Quote>& lhs,
const std::shared_ptr<Quote>& rhs) {
return lhs->isbn() < rhs->isbn();
}
// multiset保存多个报价,按照compare成员排序
std::multiset<std::shared_ptr<Quote>, decltype(compare)*> items{compare};
};
// 辅助函数,用于打印商品的信息和总价
void print_total(std::ostream& os, const Quote& item, std::size_t n) {
double total = item.net_price(n);
os << "ISBN: " << item.isbn() << " 数量: " << n << " 总价: " << total << std::endl;
}
int main() {
Basket basket;
// 添加普通商品(无折扣)
basket.add_item(std::make_shared<Quote>("0-201-82470-1", 25.0));
// 添加批量折扣商品
basket.add_item(std::make_shared<Bulk_quote>("0-201-82470-2", 30.0, 5, 0.1));
basket.add_item(std::make_shared<Bulk_quote>("0-201-82470-2", 30.0, 5, 0.1));
basket.add_item(std::make_shared<Bulk_quote>("0-201-82470-2", 30.0, 5, 0.1));
basket.add_item(std::make_shared<Bulk_quote>("0-201-82470-2", 30.0, 5, 0.1));
basket.add_item(std::make_shared<Bulk_quote>("0-201-82470-2", 30.0, 5, 0.1));
basket.total_receipt(std::cout);
return 0;
}
虚拷贝
虚拷贝的核心原理
纯虚函数 clone():
在基类Shape中定义纯虚函数clone(),要求所有派生类必须实现该函数
返回类型为std::unique_ptr,使用智能指针管理动态对象
派生类实现:
每个派生类(如Circle、Rectangle)实现clone()函数
通过调用自身的拷贝构造函数(如Circle(*this))创建新对象
返回指向新对象的智能指针
多态复制:
通过基类指针或引用调用clone()时,会动态调用实际对象类型的clone()实现
即使在编译时不知道具体类型,也能正确复制对象
#ifndef SHAPE_H
#define SHAPE_H
#include <memory>
#include <string>
#include <vector>
// 前向声明
class Drawing;
// 基类:Shape
class Shape {
public:
Shape() = default;
Shape(const std::string& color) : color(color) {}
// 拷贝构造函数(浅拷贝)
Shape(const Shape& other) : color(other.color) {}
// 赋值运算符(浅拷贝)
Shape& operator=(const Shape& other) {
if (this != &other) {
color = other.color;
}
return *this;
}
virtual ~Shape() = default;
// 纯虚函数:绘制图形
virtual void draw() const = 0;
// 纯虚函数:深拷贝
virtual std::unique_ptr<Shape> clone() const = 0;
// 获取颜色
std::string getColor() const { return color; }
private:
std::string color;
};
// 派生类:Circle(包含动态资源)
class Circle : public Shape {
public:
Circle(double radius, const std::string& color)
: Shape(color), radius(radius), points(new std::vector<int>{1, 2, 3, 4}) {}
// 拷贝构造函数(深拷贝)
Circle(const Circle& other)
: Shape(other), radius(other.radius), points(new std::vector<int>(*other.points)) {}
// 赋值运算符(深拷贝)
Circle& operator=(const Circle& other) {
if (this != &other) {
Shape::operator=(other); // 调用基类赋值
radius = other.radius;
delete points; // 释放原有资源
points = new std::vector<int>(*other.points); // 深拷贝
}
return *this;
}
// 移动构造函数
Circle(Circle&& other) noexcept
: Shape(std::move(other)), radius(other.radius), points(other.points) {
other.points = nullptr; // 转移资源所有权
}
// 移动赋值运算符
Circle& operator=(Circle&& other) noexcept {
if (this != &other) {
Shape::operator=(std::move(other)); // 调用基类移动赋值
delete points; // 释放原有资源
radius = other.radius;
points = other.points;
other.points = nullptr; // 转移资源所有权
}
return *this;
}
~Circle() override {
delete points;
}
// 实现绘制函数
void draw() const override {
std::cout << "绘制一个" << getColor() << "的圆,半径为" << radius
<< ",点集大小: " << points->size() << std::endl;
}
// 实现深拷贝
std::unique_ptr<Shape> clone() const override {
return std::make_unique<Circle>(*this); // 调用拷贝构造函数
}
private:
double radius;
std::vector<int>* points; // 动态分配的资源
};
// 派生类:Rectangle
class Rectangle : public Shape {
public:
Rectangle(double width, double height, const std::string& color)
: Shape(color), width(width), height(height) {}
// 实现绘制函数
void draw() const override {
std::cout << "绘制一个" << getColor() << "的矩形,宽为" << width
<< ",高为" << height << std::endl;
}
// 实现深拷贝
std::unique_ptr<Shape> clone() const override {
return std::make_unique<Rectangle>(*this);
}
private:
double width;
double height;
};
// 组合类:Drawing(包含多个Shape)
class Drawing {
public:
Drawing() = default;
// 拷贝构造函数(深拷贝)
Drawing(const Drawing& other) {
for (const auto& shape : other.shapes) {
shapes.push_back(shape->clone()); // 调用每个Shape的深拷贝
}
}
// 赋值运算符(深拷贝)
Drawing& operator=(const Drawing& other) {
if (this != &other) {
shapes.clear(); // 清空现有资源
for (const auto& shape : other.shapes) {
shapes.push_back(shape->clone()); // 调用每个Shape的深拷贝
}
}
return *this;
}
// 添加图形
void addShape(std::unique_ptr<Shape> shape) {
shapes.push_back(std::move(shape));
}
// 绘制所有图形
void drawAll() const {
for (const auto& shape : shapes) {
shape->draw();
}
}
private:
std::vector<std::unique_ptr<Shape>> shapes;
};
#endif // SHAPE_H
#include "shape.h"
#include <iostream>
int main() {
// 创建一个包含动态资源的Circle对象
Circle originalCircle(5.0, "红色");
originalCircle.draw();
// 1. 浅拷贝示例(通过基类引用)
Shape& shapeRef = originalCircle;
Circle shallowCopy = dynamic_cast<Circle&>(shapeRef);
std::cout << "\n浅拷贝后:" << std::endl;
shallowCopy.draw();
// 2. 深拷贝示例(通过虚函数)
std::unique_ptr<Shape> deepCopy = originalCircle.clone();
std::cout << "\n深拷贝后:" << std::endl;
deepCopy->draw();
// 3. 组合类深拷贝示例
Drawing drawing;
drawing.addShape(std::make_unique<Circle>(3.0, "蓝色"));
drawing.addShape(std::make_unique<Rectangle>(4.0, 5.0, "绿色"));
std::cout << "\n原始绘图:" << std::endl;
drawing.drawAll();
Drawing copiedDrawing = drawing; // 调用Drawing的拷贝构造函数
std::cout << "\n复制后的绘图:" << std::endl;
copiedDrawing.drawAll();
return 0;
}
文本查询程序再探
// Primer_TextQuery.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//
#include <iostream>
#include <fstream>
#include <sstream>
#include <vector>
#include <set>
#include <map>
#include <memory>
#include <algorithm>
using namespace std;
class QueryResult;
class TextQuery {
public:
using line_no = vector<string>::size_type;
TextQuery(ifstream&);
QueryResult query(const string&) const;
private:
shared_ptr<vector<string>> file;
map<string, shared_ptr<set<line_no>>> wm;
};
class QueryResult {
friend ostream& print(ostream&, const QueryResult&);
public:
QueryResult(string s,
shared_ptr<set<TextQuery::line_no>> p,
shared_ptr<vector<string>> f) :
sought(s), lines(p), file(f) {
}
const shared_ptr<vector<string>> get_file() const { return file; }
set<TextQuery::line_no>::iterator begin() { return lines->begin(); }
set<TextQuery::line_no>::iterator end() { return lines->end(); }
private:
string sought;
shared_ptr<set<TextQuery::line_no>> lines;
shared_ptr<vector<string>> file;
};
TextQuery::TextQuery(ifstream& is) : file(new vector<string>) {
string text;
while (getline(is, text)) {
file->push_back(text);
int n = file->size() - 1;
istringstream line(text);
string word;
while (line >> word) {
auto& lines = wm[word];
if (!lines)
lines.reset(new set<line_no>);
lines->insert(n);
}
}
}
QueryResult TextQuery::query(const string& sought) const {
static shared_ptr<set<line_no>> nodata(new set<line_no>);
auto loc = wm.find(sought);
if (loc == wm.end())
return QueryResult(sought, nodata, file);
else
return QueryResult(sought, loc->second, file);
}
ostream& print(ostream& os, const QueryResult& qr) {
os << qr.sought << " occurs " << qr.lines->size() << " times" << endl;
for (auto num : *qr.lines)
os << "\t(line " << num + 1 << ") " << (*qr.file)[num] << endl;
return os;
}
class Query_base {
friend class Query;
protected:
using line_no = TextQuery::line_no;
virtual ~Query_base() = default;
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&);
public:
Query(const string&);
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;
};
class WordQuery : public Query_base {
friend class Query;
WordQuery(const string& s) : query_word(s) {}
QueryResult eval(const TextQuery& t) const override { return t.query(query_word); }
string rep() const override { return query_word; }
string query_word;
};
inline Query::Query(const string& s) : q(new WordQuery(s)) {}
class NotQuery : public Query_base {
friend Query operator~(const Query&);
NotQuery(const Query& q) : query(q) {}
QueryResult eval(const TextQuery&) const override;
string rep() const override { return "~(" + query.rep() + ")"; }
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 override {
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 override;
};
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 override;
};
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 sz = result.get_file()->size();
for (line_no n = 0; n != sz; ++n) {
// 使用 begin() 和 end() 公有接口来访问 lines 的内容
auto beg = result.begin();
auto end = result.end();
if (find(beg, end, n) == end) {
ret_lines->insert(n);
}
}
return QueryResult(rep(), ret_lines, result.get_file());
}
int main() {
ifstream file("story.txt");
TextQuery tq(file);
Query q = Query("hair") | Query("Alice");
print(cout, q.eval(tq));
cout << "\n====================\n";
Query q2 = Query("hair") & Query("Alice");
print(cout, q2.eval(tq));
cout << "\n====================\n";
Query q3 = ~Query("Alice");
print(cout, q3.eval(tq));
return 0;
}