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

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

 string::size_type spos = 0;
 string::size_type pos3 = word.size()-3;
 
 // "ous", "ss", "is", "ius"
 string suffixes( "oussisius" );
 
 if ( ! word.compare( pos3, 3, suffixes, spos, 3 ) || // ous
  ! word.compare( pos3, 3, suffixes, spos+6, 3 ) || // ius
  ! word.compare( pos3+l, 2, suffixes, spos+2, 2 ) || // ss
  ! word.compare( pos3+l, 2, suffixes, spos+4, 2 ) ) // is
  return;
 В противном случае удалим последнее 's':
 // удалим последнее 's'
 word.erase( pos3+2 );
 Имена собственные, например Pythagoras, Brahms, Burne-Jones, не подпадают под общие правила. Этот случай мы оставим как упражнение для читателя, когда будем рассказывать об ассоциативных контейнерах.
 Но прежде чем перейти к ним, рассмотрим оставшиеся строковые операции.
 Упражнение 6.17
 Наша программа не умеет обрабатывать суффиксы ed (surprised), ly (surprisingly) и ing (surprisingly). Реализуйте одну из функций для этого случая:
 (a) suffix_ed() (b) suffix_ly() (c) suffix_ing()
 
 6.11. Дополнительные операции со строками
 Вторая форма функции-члена erase() принимает в качестве параметров два итератора, ограничивающих удаляемую подстроку. Например, превратим
 string name( "AnnaLiviaPlurabelle" );
 в строку "Annabelle":
 typedef string::size_type size_type;
 size_type startPos = name.find( 'L' )
 size_type endPos = name.find_1ast_of( 'b' );
 
 name.erase( name.begin()+startPos,
  name.begin()+endPos );
 Символ, на который указывает второй итератор, не входит в удаляемую подстроку.
 Для третьей формы параметром является только один итератор; эта форма удаляет все символы, начиная с указанной позиции до конца строки. Например:
 name.erase( name. begin()+4 );
 оставляет строку "Anna".
 Функция-член insert() позволяет вставить в заданную позицию строки другую строку или символ. Общая форма выглядит так:
 string_object.insert( position, new_string );
 position обозначает позицию, перед которой производится вставка. new_string может быть объектом класса string, C-строкой или символом:
 string string_object( "Missisippi" );
 string::size_type pos = string_object.find( "isi" );
 string_object.insert( pos+1, 's' );
 Можно выделить для вставки подстроку из new_string:
 string new_string ( "AnnaBelle Lee" );
 string_object += ' '; // добавим пробел
 
 // найдем начальную и конечную позицию в new_string
 pos = new_string.find( 'B' );
 string::size_type posEnd = new_string.find( ' ' );
 
 string_object.insert(
  string_object.size(), // позиция вставки
  new_string, pos, // начало подстроки в new_string
  posEnd // конец подстроки new_string
 )
 string_object получает значение "Mississippi Belle". Если мы хотим вставить все символы new_string, начиная с pos, последний параметр нужно опустить.
 Пусть есть две строки:
 string sl( "Mississippi" );
 string s2( "Annabelle" );
 Как получить третью строку со значением "Miss Anna"?
 Можно использовать функции-члены assign() и append():
 string s3;
 
 // скопируем первые 4 символа s1
 s3.assign ( s1, 4 );
 s3 теперь содержит значение "Miss".
 // добавим пробел
 s3 += ' ';
 Теперь s3 содержит "Miss ".
 // добавим 4 первых символа s2
 s3.append(s2,4);
 s3 получила значение "Miss Anna". То же самое можно сделать короче:
 s3.assign(s1,4).append(' ').append(s2,4);
 Другая форма функции-члена assign() имеет три параметра: второй обозначает позицию начала, а третий – длину. Позиции нумеруются с 0. Вот как, скажем, извлечь "belle" из "Annabelle":
 string beauty;
 
 // присвоим beauty значение "belle"
 beauty.assign( s2, 4, 5 );
 Вместо этих параметров мы можем использовать пару итераторов:
 // присвоим beauty значение "belle"
 beauty.assign( s2, s2.begin()+4, s2.end() );
 В следующем примере две строки содержат названия текущего проекта и проекта, находящегося в отложенном состоянии. Они должны периодически обмениваться значениями, поскольку работа идет то над одним, то над другим. Например:
 string current_project( "C++ Primer, 3rd Edition" );
 string pending_project( "Fantasia 2000, Firebird segment" );
 Функция-член swap() позволяет обменять значения двух строк с помощью вызова
 current_project.swap( pending_project );
 Для строки
 string first_novel( "V" );
 операция взятия индекса
 char ch = first_novel[ 1 ];
 возвратит неопределенное значение: длина строки first_novel равна 1, и единственное правильное значение индекса – 0. Такая операция взятия индекса не обеспечивает проверку правильности параметра, но мы всегда можем сделать это сами с помощью функции-члена size():
 int
 elem_count( const string &word, char elem )
 {
  int occurs = 0;
 
  // не надо больше проверять ix
  for ( int ix=0; ix < word.size(); ++-ix )
  if ( word[ ix ] == elem )
  ++occurs;
  return occurs;
 }
 Там, где это невозможно или нежелательно, например:
 void
 mumble( const string &st, int index )
 {
  // возможна ошибка
  char ch = st[ index ];
 
  // ...
 }
 следует воспользоваться функцией at(), которая делает то же, что и операция взятия индекса, но с проверкой. Если индекс выходит за границу, возбуждается исключение out_of_range:
 void
 mumble( const string &st, int index )
 {
  try {
  char ch = st.at( index );
  // ...
  }
  catch ( std::out_of_range ){...}
  // ...
 }
 Строки можно сравнивать лексикографически. Например:
 string cobol_program_crash( "abend" );
 string cplus_program_crash( "abort" );
 Строка cobol_program_crash лексикографически меньше, чем cplus_program_crash: сопоставление производится по первому отличающемуся символу, а буква e в латинском алфавите идет раньше, чем o. Операция сравнения выполняется функцией-членом compare(). Вызов
 sl.compare( s2 );
 возвращает одно из трех значений:
 если s1 больше, чем s2, то положительное;
 если s1 меньше, чем s2, то отрицательное;
 если s1 равно s2, то 0.
 Например,
 cobol_program_crash.compare( cplus_program_crash );
 вернет отрицательное значение, а
 cplus_program_crash.compare( cobol_program_crash );
 положительное. Перегруженные операции сравнения (<, >, !=, ==, <=, >=) являются более компактной записью функции compare().
 Шесть вариантов функции-члена compare() позволяют выделить сравниваемые подстроки в одном или обоих операндах. (Примеры вызовов приводились в предыдущем разделе.)
 Функция-член replace() дает десять способов заменить одну подстроку на другую (их длины не обязаны совпадать). В двух основных формах replace() первые два аргумента задают заменяемую подстроку: в первом варианте в виде начальной позиции и длины, во втором – в виде пары итераторов на ее начало и конец. Вот пример первого варианта:
 string sentence(
  "An ADT provides both interface and implementation." );
 
 string::size_type position = sentence.find_1ast_of( 'A' );
 string::size_type length = 3;
 
 // заменяем ADT на Abstract Data Type
 sentence.repiace( position, length, "Abstract Data Type" );
 position представляет собой начальную позицию, а length – длину заменяемой подстроки. Третий аргумент является подставляемой строкой. Его можно задать несколькими способами. Допустим, как объект string:
 string new_str( "Abstract Data Type" );
 sentence.replace( position, length, new_str );
 Следующий пример иллюстрирует выделение подстроки в new_str:
 #include
 typedef string::size_type size_type;
 
 // найдем позицию трех букв
 size_type posA = new_str.find( 'A' );
 size_type posD = new_str.find( 'D' );
 size_type posT = new_str.find( 'T' );
 
 // нашли: заменим T на "Type"
 sentence.replace( position+2, 1, new_str, posT, 4 );
 
 // нашли: заменим D на "Data "
 sentence.replace( position+1, 1, new_str, posD, 5 );
 
 // нашли: заменим A на "Abstract "
 sentence.replace( position, 1, new_str, posA, 9 );
 Еще один вариант позволяет заменить подстроку на один символ, повторенный заданное количество раз:
 string hmm( "Some celebrate Java as the successor to C++." );
 string:: size_type position = hmm.find( 'J' );
 // заменим Java на xxxx
 hmm.repiace( position, 4, 'x', 4 );
 В данном примере используется указатель на символьный массив и длина вставляемой подстроки:
 const char *lang = "EiffelAda95JavaModula3";
 int index[] = { 0, 6, 11, 15, 22 };
 
 string ahhem(
  "C++ is the language for today's power programmers." );
 ahhem.replace(0, 3, lang+index[1], index[2]-index[1]);
 А здесь мы используем пару итераторов:
 string sentence(
  "An ADT provides both interface and implementation." );
 
 // указывает на 'A' в ADT
 string: iterator start = sentence. begin()+3;
 
 // заменяем ADT на Abstract Data Type
 sentence.repiace( start, start+3, "Abstract Data Type" );
 Оставшиеся четыре варианта допускают задание заменяющей строки как объекта типа string, символа, повторяющегося N раз, пары итераторов и C-строки.
 Вот и все, что мы хотели сказать об операциях со строками. Для более полной информации обращайтесь к определению стандарта С++ [ISO-C++97].
 Упражнение 6.18
 Напишите программу, которая с помощью функций-членов assign() и append() из строк
 string quote1( "When lilacs last in the dooryard bloom'd" );
 string quote2( "The child "is father of the man" );
 составит предложение
 "The child is in the dooryard"
 Упражнение 6.19
 Напишите функцию:
 string generate_salutation( string generic1,
  string lastname,
  string generic2,
  string::size_type pos,
  int length );
 которая в строке
 string generic1( "Dear Ms Daisy:" );
 заменяет Daisy и Ms (миссис). Вместо Daisy подставляется параметр lastname, а вместо Ms подстрока
 string generic2( "MrsMsMissPeople" );
 длины length, начинающаяся с pos.
 Например, вызов
 string lastName( "AnnaP" );
 string greetings =
  generate_salutation( generici, lastName, generic2, 5, 4 );
 вернет строку:
 
 Dear Miss AnnaP:
 
 6.12. Строим отображение позиций слов
 В этом разделе мы построим отображение (map), позволяющее для каждого уникального слова текста сохранить номера строк и колонок, в которых оно встречается. (В следующем разделе мы изучим ассоциативный контейнер set.) В общем случае контейнер set полезен, если мы хотим знать, содержится ли определенный элемент в некотором множестве, а map позволяет связать с каждым из них какую-либо величину.
 В map хранятся пары ключ/значение. Ключ играет роль индекса для доступа к ассоциированному с ним значению. В нашей программе каждое уникальное слово текста будет служить ключом, а значением станет вектор, содержащий пары (номер строки, номер колонки). Для доступа применяется оператор взятия индекса. Например:
 string query( "pickle" );
 vector< location > *locat;
 
 // возвращается location*, ассоциированный с "pickle"
 locat = text_map[ query ];
 Ключом здесь является строка, а значение имеет тип location*.
 Для использования отображения необходимо включить соответствующий заголовочный файл:
 #include
 Какие основные действия производятся над ассоциативными контейнерами? Их заполняют элементами или проверяют на наличие определенного элемента. В следующем подразделе мы покажем, как определить пару ключ/значение и как поместить такие пары в контейнер. Далее мы расскажем, как сформулировать запрос на поиск элемента и извлечь значение, если элемент существует.
 6.12.1. Определение объекта map и заполнение его элементами
 Чтобы определить объект класса map, мы должны указать, как минимум, типы ключа и значения. Например:
 map word_count;
 Здесь задается объект word_count типа map, для которого ключом служит объект типа string, а ассоциированным с ним значением – объект типа int. Аналогично
 class employee;
 map personnel;
 определяет personnel как отображение ключа типа int (уникальный номер служащего) на указатель, адресующий объект класса employee.
 Для нашей поисковой системы полезно такое отображение:
 typedef pair location;
 typedef vector loc;
 
 map text_map;
 Поскольку имевшийся в нашем распоряжении компилятор не поддерживал аргументы по умолчанию для параметров шаблона, нам пришлось написать более развернутое определение:
 map   less, // оператор сравнения
  allocator> // распределитель памяти по умолчанию
 text_map;
 По умолчанию сортировка ассоциативных контейнеров производится с помощью операции “меньше”. Однако можно указать и другой оператор сравнения (см. раздел 12.3 об объектах-функциях).
 После того как отображение определено, необходимо заполнить его парами ключ/значение. Интуитивно хочется написать примерно так:
 #include
 #include
 map word_count;
 
 word_count[ string("Anna") ] = 1;
 word_count[ string("Danny") ] = 1;
 word_count[ string("Beth") ] = 1;
 
 // и так далее ...
 Когда мы пишем:
 word_count[ string("Anna") ] = 1;
 на самом деле происходит следующее:
 Безымянный временный объект типа string инициализируется значением "Anna" и передается оператору взятия индекса, определенному в классе map.
 Производится поиск элемента с ключом "Anna" в массиве word_count. Такого элемента нет.
 В word_count вставляется новая пара ключ/значение. Ключом является, естественно, строка "Anna". Значением – 0, а не 1.
 После этого значению присваивается величина 1.
 Если элемент отображения вставляется в отображение с помощью операции взятия индекса, то значением этого элемента становится значение по умолчанию для его типа данных. Для встроенных арифметических типов – 0.
 Следовательно, если инициализация отображения производится оператором взятия индекса, то каждый элемент сначала получает значение по умолчанию, а затем ему явно присваивается нужное значение. Если элементы являются объектами класса, у которого инициализация по умолчанию и присваивание значения требуют больших затрат времени, программа будет работать правильно, но недостаточно эффективно.
 Для вставки одного элемента предпочтительнее использовать следующий метод:
 // предпочтительный метод вставки одного элемента
 word_count.insert(
  map::
  value_type( string("Anna"), 1 )
 );
 В контейнере map определен тип value_type для представления хранимых в нем пар ключ/значение. Строки
 map< string,int >::
  value_type( string("Anna"), 1 )
 создают объект pair, который затем непосредственно вставляется в map. Для удобства чтения можно использовать typedef:
 typedef map::value_type valType;
 Теперь операция вставки выглядит проще:
 word_count.insert( valType( string("Anna"), 1 ));
 Чтобы вставить элементы из некоторого диапазона, можно использовать метод insert(), принимающий в качестве параметров два итератора. Например:
 map< string, int > word_count;
 // ... заполнить
 
 map< string,int > word_count_two;
 
 // скопируем все пары ключ/значение
 word_count_two.insert(word_count.begin(),word_count.end());
 Мы могли бы сделать то же самое, просто проинициализировав одно отображение другим:
 // инициализируем копией всех пар ключ/значение
 map< string, int > word_count_two( word_count );
 Посмотрим, как можно построить отображение для хранения нашего текста. Функция separate_words(), описанная в разделе 6.8, создает два объекта: вектор строк, хранящий все слова текста, и вектор позиций, хранящий пары (номер строки, номер колонки) для каждого слова. Таким образом, первый объект дает нам множество значений ключей нашего отображения, а второй – множество ассоциированных с ними значений.
 separate_words() возвращает эти два вектора как объект типа pair, содержащий указатели на них. Сделаем эту пару аргументом функции build_word_map(), в результате которой будет получено соответствие между словами и позициями:
 // typedef для удобства чтения
 typedef pair< short,short > location;
 typedef vector< location > loc;
 typedef vector< string > text;
 typedef pair< text*,loc* > text_loc;
 
 extern map< string, loc* >*
  build_word_map( const text_loc *text_locations );
 Сначала выделим память для пустого объекта map и получим из аргумента-пары указатели на векторы:
 map *word_map = new map< string, loc* >;
 vector *text_words = text_locations->first;
 vector *text_locs = text_locations->second;

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

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