Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Программирование на C / C++ / Ален И. Голуб. Правила программирования на Си и Си++ [pdf]

.pdf
Скачиваний:
237
Добавлен:
02.05.2014
Размер:
5.67 Mб
Скачать

С++ для начинающих

282

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

Можно выделить для вставки подстроку из new_string:

)

string_object получает значение "Mississippi Belle". Если мы хотим вставить все символы new_string, начиная с pos, последний параметр нужно опустить.

string sl( "Mississippi" );

Пусть есть две строки:

string s2( "Annabelle" );

Как получить третью строку со значением "Miss Anna"?

string s3;

// скопируем первые 4 символа s1

Можно использовать функции-члены assign() и append(): s3.assign ( s1, 4 );

// добавим пробел

s3 теперь содержит значение "Miss".

s3 += ' ';

// добавим 4 первых символа s2

Теперь s3 содержит "Miss ". s3.append(s2,4);

s3 получила значение "Miss Anna". То же самое можно сделать короче:

s3.assign(s1,4).append(' ').append(s2,4);

С++ для начинающих

283

Другая форма функции-члена assign() имеет три параметра: второй обозначает позицию начала, а третий длину. Позиции нумеруются с 0. Вот как, скажем, извлечь

string beauty;

// присвоим beauty значение "belle"

"belle" из "Annabelle": 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():

С++ для начинающих

284

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(), которая делает то же, что и операция взятия индекса, но с проверкой. Если индекс выходит за границу, возбуждается исключение

void

mumble( const string &st, int index )

{

try {

char ch = st.at( index ); // ...

}

catch ( std::out_of_range ){...} // ...

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 );

С++ для начинающих

285

возвращает одно из трех значений:

если 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 new_str( "Abstract Data Type" );

несколькими способами. Допустим, как объект string: sentence.replace( position, length, new_str );

Следующий пример иллюстрирует выделение подстроки в new_str:

С++ для начинающих

286

 

#include <string>

 

 

 

 

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].

 

 

С++ для начинающих

287

Упражнение 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:

С++ для начинающих

288

6.12. Строим отображение позиций слов

Вэтом разделе мы построим отображение (map), позволяющее для каждого уникального слова текста сохранить номера строк и колонок, в которых оно встречается. (В следующем разделе мы изучим ассоциативный контейнер set.) В общем случае контейнер set полезен, если мы хотим знать, содержится ли определенный элемент в некотором множестве, а map позволяет связать с каждым из них какую-либо величину.

Вmap хранятся пары ключ/значение. Ключ играет роль индекса для доступа к ассоциированному с ним значению. В нашей программе каждое уникальное слово текста будет служить ключом, а значением станет вектор, содержащий пары (номер строки,

string query( "pickle" ); vector< location > *locat;

// возвращается location<vector>*, ассоциированный с "pickle"

номер колонки). Для доступа применяется оператор взятия индекса. Например: locat = text_map[ query ];

Ключом здесь является строка, а значение имеет тип location<vector>*.

Для использования отображения необходимо включить соответствующий заголовочный файл:

#include <map>

Какие основные действия производятся над ассоциативными контейнерами? Их заполняют элементами или проверяют на наличие определенного элемента. В следующем подразделе мы покажем, как определить пару ключ/значение и как поместить такие пары в контейнер. Далее мы расскажем, как сформулировать запрос на поиск элемента и извлечь значение, если элемент существует.

6.12.1. Определение объекта map и заполнение его элементами

Чтобы определить объект класса map, мы должны указать, как минимум, типы ключа и значения. Например:

map<string,int> word_count;

Здесь задается объект word_count типа map, для которого ключом служит объект типа

class employee;

string, а ассоциированным с ним значением объект типа int. Аналогично map<int,employee*> personnel;

определяет personnel как отображение ключа типа int (уникальный номер служащего) на указатель, адресующий объект класса employee.

С++ для начинающих

289

typedef pair<short,short> location; typedef vector<location> loc;

Для нашей поисковой системы полезно такое отображение: map<string,loc*> text_map;

Поскольку имевшийся в нашем распоряжении компилятор не поддерживал аргументы по умолчанию для параметров шаблона, нам пришлось написать более развернутое

map<string,loc*,

// ключ, значение

less<string>,

// оператор сравнения

allocator>

// распределитель памяти по умолчанию

определение: text_map;

По умолчанию сортировка ассоциативных контейнеров производится с помощью операции меньше”. Однако можно указать и другой оператор сравнения (см. раздел 12.3 об объектах-функциях).

После того как отображение определено, необходимо заполнить его парами

#include <map> #include <string>

map<string,int> word_count;

word_count[ string("Anna") ] = 1; word_count[ string("Danny") ] = 1; word_count[ string("Beth") ] = 1;

ключ/значение. Интуитивно хочется написать примерно так:

// и так далее ...

Когда мы пишем:

word_count[ string("Anna") ] = 1;

на самом деле происходит следующее:

1.Безымянный временный объект типа string инициализируется значением "Anna" и передается оператору взятия индекса, определенному в классе map.

2.Производится поиск элемента с ключом "Anna" в массиве word_count. Такого элемента нет.

3.В word_count вставляется новая пара ключ/значение. Ключом является, естественно, строка "Anna". Значением 0, а не 1.

4.После этого значению присваивается величина 1.

С++ для начинающих

290

Если элемент отображения вставляется в отображение с помощью операции взятия индекса, то значением этого элемента становится значение по умолчанию для его типа данных. Для встроенных арифметических типов 0.

Следовательно, если инициализация отображения производится оператором взятия индекса, то каждый элемент сначала получает значение по умолчанию, а затем ему явно присваивается нужное значение. Если элементы являются объектами класса, у которого

инициализация по умолчанию и присваивание значения требуют больших затрат времени, программа будет работать правильно, но недостаточно эффективно.

// предпочтительный метод вставки одного элемента word_count.insert(

map<string,i nt>::

value_type( string("Anna"), 1 )

Для вставки одного элемента предпочтительнее использовать следующий метод:

);

В контейнере map определен тип value_type для представления хранимых в нем пар

map< string,int >::

ключ/значение. Строки

value_type( string("Anna"), 1 )

создают объект pair, который затем непосредственно вставляется в map. Для удобства чтения можно использовать typedef:

typedef map<string,int>::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());

Мы могли бы сделать то же самое, просто проинициализировав одно отображение другим:

С++ для начинающих

291

// инициализируем копией всех пар ключ/значение

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<string,loc*> *word_map =

new map< string, loc* >;

vector<string> *text_words

= text_locations->first;

указатели на векторы:

vector<location> *text_locs = text_locations->second;

Теперь нам надо синхронно обойти оба вектора, учитывая два случая:

слово встретилось впервые. Нужно поместить в map новую пару ключ/значение;

слово встречается повторно. Нам нужно обновить вектор позиций, добавив дополнительную пару (номер строки, номер колонки).

Вот текст функции: