ссылка для скачивания
Реализация Qt-подобного строкового класса для C++Builder
Если совсем кратко говорить, то суть в том, чтобы использовать Borland'овский стандартный класс UnicodeString через методы QString из Qt. Т.е. речь идет не о чем-то новом в плане разработки, а лишь об унификации стандартов: если уже есть опыт с QString и нет желания вникать в особенности UnicodeString, то можно использовать этот класс. Также использование классов с одинаковыми методами может быть удобно с точки зрения переносимости кода между разными средами разработки. Упрощенные реализации QChar и QStringList сделаны лишь в том контексте, что они требуются классу QString, не более того.
QChar — упрощённый аналог Qt::QChar:
- Хранит один символ (wchar_t).
- Методы для проверки типа символа (isDigit, isLetter и т.д.).
- Преобразование регистра (toUpper, toLower).
QString — основная строка:
- Внутреннее хранение через UnicodeString
- Поддерживает создание из разных источников: char*, wchar_t*, std::string, std::wstring.
- Реализует большинство методов Qt-строки:
- Поиск, разделение, замена, регистр.
- Подстроки (mid, left, right).
- Числовые преобразования (toInt, toDouble).
- Кодировки (UTF-8, Latin1, ISO-8859-1).
- Форматирование (arg, number). arg() реализована лишь частично (!)
- Операторы сравнения и конкатенации.
QStringList — список строк:
- Наследуется от std::vector<QString>.
- Методы: join, contains, filter.
- Перегруженный operator<< для добавления строк.
Использует встроенные возможности C++Builder (UnicodeString, UTF8String, AnsiString).
Поддерживает набор кодировок и преобразований между ними.
Реализует специфичные методы Qt, такие как section, simplified, toHtmlEscaped.
Учитывает особенности индексации UnicodeString (начинается с 1).
Модуль позволяет использовать Qt-подобный строковый API в проектах
на C++Builder без подключения Qt, что полезно для миграции кода или
поддержки кроссплатформенных исходников с Qt-стилем работы со строками.
Описание класса
QString для C++Builder — строковый класс с Qt-подобным интерфейсом, построенный поверх UnicodeString. Он позволяет использовать привычные имена методов — mid(), split(), replace(), arg() и другие — в проектах C++Builder без подключения библиотеки Qt.
Идея модуля — унифицировать работу со строками. Если вы уже пользуетесь QString в Qt, часть строковой логики можно записывать в похожем стиле и в C++Builder. При этом хранение текста и взаимодействие с VCL опираются на штатные средства среды: UnicodeString, UTF8String и AnsiString.ия.
1. Состав модуля и подключение
- qstring_nx_cb.h — классы QString и QStringList, преобразования строк и специализации std::hash для обоих классов.
- qchar_nx_cb.h — QChar для одной кодовой единицы UTF-16 и вспомогательный QCharRef для изменения символа в строке.
- qtnamespace_nx_cb.h — перечисления в namespace Qt и шаблон Qt::QFlags. Для строк используются, в частности, CaseSensitivity, SplitBehavior и SectionFlag.
Поместите все три файла в доступный для проекта каталог. Достаточно подключить qstring_nx_cb.h: два остальных заголовка он подключает сам. Реализация находится в заголовках, отдельного .cpp у этого модуля нет. Нужен компилятор с поддержкой используемых средств C++11, включая constexpr, noexcept и перемещение.
#include "qstring_nx_cb.h"
QString text(L"Привет, C++Builder!");
UnicodeString nativeText = text.toUnicodeString();
Эти классы объявлены в глобальном пространстве имён. Подключать их одновременно с настоящими одноимёнными классами Qt в одной единице трансляции нельзя: имена конфликтуют. Наличие GUI-перечислений в qtnamespace_nx_cb.h само по себе не добавляет виджеты или систему событий Qt.
2. Создание строк и взаимодействие с VCL
Строку можно создать из QChar, UnicodeString, const char*, const wchar_t*, std::string и std::wstring. Узкие строки const char* и std::string в этих конструкторах трактуются как UTF-8. Широкие строки в целевой Windows-среде передают текст UTF-16.
QString a("Hello");
QString b(L"Привет");
QString c = QString::fromUtf8("\xD0\xA2\xD0\xB5\xD0\xBA\xD1\x81\xD1\x82");
QString d(std::string("C++Builder"));
QString e(std::wstring(L"Широкая строка"));
UnicodeString native = L"Текст VCL";
QString f(native);
UnicodeString back = f; // Неявное преобразование в UnicodeString
Для русскоязычных литералов в примерах используется L"…", чтобы не зависеть от кодировки узкого строкового литерала в настройках проекта. Если передавать кириллицу через обычный "…", важно, чтобы компилятор формировал именно UTF-8.
// Пример внутри обработчика формы VCL
QString userText(Edit1->Text);
QString message = QString(L"Введено: %1").arg(userText.trimmed());
Label1->Caption = message.toUnicodeString();
3. Длина, пустая строка и индексация
length() возвращает число кодовых единиц UTF-16. Индексы QString начинаются с нуля, хотя внутренний UnicodeString использует индексы с единицы. Пересчёт выполняется внутри класса.
QString s(L"Привет");
int n = s.length(); // 6
QChar first = s.at(0); // Буква П
s[0] = QChar(L'п'); // Теперь "привет"
bool empty = s.isEmpty(); // false
s.clear();
bool isNull = s.isNull(); // true
В этой реализации isNull() совпадает с isEmpty(): отдельное состояние null-строки не сохраняется. at() при неверном индексе возвращает нулевой QChar; константный operator[] ведёт себя так же. Неконстантный operator[] проверяет границы и при неверном индексе выбрасывает std::out_of_range.
Один отображаемый знак не всегда занимает одну позицию. Например, многие эмодзи представлены суррогатной парой и дают length() == 2. at(), mid(), left(), right() и другие операции с позициями работают с кодовыми единицами UTF-16; при неосторожном разрезании можно разделить пару.
4. Поиск текста
indexOf() ищет первое вхождение, lastIndexOf() — последнее. Результат — индекс с нуля; если совпадения нет, возвращается -1. contains(), startsWith() и endsWith() отвечают на вопрос о наличии подстроки, префикса или суффикса.
QString path("config/device.ini");
int slash = path.indexOf("/"); // 6
int dot = path.lastIndexOf("."); // 13
bool found = path.contains("device"); // true
bool ini = path.endsWith(".ini"); // true
bool cfg = path.startsWith("config"); // true
int absent = path.indexOf(".xml"); // -1
int overlaps = QString("aaaa").count("aa"); // 3: перекрытия учитываются
У методов поиска QString в этом заголовке нет параметра Qt::CaseSensitivity: поиск чувствителен к регистру. Для простого поиска без учёта регистра можно преобразовать обе строки одним и тем же способом и использовать contains(). Если нужны позиции в исходной строке, учитывайте, что преобразование регистра может менять длину текста.
QString name(L"ДАТЧИК температуры");
bool found = name.toCaseFolded().contains(
QString(L"датчик").toCaseFolded()); // true
5. Получение подстрок и секций
left(n) и right(n) возвращают начало или конец строки, mid(position, n) — фрагмент заданной длины. Если n у mid() опущен, берётся остаток строки. Исходная строка при этом не меняется.
QString name("report_2026.csv");
QString prefix = name.left(6); // "report"
QString ext = name.right(3); // "csv"
QString year = name.mid(7, 4); // "2026"
QString tail = name.mid(7); // "2026.csv"
section() извлекает одну или несколько частей, разделённых заданной строкой. Номера частей начинаются с нуля, конечная граница включается. Отрицательные номера отсчитываются от конца.
QString key("plant/line/device/tag");
QString device = key.section("/", 2, 2); // "device"
QString group = key.section("/", 1, 2); // "line/device"
QString tag = key.section("/", -1, -1); // "tag"
Для section() предусмотрены флаги SectionSkipEmpty, SectionIncludeLeadingSep и SectionIncludeTrailingSep. В отличие от mid(), здесь индекс задаёт номер части, а не позицию в символах.
6. Добавление, вставка, замена и удаление
append(), prepend(), insert(), replace(), remove() и operator+= изменяют текущую строку. Они возвращают QString&, поэтому вызовы можно объединять. operator+ создаёт новый результат.
QString s("sensor");
s.prepend("[").append("]"); // "[sensor]"
s.insert(1, "new_"); // "[new_sensor]"
s.replace("new_", ""); // "[sensor]"
s.remove(0, 1); // "sensor]"
s.chop(1); // "sensor"
QString title = QString("Device: ") + s;
s += "_01"; // "sensor_01"
У remove(position, n) второй аргумент означает количество удаляемых кодовых единиц. remove(text) удаляет все вхождения текста. chop(n) убирает n единиц с конца; truncate(pos) оставляет первые pos единиц. chop() и truncate() возвращают void.
QString s("Status: OK; status: OK");
s.replace("status", "State", Qt::CaseInsensitive);
// "State: OK; State: OK"
s.truncate(9);
// "State: OK"
7. Пробелы и регистр
trimmed() удаляет пробельные символы по краям. simplified() дополнительно заменяет внутренние последовательности пробельных символов одним обычным пробелом. Методы возвращают новую строку.
QString raw(L" Датчик\t\n давления ");
QString a = raw.trimmed();
// "Датчик\t\n давления"
QString b = raw.simplified();
// "Датчик давления"
QString upper = QString(L"Привет").toUpper(); // "ПРИВЕТ"
QString lower = QString(L"Привет").toLower(); // "привет"
toUpper() и toLower() используют System::Character::TCharacter. Метод toCaseFolded() содержит таблицу полного преобразования регистра Unicode и может заменять один символ несколькими. Его удобно применять при сравнении текста без учёта регистра; это не то же самое, что простое приведение к нижнему регистру.
bool same = QString(L"Straße").toCaseFolded()
== QString("STRASSE").toCaseFolded(); // true
8. Разделение строки и QStringList
split() возвращает std::vector<QString>. По умолчанию пустые части сохраняются; Qt::SkipEmptyParts исключает их. Чтобы пользоваться join(), filter() и другими методами QStringList, явно создайте список из результата split().
QString source("red;;green;blue;");
auto all = source.split(";");
// 5 элементов: "red", "", "green", "blue", ""
QStringList colors(source.split(";", Qt::SkipEmptyParts));
QString joined = colors.join(", ");
// "red, green, blue"
QStringList содержит приватный std::vector<QString>, а не наследуется от него. Он предоставляет итераторы, size(), empty(), reserve(), push_back(), insert(), erase() и другие операции в стиле vector. Добавлять строки можно и привычным operator<<.
QStringList files;
files << "report.csv" << "notes.txt" << "backup.CSV";
QStringList csv = files.filter(".csv", Qt::CaseInsensitive);
QString list = csv.join("; "); // "report.csv; backup.CSV"
bool hasNotes = files.contains("notes.txt"); // true
for(const QString& file : csv)
{
UnicodeString nativeName = file.toUnicodeString();
// Здесь можно передать nativeName в VCL
}
contains() у списка проверяет наличие целой строки, а filter() выбирает строки, содержащие указанную подстроку. Оба метода принимают CaseSensitivity. Сортировка без учёта регистра, фильтрация и поиск списка используют toLower(), а не полный toCaseFolded().
QStringList names = {"beta", "alpha", "beta"};
names.removeDuplicates(); // Сохраняет первые вхождения
names.sort();
QString result = names.join(" | "); // "alpha | beta"
names.replaceInStrings("alpha", "ALPHA");
QString missing = names.value(100); // Пустая строка
std::vector<QString> copy = names.toStdVector();
value(i) возвращает пустую строку при выходе за границы. first() и last() тоже возвращают пустую строку для пустого списка. removeDuplicates() возвращает void и сравнивает строки с учётом регистра. Для обычного operator[] списка действуют правила vector: индекс нужно проверять.
9. Чтение чисел из текста
toInt(), toDouble() и toFloat() поддерживают необязательный указатель bool* ok. При неудаче возвращается ноль и ok становится false. Проверять ok необходимо, если нужно отличить ошибку от корректного числа 0. Пробелы по краям удаляются, остаток строки должен целиком соответствовать числу.
bool ok = false;
int count = QString(" 42 ").toInt(&ok); // 42, ok == true
double value = QString("12.5").toDouble(&ok); // 12.5, true
int bad = QString("42px").toInt(&ok); // 0, false
int zero = QString("0").toInt(&ok); // 0, true
В данной реализации toInt() не имеет параметра основания системы счисления. toDouble() и toFloat() используют точку как десятичный разделитель независимо от региональных настроек; строка "12,5" не является допустимой записью. Поддерживаются специальные значения inf, +inf, -inf и nan; для них ok устанавливается в true.
10. Форматирование чисел и arg()
QString::number(int) формирует десятичное целое. Перегрузка number(double, format, precision) поддерживает фиксированную форму f/F, экспоненциальную e/E и общую g/G. Для f точность задаёт число знаков после точки, для e — после точки в мантиссе, для g — число значащих цифр. По умолчанию здесь используется f с точностью 6.
QString a = QString::number(42); // "42"
QString b = QString::number(12.5, 'f', 2); // "12.50"
QString c = QString::number(1250.0, 'e', 2); // "1.25e+03"
QString d = QString::number(12.5, 'g', 3); // "12.5"
arg() принимает QString, ширину поля и QChar заполнения. Каждый вызов заменяет все маркеры с наименьшим найденным номером в диапазоне от %1 до %99. Цепочка вызовов последовательно подставляет несколько значений. Для числа с нужным форматом сначала вызывайте number().
QString message = QString(L"Канал %1: %2 В")
.arg(QString::number(3))
.arg(QString::number(12.5, 'f', 2));
// "Канал 3: 12.50 В"
QString padded = QString("ID=%1")
.arg(QString::number(7), 4, QChar(L'0'));
// "ID=0007"
QString repeated = QString("%1 + %1 = %2")
.arg("A").arg("B");
// "A + A = B"
Положительная ширина arg() выравнивает текст вправо, отрицательная — влево. Строка длиннее поля не обрезается. Числовых перегрузок arg() с основанием, форматом и точностью здесь нет. Маркеры вида %L1 распознаются, но локализованное форматирование чисел не выполняется.
Конструктор QString(double) использует FloatToStr(), поэтому зависит от региональных настроек. QString::number(double, …) формирует текст с точкой. Для файлов, протоколов и предсказуемых примеров предпочтительнее явный вызов number().
Также есть статические sprintf(), asprintf() и vsprintf(). Они используют printf-форматирование, после чего трактуют полученные байты как UTF-8. Типы аргументов должны соответствовать спецификаторам.
QString s = QString::asprintf("Channel %d", 3);
// "Channel 3"
11. Кодировки и обмен со стандартными строками
Для байтового текста важно явно знать кодировку. UTF-8, локальная кодовая страница Windows и Latin-1 — разные способы представления текста. В частности, Latin-1 не является Windows-1251.
| Метод / источник | Значение |
|---|
| QString(const char*), QString(std::string) | Входные байты UTF-8 |
| fromUtf8(), fromStdString() | Явное чтение UTF-8 |
| toUtf8(), toStdString() | Возвращают std::string с UTF-8 |
| fromLocal8Bit(), toLocal8Bit() | Обмен через AnsiString и локальную ANSI-кодовую страницу |
| fromLatin1(), fromIso8859_1() | Чтение ISO-8859-1; второй метод — псевдоним первого |
| fromStdWString(), toStdWString() | std::wstring; в целевой Windows-среде UTF-16 |
| fromStdU16String(), toStdU16String() | std::u16string, кодовые единицы UTF-16 |
| fromStdU32String(), toStdU32String() | std::u32string, преобразование кодовых точек |
QString text(L"Привет");
std::string bytes = text.toUtf8();
QString restored = QString::fromUtf8(
bytes.data(), static_cast<int>(bytes.size()));
bool same = text == restored; // true
const char latin1[] = {char(0xE9)};
QString accent = QString::fromLatin1(latin1, 1); // "é"
Перегрузки с параметром size позволяют передавать буфер заданной длины, в том числе с нулевыми байтами внутри. Без size байтовая строка читается до первого нулевого байта. Параметр size имеет тип int: если размер приходит из большого внешнего буфера как size_t, перед приведением проверьте его диапазон.
toUtf8() возвращает именно std::string, а не QByteArray. Поэтому указатель на байты получают через c_str() или data(). Храните сам std::string столько времени, сколько используется указатель.
std::string utf8 = text.toUtf8();
const char* bytes = utf8.c_str();
// bytes действует, пока utf8 существует и его буфер не изменён
12. QChar и изменение отдельной позиции
QChar хранит один wchar_t, то есть в целевой Windows-среде одну кодовую единицу UTF-16. Доступны isNull(), isValid(), isDigit(), isLetter(), isSpace(), isUpper(), isLower(), isLetterOrNumber(), digitValue(), toUpper(), toLower(), unicode(), unicode16() и toLatin1().
QChar letter(L'я');
bool isLetter = letter.isLetter(); // true
QChar upper = letter.toUpper(); // Я
QChar digit(L'7');
int value = digit.digitValue(); // 7
int absent = letter.digitValue(); // -1
QString s("abc");
s[1] = QChar(L'X'); // "aXc"
QChar c = s.at(1);
Есть константы QChar::Null, Space, LineFeed, CarriageReturn и Tabulation. Конструкторы из char, wchar_t и char16_t являются explicit. QChar(char) рассматривает один байт как беззнаковое значение, а не декодирует последовательность UTF-8. Для русской буквы используйте, например, QChar(L'Я').
isValid() здесь означает лишь «значение не равно нулю»: он не проверяет корректность Unicode и не исключает суррогаты. toLatin1() возвращает 0, если значение не помещается в диапазон 0…255. QCharRef служит посредником при записи через неконстантный QString::operator[]; его не следует сохранять после уничтожения исходной строки.
13. Повторение, выравнивание и HTML
QString line = QString("-=").repeated(3); // "-=-=-="
QString left = QString("A").leftJustified(4, QChar(L'.'));
// "A..."
QString right = QString("A").rightJustified(4, QChar(L'0'));
// "000A"
QString mask;
mask.fill(QChar(L'*'), 5); // "*****"
QString html = QString("<b>A & B</b>").toHtmlEscaped();
// "<b>A & B</b>"
toHtmlEscaped() экранирует &, <, >, двойную и одинарную кавычки. Метод подходит для вывода текста в HTML, но не заменяет обработку URL, JavaScript или других специальных контекстов. repeated() и методы выравнивания возвращают новую строку; fill() изменяет текущую.
14. Доступ к буферу и capacity()
constData(), константный data() и utf16() возвращают const wchar_t* на внутренний UTF-16-буфер. Неконстантный data() возвращает wchar_t* и перед этим вызывает UnicodeString::Unique(), отделяя разделяемые данные.
QString s("abc");
wchar_t* buffer = s.data();
buffer[1] = L'X'; // "aXc"; индекс буфера также с нуля
Записывайте только в существующие позиции 0…length()-1. Через указатель нельзя увеличивать длину строки или перезаписывать завершающий ноль. После изменения строки прежний указатель может стать недействительным; указатель на буфер временного объекта сохранять нельзя. Для пустой строки data() предоставляет место под нулевой терминатор, а не свободный буфер для добавления текста.
capacity() у QString возвращает текущую длину, а не реальную ёмкость выделенной памяти. Метода reserve() у QString в этом модуле нет. У QStringList методы reserve() и capacity() относятся к внутреннему vector и имеют обычный смысл для контейнера.
15. Сравнение и стандартные контейнеры
QString поддерживает ==, !=, <, <=, > и >=. Сравнение строк выполняется через UnicodeString и чувствительно к регистру. В заголовке также определены std::hash<QString> и std::hash<QStringList>, поэтому их можно использовать как ключи стандартных хеш-контейнеров.
#include <unordered_map>
std::unordered_map<QString, int> counters;
counters[QString("sensor_A")] = 10;
counters[QString("sensor_B")] = 20;
int value = counters[QString("sensor_A")]; // 10
16. Практический пример: разбор строки параметров
Небольшой пример объединяет разделение, удаление лишних пробелов, проверку числа и формирование результата. Формат входных данных здесь простой: имя;значение;единица измерения. Это не CSV-парсер с поддержкой кавычек и экранирования.
QString input(L" Давление ; 12.5 ; бар ");
QStringList fields(input.split(";", Qt::KeepEmptyParts));
QString result;
if(fields.size() == 3)
{
bool ok = false;
double value = fields[1].trimmed().toDouble(&ok);
if(ok)
{
result = QString(L"%1: %2 %3")
.arg(fields[0].trimmed())
.arg(QString::number(value, 'f', 2))
.arg(fields[2].trimmed());
}
else
{
result = QString(L"Ошибка: неверное число");
}
}
else
{
result = QString(L"Ошибка: ожидаются три поля");
}
// Для этой строки: "Давление: 12.50 бар"
// В обработчике формы можно вывести:
// Label1->Caption = result.toUnicodeString();
17. Что учитывать при переносе кода из Qt
- Набор API ограничен. В статье перечислены методы именно этого модуля; поддержка всех возможностей Qt QString, регулярных выражений и локалей не заявляется.
- split() → vector. Для QStringList требуется явное создание из результата.
- Байтовые результаты → std::string. У них нет методов QByteArray.
- Поиск QString чувствителен к регистру. Параметр CaseSensitivity есть у replace() и ряда операций QStringList, но не у indexOf() или contains() строки.
- arg() принимает строковое значение. Поддержка числовых форматов выполняется отдельным вызовом number().
- isNull() == isEmpty(). Различие пустой и null-строки не моделируется.
- capacity() строки — длина. Использовать его для оценки запаса памяти нельзя.
- Зависимость от C++Builder сохраняется. Сходный интерфейс помогает переносить строковую логику, но сама реализация использует System.hpp и RTL C++Builder.
Модуль полезен, когда хочется сохранить Qt-стиль обработки строк в проекте C++Builder и удобно передавать результат в VCL через UnicodeString. При переносе конкретного участка кода следует сверять используемые перегрузки и ожидаемое поведение с этими заголовками.