您的位置:首页 > 编程语言 > C语言/C++

C++学习笔记5 容器

2014-07-08 10:07 267 查看
1. assign and swap

assign: 操作首先删除容器中所有的元素,然后将其参数所指定的新元素插入到该容器中。与复制容器元素的构造函数一样,如果两个容器类型相同,其元素类型也相同,就可以使用赋值操作符(=)将一个容器赋值给另一个容器。如果在不同(或相同)类型的容器内,元素类型不相同但是相互兼容,则其赋值运算必须使用assign 函数。例如,可通过assign 操作实现将vector 容器中一段char* 类型的元素赋给string 类型list 容器。

由于assign 操作首先删除容器中原来存储的所有元素,因此,传递给assign 函数的迭代器不能指向调用该函数的容器内的元素。

swap: 操作以节省删除元素的成本swap 操作实现交换两个容器内所有元素的功能。要交换的容器的类型必须匹配:操作数必须是相同类型的容器,而且所存储的元素类型也必须相同。调用了 swap 函数后,右操作数原来存储的元素被存放在左操作数中,反之亦然。

vector<string> svec1(10); // vector with 10 elements

vector<string> svec2(24); // vector with 24 elements

svec1.swap(svec2);

执行 swap 后,容器 svec1 中存储 24 个 string 类型的元素,而 svec2 则存储 10 个元素。

2. 关于 swap 的一个重要问题在于:该操作不会删除或插入任何元素,而且保证在常量时间内实现交换。由于容器内没有移动任何元素,因此迭代器不会失效。

3. capacity vs size

弄清楚容器的capacity(容量)与size(长度)的区别非常重要。size指容器当前拥有的元素个数;而capacity 则指容器在必须分配新存储空间之前可以存储的元素总数。

Sample:

为了说明size 和capacity 的交互作用,考虑下面的程序:

vector<int>ivec;

// size should bezero; capacity is implementation defined

cout <<"ivec: size: " << ivec.size() << " capacity: " <<ivec.capacity() << endl;

// give ivec 24elements

for(vector<int>::size_type ix = 0; ix != 24; ++ix)

ivec.push_back(ix);

// size should be 24; capacity will be >= 24 and isimplementation defined

cout <<"ivec: size: " << ivec.size() << " capacity: " <<ivec.capacity() << endl;

在我们的系统上运行该程序时,得到以下输出结果:

ivec: size: 0capacity: 0

ivec: size: 24 capacity: 32

4. 容器的选用

程序使用这些操作的程序将决定应该选择哪种类型的容器。vector和deque 容器提供了对元素的快速随机访问,但付出的代价是,在容器的任意位置插入或删除元素,比在容器尾部插入和删除的开销更大。list类型在任何位置都能快速插入和删除,但付出的代价是元素的随机访问开销较大。

对于vector 容器,除了容器尾部外,其他任何位置上的插入(或删除)操作都要求移动被插入(或删除)元素右边所有的元素。例如,假设有一个拥有50个元素的vector 容器,我们希望删除其中的第23 号元素,则23 号元素后面的所有元素都必须向前移动一个位置。否则,vector 容器上将会留下一个空位(hole),而vector 容器的元素就不再是连续存放的了。

deque 容器拥有更加复杂的数据结构。从deque 队列的两端插入和删除元素都非常快。在容器中间插入或删除付出的代价将更高。deque 容器同时提供了list 和vector 的一些性质:

• 与 vector容器一样,在deque 容器的中间insert 或erase 元素效率比较低。

• 不同于vector 容器,deque容器提供高效地在其首部实现insert 和 erase 的操作,就像在容器尾部的一样。

• 与 vector容器一样而不同于list 容器的是,deque 容器支持对所有元素的随机访问。

• 在 deque 容器首部或尾部插入元素不会使任何迭代器失效,而首部或尾部删除元素则只会使指向被删除元素的迭代器失效。在deque 容器的任何其他位置的插入和删除操作将使指向该容器元素的所有迭代器都失效。

5. 元素的访问如何影响容器的选择

vector 和deque 容器都支持对其元素实现高效的随机访问。也就是说,我们可以高效地先访问5 号元素,然后访问15 号元素,接着访问7 号元素,等等。

由于vector 容器的每次访问都是距离其起点的固定偏移,因此其随机访问非常有效率。在list 容器中,上述跳跃访问会变得慢很多。在list 容器的

元素之间移动的唯一方法是顺序跟随指针。从5 号元素移动到15 号元素必须遍历它们之间所有的元素。

通常来说,除非找到选择使用其他容器的更好理由,否则 vector 容器都是最佳选择。

6. 选择容器的提示

下面列举了一些选择容器类型的法则:

