C++ Primer(第五版)|练习题答案与解析(第十九章:特殊工具与技术)

C++ Primer(第五版)|练习题答案与解析(第十九章:特殊工具与技术)

本博客主要记录C++ Primer(第五版)中的练习题答案与解析。
参考:C++ Primer
C++Primer
C++Primer

练习题19.1

使用malloc编写你自己的operator new(size_t)函数,使用free编写operator delete(void *)函数。

#include <iostream>
#include <cstdlib>
void *operator new(std::size_t n){
    std::cout << "new(size_t)"<<std::endl;
    if (void *mem = malloc(n))
        return mem;
    else
        throw std::bad_alloc();
}
void operator delete(void *mem) noexcept{
    std::cout << "delete(void*)<<endl";
    free(mem);
}
int main()
{
    using namespace std;
    int *a = new int(486);
    std::cout << a << " " << *a << std::endl;
    delete a;
    system("pause");
    return 0;
}

测试:

new(size_t)
0xb256f8 486
delete(void*)<<endl

练习题19.3

已知存在如下的继承体系,其中每个类别分别定义了一个公有默认构造函数和一个虚构函数:
class A { . . . };
class B : public A { . . . };
class C : public B { . . . };
class D : public B, public A { . . . };
下面的哪个dynamic_cast将失败?
(a ) A *pa = new C;
B *pb = dynamic_cast< B* >(pa);
(b ) B *pb = new B;
C *pc = dynamic_cast< C* >(pb);
(c ) A *pa = new D;
B *pb = dynamic_cast< B* >(pa);

(a )成功。“pa”的类型(类类型“C”)公共派生自目标类型“B”。
(b )失败。“pb”类型(类类型“B”)是目标类型“C”的公共基类。“pc”将等于nullptr。
(c )失败。A *pa = new D;A对于D具有二义性。将’D’指针转换为’ A '指针是不允许的。

练习题19.4

使用上一个练习定义的类改写下面代码,将表达式*pa转换成类型C&:
if (C pc = dynamic_cast< C >(pa))
// 使用C的成员
} else {
// 使用A的成员
}

#include <iostream>
#include <typeinfo>

using namespace std;

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() {}
};

int main(int argc, char const *argv[])
{
	/* 练习题 19.3 */

	A *pa = new C;
	B *pb = dynamic_cast< B* >(pa);
	if (pb) cout << "19.3 (a) succeed!" << endl;
	else cout << "19.3 (a) fail!" << endl;

	pb = new B;
	C *pc = dynamic_cast< C* >(pb);
	if (pc) cout << "19.3 (b) succeed!" << endl;
	else cout << "19.3 (b) fail!" << endl;

	//pa = new D;//直接报错:error: 'A' is an ambiguous base of 'D'
	/*pb = dynamic_cast< B* >(pa); */

	/* 练习题 19.4 */
	C c; B b;
	A &ra1 = c, &ra2 = b;
	try {
		/* succeed */
		C &rc1 = dynamic_cast<C&>(ra1);
		cout << "19.4 succeed!" << endl;
		/* fail */
		C &rc2 = dynamic_cast<C&>(ra2);
	} catch (bad_cast) {
		cout << "19.4 failed!" << endl;
	}
	return 0;
}

测试:

19.3 (a) succeed!
19.3 (b) fail!
19.4 succeed!
19.4 failed!

练习题19.5

在什么情况下你应该使用dynamic_cast替代函数?

并非任何时候都能有虚函数,假设有一个基类的指针指向其派生类,派生类中有一个成员基类中没有,这时候想通过这个基类的指针来调用这个成员就是不可以的,此时可以用dynamic_cast。

练习题19.6

编写一条表达式将Query_base指针动态转换为AndQuery指针(15.9.1,P564)。分别使用AndQuery的对象以及其他类型的对象测试转换是否有效。打印一条表示类型转换是否成功的信息,确保实际输出结果与期望一致。

    Query_base *pb1 = new AndQuery(Query("V1"), Query("V2"));  
    Query_base *pb2 = new OrQuery(Query("V1"), Query("V2"));  
    if (AndQuery *pa1 = dynamic_cast<AndQuery*>(pb1)) {  
        cout << "成功" << endl;  
    }  
    else {  
        cout << "失败" << endl;  
    }  
    if (AndQuery *pa2 = dynamic_cast<AndQuery*>(pb2)) {  
        cout << "成功" << endl;  
    }  
    else {  
        cout << "失败" << endl;  
    } 

练习题19.7

