---
course: ОАиП
lecture: 6
title: "Лекция №6. Работа со строками в C++ и C#."
---

# Лекция №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

```cpp
cout << "Hello World";
```

Это эквивалентно такому объявлению массива:

cpp

```cpp
char SayHello[] = {'Н', 'е', 'l', 'l', 'о', ' ', 'W', 'o', 'г', 'l', 'd','\0'}; 
cout << SayHello << endl;
```

Обратите внимание

Последний символ в массиве — **нулевой символ '\0'**. Он также называется **знаком окончания строки** (*string-terminating character*), поскольку указывает компилятору, что строка закончилась. Такие строки в стиле С — это частный случай символьных массивов, последним символом которых всегда является нулевой символ '\0'. Когда вы используете в коде строковый литерал, компилятор сам добавляет после него символ '\0'.

Строка при объявлении может быть инициализирована начальным значением, например, так:

cpp

```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

```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

```cpp
#include <string>
```

Класс **std::string** представляет собой контейнер, управляющий строковыми данными с автоматическим выделением и освобождением памяти.

Он является частью **Standard Template Library (STL)** и реализует строку как динамически изменяемую последовательность символов.

Преимущества **std::string** перед **C-строками**:\

- **Автоматическое управление памятью** — разработчику не нужно вручную выделять или освобождать память.
- **Безопасность** — операции проверяют границы, исключая переполнение буфера.
- **Гибкость и удобство** — богатый набор методов и перегруженных операторов.
- **Интеграция с STL** — совместимость с алгоритмами и контейнерами стандартной библиотеки.

Объявляются и инициализируются строки с типом данных **std::string** следующим образом:

cpp

```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

```cpp
std::string a = "Hello";
std::string b = " world";
std::string c = a + b;     // "Hello world"
a += "!";                  // a становится "Hello!"
```

**2. Доступ к символам**

Отдельные символы строки доступны по индексу с использованием оператора **[]**. Индексация начинается с **0**:

cpp

```cpp
std::string s = "abc";
char ch = s[1];            // ch = 'b'
s[0] = 'x';                // s становится "xbc" 
```

**3. Длина строки**

Количество символов в строке возвращает метод **length()** или его синоним **size()**:

cpp

```cpp
std::string s = "text";
size_t len = s.length();   // len = 4
```

**4. Сравнение строк**

Операторы сравнения (*==, !=, <, <=, >, >=*) позволяют сравнивать строки лексикографически:

cpp

```cpp
std::string a = "apple", 
            b = "banana";
if (a < b) 
{ 
    /* true */ 
}
```

Для более гибкого сравнения используется метод **compare()**:

cpp

```cpp
int result = a.compare(b); // Возвращает 0, >0 или <0
```

**5. Поиск подстрок**

Метод **find()** возвращает позицию первого вхождения подстроки или **std::string::npos**, если подстрока не найдена:

cpp

```cpp
std::string s = "Hello world";
size_t pos = s.find("world"); // pos = 6 
```

Метод **rfind()** выполняет поиск с конца строки.

**6. Извлечение подстроки**

Метод **substr()** возвращает новую строку — фрагмент исходной:

cpp

```cpp
std::string part = s.substr(6, 5); // "world"	
```

**7. Модификация строки**

- `insert(pos, str)` — вставка строки в указанную позицию:

cpp

```cpp
s.insert(5, ", "); // "Hello, world"
```

- `erase(pos, len)` — удаление части строки:

cpp

```cpp
s.erase(5, 2); // Удаляет 2 символа с позиции 5
```

- `replace(pos, len, str)` — замена части строки:

cpp

```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

```csharp
string name = "Alice";
```

При выполнении операции:

csharp

```csharp
name = name + " Smith";
```

создаётся новый строковый объект с значением *«Alice Smith»*, а ссылка name перенаправляется на него.

Старый объект (*«Alice»*) становится недостижимым и впоследствии удаляется сборщиком мусора.

Последствия неизменяемости:

- **Безопасность:** строки могут безопасно передаваться между методами и потоками без риска неожиданного изменения.
- **Кэширование и интернирование:** CLR автоматически кэширует строковые литералы (через механизм string interning), что позволяет экономить память при повторяющихся значениях.
- **Производительность:** частые операции с изменением строк (например, в цикле) приводят к созданию множества временных объектов, что увеличивает нагрузку на память и сборщик мусора.

### §6.3.2 Основные операции со строками в C#

Тип **string** в C# предоставляет богатый набор встроенных методов и операторов для выполнения типичных задач обработки текста. Ниже приведены ключевые операции, активно используемые в практике программирования.

**1. Конкатенация строк**

Объединение строк выполняется с помощью оператора **+** или статического метода **string.Concat()**:

csharp

```csharp
string firstName = "Alice";
string lastName = "Smith";
string fullName = firstName + " " + lastName;

// или
string fullName2 = string.Concat(firstName, " ", lastName);  
```

**2. Интерполяция строк**

Средство **интерполяции** (с префиксом *$*) позволяет встраивать выражения прямо в строковой литерал, делая код более читаемым:

csharp

```csharp
string name = "Alice";
int age = 25;
string message = $"Hello, {name}! You are {age} years old.";
```

**3. Доступ к символам**

Отдельные символы строки доступны по индексу с помощью оператора **[]**. Индексация начинается с **0**:

csharp

```csharp
char first = name[0];    // 'A'
char last = name[name.Length - 1]; // 'e'
```

Попытка доступа за пределами диапазона вызывает исключение **IndexOutOfRangeException**.

**4. Длина строки**

Количество символов в строке возвращает свойство **Length**:

csharp

```csharp
int len = name.Length;   // 5
```

**5. Сравнение строк**

- Оператор **==** сравнивает содержимое строк (не ссылки):

csharp

```csharp
if (str1 == str2) 
{ 
    // ... 
}
```

- Метод **Equals()** позволяет задать дополнительные параметры, например, игнорирование регистра:

csharp

```csharp
str1.Equals(str2, StringComparison.OrdinalIgnoreCase)
```

- Статический метод **string.Compare()** выполняет лексикографическое сравнение:

csharp

```csharp
int result = string.Compare(str1, str2, true); 
// true — игнорировать регистр
```

**6. Поиск подстрок**

- **IndexOf()** возвращает позицию первого вхождения подстроки или возвращает **-1**, если не найдено:

csharp

```csharp
int pos = text.IndexOf("world");
```

- **Contains()** проверяет наличие подстроки и возвращает **bool**:

csharp

```csharp
1. bool found = text.Contains("Hello");
```

**7. Извлечение подстроки**

Метод **Substring()** возвращает часть строки, начиная с указанной позиции:

csharp

```csharp
string part = text.Substring(6, 5); 
// с 6-го символа, 5 шт.
```

**8. Преобразование регистра**

- **ToUpper()** — преобразует строку в верхний регистр.
- **ToLower()** — преобразует в нижний регистр.

csharp

```csharp
string upper = name.ToUpper(); // "ALICE"
```

**9. Удаление пробелов**

- **Trim()** — удаляет пробелы (и другие пробельные символы) в начале и конце.
- **TrimStart()**, **TrimEnd()** — удаляют только в начале или только в конце:

csharp

```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

```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"
```