1. 如果程序要求随机访问元素,则应使用 vector 或 deque 容器。

2. 如果程序必须在容器的中间位置插入或删除元素,则应采用list 容器。

3. 如果程序不是在容器的中间位置,而是在容器首部或尾部插入或删除元素,则应采用deque 容器。

4. 如果只需在读取输入时在容器的中间位置插入元素,然后需要随机访问元素,则可考虑在输入时将元素读入到一个list 容器,接着对此容器重新排序,使其适合顺序访问,然后将排序后的list 容器复制到一个vector 容器。

如果程序既需要随机访问又必须在容器的中间位置插入或删除元素,那应该怎么办呢?

此时,选择何种容器取决于下面两种操作付出的相对代价:随机访问list 容器元素的代价,以及在vector 或deque 容器中插入/删除元素时复制元素的代价。

通常来说,应用中占优势的操作(程序中更多使用的是访问操作还是插入/删除操作)将决定应该什么类型的容器。 决定使用哪种容器可能要求剖析各种容器类型完成应用所要求的各类操作的性能。

如果无法确定某种应用应该采用哪种容器,则编写代码时尝试只使用vector 和lists 容器都提供的操作:使用迭代器,而不是下标,并且避免随机访问元素。这样编写,在必要时,可很方便地将程序从使用vector 容器修改为使用list 的容器。

7. string 类型与 vector 容器 区别

string 类型与vector 容器不同的是,它不支持以栈方式操纵容器:在string 类型中不能使用front、back和pop_back 操作。

string 支持的容器操作有:

• 迭代器类型。

• 容器构造函数,但是不包括只需要一个长度参数的构造函数。

• vector 容器所提供的添加元素的操作。注意:无论 vector 容器还是string 类型都不支持push_front 操作。

• 长度操作。

•下标和at 操作;但string 类型不提供该表列出的back 和front 操作。

• begin 和end 操作。

• erase 和clear 操作;但是string 类型不入提供 pop_back 或pop_front 操作。

• 赋值操作。

• 与vector 容器的元素一样,string的字符也是连续存储的。因此,string类型支持的capacity 和reserve 操作。

char *cp ="Hiya"; // null-terminated array

char c_array[] ="World!!!!"; // null-terminated

char no_null[] ={'H', 'i'}; // not null-terminated

string s1(cp); // s1 =="Hiya"

string s2(c_array,5); // s2 =="World"

string s3(c_array +5, 4); // s3 =="!!!!"

string s4(no_null); // runtime error:no_null not null-terminated

string s5(no_null,2); // ok: s5 =="Hi"

string s6(s1, 2); // s6 =="ya"

string s7(s1, 0, 2); // s7 =="Hi"

string s8(s1, 0, 8); // s8 =="Hiya"

8. append 和 replace 函数

string s("C++Primer");
// initialize s to "C++ Primer"

s.append(" 3rdEd.");
// s =="C++ Primer 3rd Ed."

// equivalent tos.append(" 3rd Ed.")

s.insert(s.size()," 3rd Ed.");

// starting atposition 11, erase 3 characters and then insert "4th"

s.replace(11, 3,"4th"); // s== "C++ Primer 4th Ed."

// equivalent way toreplace "3rd" by "4th"

s.erase(11, 3); // s =="C++ Primer Ed."

s.insert(11,"4th"); // s == "C++ Primer 4th Ed."

replace 操作用于删除一段指定范围的字符,然后在删除位置插入一组新字符,等效于调用erase 和insert 函数。

s.replace(11, 3, "Fourth"); // s == "C++Primer Fourth Ed."

9. string 查找操作符

s.find( args) 在 s 中查找 args 的第一次出现

s.rfind( args) 在 s 中查找 args 的最后一次出现

s.find_first_of( args) 在 s 中查找 args 的任意字符的第一次出现

s.find_last_of( args) 在 s 中查找 args的任意字符的最后一次出现

s.find_first_not_of( args) 在 s 中查找第一个不属于args 的字符

s.find_last_not_of( args) 在 s 中查找最后一个不属于args 的字符

c, pos 在 s 中,从下标pos 标记的位置开始,查找字符c。pos的默认值为0

s2, pos 在s 中,从下标pos 标记的位置开始,查找string 对象s2。pos的默认值为0

cp, pos 在s 中,从下标pos 标记的位置形参,查找指针cp 所指向的C 风格的以空字符结束的字符串。pos的默认值为0 cp, pos, n

在s 中,从下标pos 标记的位置开始,查找指针cp 所指向数组的前n 个字符。pos和n 都没有默认值

10. 小结