编写上一题类似的转换,这一次 将Query_base 对象转换为AndQuery的引用。重复上面的测试过程,确保转换能正常工作。

    try {  
        AndQuery &ra1 = dynamic_cast<AndQuery&>(*pb1);  
        cout << "成功" << endl;  
    }  
    catch (bad_cast e) {  
        cout << e.what() << endl;  
    }  
    try {  
        AndQuery &ra2 = dynamic_cast<AndQuery&>(*pb2);  
        cout << "成功" << endl;  
    }  
    catch (bad_cast e) {  
        cout << e.what() << endl;  
    }

练习题19.8

编写一条typeid表达式检测两个Query_base对象是否指向同一种类型。再检查该类型是否是AndQuery。

    if (typeid(*pb1) == typeid(*pb2))  
        cout << "pd1与pd2指向的对象类型相同" << endl;  
    else  
        cout << "pd1与pd2的动态类型不相同" << endl;  
    if (typeid(*pb1) == typeid(AndQuery))  
        cout << "pd1的动态类型是AndQuery" << endl;  
    else  
        cout << "pd1的动态类型并非是AndQuery" << endl;  
    if (typeid(*pb2) == typeid(AndQuery))  
        cout << "pd2的动态类型是AndQuery" << endl;  
    else  
        cout << "pd2的动态类型并非是AndQuery" << endl;

练习题19.9

编写与本节最后一个程序类似的代码,令其打印你的编译器为一些常见类型所起的名字。如果你得到的输出结果与本书类似,尝试编写一个函数将这些字符串翻译成人们更容易读懂的方式。

#include <iostream>
#include <vector>
#include <list>
#include <string>
#include <typeinfo>
using namespace std;
int main(int argc,char** argv)
{
	int arr[20];
	double i = 3.14;
	vector<int> vec1;
	int *p = arr;
	cout<<"typeid(arr).name():"<<typeid(arr).name()<<endl;
	cout<<"typeid(i).name():"<<typeid(i).name()<<endl;
	cout<<"typeid(vec1).name():"<<typeid(vec1).name()<<endl;
	cout<<"typeid(p).name():"<<typeid(p).name()<<endl;
	cin.get();
	return 0;
}

测试:

typeid(arr).name():A20_i
typeid(i).name():d
typeid(vec1).name():St6vectorIiSaIiEE
typeid(p).name():Pi

练习题19.10

已知存在如下的继承体系,其中每个类定义了一个默认公有的构造函数和一个虚析构函数。下面语句将打印哪些类型的名字?
class A { };
class B : public A { };
class C : public B { };

#include <iostream>
#include <vector>
#include <list>
#include <string>
#include <typeinfo>
class A { };
class B : public A { };
class C : public B { };
using namespace std;
void testA(){
    cout<<"A:"<<endl;
    A *pa = new C;
    cout<< typeid(pa).name()<< endl;
}
void testB(){
    cout<<"B:"<<endl;
    C cobj;
    A& ra = cobj;
    cout<< typeid(&ra).name()<< endl;
}
void testC(){
    cout<<"C:"<<endl;
    B *px = new B;
    A& ra = *px;
    cout<< typeid(ra).name()<< endl;                 
    
}
int main(int argc,char** argv)
{
    testA();
    testB();
    testC();
	return 0;
}

测试:

A:
P1A
B:
P1A
C:
1A

练习题19.11

普通数据指针与指向数据成员的指针有何区别?

P740.
成员指针是指可以指向类的非静态成员的指针,由于类的静态成员不属于任何对象,所以无需特殊的指向该成员的指针,成员指针的类型需要包括类的类型和成员的类型,例如:const string Screen:: *p,一个指向Screen类的const string成员的指针p。
对于普通指针变量来说,其值是它所指向的地址,0表示空指针。而对于数据成员指针变量来说,其值是数据成员所在地址相对于对象起始地址的偏移值,空指针用-1表示。

练习题19.12

定义一个成员指针,令其可以指向Screen类的cursor成员。通过该指针获得Screen::cursor的值。通过该指针获得Screen::cursor的值。

私有成员一般定义一个函数返回成员指针。参考P741。

static const std::string::size_type Screen::*data() { 
        return &Screen::cursor;  
    }  

练习题19.13

定义一个类型,使其可以表示指向Sales_data类的bookNo成员的指针。

P741,一般将该指针的函数定义为静态成员。

static const std::string Sales_data::* data()  
    {   	
        return &Sales_data::bookNo;  
    }                                                                                                                                                                                                                                                                                                                                                       

练习题19.14

下面的代码合法吗?如果合法,代码的含义是什么?如果不合法,解释原因。
auto pmf = &Screen::get_cursor;
pmf = &Screen::get;

