C++ PRIMER 读书笔记 第十章 some tips about associative containers

1.    概论:

关联容器和顺序容器的本质差别在于:关联容器通过键(key)存储和读取元素,而顺序容器则通过元素在容器中的位置顺序存储和访问元素。

关联容器类型

map

Associative array; elements stored and retrieved by key

关联数组:元素通过键来存储和读取

set

Variable-sized collection with fast retrieval by key

大小可变的集合,支持通过键实现的快速读取

multimap

map in which a key can appear multiple times

支持同一个键多次出现的 map 类型

multiset

set in which a key can appear multiple times

支持同一个键多次出现的 set 类型

 

2pair类型

utility 头文件中定义。

pairs 类型提供的操作

pair<T1, T2> p1;

Create an empty pair with two elements of types T1 and T2. The elements are value-initialized

创建一个空的 pair 对象,它的两个元素分别是 T1 T2 类型,采用值初始化

pair<T1, T2> p1(v1, v2);

 

 

Create a pair with types T1 and T2 initializing the first member from v1 and the second from v2.

创建一个 pair 对象,它的两个元素分别是 T1 T2 ,其中 first 成员初始化为 v1,而 second 成员初始化为 v2

 

make_pair(v1, v2)

Creates a new pair from the values v1 and v2. The type of the pair is inferred from the types of v1 and v2.

v1 v2 值创建一个新 pair 对象,其元素类型分别是 v1 v2 的类型

 

p1 < p2

Less than between two pair objects. Less than is defined as dictionary ordering: Returns true if p1.first < p2.first or if !(p2.first < p1.first) && p1.second < p2.second.

两个 pair 对象之间的小于运算,其定义遵循字典次序:如果 p1.first < p2.first 或者 !(p2.first < p1.first) && p1.second < p2.second,则返回 true

 

p1 == p2

Two pairs are equal if their first and second members are respectively equal. Uses the underlying element == operator.

如果两个 pair 对象的 first second 成员依次相等,则这两个对象相等。该运算使用其元素的 == 操作符

 

p.first

Returns the (public) data member of p named first.

返回 p 中名为 first 的(公有)数据成员

 

p.second

Returns the (public) data member of p named second.

返回 p 的名为 second 的(公有)数据成员

 

使用typedef简化声明:

typedef pair<string, string> Author;

Author proust("Marcel", "Proust");

与其他标准库类型不同,对于 pair 类,可以直接访问其数据成员:其成员都是仅有的,分别命名为 first second。只需使用普通的点操作符——成员访问标志即可访问其成员:

     string firstBook;

     // access and test the data members of the pair

     if (author.first == "James" && author.second == "Joyce")

         firstBook = "Stephen Hero";

除了构造函数,标准库还定义了一个 make_pair 函数,由传递给它的两个实参生成一个新的 pair 对象。可如下使用该函数创建新的 pair 对象,并赋给已存在的 pair 对象:

     pair<string, string> next_auth;

     string first, last;

     while (cin >> first >> last) {

         // generate a pair from first and last

         next_auth = make_pair(first, last);

         // process next_auth...

     }

3. 关联容器:

关联容器共享大部分——但并非全部——的顺序容器操作。关联容器不提供 frontpush_frontpop_frontbackpush_back 以及 pop_back 操作。

“容器元素根据键的次序排列”这一事实就是一个重要的结论:在迭代遍历关联容器时,我们可确保按键的顺序的访问元素,而与元素在容器中的存放位置完全无关。

4. map类型

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

