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

《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


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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值