合法,给pmf重新赋值。参考P741-742。

练习题19.15

普通函数指针何指向成员函数的指针游和区别?

P741-742,何普通函数指针不同的是,在成员函数和指向该成员的指针之间不存在自动转换的规则(必须使用&符号,显示的取地址)。

练习题19.16

声明一个类型别名,令其作为指向Sales_data的avg_price成员的指针的同义词。

P742-743,由于有时指向成员函数的指针较为复杂,我们可以使用类型别名来简化处理:using Action = char (Screen::*p) (Screen::pos,Screen::pos) const; Action的类型就是指向成员函数的指针。所以可以写为:using Avg = double (Sales_data::*)() const;

练习题19.17

为Screen的所有成员函数类型各定义一个类型别名。

char get() const { return contents[cursor]; }//using Action_c_v = char (Screen::*)()const;  
char get_cursor() const { return contents[cursor]; }//同上  
inline char get(pos ht, pos wd) const;//using Action_c_uu = char (Screen::*)(pos,pos)const;  
Screen &move(pos r, pos c);//using Action_Screen_uu = Screen &(Screen::*)(pos,pos); 

练习题19.18

编写一个函数,使用count_if统计在给定的vector中有多少个空string。

#include <iostream>
#include <algorithm>
#include <string>
#include <functional>
#include <vector>

int main(int argc, char *argv[])
{
	std::vector< std::string > v;
	std::string a;
	while (std::getline(std::cin, a)) {
		v.push_back(a);
	}
	auto m = std::count_if(v.begin(), v.end(), std::mem_fn(&std::string::empty));
	std::cout << " 使用 mem_fn,the 空行数:" << m << std::endl;
	auto n = std::count_if(v.begin(), v.end(), std::bind(&std::string::empty, std::placeholders::_1));
	std::cout << " 使用 bind,空行数:" << n << std::endl;
	auto q = std::count_if(v.begin(), v.end(), [](std::string &tem) {
		return tem.empty();
	});
	std::cout << " 使用 lamba,空行数:" << q << std::endl;
	return 0;
}

测试:

test
train
cnn

finish
^Z
 使用 mem_fn,the 空行数:1
 使用 bind,空行数:1
 使用 lamba,空行数:1

练习题19.19

编写一个函数,令其接受vector<Sales_data>并查找平均价格高于某个值的第一个元素。

核心部分:

std::vector<Sales_data>::const_iterator count(const std::vector<Sales_data> &vec, double d) {  
    auto fun = std::bind(&Sales_data::avg_price, std::placeholders::_1);  
    return find_if(vec.cbegin(), vec.cend(), [&](const Sales_data &s) { return d < fun(s); });  
} 

练习题19.20

将你的QueryResult类嵌套在TextQuery中,然后重新运行12.3.2(P435)中使用TextQuery的程序。

#include <iostream>
#include <vector>
#include <string>
#include <fstream>
#include <memory>
#include <map>
#include <set>
#include <sstream>
class TextQuery {
	public:
		class QueryResult;
		using line_no = std::vector<std::string>::size_type;
		TextQuery(std::ifstream&);
		TextQuery::QueryResult query(const std::string&) const;
	private:
		std::shared_ptr<std::vector<std::string> > file;
		std::map<std::string, std::shared_ptr<std::set<line_no> > > wm;	
};
class TextQuery::QueryResult{
	friend std::ostream& print(std::ostream&, const QueryResult&);
	public:
		QueryResult(std::string s, std::shared_ptr<std::set<line_no> > p,
				std::shared_ptr<std::vector<std::string> > f):sought(s),lines(p),file(f){};
	private:
		std::string sought; //query word
		std::shared_ptr<std::set<line_no> > lines; //lines the word show
		std::shared_ptr<std::vector<std::string> > file; //files show the word;
};
TextQuery::TextQuery(std::ifstream &is) : file(new std::vector<std::string> ){
	std::string text;
	while (getline(is, text)) {
		file->push_back(text);	
		int n = file->size() - 1;
		std::istringstream line(text);	
		std::string word;
		while (line >> word) {
			auto &lines = wm[word];	
			if (!lines) {
				lines.reset(new std::set<line_no>);	
			}
			lines->insert(n);
		}
	}
}
TextQuery::QueryResult
TextQuery::query(const std::string &sought) const{
	static std::shared_ptr<std::set<line_no> > nodata(new std::set<line_no>);
	auto loc = wm.find(sought);
	if (loc == wm.end()) {
		return TextQuery::QueryResult(sought, nodata, file);		
	}
	else {
		return TextQuery::QueryResult(sought,loc->second,file);
	}
}
std::ostream &print (std::ostream & os, const TextQuery::QueryResult & qr)
{
	os << qr.sought << " occurls " << qr.lines->size() << " time(s)" << std::endl; 
	for (auto i : *qr.lines) {
		os << "\t(line " << i+1 << ") " << *(qr.file->begin() + i) << std::endl;	
	}
	return os;
}
int main(int argc, char *argv[])
{
	std::ifstream file;
	file.open("ex19_18.cpp");
	TextQuery si(file);
	auto res = si.query("std");
	print(std::cout, res);
	return 0;
}

