<< Пред.           стр. 24 (из 121)           След. >>

Список литературы по разделу

 #inclnde
 #include
 #include
 #include
 Определение контейнера начинается именем его типа, за которым в угловых скобках следует тип данных его элементов. Например:
 vector< string > svec;
 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.
 Мы можем при создании явно указать размер массива – как константным, так и неконстантным выражением:
 #include
 #include
 #include
 
 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
 #include
 #include
 
 #int main()
 {
  vector 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!=();
  // ...
 }
 6.5. Итераторы
 Итератор предоставляет обобщенный способ перебора элементов любого контейнера – как последовательного, так и ассоциативного. Пусть iter является итератором для какого-либо контейнера. Тогда
 ++iter;
 перемещает итератор так, что он указывает на следующий элемент контейнера, а
 *iter;
 разыменовывает итератор, возвращая элемент, на который он указывает.
 Все контейнеры имеют функции-члены begin() и end().
 begin() возвращает итератор, указывающий на первый элемент контейнера.
 end() возвращает итератор, указывающий на элемент, следующий за последним в контейнере.
 Чтобы перебрать все элементы контейнера, нужно написать:
 for ( iter = container. begin();
  iter != container.end(); ++iter )
  do_something_with_element( *iter );
 Объявление итератора выглядит слишком сложным. Вот определение пары итераторов вектора типа string:
 // vector vec;
 vector::iterator iter = vec.begin();
 vector::iterator iter_end = vec.end();
 В классе vector для определения iterator используется typedef. Синтаксис
 vector::iterator
 ссылается на iterator, определенный с помощью typedef внутри класса vector, содержащего элементы типа string.
 Для того чтобы напечатать все элементы вектора, нужно написать:
 for( ; iter != iter_end; ++iter )
  cout << *iter << '\n';
 Здесь значением *iter выражения является, конечно, элемент вектора.
 В дополнение к типу iterator в каждом контейнере определен тип const_iterator, который необходим для навигации по контейнеру, объявленному как const. const_iterator позволяет только читать элементы контейнера:
 #include
 void even_odd( const vector *pvec,
  vector *pvec_even,
  vector *pvec_odd )
 {
  // const_iterator необходим для навигации по pvec
  vector::const_iterator c_iter = pvec->begin();
  vector::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::iterator iter = vec->begin()+vec.size()/2;
 iter получает значение адреса элемента из середины вектора, а выражение
 iter += 2;
 сдвигает iter на два элемента.
 Арифметические действия с итераторами возможны только для контейнеров vector и deque. list не поддерживает адресную арифметику, поскольку его элементы не располагаются в непрерывной области памяти. Следующее выражение к списку неприменимо:
 ilist.begin() + 2;
 так как для перемещения на два элемента необходимо два раза перейти по адресу, содержащемуся в закрытом члене next. У классов vector и deque перемещение на два элемента означает прибавление 2 к указателю на текущий элемент. (Адресная арифметика рассматривается в разделе 3.3.)
 Объект контейнерного типа может быть инициализирован парой итераторов, обозначающих начало и конец последовательности копируемых в новый объект элементов. (Второй итератор должен указывать на элемент, следующий за последним копируемым.) Допустим, есть вектор:
 #include
 #include
 #include
 
 int main()
 {
  vector svec;
  string intext;
  while ( cin >> intext )
  svec.push_back( intext );
 
  // обработать svec ...
 }
 Вот как можно определить новые векторы, инициализируя их элементами первого вектора:
 int main() {
  vector svec;
  // ...
 
  // инициализация svec2 всеми элементами svec
  vector svec2( svec.begin(), svec.end() );
 
  // инициализация svec3 первой половиной svec
  vector::iterator it =
  svec.begin() + svec.size()/2;
  vector svec3 ( svec.begin(), it );
 
  // ...
 }
 Использование специального типа istream_iterator (о нем рассказывается в разделе 12.4.3) упрощает чтение элементов из входного потока в svec:
 #include
 #include
 #include
 
 int mainQ
 {
  // привязка istream_iterator к стандартному вводу
  istream_iterator infile( cin );
 
  // istream_iterator, отмечающий конец потока
  istream_iterator eos;
 
  // инициализация svec элементами, считываемыми из cin;
  vector svec( infile, eos );
 
  // ...
 }
 Кроме итераторов, для задания диапазона значений, инициализирующих контейнер, можно использовать два указателя на массив встроенного типа. Пусть есть следующий массив строк:
 #include
 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::iterator it = ivec.begin();
 (b) list::iterator it = ilist.begin()+2;
 (c) vector::iterator it = &svec[0];
 (d) for ( vector::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 svec( sa, &sa[6] );
 (b) list ilist( ia+4, ia+6 );
 (c) list ilist2( ilist.begin(), ilist.begin()+2 );
 (d) vector ivec( &ia[0], ia+8 );
 (e) list slist( sa+6, sa );
 (f) vector svec2( sa, sa+6 );
 6.6. Операции с последовательными контейнерами
 Функция-член push_back() позволяет добавить единственный элемент в конец контейнера. Но как вставить элемент в произвольную позицию? А целую последовательность элементов? Для этих случаев существуют более общие операции.
 Например, для вставки элемента в начало контейнера можно использовать:
 vector< string > svec;
 list< string > slist;
 string spouse( "Beth" );
 
 slist.insert( slist.begin(), spouse );
 svec.insert( svec.begin(), spouse );
 Первый параметр функции-члена insert() (итератор, адресующий некоторый элемент контейнера) задает позицию, а второй – вставляемое перед этой позицией значение. В примере выше элемент добавляется в начало контейнера. А так можно реализовать вставку в произвольную позицию:
 string son( "Danny" );
 
 list::iterator iter;
 iter = find( slist.begin(), slist.end(), son );
 
 slist.insert( iter, spouse );
 Здесь find() возвращает позицию элемента в контейнере, если элемент найден, либо итератор end(), если ничего не найдено. (Мы вернемся к функции find() в конце следующего раздела.) Как можно догадаться, push_back() эквивалентен следующей записи:
 // эквивалентный вызов: slist.push_back( value );
 slist.insert( slist.end(), value );
 Вторая форма функции-члена insert() позволяет вставить указанное количество одинаковых элементов, начиная с определенной позиции. Например, если мы хотим добавить десять элементов Anna в начало вектора, то должны написать:
 vector svec;
 string anna( "Anna" );
 svec.insert( svec.begin(), 10, anna );
  insert() имеет и третью форму, помогающую вставить в контейнер несколько элементов. Допустим, имеется следующий массив:
 string sarray[4] = { "quasi", "simba", "frollo", "scar" };
 Мы можем добавить все его элементы или только некоторый диапазон в наш вектор строк:
 svec.insert( svec.begin(), sarray, sarray+4 );
 svec.insert( svec.begin() + svec.size()/2,
  sarray+2, sarray+4 );
 Такой диапазон отмечается и с помощью пары итераторов
 // вставляем элементы svec
 // в середину svec_two

<< Пред.           стр. 24 (из 121)           След. >>

Список литературы по разделу