Thinking in C++第二卷笔记之STL容器部分
1、容器
All the containers in the standard library hold copies of the objects you place in them, and
expand their resources as needed, so your objects must be copy-constructible (have an
accessible copy constructor) and assignable (have an accessible assignment operator). The
key difference between one container and another is the way the objects are stored in
memory and what operations are available to the user.
2、多重成员资格(multiple membership)
由于存在多重成员问题,使得自动删除指针成为问题。一个容器持有指向某个对象的指针,可能该指针在另一个容器中也出现。如果在一个容器中删除通过该指针删除了这个对象,则另外一个容器将访问不到这个对象了。
这个问题我们可以在链表中存储对象而不是指针来解决。
3、迭代器大致分5类:输入,输出,前向,双向,随机迭代器。
STL的总结
http://blog.163.com/zhoumhan_0351/blog/static/3995422720103174417603/
标准模板类(STL)(四),容器的比较、对比和总结
http://blog.163.com/zhoumhan_0351/blog/static/3995422720102267442299/
标准模板类(STL)(五),算法和函数
http://blog.163.com/zhoumhan_0351/blog/static/39954227201022783524890/
随即迭代器类似于一个指针。它具有双向迭代器的所有功能,再加上一个指针所有的功能,除了没有一种null空迭代器和空指针相对应。可以说,一个随即迭代器可以一个指针那样进行任何操作,包括operator[]进行索引。
struct input_iterator_tag {};
struct output_iterator_tag {};
struct forward_iterator_tag :
public input_iterator_tag {};
struct bidirectional_iterator_tag :
public forward_iterator_tag {};
struct random_access_iterator_tag :
public bidirectional_iterator_tag {};
4、向一个序列的中间添加元素的back_inserter(),它通过自动调用插入函数insert()而不是“压入”函数。
#include "vector"
#include "iterator"
#include "iostream"
using namespace std;
int a[5];
int main()
{
vector<int> ci;
copy(a, a + sizeof(a)/sizeof(vector<int>::value_type),
back_inserter(ci));
copy(ci.begin(), ci.end(),ostream_iterator<
typename vector<int>::value_type>(cout, " "));
}
template<class Cont>
back_insert_iterator<Cont> back_inserter(Cont& x);
template<class Cont, class Iter>
insert_iterator<Cont> inserter(Cont& x, Iter it);
5、Although it is possible to create an istream_iterator<char> and
ostream_iterator<char>, these actually parse the input and thus will, for
example, automatically eat whitespace (spaces, tabs, and newlines), which is
not desirable if you want to manipulate an exact representation of an istream.
Instead, you can use the special iterators istreambuf_iterator and
ostreambuf_iterator, which are designed strictly to move characters.Although
these are templates, they are meant to be used with template arguments of
either char or wchar_t。
istreambuf_iterator(streambuf_type *sb = 0) throw();
istreambuf_iterator(istream_type& is) throw();
The first constructor initializes the input stream-buffer pointer with sb. The
second constructor initializes the input stream-buffer pointer with
is.rdbuf(),then (eventually) attempts to extract and store an object of type E.
输入/输出流迭代器会自动吃掉空白字符,如果希望用一个输入流的精确地表现这样的动作,是不可取的,应当用输入/输出流缓冲迭代器。
#include <algorithm>
#include <fstream>
#include <iostream>
#include <iterator>
using namespace std;
int main() {
ifstream in("C:\\copy.txt");
// Exact representation of stream:
istreambuf_iterator<char> isb(in), end;
ostreambuf_iterator<char> osb(cout);
while(isb != end)
*osb++ = *isb++; // Copy 'in' to cout
cout << endl;
ifstream in2("C:\\copy.txt");
// Strips white space:
istream_iterator<char> is(in2), end2;
ostream_iterator<char> os(cout);
while(is != end2)
*os++ = *is++;
cout << endl;
} ///:~
6、操作未初始化的缓冲区
用输出迭代器raw_storage_iterator。它提供了使算法能够将其结果存储到未初始化的内存的能力。其构造函数持有一个指向某原始内存储区的迭代器,并且运算符operator=将一个对象分配给那个原始内存。
template<class FwdIt, class T>
class raw_storage_iterator
: public iterator<output_iterator_tag, void, void> {}
#include <iostream>
#include <iterator>
#include <algorithm>
using namespace std;
class Noisy{
public:
Noisy() {}
Noisy(int i_):i(i_){}
~Noisy(){cout<<"Destory"<<endl;}
operator int() const
{ return i; } //转换为int打印
private:
int i;
};
int main() {
const int QUANTITY = 10;
// Create raw storage and cast to desired type:
Noisy* np = reinterpret_cast<Noisy*>(
new char[QUANTITY * sizeof(Noisy)]);
raw_storage_iterator<Noisy*, Noisy> rsi(np);
for(int i = 0; i < QUANTITY; i++)
*rsi++ = Noisy(); // Place objects in storage
cout << endl;
copy(np, np + QUANTITY,
ostream_iterator<Noisy>(cout, " "));
cout << endl;
// Explicit destructor call for cleanup:
for(int j = 0; j < QUANTITY; j++)
(&np[j])->~Noisy();
// Release raw storage:
delete reinterpret_cast<char*>(np);
} ///:~
7、成员函数swap在两个容器间交换所有东西,这些容器通常存储指针。而非成员函数swap()算法通常采用赋值的方式来交换其参数。对于标准容器来说,已通过模板的特化定制为调用成员函数swap()了。
8、vector的内存分配溢出问题
1)Allocating a new, bigger piece of storage.
2)Copying all the objects from the old storage to the new (using the
copy-constructor).
3)Destroying all the old objects (the destructor is called for each one).
4)Releasing the old memory.
也就是如果存储的东西大于vector的默认空间容量,则重分配,并将先前的东西重新拷贝到新的地方。这通常是不能容忍的。
解决方法是:
1)事先知道要存储多少个数据,用reserve()来告诉vector预分配多大的存储区。Note that the use of reserve( ) is different from using the vector
constructor with an integral first argument; the latter initializes a prescribed
number of elements using the element type’s default constructor.
2)用其它的容器。if you’re creating all your objects in one part of the
program and randomly accessing them in another, you may find yourself
filling a deque and then creating a vector from the deque and using the vector
for rapid indexing. You don’t want to program this way habitually—just be
aware of these issues (that is, avoid premature optimization).
使用vector最安全的方法就是一次性的填入所有的元素。使用其的最佳理由是“它看起来很像一个数组”。
在vector重分配内存的过程中,把其存储的内容移到别处,则原迭代器的指针就指下一个未知的地方。
#include <iterator>
#include <iostream>
#include <vector>
using namespace std;
int main() {
vector<int> vi(10, 0);
ostream_iterator<int> out(cout, " ");
vector<int>::iterator i = vi.begin();
*i = 47;
copy(vi.begin(), vi.end(), out);
cout << endl;
// Force it to move memory (could also just add
// enough objects):
vi.resize(vi.capacity() + 1);
// Now i points to wrong memory:
*i = 48; // Access violation
copy(vi.begin(), vi.end(), out); // No change to vi[0]
} ///:~
9、双端队列
deque的典型实现是利用多个连续的存储块。它不需要重新分配存储区,所以deque比vector更有效率。只有在确切知道到底有多少个对象的时候,vector才是最佳的选择。
#include <cstddef>
#include <ctime>
#include <deque>
#include <fstream>
#include <iostream>
#include <iterator>
#include <sstream>
#include <string>
#include <vector>
using namespace std;
int main() {
char* fname = "C:\\Rand.doc";
ifstream in("fname");
vector<string> vstrings;
deque<string> dstrings;
string line;
// Time reading into vector:
clock_t ticks = clock();
while(getline(in, line))
vstrings.push_back(line);
ticks = clock() - ticks;
cout << "Read into vector: " << ticks << endl;
// Repeat for deque:
ifstream in2(fname);
ticks = clock();
while(getline(in2, line))
dstrings.push_back(line);
ticks = clock() - ticks;
cout << "Read into deque: " << ticks << endl;
// Now compare indexing:
ticks = clock();
for(size_t i = 0; i < vstrings.size(); i++) {
ostringstream ss;
ss << i;
vstrings[i] = ss.str() + ": " + vstrings[i];
}
ticks = clock() - ticks;
cout << "Indexing vector: " << ticks << endl;
ticks = clock();
for(size_t j = 0; j < dstrings.size(); j++) {
ostringstream ss;
ss << j;
dstrings[j] = ss.str() + ": " + dstrings[j];
}
ticks = clock() - ticks;
cout << "Indexing deque: " << ticks << endl;
// Compare iteration
ofstream tmp1("fname"), tmp2("fname");
ticks = clock();
copy(vstrings.begin(), vstrings.end(),
ostream_iterator<string>(tmp1, "\n"));
ticks = clock() - ticks;
cout << "Iterating vector: " << ticks << endl;
ticks = clock();
copy(dstrings.begin(), dstrings.end(),
ostream_iterator<string>(tmp2, "\n"));
ticks = clock() - ticks;
cout << "Iterating deque: " << ticks << endl;
} ///:~
说明:clock()这个函数返回从“开启这个程序进程”到“程序中调用clock()函数”时之间的CPU时钟计时单元(clock tick)数,在MSDN中称之为挂钟时间(wal-clock);若挂钟时间不可取,则返回-1。不是秒数。
10、序列容器的转换
you may need the efficiency of a deque when adding objects to the container
but the efficiency of a vector when indexing them. Each of the basic sequence
containers (vector, deque, and list) has a two-iterator constructor (indicating
the beginning and ending of the sequence to read from when creating a new
object) and an assign( ) member function to read into an existing container, so
you can easily move objects from one sequence container to another.
#include <algorithm>
#include <cstdlib>
#include <deque>
#include <iostream>
#include <iterator>
#include <vector>
using namespace std;
class sequence{
public:
int a;
sequence(){a=0;}
inline int operator()(){
return rand()%100;
}
};
int main() {
int size = 25;
deque<int> d;
generate_n(back_inserter(d), size,sequence());
cout << "\n Converting to a vector(1)" << endl;
vector<int> v1(d.begin(), d.end());
cout << "\n Converting to a vector(2)" << endl;
vector<int> v2;
v2.reserve(d.size());
v2.assign(d.begin(), d.end());
cout << "\n Cleanup" << endl;
} ///:~
从序列的两端插入或删除元素,合理地快速遍历,以及使用operator[]进行相当快速的随机访问。
11、operator[]和at()都可以访问元素,但是[]不进行越界检查,而at()要进行越界检查。如查越界,则抛出一个异常。