19.3 已知存在如下的类继承体系,其中每个类分别定义了一个公有的默认构造函数和一个虚析构函数:
class A {
public:
virtual ~A() {}
};
class B : public A {
public:
virtual ~B() {}
};
class C : public B {
public:
virtual ~C() {}
};
class D : public B, public A {
public:
virtual ~D() {}
};
void p19_3()
{
//a
A* pa = new C;
//pa实际是指向C类对象,为B类的派生类故转换成功
if (B* pb = dynamic_cast<B*>(pa))
cout << "OK" << endl;
else
cout << "No!" << endl;
//b
B* pb = new B;
//失败,因为pb指向B对象,B是C的基类,不可以由基类指针转换为派生类指针
if (C* pc = dynamic_cast<C*>(pb))
cout << "OK" << endl;
else
cout << "No!" << endl;
//c
A* pa = new D;
//成功,pa指向的是D对象,B是D的基类,派生类指针可以转换为基类指针,故可行
if (B* pb = dynamic_cast<B*>(pa))
cout << "OK!" << endl;
else
cout << "No!" << endl;
}
19.4 使用上一个练习定义的类改写下面的代码,将表达式*pa转换成类型C&:
if(C *pc = dynamic_cast<C*>(pa))
{
//使用C成员
}
else{
//使用A成员
}
答:
try{
C &rc = dynamic_cast<C &>(*pa);
//use C
}
catch(bad_cast)
{
//use A
}
19.5 在什么情况下你应该使用dynamic替代虚函数
并非所有时候都能有虚函数,当一个基类指针指向其派生类,基类中没有派生类中对应的函数(非虚函数),但是又想使用该派生类的函数就可以使用dynamic_cast将该基类指针转换为派生类指针来用派生类独有的成员
19.6 编写一条表达式将Query_base指针动态转换为AndQuery指针(参见564页)。分别使用AndQuery的对象及其他类型的对象测试转换是否有效。打印一条表示类型转换是否成功的信息,确保实际输出的结果与期望值一致。
需要将AndQuery构造函数访问权限设置为public,这破坏了封装性
#include<iostream>
#include"TextQuery.h"
using namespace chapter15;
using std::bad_cast;
void p19_6()
{
Query_base* pb = new AndQuery(Query("fiery"), Query("bird"));
//基类指针转换为派生类指针
if (AndQuery * pa = dynamic_cast<AndQuery*>(pb))
{
cout << "OK!" << endl;
}
else
{
cout << "NO!" << endl;
}
}
19.7 编写上一个练习类似的转换,这一次将Query_base对象转换为AndQuery引用。重复上面测试过程,确保转换能正常工作。
void p19_7()
{
//10
AndQuery q1(Query("fiery"), Query("bird"));
Query_base& b = q1;
try {
Query_base* pb = new AndQuery(Query("fiery"), Query("bird"));
const AndQuery& d = dynamic_cast<const AndQuery&>(b);
cout << "OK!" << endl;
}
catch (bad_cast)
{
cout << "error!" << endl;
}
}
19.8 编写一条typeid表达式检查两个Query_base对象是否指向同一种类型。再检查该类型是否为AndQuery
void p19_8()
{
Query_base* p = new AndQuery(Query("fiery"), Query("bird"));
AndQuery q1(Query("fiery"), Query("bird"));
Query_base& b = q1;
if (typeid(*p) == typeid(b))
{
cout << "the same type" << endl;
if (typeid(b) == typeid(AndQuery))
{
cout << "type is AndQuery" << endl;
}
else
{
cout << "Others" << endl;
}
}
else
{
cout << "different" << endl;
}
}
19.10 已知存在如下的继承体系,其中每个类定义了一个默认公有的构造函数和一个虚析构函数。下面的语句将打印哪些类型名字?
class A{};
class B : public A{};
class C : public B {};
void p19_10()
{
A* pa = new C;
cout << typeid(pa).name() << endl; //class A* , 因为参数为指针类型则返回的是该指针静态编译时类型
C cobj;
A& ra = cobj;
cout << typeid(&ra).name() << endl;//class A*, 传入的也是一个指针类型(地址)
B* px = new B;
A& ra = *px;//*px是b类对象
cout << typeid(ra).name() << endl; //class B,对于引用,允许时对表达式ra求值,ra实际绑定B类对象
}
19.11 普通数据指针与指向数据成员的指针有何区别?
普通数据指针指向一个对象,而数据成员指针指示的是类的成员
19.12 定义一个成员指针令其可以指向Screen类的cursor成员。通过该指针获得Screen::cursor的值
void test02()
{
//pdata是一个指向Screen类的size_type成员的 指针
const string::size_type Screen::* pdata;
pdata = &Screen::cursor; // 此时pdata为cursor成员指针
Screen myScreen;
string::size_type pos = myScreen.*pdata;
cout << pos << endl;
}
19.13 定义一个类型,使其可以表示指向Sales_data类的bookNo成员的指针
void p19_13()
{
const string Sales_data::*pdata;
pdata = &Sales_data::bookNo;
Sales_data item1;
string s = item1.*pdata;
}
19.14 下面的代码合法吗?如果合法代码的含义是什么?如果不合法,解释原因
auto pmf = &Screen::get_cursor;
pmf = &Screen::get()
合法
第一行定义pmf为指向Screen类的返回值为char,参数列表为空的函数,且与成员get_cursor绑定
第二行将pmf指向转为Screen类的get成员函数
19.15 普通函数指针和指向成员函数的指针有何区别
和普通函数指针不同的是,在成员函数和指向该成员的指针之间不存在自动转换规则。
19.16 声明一个类型别名,令其作为指向Sales_data的avg_price成员的指针的同义词
using AVG = double (Sales_data::*)() const;
Avg avgprice = &Sales_data::avg_price();
19.17 为Screen的所有成员函数类型各定义一个类型别名
//对于char get_cursor() const 和char get()const
using GET1 = char (Screen::*)()const;
//对于 char get(pos ht, pos wd) const
using GET2 = char (Screen::*)(Screen::pos, Screen::pos)const;
//对于Screen& move(Directions)
using MOVE = Screen& (Screen::*)(Directions);
19.18 编写一个函数,使用count_if统计在给定的vector中有多少个空string
void p19_18()
{
vector<string> svec = { "jj", "dsk", "", "ss", "lisk", ""};
//function<bool(const string&)> fcn = &string::empty;
cout << count_if(svec.begin(), svec.end(), mem_fn(&string::empty)) << endl;
}
19.19 编写一个函数,令其接受vector<Sales_data>并查找平均价格高于某个值的第一个元素
//使用lambda表达式
std::vector<Sales_data>::const_iterator SearchOne(const vector<Sales_data> &v, double d)
{
return find_if(v.begin(), v.end(), [d](const Sales_data &s){return s.avg_price() > d;}
}
19.21 编写你自己的Token类
#include<iostream>
#include<string>
#include"Sales_data.h"
using std::string;
using std::cout;
using std::endl;
using std::ostream;
class Token {
friend ostream& operator<<(ostream& os, const Token& t);
public:
//因为union含有一个string成员,所以Token必须定义拷贝控制成员
Token() :tok(INT), ival(0) {}
Token(const Token& t) :tok(t.tok) { copyUnion(t); }
Token& operator=(const Token&);
//如果union含有一个string成员,则我们必须销毁它
~Token()
{
if (tok == STR) sval.~basic_string();
}
//下面的赋值运算符负责设置union的不同成员
Token& operator=(const string&);
Token& operator=(char);
Token& operator=(int);
Token& operator=(double);
private:
enum {INT, CHAR, DBL, STR, BOK} tok;
union { //匿名union
char cval;
int ival;
double dval;
string sval;
Sales_data book;
};//每个Token对象含有一个该未命名union类型的未命名成员
//检查判别式,然后酌情拷贝union成员
void copyUnion(const Token&);
};
Token& Token::operator=(int i)
{
if (tok == STR) sval.~basic_string();
ival = i; //为成员赋值
tok = INT;//设定类型(判别式enum)
return *this;
}
Token& Token::operator=(char c)
{
if (tok == STR) sval.~basic_string();
cval = c;
tok = CHAR;
return *this;
}
Token& Token::operator=(double d)
{
if (tok == STR) sval.~basic_string();
dval = d;
tok = DBL;
return *this;
}
Token& Token::operator=(const string& s)
{
if (tok == STR)
sval = s; // 如果当前存储的是string,可以直接赋值
else
new(&sval) string(s);
tok = STR;
return *this;
}
//拷贝控制的联合成员
void Token::copyUnion(const Token& t)
{
switch (t.tok)
{
case Token::INT:ival = t.ival; break;
case Token::CHAR:cval = t.cval; break;
case Token::DBL:dval = t.dval; break;
//要想拷贝一个string可以使用定位new表达式在&sval指向的位置构造它
case Token::STR:new(&sval) string(t.sval); break;
case Token::BOK:new(&book) string(t.book); break;
}
}
Token& Token::operator=(const Token& t)
{
//如果此对象的值是string而t的值不是,则我们必须释放原来的string
if (tok == STR && t.tok != STR) sval.~basic_string();
if (tok == STR && t.tok == STR)
{
sval = t.sval;
}
else
{
copyUnion(t);
}
tok = t.tok;
return *this;
}
ostream& operator<<(ostream& os, const Token& t)
{
switch (t.tok)
{
case Token::INT:
cout << "INT" << t.ival ;
break;
case Token::CHAR:
cout << "CHAR" << t.cval ;
break;
case Token::DBL:
cout << "DBL" << t.dval ;
break;
case Token::STR:
cout << "STR" << t.sval ;
break;
}
return os;
}
void test01()
{
//这个类
Token t1, t2;
cout << t1 << endl;
t1 = 3;
cout << t1 << endl;
t1 = 'q';
cout << t1 << endl;
t1 = string("hello");
cout << t1 << endl;
t1 = t1;
cout << t1 << endl;
t2 = t1;
cout << t2 << endl;
}
19.22 为你的Token类添加一个Sales_data类型的成员。
class Token {
friend ostream& operator<<(ostream& os, const Token& t);
public:
//因为union含有一个string成员,所以Token必须定义拷贝控制成员
Token() :tok(INT), ival(0) {}
Token(const Token& t) :tok(t.tok) { copyUnion(t); }
Token& operator=(const Token&);
//如果union含有一个string成员,则我们必须销毁它
~Token()
{
if (tok == STR) sval.~basic_string();
if (tok == BOK) book.~Sales_data();
}
//下面的赋值运算符负责设置union的不同成员
Token& operator=(const string&);
Token& operator=(const Sales_data&);
Token& operator=(char);
Token& operator=(int);
Token& operator=(double);
private:
enum {INT, CHAR, DBL, STR, BOK} tok;
union { //匿名union
char cval;
int ival;
double dval;
string sval;
Sales_data book;
};//每个Token对象含有一个该未命名union类型的未命名成员
//检查判别式,然后酌情拷贝union成员
void copyUnion(const Token&);
};
Token& Token::operator=(int i)
{
if (tok == STR) sval.~basic_string();
if (tok == BOK) book.~Sales_data();
ival = i; //为成员赋值
tok = INT;//设定类型(判别式enum)
return *this;
}
Token& Token::operator=(char c)
{
if (tok == STR) sval.~basic_string();
if (tok == BOK) book.~Sales_data();
cval = c;
tok = CHAR;
return *this;
}
Token& Token::operator=(double d)
{
if (tok == STR) sval.~basic_string();
if (tok == BOK) book.~Sales_data();
dval = d;
tok = DBL;
return *this;
}
Token& Token::operator=(const string& s)
{
if (tok == BOK) book.~Sales_data();
if (tok == STR)
sval = s; // 如果当前存储的是string,可以直接赋值
else
new(&sval) string(s);
tok = STR;
return *this;
}
Token& Token::operator=(const Sales_data& b)
{
if (tok == STR) sval.~basic_string();
if (tok == BOK)
book = b;
else
new(&book) Sales_data(b);
tok = BOK;
return *this;
}
//拷贝控制的联合成员
void Token::copyUnion(const Token& t)
{
switch (t.tok)
{
case Token::INT:ival = t.ival; break;
case Token::CHAR:cval = t.cval; break;
case Token::DBL:dval = t.dval; break;
//要想拷贝一个string可以使用定位new表达式在&sval指向的位置构造它
case Token::STR:new(&sval) string(t.sval); break;
case Token::BOK:new(&book) Sales_data(t.book); break;
}
}
Token& Token::operator=(const Token& t)
{
//如果此对象的值是string而t的值不是,则我们必须释放原来的string
if (tok == STR && t.tok != STR) sval.~basic_string();
if (tok == BOK && t.tok != BOK) book.~Sales_data();
if (tok == STR && t.tok == STR)
{
sval = t.sval;
}
//接受赋值的对象和赋值内容都是Sales_data就直接值拷贝即可
else if (tok == BOK && t.tok == BOK)
{
book = t.book;
}
else
{
copyUnion(t);
}
tok = t.tok;
return *this;
}
ostream& operator<<(ostream& os, const Token& t)
{
switch (t.tok)
{
case Token::INT:
cout << "INT" << t.ival;
break;
case Token::CHAR:
cout << "CHAR" << t.cval;
break;
case Token::DBL:
cout << "DBL" << t.dval;
break;
case Token::STR:
cout << "STR" << t.sval;
break;
case Token::BOK:
cout << "BOK" << t.book;
}
return os;
}
void test01()
{
//这个类
Sales_data s1(string("0-201-22293-x"), 4, 4.2);
Token t1, t2;
cout << t1 << endl;
t1 = 3;
cout << t1 << endl;
t1 = 'q';
cout << t1 << endl;
t1 = string("hello");
cout << t1 << endl;
t1 = t1;
cout << t1 << endl;
t2 = t1;
cout << t2 << endl;
t1 = s1;
cout << t1 << endl;
t2 = t1;
cout << t2 << endl;
}
19.23 为你的Token类添加移动构造函数和移动赋值运算符
添加了 void moveUnion(Token&& t);
Token(Token&& t) noexcept
Token& operator=(Token&& t) noexcept
class Token {
friend ostream& operator<<(ostream& os, const Token& t);
public:
//因为union含有一个string成员,所以Token必须定义拷贝控制成员
Token() :tok(INT), ival(0) {}
Token(const Token& t) :tok(t.tok) { copyUnion(t); }
Token& operator=(const Token&);
//移动构造函数
Token(Token&& t) noexcept;
//移动赋值运算符
Token& operator=(Token&& t) noexcept;
//如果union含有一个string成员,则我们必须销毁它
~Token()
{
if (tok == STR) sval.~basic_string();
if (tok == BOK) book.~Sales_data();
}
//下面的赋值运算符负责设置union的不同成员
Token& operator=(const string&);
Token& operator=(const Sales_data&);
Token& operator=(char);
Token& operator=(int);
Token& operator=(double);
private:
enum {INT, CHAR, DBL, STR, BOK} tok;
union { //匿名union
char cval;
int ival;
double dval;
string sval;
Sales_data book;
};//每个Token对象含有一个该未命名union类型的未命名成员
//检查判别式,然后酌情拷贝union成员
void copyUnion(const Token&);
//供移动构造函数和移动赋值使用
void moveUnion(Token&&);
};
Token& Token::operator=(int i)
{
if (tok == STR) sval.~basic_string();
if (tok == BOK) book.~Sales_data();
ival = i; //为成员赋值
tok = INT;//设定类型(判别式enum)
return *this;
}
Token& Token::operator=(char c)
{
if (tok == STR) sval.~basic_string();
if (tok == BOK) book.~Sales_data();
cval = c;
tok = CHAR;
return *this;
}
Token& Token::operator=(double d)
{
if (tok == STR) sval.~basic_string();
if (tok == BOK) book.~Sales_data();
dval = d;
tok = DBL;
return *this;
}
Token& Token::operator=(const string& s)
{
if (tok == BOK) book.~Sales_data();
if (tok == STR)
sval = s; // 如果当前存储的是string,可以直接赋值
else
new(&sval) string(s);
tok = STR;
return *this;
}
Token& Token::operator=(const Sales_data& b)
{
if (tok == STR) sval.~basic_string();
if (tok == BOK)
book = b;
else
new(&book) Sales_data(b);
tok = BOK;
return *this;
}
//拷贝控制的联合成员
void Token::copyUnion(const Token& t)
{
switch (t.tok)
{
case Token::INT:ival = t.ival; break;
case Token::CHAR:cval = t.cval; break;
case Token::DBL:dval = t.dval; break;
//要想拷贝一个string可以使用定位new表达式在&sval指向的位置构造它
case Token::STR:new(&sval) string(t.sval); break;
case Token::BOK:new(&book) Sales_data(t.book); break;
}
}
Token& Token::operator=(const Token& t)
{
//如果此对象的值是string而t的值不是,则我们必须释放原来的string
if (tok == STR && t.tok != STR) sval.~basic_string();
if (tok == BOK && t.tok != BOK) book.~Sales_data();
if (tok == STR && t.tok == STR)
{
sval = t.sval;
}
//接受赋值的对象和赋值内容都是Sales_data就直接值拷贝即可
else if (tok == BOK && t.tok == BOK)
{
book = t.book;
}
else
{
copyUnion(t);
}
tok = t.tok;
return *this;
}
ostream& operator<<(ostream& os, const Token& t)
{
switch (t.tok)
{
case Token::INT:
cout << "INT" << t.ival;
break;
case Token::CHAR:
cout << "CHAR" << t.cval;
break;
case Token::DBL:
cout << "DBL" << t.dval;
break;
case Token::STR:
cout << "STR" << t.sval;
break;
case Token::BOK:
cout << "BOK" << t.book;
}
return os;
}
void Token::moveUnion(Token&& t)
{
switch (t.tok)
{
case INT:
ival = t.ival;
break;
case CHAR:
cval = t.cval;
break;
case DBL:
dval = t.dval;
break;
case STR:
new(&sval) string(std::move(t.sval)); //std::move返回右值引用(表明用后即可被析构(释放))
break;
case BOK:
new(&book) Sales_data(std::move(t.sval));
break;
}
}
Token::Token(Token&& t) noexcept
:tok(t.tok)
{
//对t调用move意味着,除了对t赋值或销毁它外,我们将不再使用它
moveUnion(std::move(t));
}
Token& Token::operator=(Token&& t) noexcept
{
if (&t == this)
return *this;
if (tok == STR && t.tok != STR)
sval.~basic_string();
if (tok != BOK && t.tok != BOK)
book.~Sales_data();
if (tok == STR && t.tok == STR)
sval = std::move(t.sval);
else if (tok == BOK && t.tok == BOK)
book = std::move(t.book);
else
moveUnion(std::move(t));
return *this;
}
19.24 如果我们将一个Token对象赋给它自己将发生什么情况?
调用拷贝赋值运算符,如果类中是string或者是Sales_data成员,则会调用对应类的拷贝赋值运算符,它们会确保成员不会被析构(如果自赋值就返回调用对象本身)。
如果是char、int等类型的union中的成员,则直接重复赋值为自己。
19.26 说明下列声明语句的含义并判断它们是否合法
extern "C" int compute(int*, int);
extern "C" double compute(double*, double);
含义:链接指示两个函数是C语言的函数,但是不合法,因为C语言不支持函数重载