22.3 – Длина и емкость std::string
После того, как вы создали строку, часто бывает полезно узнать ее длину. Здесь в игру вступают операции с длиной и емкостью.
Длина строки
Длина строки – это довольно просто, это количество символов в строке. Для определения длины строки есть две идентичные функции:
size_type string::length() const
size_type string::size() const
size_type string::size() const
Обе эти функции возвращают текущее количество символов в строке, исключая завершающий ноль.
Пример кода:
string sSource("012345678");
cout << sSource.length() << endl;
Вывод:
9
Хотя, чтобы определить, есть ли в строке какие-либо символы или нет, можно использовать length()
, но более эффективно использовать функцию empty()
:
bool string::empty() const
Возвращает true
, если в строке нет символов, иначе – false
.
Пример кода:
string sString1("Not Empty");
cout << (sString1.empty() ? "true" : "false") << endl;
string sString2; // пустая
cout << (sString2.empty() ? "true" : "false") << endl;
Вывод:
false
true
Есть еще одна функция, связанная с размером, которую вы, вероятно, никогда не будете использовать, но мы опишем ее здесь для полноты картины:
size_type string::max_size() const
Возвращает максимальное количество символов, которое может содержать строка. Это значение будет варьироваться в зависимости от операционной системы и архитектуры системы.
Пример кода:
string sString("MyString");
cout << sString.max_size() << endl;
Вывод:
4294967294
Емкость строки
Емкость (вместимость) строки показывает, сколько памяти выделено объектом строки для хранения ее содержимого. Это значение измеряется в строковых символах, исключая символ завершающего нуля. Например, строка с емкостью 8 может содержать 8 символов.
size_type string::capacity() const
Возвращает количество символов, которое строка может хранить без перераспределения памяти.
Пример кода:
string sString("01234567");
cout << "Length: " << sString.length() << endl;
cout << "Capacity: " << sString.capacity() << endl;
Вывод:
Length: 8
Capacity: 15
Обратите внимание, что емкость больше, чем длина строки! Хотя длина нашей строки равна 8, на самом деле она занимала достаточно памяти для 15 символов! Зачем так сделано?
Здесь важно понимать, что если пользователь хочет поместить в строку больше символов, чем позволяет ее емкость, то для получения большей емкости строка должна быть перераспределена в памяти. Например, если строка имеет длину и емкость 8, то добавление любых символов в строку приведет к переразмещению объекта в памяти. Сделав емкость больше размера фактической строки, пользователь получил некоторое буферное пространство для расширения строки до необходимости перераспределения.
Как оказалось, перераспределение – это плохо по нескольким причинам:
Во-первых, перераспределение строки относительно дорого. Сначала необходимо выделить новую память. Затем каждый символ в строке необходимо скопировать в новую память. Если строка большая, это может занять много времени. Наконец, необходимо освободить старую память. Если вы выполняете много перераспределений, этот процесс может значительно замедлить работу вашей программы.
Во-вторых, всякий раз, когда строка перераспределяется, адрес содержимого строки в памяти изменяется на новое значение. Это означает, что все ссылки, указатели и итераторы строки становятся недействительными!
Обратите внимание, что строки не всегда размещаются с емкостью, превышающей длину. Рассмотрим следующую программу:
string sString("0123456789abcde");
cout << "Length: " << sString.length() << endl;
cout << "Capacity: " << sString.capacity() << endl;
Эта программа выводит:
Length: 15
Capacity: 15
Результаты могут отличаться в зависимости от компилятора.
Давайте добавим к строке один символ и посмотрим, как изменится емкость:
string sString("0123456789abcde");
cout << "Length: " << sString.length() << endl;
cout << "Capacity: " << sString.capacity() << endl;
// Теперь добавим новый символ
sString += "f";
cout << "Length: " << sString.length() << endl;
cout << "Capacity: " << sString.capacity() << endl;
Это дает следующий результат:
Length: 15
Capacity: 15
Length: 16
Capacity: 31
void string::reserve()
void string::reserve(size_type unSize)
void string::reserve(size_type unSize)
Второй вариант этой функции устанавливает емкость строки минимум на unSize
(она может быть больше). Обратите внимание, что для этого может потребоваться перераспределение.
Если вызывается первый вариант этой функции или второй вариант вызывается с unSize
, меньшим, чем текущая емкость, функция попытается уменьшить емкость, чтобы она соответствовала длине. Этот запрос необязателен для выполнения.
Пример кода:
string sString("01234567");
cout << "Length: " << sString.length() << endl;
cout << "Capacity: " << sString.capacity() << endl;
sString.reserve(200);
cout << "Length: " << sString.length() << endl;
cout << "Capacity: " << sString.capacity() << endl;
sString.reserve();
cout << "Length: " << sString.length() << endl;
cout << "Capacity: " << sString.capacity() << endl;
Вывод:
Length: 8
Capacity: 15
Length: 8
Capacity: 207
Length: 8
Capacity: 207
В этом примере показаны две интересные вещи. Во-первых, хотя мы запросили емкость 200, на самом деле мы получили емкость 207. Гарантируется, что емкость всегда будет не меньше, чем вы запросили, но может быть и больше. Затем мы запросили изменение емкости, чтобы она соответствовала длине строки. Этот запрос был проигнорирован, так как емкость не изменилась.
Если вы заранее знаете, что собираетесь создать большую строку, выполняя множество строковых операций, которые увеличивают размер строки, вы можете избежать многократного перераспределения строки в памяти, сразу установив для строки необходимую ей емкость:
#include <iostream>
#include <string>
#include <cstdlib> // для rand() и srand()
#include <ctime> // для time()
using namespace std;
int main()
{
std::srand(std::time(nullptr)); // инициализация генератора случайных чисел
string sString{}; // длина 0
sString.reserve(64); // резервируем 64 символа
// Заполняем строку случайными строчными буквами
for (int nCount{ 0 }; nCount < 64; ++nCount)
sString += 'a' + std::rand() % 26;
cout << sString;
}
Результат этой программы будет меняться каждый раз. Вот результат одного выполнения:
wzpzujwuaokbakgijqdawvzjqlgcipiiuuxhyfkdppxpyycvytvyxwqsbtielxpy
Вместо того чтобы перераспределять sString
несколько раз, мы устанавливаем емкость один раз, а затем заполняем строку. Это может очень влиять на производительность при формировании больших строк с помощью конкатенации.