Метод pop_back() из std::string

Последний символ строки на C++ проще всего удаляется методом pop_back(), представленным в C++11. Причем символ удаляется за постоянное время:

#include <iostream>
#include <string>

int main() {
std::string str = "Hello, World!";
str.pop_back();
std::cout << str << std::endl; // Выводится: «Hello, World»
return 0;
}

pop_back()  —  эффективный и простой метод. Но удаленный символ им не возвращается и, если строка пуста, выдается исключение.

Метод resize()

Это метод с увеличением и уменьшением размера строки:

#include <iostream>
#include <string>

int main() {
std::string str = "Hello, World!";
str.resize(str.length() - 1);
std::cout << str << std::endl; // Выводится: «Hello, World»
return 0;
}

Метод resize() универсален: им удаляются символы из конца строки, при этом указывается новый размер.

Метод erase()

Этим методом предоставляется больше контроля за тем, какую часть строки удалить. Вот как удаляется последний символ:

#include <iostream>
#include <string>

int main() {
std::string str = "Hello, World!";
str.erase(str.length() - 1);
std::cout << str << std::endl; // Выводится: «Hello, World»
return 0;
}

При помощи erase() удаляется конкретная подстрока или диапазон символов.

Метод substr()

Этим методом создается новая строка без последнего символа:

#include <iostream>
#include <string>

int main() {
std::string str = "Hello, World!";
str = str.substr(0, str.length() - 1);
std::cout << str << std::endl; // Выводится: «Hello, World»
return 0;
}

Пригождается substr() и в ситуациях, когда нужно сохранить исходную строку.

Обработка пустых строк

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

#include <iostream>
#include <string>

void removeLastChar(std::string& str) {
if (!str.empty()) {
str.pop_back();
}
}

int main() {
std::string str1 = "Hello";
std::string str2 = "";

removeLastChar(str1);
removeLastChar(str2);

std::cout << "str1: " << str1 << std::endl; // Выводится: «Hell»
std::cout << "str2: " << str2 << std::endl; // Выводится: пустая строка

return 0;
}

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

Производительность

При работе с большими строками или частыми операциями важна производительность. Сравним эффективность методов:

#include <iostream>
#include <string>
#include <chrono>

const int ITERATIONS = 1000000;

void benchmarkPopBack(std::string str) {
auto start = std::chrono::high_resolution_clock::now();
for (int i = 0; i < ITERATIONS; ++i) {
str.pop_back();
}
auto end = std::chrono::high_resolution_clock::now();
std::chrono::duration<double> diff = end - start;
std::cout << "pop_back() time: " << diff.count() << " seconds" << std::endl;
}

void benchmarkResize(std::string str) {
auto start = std::chrono::high_resolution_clock::now();
for (int i = 0; i < ITERATIONS; ++i) {
str.resize(str.length() - 1);
}
auto end = std::chrono::high_resolution_clock::now();
std::chrono::duration<double> diff = end - start;
std::cout << "resize() time: " << diff.count() << " seconds" << std::endl;
}

void benchmarkErase(std::string str) {
auto start = std::chrono::high_resolution_clock::now();
for (int i = 0; i < ITERATIONS; ++i) {
str.erase(str.length() - 1);
}
auto end = std::chrono::high_resolution_clock::now();
std::chrono::duration<double> diff = end - start;
std::cout << "erase() time: " << diff.count() << " seconds" << std::endl;
}

int main() {
std::string longString(ITERATIONS, 'a');

benchmarkPopBack(longString);
benchmarkResize(longString);
benchmarkErase(longString);

return 0;
}

По этому тесту производительности, быстрейшим обычно получается pop_back(), за которым с небольшим отставанием располагается resize(), чуть медленнее  —  erase().

Реальный сценарий: синтаксический анализатор CSV

Реализуем простой CSV-парсер, которым из каждой строки удаляются конечные запятые:

#include <iostream>
#include <string>
#include <vector>
#include <sstream>

