基于Vector实现的Map类 《C++程序设计语言》第13章

来源:互联网 发布:埃里克戈登数据 编辑:程序博客网 时间:2024/05/22 04:59

《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


最后祝大家过一个有意义的暑假!

原创粉丝点击