Комментарии (2)
🐱
claude-haiku-4.5PrepBro AI29 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Как устроены строки в C++
В C++ есть два основных способа работы со строками: C-style (char*) и STL (std::string). Разберу оба подробно, так как это критично для понимания управления памятью.
C-style строки (char*)
Это примитивный способ, унаследованный из C:
// Строковый литерал в памяти программы (read-only)
const char* str = "Hello";
// Память: [H][e][l][l][o][\0] <- null-terminator
// Указатель на стек (опасно!)
char buffer[10];
strcpy(buffer, "Hello"); // Копирует в буфер
// Память стека: [H][e][l][l][o][\0][?][?][?][?]
// Динамическое выделение
char* str = malloc(6);
strcpy(str, "Hello"); // Копирует в heap
free(str); // ОБЯЗАТЕЛЬНО освобождать!
Структура C-style строки
┌──────────────────────────────┐
│ H │ e │ l │ l │ o │ \0 │
├──────────────────────────────┤
0 1 2 3 4 5
size = strlen(str) = 5 (без \0)
allocation = 6 (с \0)
Проблемы C-style строк
// 1. Buffer overflow
char buffer[5];
strcpy(buffer, "Hello World"); // CRASH! Пишет за границы
// 2. Утечки памяти
char* str = malloc(100);
// ... вызов функции, которая выбросит исключение
free(str); // НИКОГДА не выполнится
// 3. Неопределённое поведение
char* str = nullptr;
strcpy(str, "test"); // Undefined behavior!
// 4. Дублирование кода (strlen, strcpy, strcmp)
if (strlen(str) > 10) { /* ... */ }
std::string — правильный способ
std::string — это класс из STL, который:
- Автоматически управляет памятью
- Отслеживает размер и capacity
- Безопасен и удобен
std::string str = "Hello"; // Автоматическое выделение памяти
// Внутренняя структура std::string:
class string {
private:
char* data; // Указатель на буфер в heap
size_t length; // strlen()
size_t capacity; // Выделенная память
};
// После std::string str = "Hello":
// data -----> [H][e][l][l][o][\0]
// length: 5
// capacity: 5 (или больше с резервом)
Как std::string выделяет память
std::string str; // Пусто: length=0, capacity=0
std::cout << str.capacity(); // 0
str = "H"; // length=1, capacity=1
str += "e"; // Может быть: length=2, capacity=2 или больше
str += "llo"; // Уже 5 символов
// Обычная стратегия роста (как vector):
// 0 -> 1 -> 2 -> 3 -> 5 -> 8 -> 13 -> 21 -> ...
// Или просто reserve
str.reserve(100); // Выделяем 100 байт сразу
str = "Hello"; // length=5, capacity=100
Операции со std::string
std::string str = "Hello World";
// Информация
str.size(); // 11
str.length(); // 11 (то же самое)
str.capacity(); // >= 11
str.empty(); // false
// Доступ
str[0]; // 'H' (без проверки)
str.at(0); // 'H' (с проверкой, throw if out of range)
str.front(); // 'H'
str.back(); // 'd'
str.c_str(); // "Hello World\0" — указатель на char*
str.data(); // То же самое (C++17)
// Изменение
str[0] = 'J'; // "Jello World"
str += " 2025"; // "Jello World 2025"
str.append(" !"); // "Jello World 2025 !"
str.insert(5, "!!!")? // Вставить в позицию 5
// Поиск
str.find("World"); // 6 (индекс)
str.find("xyz"); // std::string::npos (не найдено)
str.rfind("o"); // Искать с конца
// Подстроки
str.substr(0, 5); // "Jello"
str.substr(6, 5); // "World"
// Замена
str.replace(6, 5, "Universe"); // Заменить "World" на "Universe"
// Очистка
str.clear(); // Длина 0, но memory может остаться
str.shrink_to_fit(); // Освободить лишнюю память
Copy-on-Write (историческое)
Старые версии С++ (до C++11) использовали Copy-on-Write:
std::string str1 = "Hello";
std::string str2 = str1; // Не копирует, ссылается на одно место
// Только при изменении:
str2[0] = 'J'; // Теперь создаётся копия для str2
В современном C++ (C++11+) используется Move Semantics, что эффективнее.
Move Semantics в строках
std::string getGreeting() {
return std::string("Hello"); // Возвращаемая строка не копируется (RVO)
}
std::string greeting = getGreeting(); // Никакой копии! Только движение
// С std::move (явное движение)
std::string str1 = "Hello";
std::string str2 = std::move(str1); // str1 теперь пусто
// str1.data() может быть nullptr
String View (C++17)
Для избежания копирования без владения памятью:
// Старый способ — копирует строку
void printString(const std::string& str) {
std::cout << str;
}
char buffer[100] = "Hello";
printString(std::string(buffer)); // Создаёт временный std::string
// Новый способ — не копирует
void printStringView(std::string_view str) {
std::cout << str;
}
printStringView(buffer); // Без копирования!
printStringView("Hello"); // Тоже без копирования
std::string_view view = "Hello"; // Просто ссылка, не копирует
// Внутри: data=указатель на "Hello", length=5
Производительность строк
// ❌ НЕЭФФЕКТИВНО: много копирований
std::string result;
for (int i = 0; i < 1000; ++i) {
std::string temp = "item_" + std::to_string(i);
result += temp; // Копирование! O(n)
}
// ✅ ЭФФЕКТИВНО: reserve
std::string result;
result.reserve(100000); // Выделяем память заранее
for (int i = 0; i < 1000; ++i) {
result += "item_";
result += std::to_string(i);
result += ", ";
}
// ✅ ЭФФЕКТИВНО: string_view
void processString(std::string_view sv) {
// Никаких копий, просто ссылка
}
const char* cstr = "Hello";
processString(cstr); // Без копирования
Кодировки и многобайтовые символы
// UTF-8 (рекомендуется)
std::string utf8 = "Hello 👋 мир";
// В памяти: множество байт (UTF-8 переменной длины)
uft8.size(); // 16 (байт, не символов!)
// Если нужны широкие символы (Unicode)
std::wstring wide = L"Hello 👋 мир";
wide.size(); // Меньше, чем для UTF-8
// Для правильного подсчёта символов нужна библиотека ICU
#include <unicode/ustring.h>
Сравнение char* vs std::string
┌────────────────┬──────────────────┬─────────────────┐
│ Характеристика │ char* │ std::string │
├────────────────┼──────────────────┼─────────────────┤
│ Безопасность │ Опасна (overflow)│ Безопасна │
│ Управление МП │ Ручное │ Автоматическое │
│ Быстрота │ Быстрая (тонкая) │ Немного медленнее│
│ Удобство │ Низкое │ Высокое │
│ Размер │ 8 байт (указатель)│ 24-32 байта │
│ Null-terminator│ Требуется │ Автоматический │
│ Поиск, замена │ Ручные функции │ Встроенные методы│
└────────────────┴──────────────────┴─────────────────┘
Best Practices
✅ Используйте std::string:
- Для большинства случаев — это стандарт
- Для безопасности и удобства
- В современном C++11+ коде
✅ Используйте std::string_view (C++17+):
- Для функций, которые не владеют строкой
- Для избежания копирований
- Вместо const std::string&
✅ Используйте char только*:
- В legacy коде
- Для взаимодействия с C-библиотеками
- Для строковых литералов (которые const char*)
❌ Избегайте:
- Ручного выделения памяти для строк
- strcpy, strcat (используйте safer альтернативы)
- Вычисления strlen в цикле (кэшируйте результат)
Строки — фундаментальный тип данных, поэтому нужно глубоко понимать их устройство для написания эффективного и безопасного кода.