练习题19.21

编写你自己的Token类。

练习题19.22

为你的Token类添加一个Sales_data类型的成员。

练习题19.23

为你的Token类添加移动构造函数何移动赋值运算符。

练习题19.24

如果我们将一个Token对象赋给它自己将发生声明情况?

练习题19.25

编写一系列赋值运算符,令其分别接受union中各种类型的值。

#include <iostream>
#include <string>
using std::string;
class Sales_data {
public:
	Sales_data() = default;
	~Sales_data() = default;
	Sales_data(int i) :a(i) {}
	Sales_data(const Sales_data &rhs) :a(rhs.a) {}
	Sales_data& operator=(const Sales_data&rhs) {
		a = rhs.a;
		return *this;
	}
private:
	int a = 0;
};

class Token {
public:
	Token() :tok(INT), ival(0) {}
	Token(const Token&t) : tok(t.tok) { copyUnion(t); }
	~Token() {
		if (tok == STR) sval.~string();
		if (tok == SAL) item.~Sales_data();
	}
	Token& operator=(Token &&);
	Token(Token&&);
	Token& operator=(const Token&);
	Token& operator=(const string&);
	Token& operator=(const int&);
	Token& operator=(const char&);
	Token& operator=(const Sales_data&);
private:
	enum { INT, CHAR, STR, SAL } tok;
	union {
		char cval;
		int ival;
		std::string sval;
		Sales_data item;
	};
	void copyUnion(const Token&);
	//move edition
	void copyUnion(Token&&);
};
void Token::copyUnion(Token&& t) {
	switch (t.tok) {
	case INT: ival = t.ival; break;
	case CHAR: cval = t.cval; break;
	case STR: std::move(t.sval); break;
	case SAL: std::move(t.item); break;
	}
}
void Token::copyUnion(const Token &t) {
	switch (t.tok) {
	case INT: ival = t.ival; break;
	case CHAR: cval = t.cval; break;
	case STR: new(&sval) string(t.sval); break;
	case SAL: new(&item) Sales_data(t.item); break;
	}
}
Token& Token::operator=(const Token&t) {
	if (tok == STR && t.tok != STR) sval.~string();
	if (tok == SAL && t.tok != SAL) item.~Sales_data();
	if (tok == STR && t.tok == STR) sval = t.sval;
	if (tok == SAL && t.tok == SAL) item = t.item;
	else copyUnion(t);
	tok = t.tok;
	return *this;
}
//move constructor
Token& Token::operator=(Token&& t) {
	if (this != &t) {
		this->~Token();
		copyUnion(t);
		tok = std::move(t.tok);
	}
	return *this;
}
Token::Token(Token &&t) {
	copyUnion(t);
	tok = std::move(t.tok);
}
Token& Token::operator=(const Sales_data& rhs) {
	if (tok == STR) sval.~string();
	if (tok == SAL)
		item = rhs;
	else
		new(&item) Sales_data(rhs);
	tok = SAL;
	return *this;
}
Token& Token::operator=(const int& i) {
	if (tok == STR) sval.~string();
	if (tok == SAL) item.~Sales_data();
	ival = i;
	tok = INT;
	return *this;
}
int main(int argc, char const *argv[]) {
	Token s;
	Sales_data sal(5);
	s = sal;
	int i = 5;
	std::cout << i << std::endl;
	return 0;
}

赋给自己时,每个成员都会调用自身类所拥有的赋值构造函数。

练习题19.26

说明下列什么语句的含义并判断它们是否合法:
extern “C” int compute(int *, int);
extern “C” double compute(double *, double);

P761,C语言不支持重载,因此C链接提示只能说明重载函数中的一个,所以不合法。


(分割线)
2020.3.11
首先感谢参考的各种资料的作者,给予了很大的便利。
这本巨厚的书大致过了一遍,基本上有了大致的印象,时间原因,还有很多细节没有完全掌握。
剩下的细节希望在以后的学习中,查漏补缺。

发布了82 篇原创文章 · 获赞 62 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/qq_24739717/article/details/104777372