使用map必须包含 map 头文件。在定义 map 对象时,必须分别指明键和值的类型(value type

     // count number of times each word occurs in the input

     map<string, int> word_count; // empty map from string to int

map 的构造函数

map<k, v> m;

Create an empty map named m with key and value types k and v.

创建一个名为 m 的空 map 对象,其键和值的类型分别为 k v

map<k, v> m(m2);

Create m as a copy of m2; m and m2 must have the same key and value types.

创建 m2 的副本 mm m2 必须有相同的键类型和值类型

map<k, v> m(b, e);

Create m as a copy of the elements from the range denoted by iterators b and e. Elements must have a type that can be converted to pair<const k, v>.

创建 map 类型的对象 m,存储迭代器 b e 标记的范围内所有元素的副本。元素的类型必须能转换为 pair<const k, v>

 

在实际应用中,键类型必须定义 < 操作符,而且该操作符应能“正确地工作”,这一点很重要。

map 对象的元素是键-值对,也即每个元素包含两个部分:键以及由键关联的值。map value_type 是存储元素的键以及值的 pair 类型,而且键为 const。例如,word_count 数组的 value_type pair<const string, int> 类型。

map 类定义的类型

map<K, V>::key_type

The type of the keys used to index the map.

map 容器中,用做索引的键的类型

map<K, V>::mapped_type

The type of the values associated with the keys in the map.

map 容器中,键所关联的值的类型

map<K, V>::value_type

A pair whose first element has type const map<K, V>::key_type and second has type map<K, V>::mapped_type.

一个 pair 类型,它的 first 元素具有 const map<K, V>::key_type 类型,而 second 元素则为 map<K, V>::mapped_type 类型

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

     // get an iterator to an element in word_count

     map<string, int>::iterator map_it = word_count.begin();

对迭代器进行解引用将获得一个 pair 对象,它的 first 成员存放键,为 const,而 second 成员则存放值。

谨记 value_type pair 类型,它的值成员可以修改,但键成员不能修改。可以使用作用域操作符(scope operator)来获取类型成员,如 map<string, int>::key_type

使用下标访问 map 对象

如下编写程序时:

     map <string, int> word_count; // empty map

     // insert default initialzed element with key Anna; then assign 1 to its value

     word_count["Anna"] = 1;

将发生以下事情:

1) 在 word_count 中查找键为 Anna 的元素,没有找到。

 2)将一个新的键-值对插入到 word_count 中。它的键是 const string 类型的对象,保存 Anna。而它的值则采用值初始化,这就意味着在本例中值为 0

3)将这个新的键-值对插入到 word_count 中。

4)读取新插入的元素,并将它的值赋为 1

 使用下标访问 map 与使用下标访问数组或 vector 的行为截然不同:用下标访问不存在的元素将导致在 map 容器中添加一个新元素,它的键即为该下标值。

 注意一点

通常来说,下标操作符返回左值它返回的左值是特定键所关联的值。可如下读或写元素:

     cout << word_count["Anna"]; // fetch element indexed by Anna; prints 1

     ++word_count["Anna"];       // fetch the element and add one to it

     cout << word_count["Anna"]; // fetch the element and print it; prints 2

有别于 vector string 类型,map 下标操作符返回的类型与对 map 迭代器进行解引用获得的类型不相同。

 map 迭代器返回 value_type 类型的值——包含 const key_type mapped_type 类型成员的 pair 对象;下标操作符则返回一个 mapped_type 类型的值。

统计单词次数的小程序:

map<string, int> word_count; // empty map from string to int

     string word;

     while (cin >> word)

       ++word_count[word];

 

除了下标法,插入元素的另一个方法是:直接使用 insert 成员,其语法更紧凑:

     // if Anna not already in word_count, inserts new element with value 1

     word_count.insert(map<string, int>::value_type("Anna", 1));

使用 insert 成员可避免使用下标操作符所带来的副作用:不必要的初始化。

以上可用两种方法简化:使用 make_pair:

   word_count.insert(make_pair("Anna", 1));

或使用 typedef

     typedef map<string,int>::value_type valType;

     word_count.insert(valType("Anna", 1));

map 容器提供的 insert 操作

m.insert(e)

e is a value of the value_type for m. If the key(e.first) is not in m, inserts a new element with value e.second. If the key is in m, then m is unchanged. Returns a pair containing a map iterator referring to the element with key e.first and a bool indicating whether the element was inserted or not.

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

m.insert(beg, end)

beg and end are iterators that denote a range of values that are keyvalue pairs with the same type as m's value_type. For each element in the range, if the given key is not already in m, it inserts the key and its associated value into m. Returns void.

beg end 是标记元素范围的迭代器,其中的元素必须为 m.value_type 类型的键-值对。对于该范围内的所有元素,如果它的键在 m 中不存在,则将该键及其关联的值插入到 m。返回 void 类型

m.insert(iter, e)

e is a value of the value_type for m. If the key(e.first) is not in m, inserts the new element using the iterator iter as a hint for where to begin the search for where the new element should be stored. Returns an iterator that refers to the element in m with given key.

e 是一个用在 m 上的 value_type 类型的值。如果键(e.first)不在 m 中,则创建新元素,并以迭代器 iter 为起点搜索新元素存储的位置。返回一个迭代器,指向 m 中具有给定键的元素

 

