Лекция №6. Работа со строками в C++ и C#.
§6.1 Понятие строки и её роль в программировании
Строка
— это последовательность символов, представляющая собой фундаментальный тип данных для работы с текстовой информацией в программировании.
В языках C++ и C# строки используются для хранения и обработки текстовых данных, таких как имена пользователей, сообщения, файлы конфигурации, входные и выходные данные программ.
В отличие от числовых типов, строки требуют специальных механизмов для хранения, модификации и сравнения. Их обработка включает операции конкатенации, поиска подстрок, извлечения фрагментов, изменения регистра и форматирования. Эффективная работа со строками является ключевой при разработке приложений, взаимодействующих с пользователем или обрабатывающих текстовые данные.
Несмотря на различия в реализации, в обоих языках строки играют центральную роль в повседневном программировании, а понимание их особенностей необходимо для написания безопасного, эффективного и читаемого кода.
§6.2 Работа со строками в С++
В C++ поддержка строк реализована на нескольких уровнях. На низком уровне используются C-строки — массивы символов, завершаемые нулевым символом \0. Такой подход требует ручного управления памятью и уязвим к ошибкам, таким как переполнение буфера.
Для удобной и безопасной работы со строками в стандартной библиотеке C++ предусмотрен класс std::string, определённый в заголовке <string>.
Этот класс предоставляет широкий набор методов для выполнения типичных операций: конкатенации, сравнения, поиска, извлечения подстрок и изменения содержимого.
Объекты std::string управляют памятью автоматически, что упрощает разработку и повышает надёжность программ.
Использование std::string является современной и рекомендуемой практикой при работе с текстовыми данными в C++.
§6.2.1 С – строки и их ограничения
Символ
– элементарная единица, некоторый набор которых несет определенный смысл. В языке программирования С++ предусмотрено использование символьных констант.
Символьная константа
– это целочисленное значение, представленное в виде символа, заключённого в одинарные кавычки, например, 'a'. В таблице ASCII представлены символы и их целочисленные значения.
Строки в стиле С (C-style string)
— это частный случай массива символьных констант.
Вы уже видели несколько примеров таких строк в виде строковых литералов, когда писали такой код:
cpp
cout << "Hello World";
Это эквивалентно такому объявлению массива:
cpp
char SayHello[] = {'Н', 'е', 'l', 'l', 'о', ' ', 'W', 'o', 'г', 'l', 'd','\0'};
cout << SayHello << endl;
Обратите внимание
Последний символ в массиве — нулевой символ '\0'. Он также называется знаком окончания строки (string-terminating character), поскольку указывает компилятору, что строка закончилась. Такие строки в стиле С — это частный случай символьных массивов, последним символом которых всегда является нулевой символ '\0'. Когда вы используете в коде строковый литерал, компилятор сам добавляет после него символ '\0'.
Строка при объявлении может быть инициализирована начальным значением, например, так:
cpp
char SayHello[] = "Hello World"
При такой инициализации строки заключаются в двойные кавычки.
Компилятор автоматически выделяет память под 12 символов (11 букв и завершающий \0) и инициализирует массив. Доступ к отдельным символам осуществляется по индексу: str[0] = 'H';.
Для работы с символьными строками выделяется специальный набор функций. Например, с помощью функции gets(имя_переменной) считаются все введённые символы с пробелами до тех пор, пока во вводимом потоке не встретится код клавиши enter. Если использовать операцию cin то из всего введённого считается последовательность символов до первого пробела.
В таблице ниже представлены функции для работы со строковым типом:
| Функция | Пояснение |
|---|---|
strlen(имя_строки) | определяет длину указанной строки, без учёта нуль-символа |
| Копирование строк | |
strcpy(s1,s2) | выполняет копирование символов из строки s2 в строку s1 |
strncpy(s1,s2,n) | выполняет копирование n символов из строки s2 в строку s1. возвращает значения s1 |
| Конкатенация строк | |
strcat(s1,s2) | объединяет строку s2 со строкой s1. Результат сохраняется в s1 |
strncat(s1,s2,n) | объединяет n символов строки s2 со строкой s1. Результат сохраняется в s1 |
| Сравнение строк | |
strcpm(s1,s2) | сравнивает строку s1 со строкой s2 и возвращает результат типа int: 0 –если строки эквивалентны, >0 – если s1s2 С учётом регистра |
strncmp(s1,s2,n) | сравнивает n символов строки s1 со строкой s2 и возвращает результат типа int: 0 –если строки эквивалентны, >0 – если s1s2 С учётом регистра |
stricmp(s1,s2) | сравнивает строку s1 со строкой s2 и возвращает результат типа int: 0 – если строки эквивалентны, >0 – если s1s2 Без учёта регистра |
strnicmp(s1,s2,n) | сравнивает n символов строки s1 со строкой s2 и возвращает результат типа int: 0 – если строки эквивалентны, >0 – если s1s2 Без учёта регистра |
| Обработка символов | |
isalnum(c) | возвращает значение true, если с является буквой или цифрой, и false в других случаях |
isalpha(c) | возвращает значение true, если с является буквой, и false в других случаях |
isdigit(c) | возвращает значение true, если с является цифрой, и false в других случаях |
islower(c) | возвращает значение true, если с является буквой нижнего регистра, и false в других случаях |
isupper(c) | возвращает значение true, если с является буквой верхнего регистра, и false в других случаях |
isspace(c) | возвращает значение true, если с является пробелом, и false в других случаях |
toupper(c) | если символ с, является символом нижнего регистра, то функция возвращает преобразованный символ с в верхнем регистре, иначе символ возвращается без изменений. |
| Функции поиска | |
strchr(s,c) | поиск первого вхождения символа с в строке s. В случае удачного поиска возвращает указатель на место первого вхождения символа с. |
strcspn(s1,s2) | определяет длину начального сегмента строки s1, содержащего те символы, которые не входят в строку s2 |
strspn(s1,s2) | возвращает длину начального сегмента строки s1, содержащего только те символы, которые входят в строку s2 |
| Функции преображения | |
atof(s1) | преобразует строку s1 в тип double |
atoi(s1) | преобразует строку s1 в тип int |
atol(s1) | преобразует строку s1 в тип long int |
| Функции стандартной библиотеки ввода/вывода <stdio> | |
getchar(с) | считывает символ с со стандартного потока ввода, возвращает символ в формате int |
gets(s) | считывает поток символов со стандартного устройства ввода в строку s до тех пор, пока не будет нажата клавиша ENTER |
Несмотря на простоту, C-строки имеют ряд существенных ограничений:
- Ручное управление памятью. При работе с динамическими строками разработчик сам отвечает за выделение (new[]) и освобождение памяти (delete[]), что увеличивает вероятность утечек памяти.
- Риск переполнения буфера. Функции стандартной библиотеки, такие как strcpy, strcat, gets, не выполняют проверку границ массива. Например:
cpp
char buffer[10];
strcpy(buffer, "This is too long"); // Переполнение — неопределённое поведение
- Отсутствие встроенных методов. C-строки не являются объектами — для выполнения операций (сравнение, конкатенация, определение длины) необходимо использовать отдельные функции из <cstring>.
Из-за этих недостатков использование C-строк в современном C++ не рекомендуется в пользу безопасных и удобных альтернатив, таких как std::string.
§6.2.2 Класс std::string
Для удобной и безопасной работы со строками в C++ стандартная библиотека предоставляет класс std::string, определённый в заголовочном файле <string>. Подключение этого заголовка необходимо для использования класса:
cpp
#include <string>
Класс std::string представляет собой контейнер, управляющий строковыми данными с автоматическим выделением и освобождением памяти.
Он является частью Standard Template Library (STL) и реализует строку как динамически изменяемую последовательность символов.
Преимущества std::string перед C-строками:\
- Автоматическое управление памятью — разработчику не нужно вручную выделять или освобождать память.
- Безопасность — операции проверяют границы, исключая переполнение буфера.
- Гибкость и удобство — богатый набор методов и перегруженных операторов.
- Интеграция с STL — совместимость с алгоритмами и контейнерами стандартной библиотеки.
Объявляются и инициализируются строки с типом данных std::string следующим образом:
cpp
// Пустая строка
std::string s1;
// Инициализация строковым литералом
std::string s2 = "Hello";
// Копирование строки
std::string s3(s2);
// Конкатенация при инициализации
std::string s4 = s2 + " world";
Переменные типа std::string можно легко копировать, присваивать и передавать в функции. Они уничтожаются автоматически при выходе из области видимости, что предотвращает утечки памяти.
Использование std::string является рекомендуемым подходом при работе со строками в современном C++, так как это гораздо удобнее, чем использовать С – строки.
§6.2.3 Основные операции со строками в С++
Класс std::string предоставляет широкий набор методов и перегруженных операторов для выполнения типичных операций обработки строк.
Ниже приведены ключевые операции с краткими пояснениями и примерами.
1. Конкатенация строк
Поддерживается с помощью операторов + и +=, что позволяет легко объединять строки:
cpp
std::string a = "Hello";
std::string b = " world";
std::string c = a + b; // "Hello world"
a += "!"; // a становится "Hello!"
2. Доступ к символам
Отдельные символы строки доступны по индексу с использованием оператора []. Индексация начинается с 0:
cpp
std::string s = "abc";
char ch = s[1]; // ch = 'b'
s[0] = 'x'; // s становится "xbc"
3. Длина строки
Количество символов в строке возвращает метод length() или его синоним size():
cpp
std::string s = "text";
size_t len = s.length(); // len = 4
4. Сравнение строк
Операторы сравнения (==, !=, <, <=, >, >=) позволяют сравнивать строки лексикографически:
cpp
std::string a = "apple",
b = "banana";
if (a < b)
{
/* true */
}
Для более гибкого сравнения используется метод compare():
cpp
int result = a.compare(b); // Возвращает 0, >0 или <0
5. Поиск подстрок
Метод find() возвращает позицию первого вхождения подстроки или std::string::npos, если подстрока не найдена:
cpp
std::string s = "Hello world";
size_t pos = s.find("world"); // pos = 6
Метод rfind() выполняет поиск с конца строки.
6. Извлечение подстроки
Метод substr() возвращает новую строку — фрагмент исходной:
cpp
std::string part = s.substr(6, 5); // "world"
7. Модификация строки
insert(pos, str)— вставка строки в указанную позицию:
cpp
s.insert(5, ", "); // "Hello, world"
erase(pos, len)— удаление части строки:
cpp
s.erase(5, 2); // Удаляет 2 символа с позиции 5
replace(pos, len, str)— замена части строки:
cpp
s.replace(6, 5, "C++"); // "Hello C++"
§6.3 Работа со строками в С#
В языке C# строковый тип string является ссылочным типом данных и предназначен для хранения текстовой информации. Он реализован как класс System.String** в .NET-платформе и предоставляет богатый набор встроенных методов для обработки строк.
§6.3.1 Тип string и его особенности
В C# тип string представляет собой встроенный псевдоним для класса System.String и предназначен для хранения текстовых данных. Несмотря на синтаксическую простоту, string является ссылочным типом, а не значимым, что означает, что переменная типа string хранит ссылку на объект в управляемой памяти (куче).
Пример объявления и инициализации:
csharp
string name = "Alice";
При выполнении операции:
csharp
name = name + " Smith";
создаётся новый строковый объект с значением «Alice Smith», а ссылка name перенаправляется на него.
Старый объект («Alice») становится недостижимым и впоследствии удаляется сборщиком мусора.
Последствия неизменяемости:
- Безопасность: строки могут безопасно передаваться между методами и потоками без риска неожиданного изменения.
- Кэширование и интернирование: CLR автоматически кэширует строковые литералы (через механизм string interning), что позволяет экономить память при повторяющихся значениях.
- Производительность: частые операции с изменением строк (например, в цикле) приводят к созданию множества временных объектов, что увеличивает нагрузку на память и сборщик мусора.
§6.3.2 Основные операции со строками в C#
Тип string в C# предоставляет богатый набор встроенных методов и операторов для выполнения типичных задач обработки текста. Ниже приведены ключевые операции, активно используемые в практике программирования.
1. Конкатенация строк
Объединение строк выполняется с помощью оператора + или статического метода string.Concat():
csharp
string firstName = "Alice";
string lastName = "Smith";
string fullName = firstName + " " + lastName;
// или
string fullName2 = string.Concat(firstName, " ", lastName);
2. Интерполяция строк
Средство интерполяции (с префиксом $) позволяет встраивать выражения прямо в строковой литерал, делая код более читаемым:
csharp
string name = "Alice";
int age = 25;
string message = $"Hello, {name}! You are {age} years old.";
3. Доступ к символам
Отдельные символы строки доступны по индексу с помощью оператора []. Индексация начинается с 0:
csharp
char first = name[0]; // 'A'
char last = name[name.Length - 1]; // 'e'
Попытка доступа за пределами диапазона вызывает исключение IndexOutOfRangeException.
4. Длина строки
Количество символов в строке возвращает свойство Length:
csharp
int len = name.Length; // 5
5. Сравнение строк
- Оператор == сравнивает содержимое строк (не ссылки):
csharp
if (str1 == str2)
{
// ...
}
- Метод Equals() позволяет задать дополнительные параметры, например, игнорирование регистра:
csharp
str1.Equals(str2, StringComparison.OrdinalIgnoreCase)
- Статический метод string.Compare() выполняет лексикографическое сравнение:
csharp
int result = string.Compare(str1, str2, true);
// true — игнорировать регистр
6. Поиск подстрок
- IndexOf() возвращает позицию первого вхождения подстроки или возвращает -1, если не найдено:
csharp
int pos = text.IndexOf("world");
- Contains() проверяет наличие подстроки и возвращает bool:
csharp
1. bool found = text.Contains("Hello");
7. Извлечение подстроки
Метод Substring() возвращает часть строки, начиная с указанной позиции:
csharp
string part = text.Substring(6, 5);
// с 6-го символа, 5 шт.
8. Преобразование регистра
- ToUpper() — преобразует строку в верхний регистр.
- ToLower() — преобразует в нижний регистр.
csharp
string upper = name.ToUpper(); // "ALICE"
9. Удаление пробелов
- Trim() — удаляет пробелы (и другие пробельные символы) в начале и конце.
- TrimStart(), TrimEnd() — удаляют только в начале или только в конце:
csharp
string clean = " text ".Trim(); // "text"
§6.3.3 Использование StringBuilder в C#
Несмотря на удобство типа string, его неизменяемость становится проблемой при выполнении множественных операций модификации, особенно в циклах. Каждая конкатенация создаёт новый объект, что приводит к избыточному использованию памяти и снижению производительности.
Для эффективной работы со строками, подвергающимися частым изменениям, в .NET предоставляется класс System.Text.StringBuilder. В отличие от string, StringBuilder является изменяемым (mutable) — он позволяет модифицировать содержимое строки без создания новых объектов.
StringBuilder необходимо использовать:
- При построении строк в циклах (например, формирование отчёта, лога, HTML-кода).
- При большом количестве операций конкатенации.
- Когда важна производительность и оптимизация памяти.
Как и у любой конструкции, у StringBuilder есть основные методы:
- Append(value) — добавляет значение в конец строки.
- AppendLine(value) — добавляет значение и переход на новую строку.
- ToString() — возвращает итоговую строку типа string.
- Clear() — очищает содержимое.
- Length, Capacity — позволяют управлять размером.
csharp
using System.Text;
StringBuilder sb = new StringBuilder();
sb.Append("Hello");
sb.AppendLine(", world!");
sb.Append("Today is ");
sb.Append(DateTime.Now.ToShortDateString());
string result = sb.ToString();
// Результат: Hello
// , world! Today is 09.08.2025"