《C++程序设计语言》 习题13.9[8]
这是一道关于C++模板机制的不错的练习,是对模板实例化、模板专门化的漂亮展示。特别地,它考察了对关联容器Map类的理解(尽管这里的Map类只是基于Vector类的包装)。
同其他很多习题一样,这道题在《习题解答》里面也没有给出解答。。或许根本就没有所谓的“标准答案”,这也是国外的教育方式与国内的差异所在。
先来看一下11.8节的Assoc类
//Assoc.h
#ifndef ASSOC_H
#define ASSOC_H
#include <iostream>
#include <vector>
#include <string>
class Assoc{
struct Pair{
std::string name;
double val;
Pair(std::string n="",double v=0):name(n),val(v){}
};
std::vector<Pair> vec;
Assoc(const Assoc&);
Assoc& operator=(const Assoc&);
public:
Assoc(){}
const double& operator[](const std::string&);
double& operator[](std::string&);
void print_all() const;
};
double& Assoc::operator[](std::string& s)
{
for(std::vector<Pair>::iterator p=vec.begin();p!=vec.end();++p)
if(s==p->name) return p->val;
vec.push_back(Pair(s,0));
return vec.back().val;
}
const double& Assoc::operator[](const std::string& s)
{
for(std::vector<Pair>::const_iterator p=vec.begin();p!=vec.end();++p)
if(s==p->name) return p->val;
vec.push_back(Pair(s,0));
return vec.back().val;
}
void Assoc::print_all() const
{
for(std::vector<Pair>::const_iterator p
=vec.begin();p!=vec.end();++p)
std::cout<<p->name<<":"<<p->val<<std::endl;
}
#endif
这其实只是一个包装后的Vector向量类。只不过定义了关联容器Map的一些界面,如operator[]下标操作。需要注意的是,这里的下标操作非常低效:
double& Assoc::operator[](std::string& s)
{
for(std::vector<Pair>::iterator p=vec.begin();p!=vec.end();++p)
if(s==p->name) return p->val;
vec.push_back(Pair(s,0));
return vec.back().val;
}
需要首先遍历Vector查找这个键是否存在。
其次就是,这个Map类是非常局限的,键类型只能是string,关联的值类型也只能是double。
在第十三章的练习中,基于这个Assoc类(基本的数据结构没有变,即类的表示没有变),我实现了一个模板类Map。
这个Map练习中有几个比较重要的设计难点,这里我拿出来交流一下:
1.首先是题目中说,“保证这个Map类对于将C风格字符串或string作为关键码时都能工作”。这句话就表明,需要定义模板类Map对于以C风格字符串作为键类型的专门化。因为string类是定义了比较操作符的,因此在插入元素时可以直接对string的键进行比较。但如果键类型是C风格字符串const char*的话,对const char*进行比较的结果是比较两个C风格字符串的内存地址,而不是比较实际字符串。这样比较的结果是两个字符串永远都不会相等。因此,应该在Map类对于C风格字符串的专门化中,用cstring库中的strcmp函数对两个C风格字符串进行比较,而不是用默认的比较运算符==。
2.“保证这个Map类对于没有默认构造函数的类型正确工作”。这句话一开始让我有点困惑。我查阅了一下《C++ Primer》,上面说,”在对Map使用下标操作时,新创建的元素的值默认使用值初始化“。
也就是说,如果
Map<string,int> m;
m["C++];
这里,使用下标操作插入了一个键为”C++“的元素。这个元素所关联的int值会默认初始化为0。
但如果Map关联的值类型是一个没有默认构造函数的类类型,那么值初始化就会失败。我试了试,是无法通过编译的:
显示标准库stl_map.h的第339行的insert(_i,value_type(_k,mapped_type()));出错,因为没有mapped_type()这个默认构造函数。
因此,如果要将map用于没有默认构造函数的类类型,那么唯一的办法就是定义一个insert函数。此时使用insert函数插入一个包含了键和值的Pair类型(在这道习题的解答中我自己定义了一个模板类型Pair)。
3.”为在Map类型上进行迭代提供一种方式“。这里我就是简单地按照标准库的”指针惯用法“思路,为我的Map类型定义了一个迭代器类型iterator(实际上就是Vector的iterator),又定义了一个begin()和end()函数,返回Map的首元素和尾元素。这样,就可以按照标准库的迭代器方式,对我的Map类进行遍历了。
下面是源代码:
//13.9[8](*2.5)
#include <iostream>
#include <vector>
#include <string>
#include <cstring>
template<class T1,class T2> struct Pair{
T1 first;
T2 second;
Pair(T1 f=T1(),T2 s=T2()):first(f),second(s){}
};
template<class K,class V> class Map{
public:
typedef Pair<const K,V> value_type;
typedef typename std::vector<value_type>::iterator iterator;
Map(){}
V& operator[](const K&);
Pair<iterator,bool> insert(const value_type&);
iterator begin(){return vec.begin();}
iterator end(){return vec.end();}
private:
std::vector<value_type> vec;
};
template<class K,class V> V& Map<K,V>::operator[](const K& k)
{
for(iterator p=vec.begin();p!=vec.end();++p)
if(p->first==k) return p->second;
vec.push_back(Pair<const K,V>(k,V()));
return vec.back().second;
}
template<class K,class V>
Pair<typename Map<K,V>::iterator,bool> Map<K,V>::insert(const value_type& val)
{
for(iterator p=vec.begin();p!=vec.end();++p)
if(p->first==val.first) return Pair<iterator,bool>(p,false);
vec.push_back(val);
return Pair<iterator,bool>(&vec.back(),true);
}
template<class V> class Map<const char*,V>{
public:
typedef Pair<const char*,V> value_type;
typedef typename std::vector<value_type>::iterator iterator;
Map(){}
V& operator[](const char*);
Pair<iterator,bool> insert(const value_type&);
iterator begin(){return vec.begin();}
iterator end(){return vec.end();}
private:
std::vector<value_type> vec;
};
template<class V>
V& Map<const char*,V>::operator[](const char* s)
{
for(iterator p=vec.begin();p!=vec.end();++p)
if(strcmp(p->first,s)==0) return p->second;
vec.push_back(Pair<const char*,V>(s,V()));
return vec.back().second;
}
template<class V>
Pair<typename Map<const char*,V>::iterator,bool> Map<const char*,V>::insert(const value_type& val)
{
for(iterator p=vec.begin();p!=vec.end();++p)
if(strcmp(p->first,val.first)==0) return Pair<iterator,bool>(p,false);
vec.push_back(val);
return Pair<iterator,bool>(--vec.end(),true);
}
主程序就是简单地测试这个Map类:
int main()
{
Map<std::string,int> smap;
smap["hello"]=2;
std::cout<<smap.begin()->first<<"\t"
<<smap.begin()->second<<std::endl;
Map<const char*,int> csmap;
Map<const char*,int>::iterator iter=
csmap.insert(Pair<const char*,int>("world",3)).first;
std::cout<<iter->first<<"\t"
<<iter->second<<std::endl;
std::cout<<std::endl;
return 0;
}
输出:
hello 2
world 3
最后祝大家过一个有意义的暑假!