带有一个键-值 pair 形参的 insert 版本将返回一个值:包含一个迭代器和一个 bool 值的 pair 对象,其中迭代器指向 map 中具有相应键的元素,而 bool 值则表示是否插入了该元素。如果该键已在容器中,则其关联的值保持不变,返回的 bool 值为 falsefalse,如果该键不存在,则插入新元素,且bool值为true。在这两种情况下,迭代器都将指向具有给定键的元素。下面是使用 insert 重写的单词统计程序:

     // count number of times each word occurs in the input

     map<string, int> word_count; // empty map from string to int

     string word;

     while (cin >> word) {

         // inserts element with key equal to word and value 1;

         // if word already in word_count, insert does nothing

         pair<map<string, int>::iterator, bool> ret =

                   word_count.insert(make_pair(word, 1));

         if (!ret.second)          // word already in word_count

             ++ret.first->second;  // increment counter

(注意:*p.first等价于p->first)

查找并读取 map 中的元素

下标操作符给出了读取一个值的最简单方法:

     map<string,int> word_count;

     int occurs = word_count["foobar"];

但是,使用下标存在一个很危险的副作用:如果该键不在 map 容器中,那么下标操作会插入一个具有该键的新元素。

map 容器提供了两个操作:count find,用于检查某个键是否存在而不会插入该键。

不修改 map 对象的查询操作

m.count(k)

Returns the number of occurrences of k within m.

返回 m k 的出现次数

m.find(k)

Returns an iterator to the element indexed by k, if there is one, or returns an off-the-end iterator  if the key is not present.

如果 m 容器中存在按 k 索引的元素,则返回指向该元素的迭代器。如果不存在,则返回超出末端迭代器

 

map删除元素

map 容器中删除元素的 erase 操作有三种变化形式。与顺序容器一样,可向 erase 传递一个或一对迭代器,来删除单个元素或一段范围内的元素。其删除功能类似于顺序容器,但有一点不同:map 容器的 erase 操作返回 void,而顺序容器的 erase 操作则返回一个迭代器,指向被删除元素后面的元素

除此之外,map 类型还提供了一种额外的 erase 操作,其参数是 key_type 类型的值,如果拥有该键的元素存在,则删除该元素。对于单词统计程序,可使用这个版本的 erase 函数来删除 word_count 中指定的单词,然后输出被删除的单词:

if (word_count.erase(removal_word))

          cout << "ok: " << removal_word << " removed/n";

     else cout << "oops: " << removal_word << " not found!/n";

erase 函数返回被删除元素的个数。对于 map 容器,该值必然是 0 1。如果返回 0,则表示欲删除的元素在 map 不存在。

map 对象中删除元素

m.erase(k)

Removes the element with key k from m. Returns size_type indicating the number of elements removed.

删除 m 中键为 k 的元素。返回 size_type 类型的值,表示删除的元素个数

m.erase(p)

Removes element referred to by the iterator p from m. p must refer to an actual element in m; it must not be equal to m.end(). Returns void.

m 中删除迭代器 p 所指向的元素。p 必须指向 m 中确实存在的元素,而且不能等于 m.end()。返回 void

m.erase(b, e)

Removes the elements in the range denoted by the iterator pair b, e. b and e must be a valid range of elements in m: b and e must refer to elements in m or one past the last element in m. b and e must either be equalin which case the range is emptyor the element to which b refers must occur before the element referred to by e. Returns void.

m 中删除一段范围内的元素,该范围由迭代器对 b e 标记。b e 必须标记 m 中的一段有效范围:即 b e 都必须指向 m 中的元素或最后一个元素的下一个位置。而且,b e 要么相等(此时删除的范围为空),要么 b 所指向的元素必须出现在 e 所指向的元素之前。返回 void 类型

 

5.set类型:

set 容器只是单纯的键的集合。set 不支持下标操作符,而且没有定义 mapped_type 类型。在 set 容器中,value_type 不是 pair 类型,而是与 key_type 相同的类型。它们指的都是 set 中存储的元素类型。这一差别也体现了 set 存储的元素仅仅是键,而没有所关联的值。与 map 一样,set 容器存储的键也必须唯一,而且不能修改。

为了使用 set 容器,必须包含 set 头文件。

set中添加元素:

可使用 insert 操作在 set 中添加元素:

     set<string> set1;         // empty set

     set1.insert("the");       // set1 now has one element

     set1.insert("and");       // set1 now has two elements

另一种用法是,调用 insert 函数时,提供一对迭代器实参,插入其标记范围内所有的元素。该版本的 insert 函数类似于形参为一对迭代器的构造函数——对于一个键,仅插入一个元素:

     set<int>    iset2; //    empty set

     vector<int> ivec;

     for (vector<int>::size_type i = 0; i != 10; ++i) {

         ivec.push_back(i);

         ivec.push_back(i); // duplicate copies of each number

     }

     iset2.insert(ivec.begin(), ivec.end());     // iset2 has 10 elements

map 容器的操作一样,带有一个键参数的 insert 版本返回 pair 类型对象,包含一个迭代器和一个 bool 值,迭代器指向拥有该键的元素,而 bool 值表明是否添加了元素。使用迭代器对的 insert 版本返回 void 类型。

创建“单词排除”集:

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];

}

