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