class CSVParser {
private:
std::vector<std::vector<std::string>> data;

void removeTrailingComma(std::string& str) {
if (!str.empty() && str.back() == ',') {
str.pop_back();
}
}

public:
void parse(const std::string& input) {
std::istringstream stream(input);
std::string line;

while (std::getline(stream, line)) {
removeTrailingComma(line);

std::vector<std::string> row;
std::istringstream lineStream(line);
std::string cell;

while (std::getline(lineStream, cell, ',')) {
row.push_back(cell);
}

data.push_back(row);
}
}

void print() const {
for (const auto& row : data) {
for (const auto& cell : row) {
std::cout << cell << "\t";
}
std::cout << std::endl;
}
}
};

int main() {
CSVParser parser;
std::string csvData =
"Name,Age,City,\n"
"John,30,New York,\n"
"Alice,25,London,\n"
"Bob,35,Paris,";

parser.parse(csvData);
parser.print();

return 0;
}

Это практический пример удаления последнего символа строки методом pop_back().

«Юникод» и многобайтовые символы

При работе с «Юникодом» или многобайтовыми символами удалить последний символ сложнее. Методом pop_back() удаляется последний байт, который в многобайтовой кодировке может оказаться не последним символом.

Для строк «Юникода» используется соответствующая библиотека, такая как International Components for Unicode, или функции с поддержкой UTF-8:

#include <iostream>
#include <string>
#include <codecvt>
#include <locale>

std::wstring removeLastUnicodeChar(const std::wstring& str) {
if (str.empty()) {
return str;
}
return str.substr(0, str.length() - 1);
}

int main() {
std::wstring str = L"Hello, 世界!";
str = removeLastUnicodeChar(str);

std::wstring_convert<std::codecvt_utf8<wchar_t>> converter;
std::string utf8Str = converter.to_bytes(str);

std::cout << utf8Str << std::endl; // Выводится: «Hello, 世界»

return 0;
}

В этом примере символы «Юникода» корректно обрабатываются строками в многобайтовой кодировке std::wstring.

Функциональный подход с алгоритмами

Для более функционального стиля программирования используются алгоритмы из стандартной библиотеки C++:

#include <iostream>
#include <string>
#include <algorithm>

std::string removeLastChar(std::string str) {
return std::string(str.begin(), str.end() - (str.empty() ? 0 : 1));
}

int main() {
std::string str = "Hello, World!";
str = removeLastChar(str);
std::cout << str << std::endl; // Выводится: «Hello, World»

return 0;
}

При таком подходе новая строка создается без изменения исходной, в некоторых сценариях пригодится.

Типичные ошибки и как их избежать

  1. Не проверяется наличие пустых строк. До удаления последнего символа всегда проверяйте, не пуста ли строка.
  2. Предполагается, что символы однобайтовые. Будьте осторожны при работе с «Юникодом» или многобайтовыми кодировками.
  3. Изменение строковых литералов. Не забывайте, что строковые литералы неизменяемы, при изменении строк всегда работайте с объектами std::string.
  4. Упускаются из виду строковые представления. Чтобы повысить производительность, для строковых операций без изменения строк в версии C++17 или новее используйте std::string_view.
  5. Лишнее копирование. Во избежание ненужных копий строк  —  особенно больших строк  —  по возможности используйте ссылки.

Заключение

Удаление последнего символа строки на C++  —  распространенная операция с разными вариантами реализации. В простых сценариях pop_back() обычно эффективнее, в сложных методы resize(), erase() и substr() оказываются гибче.

При работе со строками на C++ всегда учитывайте конкретные требования задачи. Имеется «Юникод»? Нужно ли сохранить исходную строку? Важна ли производительность?

Ответив на эти вопросы, вы выберете оптимальный метод удаления последнего символа строки.

Читайте также:

Читайте нас в Telegram, VK и Дзен


Перевод статьи ryan: C++ Remove Last Character from String: Methods and Applications

Предыдущая статьяАвтомасштабирование по запаздыванию Kafka с KEDA
Следующая статьяПочему не рекомендуется использовать JWT?