C++ 标准库定义了一系列顺序容器类型。容器是用于存储某种给定类型对象的模板类型。在顺序容器中,所有元素根据其位置排列和访问。顺序容器共享一组通用的已标准化的接口:如果两种顺序容器都提供某一操作,那么该操作具有相同的接口和含义。所有容器都提供(有效的)动态内存管理。程序员在容器中添加元素时,不必操心元素存放在哪里。容器自己实现其存储管理。

最经常使用的容器类型是 vector,它支持对元素的快速随机访问。可高效地在vector 容器尾部添加和删除元素,而在其他任何位置上的插入或删除运算则要付出比较昂贵的代价。deque类与vector 相似,但它还支持在deque 首部的快速插入和删除运算。list类只支持元素的顺序访问,但在list 内部任何位置插入和删除元素都非常快速。

容器定义的操作非常少,只定义了构造函数、添加或删除元素的操作、设置容器长度的操作以及返回指向特殊元素的迭代器的操作。其他一些有用的操作,如排序、查找,则不是由容器类型定义,而是由第十一章介绍的标准算法定义。

在容器中添加或删除元素可能会使已存在的迭代器失效。当混合使用迭代器操作和容器操作时,必须时刻留意给定的容器操作是否会使迭代器失效。许多使一个迭代器失效的操作,例如insert 或erase,将返回一个新的迭代器,让程序员保留容器中的一个位置。使用改变容器长度的容器操作的循环必须非常小心其迭代器的使用。

11. map 迭代器进行解引用将产生pair 类型的对象 对迭代器进行解引用时,

将获得一个引用,指向容器中一个value_type 类型的值。对于map 容器,其value_type 是pair 类型:

// get an iterator toan element in word_count

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

// *map_it is areference to a pair<const string, int> object

cout <<map_it->first;
// prints the key for this

element

cout << "" << map_it->second; // prints the value of the element

map_it->first ="new key"; // error: key is const

++map_it->second; // ok: wecan change value through an iterator

12. 下标行为的编程意义

对于map 容器,如果下标所表示的键在容器中不存在,则添加新元素,这一特性可使程序惊人地简练:

// count number oftimes each word occurs in the input

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

string word;

while (cin >>word)

++word_count[word];

这段程序创建一个map 对象,用来记录每个单词出现的次数。while循环每次从标准输入读取一个单词。如果这是一个新的单词,则在word_count 中添加以该单词为索引的新元素。如果读入的单词已在map 对象中,则将它所对应的值加1。其中最有趣的是,在单词第一次出现时,会在word_count 中创建并插入一个以该单词为索引的新元素,同时将它的值初始化为0。然后其值立即加1,所以每次在map 中添加新元素时,所统计的出现次数正好从1 开始。

13. 关联容器和顺序容器

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

14. 关联容器

关联容器共享大部分——但并非全部——的顺序容器操作。关联容器不提供 front、 push_front、 pop_front、back、push_back 以及 pop_back 操作。

15. map 类定义的类型

map<K, V>::key_type 在 map 容器中,用做索引的键的类型

map<K, V>::mapped_type 在 map 容器中,键所关联的值的类型

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

在学习 map 的接口时,需谨记 value_type 是 pair 类型,它的值成员可以修改,但键成员不能修改。

16.map 迭代器进行解引用将产生 pair 类型的对象

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

// get an iterator to an element in word_count

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

// *map_it is a reference to a pair<const string, int> object

cout << map_it->first; // prints the key for this element

cout << " " << map_it->second; // prints the value of the element

map_it->first = "new key"; // error: key is const

++map_it->second; // ok: we can change value through an iterator

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

17. 小结

关联容器的元素按键排序和访问。关联容器支持通过键高效地查找和读取元素。键的使用,使关联容器区别于顺序容器,顺序容器的元素是根据位置访问的。map 和 multimap 类型存储的元素是键-值对。它们使用在 utility 头文件中定义的标准库 pair 类,来表示这些键-值对元素。对 map 或 multimap 迭代器进行解引用将获得 pair 类型的值。pair 对象的 first 成员是一个 const 键,而 second 成员则是该键所关联的值。set 和 multiset 类型则专门用于存储键。在
map 和 set 类型中,一个键只能关联一个元素。而 multimap 和 multiset 类型则允许多个元素拥有相同的键。

关联容器共享了顺序容器的许多操作。除此之外,关联容器还定义一些新操作,并对某些顺序容器同样提供的操作重新定义了其含义或返回类型,这些操作的差别体现了关联容器中键的使用。

关联容器的元素可用迭代器访问。标准库保证迭代器按照键的次序访问元素。begin 操作将获得拥有最小键的元素,对此迭代器作自增运算则可以按非降序依次访问各个元素。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: