C++ PRIMER 学习笔记 第十章

Posted on 2022-08-10 10:38  金色的省略号  阅读(24)  评论(0编辑  收藏  举报

第十章 关联容器 

    关联容器和顺序容器的本质差别在于:关联容器 通过键(key) 存储和读取元素,而顺序容器则通过 元素在容器中的位置顺序 存储和访问元素
  关联容器支持通过键来高效地查找和读取元素,两个基本的关联容器类型是 map 和 set,map的元素以键-值对的形式组织,set仅包含一个键
  map和set类型的对象,所包含的元素 都具有不同的键,不允许为同一个键添加第二个元素,如果一个键必须对应多个实例,则需使用multimap或multiset类型 

  10.1 引言:pair类型

  pair类型是标准库类型,该类型在utility头文件中定义,pair是一种模板类型,pair包含两个公有数据成员,各自对应类型名字,这两个类型不必相同,创建pair对象时不提供 初始化式,则调用 默认构造函数 对其成员采取值初始化,除了构造函数,标准库还定义了一个make_pair函数,由传递给它的两个实参生成一个新的pair对象

  10.2 关联容器

  关联容器 共享大部分顺序容器的操作,但 不提供front、 push_front、 pop_front、 back、 push_back、 pop_back操作关联容器 与 顺序容器 公共的操作 :三种构造函数,关系运算,begin、end、rbegin和rend操作,类型别名typedef (对于map容器,value_type并非元素的类型(Note that for map, the value_type is not the same as the element type),而是描述键及其关联值类型的pair类型),swap和赋值操作,clear和erease操作,容器大小的操作(resize函数不能用于关联容器

  关联容器元素根据键的次序排列,在迭代遍历关联容器时,按键的顺序访问元素,与元素在容器中的存放位置完全无关

  10.3 map类型

  map是键值对的集合,map类型通常可理解为关联数组:可使用键作为下标来获取一个值,正如内置数组类型一样,而关联的本质在于元素的值与某个特定的键相关联,而并非通过元素在数组中的位置来获取

    10.3.1 map对象的定义

  在定义map对象时,必须分别指明键和值得类型,键类型的约束:在使用关联容器时,它的键不但有一个类型,而且还有一个相关的比较函数,默认情况下,标准库使用键类型定义的 < 操作符来实现键的比较,所用的比较函数必须在键类型上定义严格弱排序,可理解为键类型数据上的小于关系

    10.3.2 map定义的类型 

  map定义的类型:map<K,V >:: key_type(在map容器中,用做索引的键的类型)、map<K,V>::mapped_type(键所关联的类型)、map<K,V>::value_type(一个pair类型,它的first元素具有const map<K,V>::key_type类型,second元素则为map<K,V>::mapped_type类型

  对map迭代器进行解引用,将获得一个引用,指向容器中一个value_type类型的值,对于map容器,其value_type是pair类型

#include <iostream>
#include <string>
#include <map>
using namespace std;

int main()
{
    map<string,int> m;
    
    m.insert(make_pair("Hello",5) );
    map<string,int>::const_iterator ci = m.begin();
    map<string,int>::value_type vt = *ci; //迭代器解引用,获得一个引用,指向map容器中一个value_type类型的值,value_type是pair类型

    map<string,int>::key_type kt    = vt.first;// key_type是map容器中用做索引的键的类型
    map<string,int>::mapped_type mt = vt.second;//mapped_type是map容器中,键所关联的值得类型
    
    cout << kt << "," << mt << endl;
    
    return 0;
}
View Code
    10.3.3 给map添加元素

  在map容器中添加键值元素对,可使用insert成员,或者,先用下标操作符获取元素,然后给获取的元素赋值 

    10.3.4 使用下标访问map对象

  用下标访问不存在的元素将导致在map容器中添加一个新的元素,它的键即为该下标值(map的下标也使用索引,如果该键已在容器中,返回该键所关联的值),下标操作符返回左值,下标操作符返回一个mapped_type类型的值

    10.3.5 map::insert的使用

  m.insert(e ) //e是一个用在m上的value_type类型的值,该函数返回一个pair类型对象包含指向键的e.first的元素的map迭代器以及一个bool类型的对象,表示是否插入了该元素,如果该键已在容器中,则其关联的值保持不变,返回的bool值为false 

    10.3.6 查找并读取map中的元素

  使用下标操作符 读取一个值,如果该键不在map容器中,那么下标操作符会插入一个具有该键的新元素,map容器提供了两个操作:count 和 find,用于检查某个键是否存在而不会插入该键,如果希望元素存在就使用它,应该用find操作,find操作返回指向元素的迭代器,如果元素不存在,返回end迭代器

    10.3.7 从map对象中删除元素
    10.3.8 map对象的迭代遍历
    10.3.9 “单词转换”map对象

  10.4 set类型

  当只想知道一个值是否存在时,使用set容器是最合适的,set容器支持 大部分的map操作:所有通用的容器操作、构造函数、insert操作、count、find操作、erase操作,set不支持下标操作,而且没有定义mapped_type类型,在set容器中,value_type指的是set中存储的元素类型,set容器存储的键是唯一的且不能修改

    10.4.1 set容器的定义和使用

  insert操作在set中添加元素:带一个键参数的insert版本 返回pair类型对象,包含一个迭代器和一个bool值,迭代器指向拥有该键的元素,而bool值表明是否添加了元素,使用迭代器对的insert版本 返回void类型,使用 find运算 从set中获取元素,返回迭代器,使用 count运算,返回set中该键对象的元素个数,只能是1或0,set中的键为const,只能做读操作,不能做写操作

    10.4.2 创建“单词排除”集

  函数读取传递进来的文件,该文件列出了所有被排除的单词,读入这些单词并 存储在一个名为excluded的set容器中输入单词,检查该单词是否出现在排除集中如果没有出现过,则将它插入到map容器对象word_count中,并在插入元素后将它关联的值初始化为0,然后不管是否插入了新元素,相应元素的值都加1

void restricted_wc(ifstream &remove_file, map<string, int> &word_count )
{
    set<string> excluded;
    string remove_word;
    while(remove_file >> remove_word ){
        excluded.insert(remove_word );
    }
    string word;
    while(cin >> word ){
        if(!excluded.count(word) )
            ++word_count[word];
    }
}
View Code

  10.5 nultmap和multset类型

    10.5.1 元素的添加和删除
    10.5.2 在multmap和multset中查找元素

  10.6 容器的综合应用:文本查询程序

  程序将读取用户指定的任意文本文件,然后允许用户从该文件中查找单词,查询的结果是该单词出现的次数,并列出每次出现所在的行,如果某单词在同一行中多次出现,程序将只显示该行一次,行号按升序显示

   设计程序的一个良好的习惯是首先将程序所涉及的操作列出来,明确需要提供的操作有助于建立需要的数据结构和实现这些行为

  操作:接口成员函数:read_file,形参为一个ifstream&类型对象,run_query,形参为一个string类型对象,返回一个set对象,text_line,形参为一个行号,返回输入文本中该行号对应的文本行;private函数(为实现read_file功能):store_file函数读入文件,并将文件内容存储在vector容器对象中,build_map,将每一行分解为各个单词,创建map容器对象,同时记录每个单词出现的行号

  数据结构:存储输入文件的vector对象,一个map容器对象,该对象关联每个输入的单词以及记录该单词所在行号的set容器对象

#include <iostream>
#include <fstream>  //ifstream 
#include <sstream>  //istringstream
#include <vector>
#include <set>
#include <map>
#include <string>

class TextQuery{
public:
    typedef std::vector<std::string>::size_type line_no;
    void read_file(std::ifstream &is){  //std::ifstream &
        store_file(is);
        build_map();
    }
    std::set<line_no> run_query(const std::string & )const;
    std::string text_line(line_no )const;
private:
    void store_file(std::ifstream & );  //std::ifstream &
    void build_map();
    std::vector<std::string> lines_of_text;
    std::map< std::string, std::set<line_no> > word_map;
};

void TextQuery::store_file(std::ifstream &is )
{
    std::string textline;
    while(getline(is,textline) ){
        lines_of_text.push_back(textline );
    }
}

void TextQuery::build_map()
{
    for( line_no line_num = 0; 
         line_num != lines_of_text.size();
         ++line_num )
    {
        std::istringstream line(lines_of_text[line_num] );//从std::vector读到 std::istringstream
        std::string word;
        while(line >> word ){
            word_map[word].insert(line_num );
        }
    }    
}

std::set<TextQuery::line_no> TextQuery::run_query(const std::string &query_word )const
{
    std::map<std::string,std::set<line_no> > ::const_iterator loc = word_map.find(query_word ); //std::map里查找行std::set
    if(loc == word_map.end() )
        return std::set<line_no>();  //没找到返回set空对象
    else
        return loc->second;
}

std::string TextQuery::text_line(line_no line ) const
{
    if(line <lines_of_text.size() )
        return lines_of_text[line ];
    throw std::out_of_range("line number out of range");
}

std::ifstream& open_file(std::ifstream &in, const std::string &file ); //open_file
void print_results(const std::set<TextQuery::line_no > &locs, 
                   const std::string &sought, 
                   const TextQuery &file  );            //print_results
std::string make_plural(std::size_t ctr, 
                            const std::string &word,
                            const std::string &ending );//make_plural //单词复数形式

int main(int argc, char**argv )
{
    std::ifstream infile;
    if(argc<2 ||!open_file(infile,argv[1] ) ) {
        std::cerr << "No input file!" << std::endl;
        return EXIT_FAILURE;
    }
    TextQuery tq;
    tq.read_file(infile );
    while(true){
        std::cout << "enter word to look for, or q to quit: ";
        std::string s;
        std::cin >> s;
        if(!std::cin || s == "q" ) break;  //退出循环条件
        std::set<TextQuery::line_no > locs = tq.run_query(s );
        print_results(locs, s, tq );
    }
        
    return 0;
}

std::ifstream& open_file(std::ifstream &in, const std::string &file )
{
    in.close(); //如果已经打开
    in.clear();
    in.open(file.c_str() ); //打开文件
    return in;
}

void print_results(const std::set<TextQuery::line_no > &locs, 
                   const std::string &sought, 
                   const TextQuery &file  )
{
    typedef std::set<TextQuery::line_no > line_nums;
    line_nums::size_type size = locs.size();
    std::cout << "\n" << sought << " occurs "
            << size << " "
            << make_plural(size, "time", "s" ) << std::endl;
    line_nums::const_iterator it = locs.begin();
    for( ; it != locs.end(); ++it ){
        std::cout << "\t(line "
            << (*it) + 1 << ")"
            << file.text_line(*it ) << std::endl;
    }
}

std::string make_plural(std::size_t ctr, 
                            const std::string &word,
                            const std::string &ending )    //make_plural //单词复数形式
{
    return (ctr == 1 ) ? word : word + ending;
}

/* 
Alice Emma has long flowing red hair. 
Her Daddy says when the wind blows 
through her hair, it looks almost alive, 
like a fiery bird in flight. 
A beautiful fiery bird, he tells her, 
magical but untamed. 
"Daddy, shush, there is no such thing," 
she tells him, at the same time wanting 
him to tell her more.
Shyly, she asks, "I mean, Daddy, is there?"    
 */
View Code

  map 与 set 组合使用

#include <iostream>
#include <string>
#include <map>
#include <set>
using namespace std;

/* 单词"Hello" 所在的文本的行号 */

int main()
{
    map<string,set<int> > m;
    
    //1.
    pair< map<string,set<int> >::iterator, bool > p_mb = m.insert( make_pair( "Hello", set<int>() ) );  //const_iterator不可以
                                                         //插入键为 "Hello" 空的set<int>对象,insert() 返回pair
    cout << p_mb.first->first;      //map插入返回类型为pair,first为map类型,second为bool类型
    ((p_mb.first )->second ).insert(120 );    //map的pair类型的second,是set<int>类型,键的值是set<int>对象
                            // 向键"Hello"关联的值set<int>对象插入值
    //2.
    pair<set<int>::iterator,bool > p_sb = (m["Hello"].insert(130) );  //set的insert() 返回pair
    //cout << *(p_sb.first);
    
    //遍历
    /* set<int>::iterator it_b = (p_mb.first->second).begin(); 
    set<int>::iterator it_e = (p_mb.first->second).end();  */
    
    map<string,set<int> >::iterator it = m.find("Hello" ); //map的find函数,返回map类型的迭代器
    set<int>::iterator it_b = (it->second).begin();        //通过map迭代器获取pair类型的second,该pair的second是set<int>类型
    set<int>::iterator it_e = (it->second).end();
    
    while(it_b!=it_e ){
        cout << *it_b++;
    }
      
    return 0;
}
View Code