Как определить последовательный контейнер?
Для того чтобы определить объект контейнерного типа, необходимо сначала включить соответствующий заголовочный файл:
#include <vector>
#inclnde <list>
#include <deque>
#include <map>
| #include <set>
Определение контейнера начинается именем его типа, за которым в угловых скобках следует тип данных его элементов[12]. Например:
list< int > ilist;
Переменная svec определяется как вектор, способный содержать элементы типа string, а ilist – как список с элементами типа int. Оба контейнера при таком определении пусты. Чтобы убедиться в этом, можно вызвать функцию-член empty():
if ( svec.empty() != true )
| ; // что-то не так
Простейший метод вставки элементов – использование функции-члена push_back(), которая добавляет элементы в конец контейнера. Например:
string text_word;
while ( cin >> text_word )
| svec.push_back( text_word );
Здесь строки из стандартного ввода считываются в переменную text_word, и затем копия каждой строки добавляется в контейнер svec с помощью push_back().
Список имеет функцию-член push_front(), которая добавляет элемент в его начало. Пусть есть следующий массив:
int ia[ 4 ] = { 0, 1, 2, 3 };
Использование push_back()
for ( int ix=0; ix<4; ++ix )
| ilist.push_back( ia[ ix ] );
создаст последовательность 0, 1, 2, 3, а push_front()
for ( int ix=0; ix<4; ++ix )
| ilist.push_front( ia[ ix ] );
создаст последовательность 3, 2, 1, 0. [13]
Мы можем при создании явно указать размер массива – как константным, так и неконстантным выражением:
#include <list>
#include <vector>
#include <string>
extern int get_word_count( string file_name );
const int list_size = 64;
list< int > ilist( list_size );
| vector< string > svec(get_word_count(string("Chimera")));
Каждый элемент контейнера инициализируется значением по умолчанию, соответствующим типу данных. Для int это 0. Для строкового типа вызывается конструктор по умолчанию класса string.
Мы можем указать начальное значение всех элементов:
list< int > ilist( list_size, -1 );
| vector< string > svec( 24, "pooh" );
Разрешается не только задавать начальный размер контейнера, но и впоследствии изменять его с помощью функции-члена resize(). Например:
svec.resize( 2 * svec.size() );
Размер svec в этом примере удваивается. Каждый новый элемент получает значение по умолчанию. Если мы хотим инициализировать его каким-то другим значением, то оно указывается вторым параметром функции-члена resize():
// каждый новый элемент получает значение "piglet"
| svec.resize( 2 * svec.size(), "piglet" );
Кстати, какова наиболее вероятная емкость svec при определении, если его начальный размер равен 24? Правильно, 24! В общем случае минимальная емкость вектора равна его текущему размеру. При удвоении размера емкость, как правило, тоже удваивается
Мы можем инициализировать новый контейнер с помощью существующего. Например:
vector< string > svec2( svec );
| list< int > ilist2( ilist ) ;
Каждый контейнер поддерживает полный набор операций сравнения: равенство, неравенство, меньше, больше, меньше или равно, больше или равно. Сопоставляются попарно все элементы контейнера. Если они равны и размеры контейнеров одинаковы, то эти контейнеры равны; в противном случае – не равны. Результат операций “больше” или “меньше” определяется сравнением первых двух неравных элементов. Вот что печатает программа, сравнивающая пять векторов:
ivecl: 1 3 5 7 9 12
ivec2: 0 1 1 2 3 5 8 13
ivec3: 1 3 9
ivec4: 1 3 5 7
ivec5: 2 4
// первый неравный элемент: 1, О
// ivecl больше чем ivec2
ivecl < ivec2 //false
ivec2 < ivecl //true
// первый неравный элемент: 5, 9
ivecl < ivec3 //true
// все элементы равны, но ivec4 содержит меньше элементов
// следовательно, ivec4 меньше, чем ivecl
ivecl < ivec4 //false
// первый неравный элемент: 1, 2
ivecl < ivec5 //true
ivecl == ivecl //true
ivecl == ivec4 //false
ivecl != ivec4 //true
ivecl > ivec2 //true
ivec3 > ivecl //true
ivec5 > ivec2 //true
Существуют три ограничения на тип элементов контейнера (практически это касается только пользовательских классов). Для должны быть определены:
· операция “равно”;
· операция “меньше” (все операции сравнения контейнеров, о которых говорилось выше, используют только эти две операции сравнения);
· значение по умолчанию (для класса это означает наличие конструктора по умолчанию).
Все предопределенные типы данных, включая указатели и классы из стандартной библиотеки С++ удовлетворяют этим требованиям.
Упражнение 6.5
Объясните, что делает данная программа:
#include <string>
#include <vector>
#include <iostream>
#int main()
{
vector<string> svec;
svec.reserve( 1024 );
string text_word;
while ( cin >> text_word )
svec.push_back( text_word );
svec.resize( svec.size()+svec.size()/2 );
// ...
| }
Упражнение 6.6
Может ли емкость контейнера быть меньше его размера? Желательно ли, чтобы емкость была равна размеру: изначально или после вставки элемента? Почему?
Упражнение 6.7
Если программа из упражнения 6.5 прочитает 256 слов, то какова наиболее вероятная емкость контейнера после изменения размера? А если она считает 512 слов? 1000? 1048?
Упражнение 6.8
Какие из данных классов не могут храниться в векторе:
(a) class cl1 {
public:
c11( int=0 );
bool operator==();
bool operator!=();
bool operator<=();
bool operator<();
// ...
};
(b) class c12 {
public:
c12( int=0 );
bool operator!=();
bool operator<=();
// ...
};
(с) class c13 {
public:
int ival;
};
(d) class c14 {
public:
c14( int, int=0 );
bool operator==();
bool operator!=();
// ...
| }
Итераторы
Итератор предоставляет обобщенный способ перебора элементов любого контейнера – как последовательного, так и ассоциативного. Пусть iter является итератором для какого-либо контейнера. Тогда
++iter;
перемещает итератор так, что он указывает на следующий элемент контейнера, а
*iter;
разыменовывает итератор, возвращая элемент, на который он указывает.
Все контейнеры имеют функции-члены begin() и end().
· begin() возвращает итератор, указывающий на первый элемент контейнера.
· end() возвращает итератор, указывающий на элемент, следующий за последним в контейнере.
Чтобы перебрать все элементы контейнера, нужно написать:
for ( iter = container. begin();
iter != container.end(); ++iter )
| do_something_with_element( *iter );
Объявление итератора выглядит слишком сложным. Вот определение пары итераторов вектора типа string:
// vector<string> vec;
vector<string>::iterator iter = vec.begin();
| vector<string>::iterator iter_end = vec.end();
В классе vector для определения iterator используется typedef. Синтаксис
vector<string>::iterator
ссылается на iterator, определенный с помощью typedef внутри класса vector, содержащего элементы типа string.
Для того чтобы напечатать все элементы вектора, нужно написать:
for( ; iter != iter_end; ++iter )
| cout << *iter << '\n';
Здесь значением *iter выражения является, конечно, элемент вектора.
В дополнение к типу iterator в каждом контейнере определен тип const_iterator, который необходим для навигации по контейнеру, объявленному как const. const_iterator позволяет только читать элементы контейнера:
#include <vector>
void even_odd( const vector<int> *pvec,
vector<int> *pvec_even,
vector<int> *pvec_odd )
{
// const_iterator необходим для навигации по pvec
vector<int>::const_iterator c_iter = pvec->begin();
vector<int>::const_1terator c_iter_end = pvec->end();
for ( ; c_iter != c_iter_end; ++c_iter )
if ( *c_iter % 2 )
pvec_even->push_back( *c_iter );
else pvec_odd->push_back( *c_iter );
| }
Что делать, если мы хотим просмотреть некоторое подмножество элементов, например взять каждый второй или третий элемент, или хотим начать с середины? Итераторы поддерживают адресную арифметику, а значит, мы можем прибавить некоторое число к итератору:
vector<int>::iterator iter = vec->begin()+vec.size()/2;
iter получает значение адреса элемента из середины вектора, а выражение
iter += 2;
сдвигает iter на два элемента.
Арифметические действия с итераторами возможны только для контейнеров vector и deque. list не поддерживает адресную арифметику, поскольку его элементы не располагаются в непрерывной области памяти. Следующее выражение к списку неприменимо:
ilist.begin() + 2;
так как для перемещения на два элемента необходимо два раза перейти по адресу, содержащемуся в закрытом члене next. У классов vector и deque перемещение на два элемента означает прибавление 2 к указателю на текущий элемент. (Адресная арифметика рассматривается в разделе 3.3.)
Объект контейнерного типа может быть инициализирован парой итераторов, обозначающих начало и конец последовательности копируемых в новый объект элементов. (Второй итератор должен указывать на элемент, следующий за последним копируемым.) Допустим, есть вектор:
#include <vector>
#include <string>
#include <iostream>
int main()
{
vector<string> svec;
string intext;
while ( cin >> intext )
svec.push_back( intext );
// обработать svec ...
| }
Вот как можно определить новые векторы, инициализируя их элементами первого вектора:
int main() {
vector<string> svec;
// ...
// инициализация svec2 всеми элементами svec
vector<string> svec2( svec.begin(), svec.end() );
// инициализация svec3 первой половиной svec
vector<string>::iterator it =
svec.begin() + svec.size()/2;
vector<string> svec3 ( svec.begin(), it );
// ...
| }
Использование специального типа istream_iterator (о нем рассказывается в разделе 12.4.3) упрощает чтение элементов из входного потока в svec:
#include <vector>
#include <string>
#include <iterator>
int mainQ
{
// привязка istream_iterator к стандартному вводу
istream_iterator<string> infile( cin );
// istream_iterator, отмечающий конец потока
istream_iterator<string> eos;
// инициализация svec элементами, считываемыми из cin;
vector<string> svec( infile, eos );
// ...
| }
Кроме итераторов, для задания диапазона значений, инициализирующих контейнер, можно использовать два указателя на массив встроенного типа. Пусть есть следующий массив строк:
#include <string>
string words[4] = {
"stately", "plump", "buck", "mulligan"
| };
Мы можем инициализировать вектор с помощью указателей на первый элемент массива и на элемент, следующий за последним:
vector< string > vwords( words, words+4 );
Второй указатель служит “стражем”: элемент, на который он указывает, не копируется.
Аналогичным образом можно инициализировать список целых элементов:
int ia[6] = { 0, 1, 2, 3, 4, 5 };
| list< int > ilist( ia, ia+6 );
В разделе 12.4 мы снова обратимся к итераторам и опишем их более детально. Сейчас информации достаточно для того, чтобы использовать итераторы в нашей системе текстового поиска. Но прежде чем вернуться к ней, рассмотрим некоторые дополнительные операции, поддерживаемые контейнерами.
Упражнение 6.9
Какие ошибки допущены при использовании итераторов:
const vector< int > ivec;
vector< string > svec;
list< int > ilist;
(a) vector<int>::iterator it = ivec.begin();
(b) list<int>::iterator it = ilist.begin()+2;
(c) vector<string>::iterator it = &svec[0];
(d) for ( vector<string>::iterator
it = svec.begin(); it != 0; ++it )
| // ...
Упражнение 6.10
Найдите ошибки в использовании итераторов:
int ia[7] = { 0, 1, 1, 2, 3, 5, 8 };
string sa[6] = {
"Fort Sumter", "Manassas", "Perryville", "Vicksburg",
"Meridian", "Chancellorsvine" };
(a) vector<string> svec( sa, &sa[6] );
(b) list<int> ilist( ia+4, ia+6 );
(c) list<int> ilist2( ilist.begin(), ilist.begin()+2 );
(d) vector<int> ivec( &ia[0], ia+8 );
(e) list<string> slist( sa+6, sa );
| (f) vector<string> svec2( sa, sa+6 );
Не нашли, что искали? Воспользуйтесь поиском по сайту:
©2015 - 2024 stydopedia.ru Все материалы защищены законодательством РФ.
|