15.1 什么是虚成员
即虚函数,对于某些成员函数,基类希望它的派生类个自定义适合自身的版本,此时基类就将这些函数声明成虚函数,并且通过基类指针和引用调用虚函数时会根据绑定的实际对象来选择合适版本的函数,即动态绑定(运行时绑定)。
15.2 protected访问说明符与private有何区别
protected访问说明符所控制的成员只能在类内使用或者是派生类内被访问,友元也是可以访问的,
private控制的成员只能在类内或者被友元访问
15.4 下面哪条声明语句是不正确的?请解释原因
class Base{...};
(a) class Derived : public Derived{...};
(b) class Derived : public Base{....};
(c) class Derived : public Base;
(a)是错误的,不能自己继承自己
©是错误的,派生列表以及与定义有关的其他细节必须与类的主体一起出现
15.8 给出静态类型和动态类型的定义
表达式的静态类型在编译时总是已知的,他是变量声明时的类型或表达式生成的类型。
动态类型是变量或表达式表示的内存中的对象的类型。动态类型直到运行时才可知
15.9 在什么情况下表达式的静态类型可能与动态类型不同?请给出三个静态类型与动态类型不同的例子。
当表达式为指针或引用的时候,表达式的静态类型可能与动态类型不同。
父类指针指向子类对象的时候,该父类指针静态类型就是父类类型,而动态类型则是子类类型
父类引用绑定子类对象的时候,该引用静态类型则为父类引用,而动态类型为子类类型
用一个派生类初始化基类对象
15.10
istream& read(istream& is, Sales_data& item)
接受一个istream&,当传入std::cin时候则是调用对应>>,将键盘输入读取并写入到对应变量中。当传入的实参为一个ifstream对象时,则调用这个派生类对应的右移(>>)运算符读出文件内容写入到变量中
15.11 为你的Quote类体系添加一个名为debug的虚函数,令其分别显示每个类的数据成员
#pragma once
#ifndef QUOTE_H
#define QUOTE_H
#include<iostream>
#include<string>;
using std::ostream;
using std::cout;
using std::cin;
using std::endl;
using std::string;
class Quote {
public:
Quote() = default;
Quote(const string& book, 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 ostream& debug(ostream &os) const
{
cout << "ISBN : " << isbn() << " "
<< "price :" << price;
return os;
}
virtual ~Quote() = default;//对析构函数进行动态绑定
private:
string bookNo; //书籍的ISBN编号
protected:
double price = 0.0; //代表普通状态下不打折的价格
};
class Bulk_quote :public Quote {
public:
Bulk_quote() = default;
Bulk_quote(const string&, double, size_t, double);
//覆盖基类的函数版本已实现基于大量购买的折扣政策
double net_price(size_t) const override;
ostream& debug(ostream &os) const override
{
this->Quote::debug(os) << " min_qty: " << min_qty << " "
<< "discount: " << discount;
return os;
}
private:
size_t min_qty = 0; //适用折扣的最低购买量
double discount = 0.0; //以小数表示折扣额
};
class Limit_quote :public Quote {
public:
Limit_quote() = default;
Limit_quote(const string&, double, size_t, double);
double net_price(size_t) const override;
ostream& debug(ostream& os) const override
{
this->Quote::debug(os) << " max_qty: " << max_qty << " "
<< "discount: " << discount;
return os;
}
private:
size_t max_qty = 0; //适用折扣的最大购买量
double discount = 0.0;
};
//计算并打印销售给定数量的某种书籍所得的费用
//必须放在Quote.cpp定义
double print_total(ostream& os, const Quote& item, size_t n);
#endif
15.12 有必要将一个成员函数同时声明成override和final吗?
可以,override表示希望覆盖基类中的虚函数;final表示之后该类的派生类都不可以覆盖这个成员函数。
15.13 给定下面得到类,解释每个print函数的机理:
class Base {
public:
string name() { return basename; }
virtual void print(ostream& os)
{
os << basename;
}
private:
string basename;
};
class Derived :public Base
{
public:
void print(ostream& os)
{
print(os); //这里会调用Derived::print(ostream& os)造成无限递归
os << " " << i;
}
private:
int i;
};
上述代码中存在问题吗?如果有?你该如何修改它?
答:会造成无限递归死循环,应该改为:
class Base {
public:
string name() { return basename; }
virtual void print(ostream& os)
{
os << basename;
}
private:
string basename;
};
class Derived :public Base
{
public:
void print(ostream& os)
{
Base::print(os); //这里会调用Derived::print(ostream& os)造成无限递归
os << " " << i;
}
private:
int i;
};
15.14 给定上一题中的类以及下面这些对象,说明在运行时调用哪个函数
void p15_14()
{
Base bobj;
Derived dobj;
Base* bp1 = &bobj;
Base* bp2 = &dobj;
Base& br1 = bobj;
Base& br2 = dobj;
bobj.print(cout); //Base::print()
dobj.print(cout); //调用Derived::print();
bp1->name(); // 使用Base::name()
bp2->name(); //调用派生类中基类部分的name()
br1.print(cout); //调用Base::print()
br2.print(cout); //调用Derived::print()
}
15.17 尝试定义一个Disc_quote(抽象基类)的对象,看看编译器给出的错误信息是什么?
不允许使用抽象类型Disc_quote
的对象
Disc_quote
无法实例化抽象类
15.18
class Base {
friend class Pal;
public:
void pub_mem();
protected:
int prot_mem; //protected成员
private:
char priv_mem;
};
struct Pub_Derv :public Base {
//正确:派生类能访问protected成员
int f() { return prot_mem; }
//错误private成员对于派生类来说不可访问
//int g() { return priv_mem; }
};
struct Priv_Derv :private Base {
//private 不影响派生类的访问权限,但是对类外是派生类对象基类的成员都为private从而无法访问。
int f1() const { return prot_mem; }
};
struct Prot_Derv :protected Base //保护继承使得基类成员(public和protected的)到了派生类中全为protected
{
int f1() const { return prot_mem; }
};
struct Derived_from_Public :public Pub_Derv {
//Base::prot_mem在Pub_Derv中仍为protected
int use_base() { return prot_mem; }
};
struct Derived_from_Private :public Priv_Derv {
};
struct Derived_from_Protected :public Prot_Derv {
//(派生类中的基类部分)Base:prot_mem在Prot_Derv中是protected
int use_base() { return prot_mem; }
};
void p15_18()
{
Pub_Derv d1;
Priv_Derv d2;
Prot_Derv d3;
Derived_from_Public dd1;
Derived_from_Private dd2;
Derived_from_Protected dde;
Base* p = &d1; // 合法,父类指针指向子类对象
p = &d2; //基类指针不能绑定私有继承的派生类对象
p = &d3; //基类指针也不能指向保护继承的派生类对象
p = &dd1; //合法,间接基类指针指向二层派生的对象
p = &dd2; //不合法
p = &dd3; //不合法
}
15.19 假设上题的每个类都有如下形式的成员函数:
void memfcn(Base &b) { b = *this;}
对于每个类,分别判断上面的函数是否合法?
Derived_from_Private类中写该函数不合法
struct Derived_from_Private :public Priv_Derv {
void memfcn(Base& b) { b = *this; } //不合法
};
15.23
class Base {
public:
virtual int fcn()
{
cout << "Base::fcn()" << endl;
return 0;
}
};
class D1 :public Base {
public:
//隐藏基类的fcn,这个fcn不是虚函数
//D1继承了Base::fcn()的定义
int fcn() //覆盖了Base::fcn()
{
cout << "D1::fcn()" << endl;
return 0;
}
virtual void f2() //是一个新的虚函数,在Base中不存在
{
cout << "D1::f2()" << endl;
}
};
class D2 :public D1 {
public:
int fcn(int)//是一个普通函数,隐藏了D1::fcn(int)
{
cout << "D2::fcn(int);" << endl;
return 0;
}
int fcn() // 覆盖了Base的虚函数fcn
{
cout << "D2::fcn()" << endl;
return 0;
}
void f2() //覆盖了D1的虚函数f2
{
cout << "D2::f2()" << endl;
}
};
void p15_23()
{
Base bobj;
D1 d1obj;
D2 d2obj;
Base* bp1 = &bobj, * bp2 = &d1obj, * bp3 = &d2obj;
bp1->fcn(); //运行时调用Base::fcn()
bp2->fcn(); //运行时调用了D1::fcn()
bp3->fcn(); //运行时调用了D2::fcn()
D1* d1p = &d1obj;
D2* d2p = &d2obj;
//bp2->f2();
d1p->f2(); //运行时调用了D1::f2()
d2p->f2(); //运行时调用了D2::f2()
//非虚函数调用
Base* p1 = &d2obj;
D1* p2 = &d2obj;
D2* p3 = &d2obj;
//p1->fcn(42); // Base中没有接受一个int的fcn
//p2->fcn(42); //D1中没有接受int的fcn
p3->fcn(42); //静态绑定,调用D2::fcn(int)
}
15.24 哪种类需要虚析构函数?虚析构函数必须执行什么样的操作?
当类为继承关系中的基类的时候需要虚析构函数。基类中将析构函数定义成虚函数确保运行时运行正确的析构函数版本。
15.25 我们为什么Disc_quote定义一个默认构造函数?如果去掉该构造函数的话会对Bulk_quote的行为产生什么影响
-
使得派生类Bulk_quote可以通过调用基类Disc_quote的默认构造函数来初始化派生类对象的基类部分。
-
如果去掉基类构造函数,则派生类的构造函数也会被删除。导致派生类对象无法默认初始化
15.26
#pragma once
#ifndef QUOTE_H
#define QUOTE_H
#include<iostream>
#include<string>;
using std::ostream;
using std::cout;
using std::cin;
using std::endl;
using std::string;
class Quote {
public:
Quote() = default;
Quote(const string& book, double sales_price) :
bookNo(book), price(sales_price) {}
//拷贝构造函数
Quote(const Quote& qr) :
bookNo(qr.bookNo), price(qr.price)
{
cout << "Quote(const Quote&)" << endl;
}
//move constructor
Quote(const Quote&& q)noexcept :
bookNo(std::move(q.bookNo)), price(std::move(q.price))
{
cout << "Quote(const Quote&&)" << endl;
}
//拷贝赋值运算符
Quote& operator=(const Quote& rhs)
{
bookNo = rhs.bookNo;
price = rhs.price;
cout << "operator=(const Quote& rhs)" << endl;
return *this;
}
//move assignment
Quote& operator=(Quote&& rhs) noexcept
{
cout << "operator=(Quote&&) move assignment" << endl;
bookNo = std::move(rhs.bookNo);
price = std::move(rhs.price);
return *this;
}
string isbn() const { return bookNo; }
//返回给定数量的书籍销售额
//派生类负责重写并使用不同的折扣计算方法
virtual double net_price(size_t n) const // 隐式内联
{
return n * price;
}
virtual ostream& debug(ostream &os) const
{
cout << "ISBN : " << isbn() << " "
<< "price :" << price;
return os;
}
virtual ~Quote() = default;//对析构函数进行动态绑定
private:
string bookNo; //书籍的ISBN编号
protected:
double price = 0.0; //代表普通状态下不打折的价格
};
//用于保存折扣值和购买量的类,派生类使用这些数据可以实现不同的价格策略
//抽象基类,表示一本打折书籍的通用概念
class Disc_quote :public Quote {
public:
Disc_quote() = default;
Disc_quote(const string& book, double price, size_t qty, double disc) :
Quote(book, price), quantity(qty), discount(disc){}
double net_price(size_t) const = 0; //纯虚函数
//拷贝控制成员,通过基类的拷贝构造来拷贝派生类对象的基类部分
Disc_quote(const Disc_quote& q) :
Quote(q), quantity(q.quantity), discount(q.discount)
{
cout << "Disc_quote(const Disc_quote&)" << endl;
}
Disc_quote(Disc_quote&& q) :
Quote(std::move(q)), quantity(std::move(q.quantity)), discount(std::move(q.discount))
{
cout << "Disc_quote(Disc_quote&&)" << endl;
}
Disc_quote& operator=(const Disc_quote& rhs)
{
Quote::operator=(rhs);
cout << "operator(const Disc_quote&)" << endl;
quantity = rhs.quantity;
discount = rhs.discount;
return *this;
}
Disc_quote& operator=(Disc_quote&& rhs)
{
Quote::operator=(std::move(rhs));
cout << "operator=(Disc_quote&&)" << endl;
quantity = rhs.quantity;
discount = rhs.discount;
return *this;
}
protected: //这个权限是为了让派生类可以访问
size_t quantity = 0; //折扣适用的购买量
double discount = 0.0; // 表示折扣的最小数量
};
class Bulk_quote :public Disc_quote {
public:
Bulk_quote() = default;
//@param1:ISBN param2:单价 param3: 折扣最低购买量 param4: 折扣率
Bulk_quote(const string& book, double p, size_t qty, double disc):
Disc_quote(book, p, qty, disc) {}
//拷贝控制成员
Bulk_quote(const Bulk_quote& q) :
Disc_quote(q)
{
cout << "Bulk_quote(const Bulk_quote&)" << endl;
}
Bulk_quote(Bulk_quote&& rhs)
:Disc_quote(std::move(rhs))
{
cout << "Bulk_quote(Bulk_quote&&)" << endl;
}
Bulk_quote& operator=(const Bulk_quote& rhs)
{
Disc_quote::operator=(rhs);
cout << "operator=(const Bulk_quote&)" << endl;
return *this;
}
Bulk_quote& operator=(Bulk_quote&& rhs)
{
Disc_quote::operator=(std::move(rhs));
return *this;
}
~Bulk_quote() = default;
//覆盖基类的函数版本已实现基于大量购买的折扣政策
double net_price(size_t) const override;
ostream& debug(ostream &os) const override
{
//quantity和discount是在基类子对象的成员
this->Quote::debug(os) << " min_qty: " << quantity << " "
<< "discount: " << discount;
return os;
}
};
//不超过某一数量时打折,若超过某一数量则超出部分按原价算
class Limit_quote :public Disc_quote {
public:
Limit_quote() = default;
Limit_quote(const string& book, double p, size_t qty, double disc) :
Disc_quote(book, p, qty, disc) {}
double net_price(size_t) const override;
ostream& debug(ostream& os) const override
{
this->Quote::debug(os) << " max_qty: " << quantity << " "
<< "discount: " << discount;
return os;
}
};
//计算并打印销售给定数量的某种书籍所得的费用
//必须放在Quote.cpp定义
double print_total(ostream& os, const Quote& item, size_t n);
#endif
测试代码:
void p15_26()
{
Bulk_quote b;
Bulk_quote b1(b);
cout << "------------" << endl;
Bulk_quote b2(std::move(b));
cout << "------------" << endl;
b1 = b2;
cout << "------------" << endl;
b1 = std::move(b);
}
测试结果:
Quote(const Quote&)
Disc_quote(const Disc_quote&)
Bulk_quote(const Bulk_quote&)
------------
Quote(const Quote&&)
Disc_quote(Disc_quote&&)
Bulk_quote(Bulk_quote&&)
------------
operator=(const Quote& rhs)
operator(const Disc_quote&)
operator=(const Bulk_quote&)
------------
operator=(Quote&&) move assignment
operator=(Disc_quote&&)
operator=(Bulk_quote&&)
15.31 已知s1、s2、s3和s4都是string,判断下面的表达式分别创建了什么样的对象
(a) Query(s1) | Query(s2) & ~Query(s3);
(b) Query(s1) | (Query(s2) & ~Query(s3));
(c) (Query(s1) & Query(s2)) | (Query(s3) & Query(s4));
(a)OrQuery,AndQuery,WordQuery,NotQuery
(b)WordQuery,OrQuery,AndQuery, NotQuery
©WordQuery,AndQuery, NotQuery
15.32 当一个Query类型的对象被拷贝、移动、赋值或销毁时将分别发生什么?
拷贝时合成的拷贝构造函数被调用,shared_ptr所绑定的对象的引用计数加1
移动的时候合成的移动构造函数被调用。它将移动数据成员至新的对象。这时新对象的智能指针将会指向原对象的地址,而原对象智能指针为nullptr,新对象的只能指针引用计数为1
赋值的时候合成的赋值运算符被调用,左边对象中Shared_ptr指向对象的引用计数-1,右边对象shared_ptr指向对象的引用计数+1
销毁的时候,合成的析构函数被调用,当前对象shared_ptr指向的对象的其他指针引用计数-1,计数为0时则释放这个内存空间。
15.33 当一个Query_base对象被拷贝、移动、赋值或销毁时将分别发生什么
Query_base是一个抽象基类,不可以创建对象,它的对象实际上是它的派生类对象
15.34
(a) WordQuery::WordQuery(const std::string &)
NotQuery::NotQuery(const Query &)
BinaryQuery::BinaryQuery(const Query &l, const Query &r, string s)
AndQuery::AndQuery(const Query&, const Query&);
OrQuery::OrQuery(const Query&, const Query&);
(b)Query::rep()
WordQuery::rep()
NotQuery::rep()
BinaryQuery::rep()
© Query::eval(const TextQuery &t) const
WordQuery::eval(const TextQuery &t) const
NotQuery::eval(const TextQuery &t) const
AndQuery::eval(const TextQuery &t) const
OrQuery::eval(const TextQuery &t) const
15.37 如果派生类中含有shared_ptr<Query_base>
类型的成员而非Query类型的成员,则你的类需要做出怎样的改变
应该在派生类中声明friend class Query_base;
15.38 下面声明合法吗?如果不合法,解释原因,如果合法,请指出该声明的含义
BinaryQuery a = Query("fiery") & Query("bird");//错误,BinaryQuery是抽象基类不可以创建对象
AndQuery b = Query("fiery") & Query("bird"); // 不合法Query类对象不能转换为AndQuery对象
OrQuery c = Query("fiery") & Query("bird"); //不合法,Query类对象不能转换为OrQuery对象