《C++ Primer》第16章 模板与泛型编程
16.5节模板特例化 习题答案
练习16.62:定义你自己版本的hash<Sales_data>,并定义一个Sales_data对象的unordered_multiset。将多条交易记录保存到容器中,并打印其内容。
【出题思路】
本题练习类模板特例化。
【解答】
#ifndef PROGRAM16_62_SALES_DATA_H #define PROGRAM16_62_SALES_DATA_H #include <string> #include <iostream> // unchanged from chapter 14 except for added friend declaration for hash class Sales_data { friend class std::hash<Sales_data>; friend std::ostream &operator<< (std::ostream&, const Sales_data&); friend std::istream &operator>>(std::istream&, Sales_data&); friend bool operator==(const Sales_data &, const Sales_data &); friend std::ostream &print(std::ostream&, const Sales_data&); friend std::istream &read(std::istream&, Sales_data&); public: // constructors Sales_data() = default; Sales_data(const std::string &s): bookNo(s) { } Sales_data(const std::string &s, unsigned n, double p): bookNo(s), units_sold(n), revenue(p*n) { } Sales_data(std::istream &); std::string isbn() const { return bookNo; } Sales_data& operator+=(const Sales_data&); private: double avg_price() const; std::string bookNo; unsigned units_sold = 0; double revenue = 0.0; }; namespace std { template <> // we're defining a specialization with struct hash<Sales_data> // the template parameter of Sales_data { // the type used to hash an unordered container must define these types typedef size_t result_type; typedef Sales_data argument_type; // by default, this type needs == size_t operator()(const Sales_data& s) const; // our class uses synthesized copy control and default constructor // other members as before }; } // close the std namespace; note: no semicolon after the close curly // non-member Sales_data operations inline bool compareIsbn(const Sales_data &lhs, const Sales_data &rhs) { return lhs.isbn() < rhs.isbn(); } inline bool operator==(const Sales_data &lhs, const Sales_data &rhs) { return lhs.isbn() == rhs.isbn() && lhs.units_sold == rhs.units_sold && lhs.revenue == rhs.revenue; } inline bool operator!=(const Sales_data &lhs, const Sales_data &rhs) { return !(lhs == rhs); } Sales_data add(const Sales_data&, const Sales_data&); std::ostream &print(std::ostream&, const Sales_data&); std::istream &read(std::istream&, Sales_data&); Sales_data operator+(const Sales_data&, const Sales_data&); std::ostream &operator<<(std::ostream&, const Sales_data&); std::istream &operator>>(std::istream&, Sales_data&); #endif // PROGRAM16_62_SALES_DATA_H
#include "program16_62_sales_data.h" #include <string> using std::istream; using std::ostream; // define the hash interface for Sales_data namespace std { size_t hash<Sales_data>::operator()(const Sales_data& s) const { return hash<string>()(s.bookNo) ^ hash<unsigned>()(s.units_sold) ^ hash<double>()(s.revenue); } } // close the std namespace; note: no semicolon after the close curly // remaining members unchanged from chapter 14 Sales_data::Sales_data(istream &is) { is >> *this; // read a transaction from is into this object } double Sales_data::avg_price() const { if (units_sold) return revenue/units_sold; else return 0; } // member binary operator: left-hand operand is bound to the implicit this pointer // assumes that both objects refer to the same book Sales_data& Sales_data::operator+=(const Sales_data &rhs) { units_sold += rhs.units_sold; revenue += rhs.revenue; return *this; } // assumes that both objects refer to the same book Sales_data operator+(const Sales_data &lhs, const Sales_data &rhs) { Sales_data sum = lhs; // copy data members from lhs into sum sum += rhs; // add rhs into sum return sum; } istream &operator>>(istream &is, Sales_data &item) { double price; // no need to initialize; we'll read into price before we use it is >> item.bookNo >> item.units_sold >> price; if (is) // check that the inputs succeeded item.revenue = item.units_sold * price; else item = Sales_data(); // input failed: give the object the default state return is; } ostream &operator<<(ostream &os, const Sales_data &item) { os << item.isbn() << " " << item.units_sold << " " << item.revenue << " " << item.avg_price(); return os; } // operators replace these original named functions istream &read(istream &is, Sales_data &item) { double price = 0; is >> item.bookNo >> item.units_sold >> price; item.revenue = price * item.units_sold; return is; } ostream &print(ostream &os, const Sales_data &item) { os << item.isbn() << " " << item.units_sold << " " << item.revenue << " " << item.avg_price(); return os; } Sales_data add(const Sales_data &lhs, const Sales_data &rhs) { Sales_data sum = lhs; // copy data members from lhs into sum sum += rhs; // add rhs into sum return sum; }
#include <cstddef> using std::size_t; #include <string> using std::string; #include <iostream> using std::cin; using std::cout; using std::endl; #include <unordered_set> using std::unordered_multiset; #include <functional> #include "program16_62_sales_data.h" using std::hash; int main() { //使用hash<Sales_data>和Sales_data的==运算符 unordered_multiset<Sales_data> SDset; Sales_data item; while(cin >> item){ SDset.insert(item); } cout << "SDset.size = " << SDset.size() << endl; for(auto sd: SDset) cout << sd << endl; return 0; }
运行结果:
练习16.63:定义一个函数模板,统计一个给定值在一个vector中出现的次数。测试你的函数,分别传递给它一个double的vector,一个int的vector以及一个string的vector。
【出题思路】
本题练习定义模板函数。
【解答】
#include <iostream> #include <vector> #include <cstring> using namespace std; template <typename T> int occur(vector<T> &vec, const T &v) { int ret = 0; for(auto a:vec) { if(a == v) ret++; } return ret; } template <> int occur(vector<char *> &vec, char *const &v) { int ret = 0; for(auto a:vec) { if(!strcmp(a, v)) ret++; } return ret; } int main() { vector<double> vd = {1.1, 3.14, 2.2, 3.14, 3.3, 4.4}; cout << "occur(vd, 3.14)=" << occur(vd, 3.14) << endl; vector<int> vi = {0, 1, 2, 3, 4, 5}; cout << "occur(vi, 0)=" << occur(vi, 0) << endl; vector<string> vs = {"Hello", "World", "!"}; cout << "occur(vs, string(end))=" << occur(vs, string("end")) << endl; vector<char *> vp; vp.push_back(new char[6]); vp.push_back(new char[6]); vp.push_back(new char[2]); strcpy(vp[0], "Hello"); strcpy(vp[1], "World"); strcpy(vp[2], "!"); char *w = new char[6]; strcpy(w, "World"); cout << "occur(vp, w)=" << occur(vp, w) << endl; delete []w; delete vp[2]; delete vp[1]; delete vp[0]; return 0; }
运行结果:
练习16.64:为上一题中的模板编写特例化版本来处理vector<const char*>。编写程序使用这个特例化版本。
【出题思路】
本题练习定义特例化版本。
【解答】
见上一题。注意,当注释掉特例化版本时,最后一个occur调用会匹配通用版本,用==比较char *,从而无法找到相等的C风格字符串。
练习16.65:在16.3节(第617页)中我们定义了两个重载的debug_rep版本,一个接受const char*参数,另一个接受char*参数。将这两个函数重写为特例化版本。
【出题思路】
本题练习定义特例化版本。
【解答】
两个特例化版本如下,完整程序见练习16.48。
template<> std::string debug_rep(char *p) { return debug_rep(std::string(p)); } template <> std::string debug_rep(const char *cp) { return debug_rep(std::string(cp)); }
练习16.66:重载debug_rep函数与特例化它相比,有何优点和缺点?
【出题思路】
理解特例化与重载的区别。
【解答】
重载是会影响函数匹配的,也就是说,编译器在函数匹配过程中会将新的重载版本作为候选之一来选择最佳匹配。这就需要小心设计,避免实际匹配不如我们所愿。特例化则不影响函数匹配,它并不是为编译器进行函数匹配提供一个新的选择,而是为模板的一个特殊实例提供不同于原模板的特殊定义,本质上是接管了编译器在完成函数匹配后的部分实例化工作。即,当某个模板是最佳匹配时,且需要实例化为这个特殊实例时,不再从原模板进行实例化,而是直接使用这个特例化版本。因此,特例化更为简单——当某个模板不符合我们的需求时,只需设计满足需求的特例化版本即可。
练习16.67:定义特例化版本会影响debug_rep的函数匹配吗?如果不影响,为什么?
【出题思路】
理解特例化。
【解答】
如上题。