Литмир - Электронная Библиотека
A
A

20.7. Классы vector, list и string

Почему для хранения строк мы используем класс

list
, а для символов — класс
vector
? Точнее, почему для хранения последовательности строк мы используем класс
list
, а для хранения последовательности символов — класс
vector
? Более того, почему для хранения строки мы не используем класс
string
?

Сформулируем немного более общий вариант этого вопроса. Для хранения последовательности символов у нас есть четыре способа.

char[]
(массив символов)

vector<char>

string

list<char>

 

Программирование. Принципы и практика использования C++ Исправленное издание - _002.png
 Какой из этих вариантов выбрать для решения конкретной задачи? Для действительно простой задачи все эти варианты являются взаимозаменяемыми; иначе говоря, у них очень похожие интерфейсы. Например, имея итератор, мы можем перемещаться по элементам с помощью операции
++
и использовать оператор
*
для доступа к символам. Если посмотреть на примеры кода, связанного с классом Document, то мы действительно можем заменить наш класс
vector<char>
классом
list<char>
или
string
без каких-либо проблем. Такая взаимозаменяемость является фундаментальным преимуществом, потому что она позволяет нам сделать выбор, ориентируясь на эффективность. Но, перед тем как рассматривать вопросы эффективности, мы должны рассмотреть логические возможности этих типов: что такого может делать каждый из них, чего не могут другие?

Elem[]
. Не знает своего размера. Не имеет функций
begin()
,
end()
и других контейнерных функций-членов. Не может систематически проверять выход за пределы допустимого диапазона. Может передаваться функциям, написанным на языке C или в стиле языка C. Элементы в памяти располагаются последовательно в смежных ячейках. Размер массива фиксируется на этапе компиляции. Операции сравнения (
==
и
!=
) и вывода (
<<
) используют указатель на первый элемент массива, а не на все элементы.

vector<Elem>
. Может выполнять практически все, включая функции
insert()
и
erase()
. Предусматривает индексирование. Операции над списками, такие как
insert()
и
erase()
, как правило, связаны с перемещением элементов (что может оказаться неэффективным для крупных элементов и при большом количестве элементов). Может проверять выход за пределы допустимого диапазона. Элементы в памяти располагаются последовательно в смежных ячейках. Объект класса
vector
может увеличиваться (например, использует функцию
push_back()
). Элементы вектора хранятся в массиве (непрерывно). Сравнение элементов осуществляется с помощью операторов
==
,
!=
,
<
,
<=
,
>
и
>=
.

string
. Предусматривает все обычные и полезные операции, а также специфические манипуляции текстами, такие как конкатенация (
+
и
+=
). Элементы хранятся в смежных ячейках памяти. Объект класса
string
можно увеличивать. Сравнение элементов осуществляется с помощью операторов
==
,
!=
,
<
,
<=
,
>
и
>=
.

list<Elem>
. Предусматривает все обычные и полезные операции, за исключением индексирования. Операции
insert()
и
delete()
можно выполнять без перемещения остальных элементов. Для хранения каждого элемента необходимы два дополнительных слова (для указателей на узлы). Объект класса
list
можно увеличивать. Сравнение элементов осуществляется с помощью операторов (
==
,
!=
,
<
,
<=
,
>
и
>=
).

Как мы уже видели (см. разделы 17.2 и 20.5), массивы полезны и необходимы для управления памятью на самом нижнем уровне, а также для обеспечения взаимодействия с программами, написанными на языке C (подробнее об этом — в разделах 27.1.2 и 27.5). В отличие от этого, класс

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

ПОПРОБУЙТЕ

Что означает этот список отличий в реальном коде? Определите массивы объектов типа

char
,
vector<char>
,
list<char>
и
string
со значением "
Hello
", передайте его в функцию в качестве аргумента, напишите количество символов в передаваемой строке, попытайтесь сравнить его со строкой "
Hello
" в функции (чтобы убедиться, что вы действительно передали строку "
Hello
"), а затем сравните аргумент со строкой "
Howdy
", чтобы увидеть, какое из этих слов появляется в словаре первым. Скопируйте аргумент в другую переменную того же типа.

ПОПРОБУЙТЕ

Выполните предыдущее задание ПОПРОБУЙТЕ для массива объектов типа

int
,
vector<int>
и
list<int>
со значениями {
1
,
2
,
3
,
4
,
5
 } . 

20.7.1. Операции insert и erase

 

Программирование. Принципы и практика использования C++ Исправленное издание - _001.png
 В качестве контейнера по умолчанию используется стандартный класс vector. Он имеет большинство желательных свойств, поэтому альтернативу следует использовать только при необходимости. Его основной недостаток заключается в том, что при выполнении операций, характерных для списка (
insert()
и
erase()
), в векторе происходит перемещение остальных элементов; это может оказаться связано с неприемлемыми затратами, если вектор содержит большое количество элементов или элементы вектора сами являются крупными объектами. Однако слишком беспокоиться об этом не следует. Мы без заметных проблем считали полмиллиона значений с плавающей точкой в вектор, используя функцию
push_back()
. Измерения подтвердили, что предварительное выделение памяти не приводит к заметным последствиям. Прежде чем вносить значительные изменения, стремясь к эффективности, проведите измерения (угадать степень эффективности кода трудно даже экспертам).

280
{"b":"847443","o":1}