Пятница, 26.12.2025 17:50

Шаблоны классов C++ на примере работы со строками std::string и std::wstring

Шаблоны классов C++ на примере работы со строками std::string и std::wstring

При создании классов в C++ вы, возможно, столкнетесь с ситуацией, когда для обработки похожих типов данных потребуется создать практически идентичные классы, различающиеся лишь используемыми типами. Для решения таких задач можно использовать шаблоны классов C++.

В предыдущей статье мы рассмотрели создание класса для работы со строками std::string. Сегодня мы рассмотрим создание универсального класса, который позволит нам работать как со строками std::string, так и std::wstring.

Для чего нужны строки std::wstring?

Давайте проверим, что будет, если мы используем класс, созданный в предыдущей статье с русским текстом.

Обратите внимание! Прежде чем продолжить убедитесь, что файл кодировка main.cpp установлена как UTF-8!

Добавим в начало файла строки:

setlocale(LC_ALL, "Russian");

StringUtils s;

std::string str = " ПРИВЕТ МИР ";
std::cout << "\n|" << str << "|\n";
str = s.rtrim(str);
std::cout << "\n|" << str << "|\n";
str = s.ltrim(str);
std::cout << "\n|" << str << "|\n";

Запустим:

| Р?Р Р?Р'РРў Р?Р?Р |
| Р?Р Р?Р'РРў Р?Р?Р |
|Р?Р Р?Р'РРў Р?Р?Р |

Вместо русского текста - крякозябры.

Это происходит потому, что для хранения символов в формате UTF-8 используется 2 байта, а std::cout не умеет работать с таким типом строк.

Добавим в конец файл строки:

std::wstring str1 = L" ПРИВЕТ МИР ";
std::wcout << "\n|" << str1 << "|\n";

Запустим:

| ПРИВЕТ МИР |

Теперь русские буквы корректно отображаются.

Обратите внимание! Для правильного отображения мы должны в начале файла main.cpp задать локаль UTF-8 с помощью:

#include <io.h>
#include <fcntl.h>

_setmode(_fileno(stdout), _O_U16TEXT);
_setmode(_fileno(stdin),  _O_U16TEXT);
_setmode(_fileno(stderr), _O_U16TEXT);

Самый простой способ заставить уже созданный класс работать с русскими строками - создать его копию и везде поменять std::string на std::wstring.

Но, если нам потребуется доработать класс, нам придется вносить изменения в оба класса что очень неудобно! Для решения подобных проблем были созданы шаблоны классов C++.

По сути код шаблона класса представляет собой шаблон, в который компилятор без участия программиста в процессе компиляции вставляет заданные типы данных, чем существенно экономит время на разработку!

Создаем шаблон класса C++

Удалим содержимое файлов stringutils.cpp и stringutils.h.

Особенностью шаблонов класса является то, что самый простой способ его объявления и реализации - использовать для этого только заголовочный файл и сразу реализовывать все методы класса внутри его объявления.

В файл stringutils.cpp добавим строку:

#include "stringutils.h"

В принципе вы можете ограничится только одним заголовочным файлом, но для личного удобства можно оставить .cpp-файл.

В файл stringutils.h добавим наш класс:

#ifndef STRINGUTILS_H
#define STRINGUTILS_H
#include <list>

#pragma once

template <typename T> class StringUtils {

public:

 typedef std::list<T> strlist;
 
 const char *TRIMABLES = " \t\n\r";
 
 explicit StringUtils() {};
 ~StringUtils() {};
 
 T rtrim(T sourceStr) {
   sourceStr.erase(sourceStr.find_last_not_of(*TRIMABLES) + 1);
   return sourceStr;
 }
 
 T ltrim(T sourceStr) {
   sourceStr.erase(0, sourceStr.find_first_not_of(*TRIMABLES));
   return sourceStr;
 }
 
 T trim(T sourceStr) {
   T result = StringUtils::ltrim(StringUtils::rtrim(sourceStr));
   return result;
 }
 
 StringUtils<T>::strlist split(T sourceStr, T splitter) {
   StringUtils<T>::strlist data;
   T tmp;
   for (auto i : sourceStr) {
     if (i == splitter[0]) {
       data.insert(data.end(), tmp);
       tmp.clear();
     } else {
       tmp.append(1, i);
     }
   }
   data.insert(data.end(), tmp);
   return data;
 }
private:
};
#endif

Рассмотрим его подробнее.

Перед объявлением класса-шаблона всегда нужно указывать:

template <typename T>

где T - наш тип, который будет подставляться в код класса.

Мы перенесли объявления

typedef std::list<T> strlist;
const char *TRIMABLES = " \t\n\r";

Внутрь класса, чтобы избежать проблем с видимостью.

Так же для типа strlist мы указываем шаблонный тип t

typedef std::list<T> strlist;

При компиляции эта строка будет преобразована в

typedef std::list<std::string> strlist;

или

typedef std::list<std::wstring> strlist;

В зависимости от указанного типа.

Подобным образом T заменяется на указанный тип в остальном коде класса-шаблона.

