При створенні рядка не завадило б вказати його довжину і ємність (або хоча б знати ці параметри).
Довжина std::string
Довжина рядка — це кількість символів, які містить рядок. Є дві ідентичні функції для визначення довжини рядка:
size_type string::length() const
size_type string::size() const
Ці функції повертають поточну кількість символів, які містить рядок, без врахування нуль-термінатора. Наприклад:
1 2 3 4 5 6 7 8 9 |
#include <iostream> int main() { std::string sSomething("012345"); std::cout << sSomething.length() << std::endl; return 0; } |
Результат:
6
Хоча також можна використовувати функцію length() для визначення того, чи містить рядок взагалі якісь символи чи ні, ефективніше використовувати функцію empty():
bool string::empty() const — ця функція повертає true
, якщо в рядку немає символів, і false
— в протилежному випадку.
Наприклад:
1 2 3 4 5 6 7 8 9 10 11 |
#include <iostream> int main() { std::string sString1("Not Empty"); std::cout << (sString1.empty() ? "true" : "false") << std::endl; std::string sString2; // пустий рядок std::cout << (sString2.empty() ? "true" : "false") << std::endl; return 0; } |
Результат:
false
true
Є ще одна функція, яка пов’язана з довжиною рядка, яку ви, ймовірно, ніколи не будете використовувати, але ми все одно її розглянемо:
size_type string::max_size() const — ця функція повертає максимальну кількість символів, які може містити рядок. Це значення може варіюватися в залежності від операційної системи і архітектури операційної системи.
Наприклад:
1 2 3 4 5 6 7 |
#include <iostream> int main() { std::string sString("MyString"); std::cout << sString.max_size() << std::endl; } |
Результат:
Ємність std::string
Ємність рядка — це максимальний обсяг пам’яті, виділений рядку для зберігання свого вмісту. Це значення вимірюється в символах рядка без врахування нуль-термінатора. Наприклад, рядок з ємністю 8 може містити до 8 символів.
size_type string::capacity() const — ця функція повертає кількість символів, які може зберігати рядок без додаткового перерозподілу/перевиділення пам’яті.
Наприклад:
1 2 3 4 5 6 7 8 9 10 |
#include <iostream> int main() { std::string sString("0123456789"); std::cout << "Length: " << sString.length() << std::endl; std::cout << "Capacity: " << sString.capacity() << std::endl; return 0; } |
Результат:
Length: 10
Capacity: 15
Примітка: Запускати цю та наступні програми слід в повноцінних IDE, а не в веб-компіляторах.
Зверніть увагу, ємність рядка більше його довжини! Хоча довжина нашого рядка дорівнює 10, пам’яті для нього виділено аж на 15 символів! Чому так?
Тут важливо розуміти, що, якщо користувач захоче помістити в рядок більше символів, ніж він може вмістити, то рядок буде перерозподілено і, відповідно, ємність стане більшою. Наприклад, якщо рядок має довжину і ємність рівну 10, то додавання нових символів в рядок призведе до його перерозподілу. Роблячи ємність рядка більше його довжини, ми надаємо користувачеві деякий буферний простір для розширення рядка (додавання нових символів).
Але в перерозподілі є також кілька нюансів:
По-перше, це (відносно) затратно. Спочатку повинна бути виділена нова пам’ять. Потім кожен символ рядка копіюється в нову пам’ять. Якщо рядок великий, то витрачається багато часу. Нарешті, стара пам’ять повинна бути видалена/звільнена. Якщо ви робите багато перерозподілів, то цей процес може значно знизити продуктивність вашої програми.
По-друге, всякий раз, коли рядок перерозподіляється, його вміст отримує нову адресу в пам’яті. Це означає, що всі поточні посилання, вказівники і ітератори рядка стають недійсними!
Зверніть увагу, не завжди рядки створюються з ємністю, яка перевищує їх довжину. Розглянемо наступну програму:
1 2 3 4 5 6 7 8 9 10 |
#include <iostream> int main() { std::string sString("0123456789abcde"); std::cout << "Length: " << sString.length() << std::endl; std::cout << "Capacity: " << sString.capacity() << std::endl; return 0; } |
Результат:
Length: 15
Capacity: 15
Примітка: Результати можуть відрізнятися в залежності від компілятора.
Тепер давайте додамо ще один символ в кінець рядка і подивимося на зміну його ємності:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
#include <iostream> int main() { std::string sString("0123456789abcde"); std::cout << "Length: " << sString.length() << std::endl; std::cout << "Capacity: " << sString.capacity() << std::endl; // Додаємо новий символ sString += "f"; std::cout << "Length: " << sString.length() << std::endl; std::cout << "Capacity: " << sString.capacity() << std::endl; return 0; } |
Результат:
Length: 15
Capacity: 15
Length: 16
Capacity: 31
Є ще одна функція (а точніше 2 варіанти цієї функції) для роботи з ємністю рядка:
void string::reserve(size_type unSize) — при виклику цієї функції ми встановлюємо ємність рядка, рівну, як мінімум, unSize
(вона може бути і більшою). Зверніть увагу, для виконання цієї функції може знадобитися перерозподіл.
void string::reserve() — якщо викликається ця функція або вищенаведена функція з unSize
менше поточної ємності, то компілятор спробує зрізати (зменшити) ємність рядка до розміру його довжини. Це необов’язковий запит.
Наприклад:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
#include <iostream> int main() { std::string sString("0123456789"); std::cout << "Length: " << sString.length() << std::endl; std::cout << "Capacity: " << sString.capacity() << std::endl; sString.reserve(300); std::cout << "Length: " << sString.length() << std::endl; std::cout << "Capacity: " << sString.capacity() << std::endl; sString.reserve(); std::cout << "Length: " << sString.length() << std::endl; std::cout << "Capacity: " << sString.capacity() << std::endl; return 0; } |
Результат:
Length: 10
Capacity: 15
Length: 10
Capacity: 303
Length: 10
Capacity: 303
Тут ми можемо спостерігати дві цікаві речі. По-перше, хоча ми запросили ємність рівну 300, ми фактично отримали 303. Ємність завжди буде не менше, ніж ми просимо (але може бути і більше). Потім ми відсилаємо запит на зміну ємності відповідно рядку. Цей запит був проігнорований, тому що очевидно, що ємність не змінилася.
Якщо ви заздалегідь знаєте, що вам потрібен більший рядок, тому що ви виконуватимете з ним безліч операцій, які потенційно можуть збільшити його довжину або ємність, то ви можете уникнути перерозподілів, відразу встановивши рядку його остаточну ємність:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
#include <iostream> #include <string> #include <cstdlib> // для rand() і srand() #include <ctime> // для time() int main() { srand(time(0)); // генерація випадкового числа std::string sString; // довжина 0 sString.reserve(80); // резервуємо 80 символів // Заповнюємо рядок випадковими рядковими символами for (int nCount = 0; nCount < 80; ++nCount) sString += 'a' + rand() % 26; std::cout << sString; } |
Результат цієї програми змінюватиметься при кожному її новому запуску:
tregsxxmselsqlfoahsvsxfmfwurcmmjclfcqqgzkzohztirriibto
Замість того, щоб перерозподіляти sString
кілька разів, ми встановлюємо його ємність один раз, а потім просто заповнюємо даними.