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
首先感謝參考的各種資料的作者,給予了很大的便利。
這本巨厚的書大致過了一遍,基本上有了大致的印象,時間原因,還有很多細節沒有完全掌握。
剩下的細節希望在以後的學習中,查漏補缺。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章