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