Обработка строк в C++
При работе со строками в C++ вам, возможно, придется заниматься очисткой строк от лишних пробелов, а так же разделением их на части, особенно, если вы работаете с фамилиями или адресами, загружаемыми из внешних источников.
Сегодня мы рассмотрим создания класса C++ для работы со строками, а именно реализуем функции ltrim(), rtrim() и trim() для std::string. Так же мы рассмотрим итераторы при работе со строками std::string и напишем функцию split() для разделения строки используя заданные разделители.
Подготовка
Для этого проекта я буду использовать новый шаблон, для прострого проекта на C++ с использованием CMake.
Загрузить исходный код вы можете из репозитория - https://gitflic.ru/project/vasiliyaltunin/cpp_tempalte_app
Создание класса
В папку src добавим файл stringutils.h с содержимым:
#ifndef STRINGUTILS_H
#define STRINGUTILS_H
#pragma once
class StringUtils
{
public:
StringUtils();
~StringUtils();
private:
};
#endifИ файл stringutils.cpp с содержимым:
#include "stringutils.h"
StringUtils::StringUtils()
{
}
StringUtils::~StringUtils()
{
}В файл cmake/src.cmake.in добавим файлы .h и .cpp нашего класса, приведя его к вид:
# Путь к файлам с исходным кодом
set(MY_SRC
"src/helloworld.h"
"src/helloworld.cpp"
"src/stringutils.h"
"src/stringutils.cpp"
"main.cpp")Удаляем все пробелы из конца строки
При работе со строками, особенно когда программа ожидает от пользователя ввода текста, хорошей идеей является очистка строки от пробелов в начале и конце строки которые пользователь может поставить случайно или они попадут в нее при копировании/вставке из другой программы.
Добавим метод удаляющий пробелы из конца строки.
В файл stringutils.h добавим:
#include <string>Добавим метод rtim() в класс StringUtils:
std::string StringUtils::rtrim(std::string sourceStr);В файл stringutils.cpp добавим реализацию метода:
std::string StringUtils::rtrim(std::string sourceStr) {
{
int spacePos = sourceStr.find_last_not_of(" ") + 1;
std::cout << "\nFirst space=" << spacePos << "\n";
sourceStr.erase(sourceStr.find_last_not_of(" ") + 1);
return sourceStr;
}В файл main.cpp добавим код:
std::string str = "HELLO WORLD ";
std::cout << "\n|" << str << "|\n";
str = s.rtrim(str);
std::cout << "\n|" << str << "|\n";Обратите внимание на строку:
HELLO WORLD
01234567890123У нас три пробела с конца строки а сама строка длинной 13 символов!
Запустим конфигурирование и сборку и запустим:
cmake -S . -B build & cmake --build build & .\build\cppapp.exeРезультат:
|HELLO WORLD |
First space=11
|HELLO WORLD|В методе rtrim() мы сначала получаем позицию первого символа, который отличается от любого из символов, переданных в параметре фикции find_last_not_of.
В нашем случае это любой символ не являющийся пробелом. Метод find_last_not_of() производит поиск в строке начиная с самого последнего символа и двигается к началу строки пока не найдет первый подходящий символ и возвращает его позицию в строке.
Далее мы добавляем к результату единицу, так как нам нужна позиция первого пробела с конца строки и удаляем все символы после этой позиции до конца строки с помощью метода erase().
Аналогично добавим метод для удаления пробелов с начала строки.
Добавим в начало строки два пробела.
std::string str = " HELLO WORLD ";Добавим в класс StringUtils метод:
std::string StringUtils::ltrim(std::string sourceStr) {
int spacePos = sourceStr.find_first_not_of(" ");
std::cout << "\nFirst space=" << spacePos << "\n";
sourceStr.erase(0, sourceStr.find_first_not_of(" "));
return sourceStr;
}Добавим в main.cpp
str = s.ltrim(str);
std::cout << "\n|" << str << "|\n";Запустим сборку и программу:
| HELLO WORLD |
First space=13
| HELLO WORLD|
First space=2
|HELLO WORLD|Мы удалили пробелы из начала и из конца строки.
Удалим избыточный код из методов, приведя к виду:
std::string StringUtils::rtrim(std::string sourceStr) {
sourceStr.erase(sourceStr.find_last_not_of(" ") + 1);
return sourceStr;
}
std::string StringUtils::ltrim(std::string sourceStr) {
sourceStr.erase(0, sourceStr.find_first_not_of(" "));
return sourceStr;
}Универсальный метод для удаления пробелов с начала и с конца строки
В класс StringUtils добавим метод:
std::string StringUtils::trim(std::string sourceStr) {
return StringUtils::ltrim(StringUtils::rtrim(sourceStr));
}В файл main.cpp добавим строки:
str = " HELLO WORLD ";
std::cout << "\n|" << str << "|\n";
str = s.trim(str);
std::cout << "\n|" << str << "|\n";Запустим:
| HELLO WORLD |
|HELLO WORLD|Очищаем строку от невидимых символов
Мы написали метод, который очищает строку от пробелов с начала и конца. Но помимо пробелов, в строке могут попасться прочие символы, которые мы захотим удалить, например символ табуляции или возврата строки. Давайте модифицируем методы класса StringUtils для очистки строки и от таких символов.
Объявим строку, в которой перечислим все символы, которые мы хотим удалять:
Перед объявлением класса добавим:
const std::string TRIMABLES = " \t\n\r";Здесь мы добавили символы пробела, табуляции, новой строки и возврата каретки. При необходимости вы можете добавить прочие символы для удаления.
В методы rtim и ltrim добавим нашу строку TRIMABLES изменив их следующим образом:
std::string StringUtils::rtrim(std::string sourceStr) {
sourceStr.erase(sourceStr.find_last_not_of(TRIMABLES) + 1);
return sourceStr;
}
std::string StringUtils::ltrim(std::string sourceStr) {
sourceStr.erase(0, sourceStr.find_first_not_of(TRIMABLES));
return sourceStr;
}Запустим, всё работает как и раньше, даже если мы добавим в конец строки символ \n или \t.
Разбиваем строку на части с использованием пробелов и разделителей
Очень часто при работе со строками может потребоваться разделять их на части. Например при работе с ФИО людей может потребоваться превратить строку в массив, содержащий фамилию, имя и отчество.
Здесь мы сразу объявим новый тип - strlist, так как часто будем использовать его далее и он позволит нам существенно сократить программный код.
Перед объявлением класса StringUtils добавим:
typedef std::list<std::string> strlist;Добавим в класс StringUtils метод split:
std::list<std::string> StringUtils::split(std::string sourceStr,
char splitter) {
strlist data;
std::string tmp;
for (char buffer : sourceStr) {
if (buffer == splitter) {
data.insert(data.end(), tmp);
tmp.clear();
} else {
tmp.append(1, buffer);
}
}
data.insert(data.end(), tmp);
return data;
}Здесь мы используем итератор для обхода всей строки, когда нам встречается символ разделителя, все предыдущие символы мы добавляем в качестве нового элемента списка в виде строки std::string. В самом конце мы добавляем все оставшиеся символы в новый элемент списка и возвращаем сам список.
Чтобы добавить строку в конец списка мы получаем итератор конца списка с помощью data.end().
Добавим в main.cpp строки:
str = "HELLO MY NEW PERFECT WORLD";
std::cout << "\n|" << str << "|\n";
strlist result;
result = s.split(str, ' ');
for (std::string s : result) {
std::cout << s << "=";
}Запустим:
|HELLO MY NEW PERFECT WORLD|
HELLO=MY=NEW=PERFECT=WORLD=Мы получили список из всех слов строки.
Мы так же можем использовать другой разделитель, например код:
str = "HELLO MY NEW|PERFECT WORLD";
result = s.split(str, '|');
for (std::string s : result) {
std::cout << s << "=";
}Выведет:
HELLO MY NEW=PERFECT WORLD=Заключение
Сегодня мы создали класс для работы со строками:
Создали класс StringUtils;
Добавили метод для удаления пробелов с конца строки;
Добавили метод для удаления пробелов из начала строки;
Добавили методы для удаления пробелов и с начала и с конца строки;
Добавили константу для хранения списка невидимых символов, подлежащих удалению и доработали методы очистки строки от пробелов;
Добавили метод разбивающий строку на на части с использованием пробелов или любого символа указанного в параметре.
Добавить комментарий