Чтобы работать с разными типами строк мы изменили тип:

const char *TRIMABLES = " \t\n\r";

Так как мы объявили тип:

strlist

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

StringUtils<T>::strlist

Основные изменения коснулись метода split():

Мы изменили параметр:

splitter

Теперь он использует шаблон типа T.

Таким образом мы можем использовать его с любым производным типом.

Для обходя всех символов строки мы используем итератор в цикле for:

for (auto i : sourceStr) {
}

Указывая тип auto нам не нужно заботиться о подставляемом в шаблоне типе!

Для сравнения текущего символа и разделителя нам достаточно использовать:

i == splitter[0]

Так как i и splitter[0] после подстановки в шаблон одного типа.

В остальном алгоритм не поменялся.

Использование шаблона класса

Для использования шаблона класса нам не придется вносить множество изменений.

Изменим код в файле main.cpp

setlocale(LC_ALL, "Russian");

StringUtils<std::string> s;

std::string str = " HELLO WORLD ";
std::cout << "\n|" << str << "|\n";

str = s.rtrim(str);
std::cout << "\n|" << str << "|\n";

str = s.ltrim(str);
std::cout << "\n|" << str << "|\n";

str = " HELLO WORLD ";
std::cout << "\n|" << str << "|\n";

str = s.trim(str);
std::cout << "\n|" << str << "|\n";

str = "HELLO MY NEW|PERFECT WORLD";
std::cout << "\n|" << str << "|\n";

StringUtils<std::string>::strlist result;
result = s.split(str, "|");
for (std::string s : result) {
std::cout << s << "=";
}

str = "HELLO MY NEW PERFECT WORLD";
std::cout << "\n|" << str << "|\n";
result = s.split(str, " ");
for (std::string s : result) {
std::cout << s << "=";
}


Запустим:

| HELLO WORLD |
| HELLO WORLD|
|HELLO WORLD|
| HELLO WORLD |
|HELLO WORLD|
|HELLO MY NEW|PERFECT WORLD|
HELLO MY NEW=PERFECT WORLD= 
|HELLO MY NEW PERFECT WORLD|
HELLO=MY=NEW=PERFECT=WORLD=

Для объявления типа переменной теперь мы указываем тип для шаблона в скобках <>

StringUtils<std::string> s;

Так же мы указываем тип для шаблона при объявлении типа strlist:

StringUtils<std::string>::strlist result;

Мы так же теперь можем использовать класс StringUtils для работы с русским языком:

StringUtils<std::wstring> s1;

std::wstring str1 = L" ПРИВЕТ МИР ";

std::wcout << "\n|" << str1.c_str() << "|\n";
str1 = s1.rtrim(str1);
std::wcout << "\n|" << str1 << "|\n";

str1 = L" ПРИВЕТ МИР ";
str1 = s1.ltrim(str1);
std::wcout << "\n|" << str1 << "|\n";

str1 = L" ПРИВЕТ МИР ";
std::wcout << "\n|" << str1 << "|\n";
str1 = s1.trim(str1);
std::wcout << "\n|" << str1 << "|\n";

str1 = L"ПРИВЕТ МОЙ НОВЫЙ|ИДЕАЛЬНЫЙ МИР";
std::wcout << "\n|" << str1 << "|\n";

StringUtils<std::wstring>::strlist result1;
result1 = s1.split(str1, L"|");
for (std::wstring s : result1) {
std::wcout << s << "=";
}

str1 = L"ПРИВЕТ МОЙ НОВЫЙ ИДЕАЛЬНЫЙ МИР";
std::wcout << "\n|" << str1 << "|\n";

result1 = s1.split(str1, L" ");
for (std::wstring s : result1) {
std::wcout << s << "=";
}

Запустим:

| ПРИВЕТ МИР |
| ПРИВЕТ МИР|
|ПРИВЕТ МИР |
| ПРИВЕТ МИР |
|ПРИВЕТ МИР|
|ПРИВЕТ МОЙ НОВЫЙ|ИДЕАЛЬНЫЙ МИР|
ПРИВЕТ МОЙ НОВЫЙ=ИДЕАЛЬНЫЙ МИР=
|ПРИВЕТ МОЙ НОВЫЙ ИДЕАЛЬНЫЙ МИР|
ПРИВЕТ=МОЙ=НОВЫЙ=ИДЕАЛЬНЫЙ=МИР=

Заключение

Сегодня мы рассмотрели создание шаблона класса StringUtils для работы с любыми типами строк:

Создали шаблон класса;

Рассмотрели его объявление;

Рассмотрели объявление типов с шаблоном типа T;

Рассмотрели объявление методов с шаблоном типа T;

Рассмотрели изменения в методе split() для работы с типами std::string и std::wstring;

Рассмотрели использование шаблона класса для создания экземпляра класса.

Категория C++
Теги Cpp class

Добавить комментарий

Простой текст

  • HTML-теги не обрабатываются и показываются как обычный текст
  • Строки и абзацы переносятся автоматически.
  • Адреса веб-страниц и email-адреса преобразовываются в ссылки автоматически.
Просмотров: 45