Po utworzeniu ciągów znaków często warto wiedzieć, jak długie są. Tutaj w grę wchodzą operacje na długości i pojemności. Omówimy także różne sposoby konwersji std::string z powrotem na ciągi w stylu C, dzięki czemu można ich używać z funkcjami, które oczekują ciągów typu char*.
Długość ciągu
Długość ciągu jest dość prosta — jest to liczba znaków w ciągu. Istnieją dwie identyczne funkcje do określania długości łańcucha:
| size_type string::length() const size_type string::size() const
Przykładowy kod: Wyjście: 9 |
Chociaż możliwe jest użycie funkcjilength() do określenia, czy ciąg znaków zawiera jakieś znaki, czy nie, bardziej efektywne jest użyj funkcji pusty():
bool string::empty() const
Przykładowy kod: Wyjście: false true |
Jest jeszcze jedna funkcja związana z rozmiarem, której prawdopodobnie nigdy nie użyjesz, ale uwzględnimy ją tutaj dla kompletności:
size_type string::max_size() const
Przykładowy kod: Wyjście: 4294967294 |
Pojemność ciągu
Pojemność ciągu odzwierciedla ilość pamięci przydzielonej przez ciąg do przechowywania jego zawartości. Wartość ta jest mierzona w znakach ciągu, z wyłączeniem terminatora NULL. Na przykład ciąg znaków o pojemności 8 może pomieścić 8 znaków.
size_type string::capacity() const
Przykładowy kod: Wyjście: Length: 8 Capacity: 15 |
Pamiętaj, że pojemność jest większa niż długość łańcucha! Chociaż nasz ciąg miał długość 8, faktycznie przydzielił on wystarczającą ilość pamięci na 15 znaków! Dlaczego to zrobiono?
Ważną rzeczą, o której należy pamiętać, jest to, że jeśli użytkownik chce umieścić w ciągu znaków więcej znaków, niż jest w stanie pomieścić ciąg, należy ponownie przydzielić ciąg do większej pojemności. Na przykład, jeśli ciąg znaków miał zarówno długość, jak i pojemność 8, dodanie do niego jakichkolwiek znaków wymusiłoby ponowną alokację. Zwiększając pojemność niż rzeczywisty ciąg, daje to użytkownikowi trochę miejsca w buforze na rozwinięcie ciągu, zanim konieczna będzie ponowna alokacja.
Jak się okazuje, ponowna alokacja jest zła z kilku powodów:
Po pierwsze, ponowna alokacja ciągu jest stosunkowo kosztowna. Najpierw należy przydzielić nową pamięć. Następnie każdy znak ciągu należy skopiować do nowej pamięci. Może to zająć dużo czasu, jeśli ciąg jest duży. Na koniec należy zwolnić przydział starej pamięci. Jeśli wykonujesz wiele ponownych alokacji, proces ten może znacznie spowolnić program.
Po drugie, za każdym razem, gdy łańcuch jest ponownie alokowany, zawartość ciągu zmienia się na nowy adres pamięci. Oznacza to, że wszystkie odniesienia, wskaźniki i iteratory do łańcucha stają się nieprawidłowe!
Zauważ, że nie zawsze jest tak, że ciągi będą przydzielane z pojemnością większą niż długość. Rozważmy następujący program:
std::string s { "0123456789abcde" };
std::cout << "Length: " << s.length() << '\n';
std::cout << "Capacity: " << s.capacity() << '\n';Ten program wyprowadza:
Length: 15 Capacity: 15
(Wyniki mogą się różnić w zależności od kompilatora).
Dodajmy jeden znak do łańcucha i obserwujmy zmianę pojemności:
std::string s("0123456789abcde");
std::cout << "Length: " << s.length() << '\n';
std::cout << "Capacity: " << s.capacity() << '\n';
// Now add a new character
s += "f";
std::cout << "Length: " << s.length() << '\n';
std::cout << "Capacity: " << s.capacity() << '\n';Daje to wynik:
Length: 15 Capacity: 15 Length: 16 Capacity: 31
| void string::reserve() void string::reserve(size_type unSize)
Przykładowy kod: Wyjście: Length: 8 Capacity: 15 Length: 8 Capacity: 207 Length: 8 Capacity: 207 |
Ten przykład pokazuje dwie interesujące rzeczy. Po pierwsze, chociaż poprosiliśmy o pojemność 200, w rzeczywistości otrzymaliśmy pojemność 207. Zawsze gwarantujemy, że pojemność będzie co najmniej tak duża, jak wymagana jest Twoja prośba, ale może być większa. Następnie poprosiliśmy o zmianę pojemności w celu dopasowania ciągu. To żądanie zostało zignorowane, ponieważ pojemność nie uległa zmianie.
Jeśli wiesz z góry, że będziesz konstruować duży ciąg znaków, wykonując wiele operacji na łańcuchach, które zwiększą jego rozmiar, możesz uniknąć wielokrotnej ponownej alokacji łańcucha, rezerwując od początku wystarczającą pojemność:
#include <iostream>
#include <string>
#include <cstdlib> // for rand() and srand()
#include <ctime> // for time()
int main()
{
std::srand(std::time(nullptr)); // seed random number generator
std::string s{}; // length 0
s.reserve(64); // reserve 64 characters
// Fill string up with random lower case characters
for (int count{ 0 }; count < 64; ++count)
s += 'a' + std::rand() % 26;
std::cout << s;
}Wynik działania tego programu będzie się zmieniać za każdym razem, ale oto wynik jednego wykonania:
wzpzujwuaokbakgijqdawvzjqlgcipiiuuxhyfkdppxpyycvytvyxwqsbtielxpy
Zamiast konieczności reallocate s wielokrotnie, ustawiamy pojemność raz, a następnie wypełniamy ciąg. Może to mieć ogromne znaczenie w wydajności podczas konstruowania dużych ciągów poprzez konkatenację.