6multisetmultimap

map set 容器中,一个键只能对应一个实例。而 multiset multimap 类型则允许一个键对应多个实例。multimap multiset 类型与相应的单元素版本具有相同的头文件定义:分别是 map set 头文件。

multimap multiset 所支持的操作分别与 map set 的操作相同,只有一个例外:multimap 不支持下标运算。

带有一个键参数的 erase 版本将删除拥有该键的所有元素,并返回删除元素的个数。而带有一个或一对迭代器参数的版本只删除指定的元素,并返回 void 类型:

multimap<string, string>::size_type cnt =  authors.erase(search_item);

回迭代器的关联容器操作

m.lower_bound(k)

Returns an iterator to the first element with key not less than k.

返回一个迭代器,指向键不小于 k 的第一个元素

m.upper_bound(k)

Returns an iterator to the first element with key greater than k.

返回一个迭代器,指向键大于 k 的第一个元素

m.equal_range(k)

Returns a pair of iterators.

返回一个迭代器的 pair 对象

The first member is equivalent to m.lower_bound(k) and second is equivalent to m.upper_bound(k).

它的 first 成员等价于 m.lower_bound(k)。而 second 成员则等价于 m.upper_bound(k)

运用如下:

typedef multimap<string, string>::iterator authors_it;

     authors_it beg = authors.lower_bound(search_item),

                end = authors.upper_bound(search_item);

     // loop through the number of entries there are for this author

     while (beg != end) {

         cout << beg->second << endl; // print each title

         ++beg;

     }

使用 equal_range 函数修改程序:

    // definitions of authors and search_item as above

    // pos holds iterators that denote range of elements for this key

     pair<authors_it, authors_it>

                      pos = authors.equal_range(search_item);

     // loop through the number of entries there are for this author

     while (pos.first != pos.second) {

         cout << pos.first->second << endl; // print each title

         ++pos.first;

     }

一个文本查询程序:

//头文件定义类

class TextQuery {

public:

       typedef std::vector<std::string>::size_type line_no;

       void read_file(std::ifstream &is)

       {store_file(is);build_map();}

       std::set_new_handler<line_no> run_query(const std::string&) const;

       std::string text_line(line_no) const;

private:

       void store_file(std::ifstream&);

       void build_map();

       std::vector<std::string> lines_of_text;

       std::map<std::string,std::set_new_handler<line_no> >word_map;

};

注:有一种情况下,必须总是使用完全限定的标准库名字:在头文件中。理由是头文件的内容会被预处理器复制到程序中。用 #include 包含文件时,相当于头文件中的文本将成为我们编写的文件的一部分。如果在头文件中放置 using 声明,就相当于在包含该头文件 using 的每个程序中都放置了同一 using,不论该程序是否需要 using 声明。

通常,头文件中应该只定义确实必要的东西。请养成这个好习惯。

 

 

 

int main(int argc,char **argv)

{

       ifstream infile;

       if(argc<2||!open_file(infile,argv[1])) {

              cerr<<"no input file!"<<endl;

              return EXIT_FAILURE;

       }

       TextQuery tq;

       tq.read_file(infile);

       while(true) {

              cout<<"enter word to look for,or q to quit:";

              string s;

              cin>>s;

              if(!cin||s=="q") break;

              set<TextQuery::line_no> locs=tq.run_query(s);

              print_results(locs,s,tq);

       }

       return 0;

}

void print_results(const set<TextQuery::line_no>& locs,

                               const string& sought,const TextQuery &file)

{

       typedef set<TextQuery::line_no> line_nums;

       line_nums::size_type size=locs.size();

       cout<<"/n"<<sought<<"occurs"

              <<size<<" "

              <<make_plural(size,"time","s")<<endl;

       line_nums::const_iterator it=locs.begin();

       for(;it!=locs.end();++it) {

              cout<<"/t(line"

                     <<(*it)+1<<")"

                     <<file.text_line(*it)<<endl;

       }

}

void TextQuery::store_file(ifstream &is)

{

       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)

       {

              istringstream line(lines_of_text[line_num]);

              string word;

              while(line>>word)

                     word_map[word].insert(line_num);

       }

}

set<TextQuery::line_no>

TextQuery::run_query(const string &query_word) const{

       map<string,set<line_no> >::const_iterator

              loc=word_map.find(query_word);

       if(loc==word_map.end())

              return set<line_no>();

       else

              return loc->second;

}

string TextQuery::text_line(line_no line) const

{

       if(line<lines_of_text.size())

              return lines_of_text[line];

       throw std::out_range("line number out of range");

}

 

 

你可能感兴趣的:(C++ PRIMER 读书笔记 第十章 some tips about associative containers)