cppPrimer第十九章

本文详细讲解了C++中的类继承体系,涉及虚析构函数的使用,动态类型转换(如`dynamic_cast`)的应用,以及如何在不同上下文中正确地进行类型转换。通过实例演示了类型转换的成功与失败条件,并探讨了虚函数和动态转换在特定场景下的运用。
摘要由CSDN通过智能技术生成
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语言不支持函数重载

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值