Глава 7: Массивы и Строки — Работа с Коллекциями Данных и Текстом ================================================================== Массивы и строки — это **фундаментальные конструкции** языка C, которые позволяют эффективно работать с упорядоченными наборами данных и текстом. Понимание того, как они функционируют на низком уровне, является ключом к эффективному программированию на C. В этой главе мы подробно рассмотрим: * **Одномерные массивы**: Как хранить коллекции однотипных данных. * **Строки (`char[]`)**: Особенность строк в C как массивов символов, завершающихся нулевым байтом. * **Основные функции для работы со строками** из библиотеки ``. * **Важные советы** по безопасному и эффективному использованию массивов и строк. --- Одномерные массивы ------------------ **Массив** — это коллекция элементов **одного и того же типа**, хранящихся в **непрерывной области памяти**. Каждый элемент массива доступен по своему **индексу**. **Объявление массива:** При объявлении массива необходимо указать тип его элементов и желаемый размер (количество элементов). .. code-block:: c int numbers[5]; // Объявляет массив 'numbers', который может хранить 5 целых чисел. char letters[10]; // Объявляет массив 'letters', который может хранить 10 символов. **Важно**: Элементы массива индексируются **от `0` до `n - 1`**, где `n` — это размер массива. То есть, первый элемент находится по индексу `0`, а последний — по индексу `n-1`. **Инициализация массива:** Массивы можно инициализировать при их объявлении, присваивая значения его элементам. .. code-block:: c // Инициализация массива 'a' с явным указанием размера и всех элементов. int a[3] = {1, 2, 3}; // Инициализация массива 'vowels' символьными литералами. char vowels[5] = {'a', 'e', 'i', 'o', 'u'}; Если вы не указываете размер массива при инициализации, компилятор автоматически определяет его по количеству предоставленных элементов: .. code-block:: c // Размер массива 'nums' будет автоматически установлен в 3, так как предоставлено 3 элемента. int nums[] = {10, 20, 30}; ### Пример 7.1: Вывод Элементов Массива Этот пример демонстрирует, как объявить, инициализировать и затем итерировать по элементам массива, выводя их значения. .. code-block:: c // array_example.c #include // Подключаем стандартную библиотеку ввода-вывода для функции printf. int main() { // Объявление и инициализация одномерного целочисленного массива 'data'. // Массив имеет размер 5 элементов. // Элементы инициализируются значениями {10, 20, 30, 40, 50}. // Память под эти 5 целых чисел выделяется непрерывно. int data[5] = {10, 20, 30, 40, 50}; // Объявляем переменную-счетчик 'i' для использования в цикле. int i; printf("Элементы массива:\n"); // Выводим заголовок. \n переносит на новую строку. // Цикл for для итерации по элементам массива. // Массивы в C индексируются с 0. // 'i' начинается с 0, идет до 4 (пока i < 5). for (i = 0; i < 5; i++) { // Выводим индекс элемента (data[i]) и его значение (data[i]). // %d - спецификатор формата для вывода целых чисел. // data[i] - обращение к элементу массива по индексу 'i'. printf("data[%d] = %d\n", i, data[i]); } // Возвращаем 0, чтобы сигнализировать операционной системе, // что программа завершилась успешно. return 0; } Вывод при компиляции и запуске ``array_example.c``: :: Элементы массива: data[0] = 10 data[1] = 20 data[2] = 30 data[3] = 40 data[4] = 50 --- Строки в стиле C (`char[]`) --------------------------- В языке C **строка** — это особый вид **массива символов (`char[]`)**, который **обязательно завершается** специальным нулевым символом (`\0`). Этот нулевой символ служит маркером конца строки, позволяя функциям, работающим со строками, знать, где заканчивается текст. **Пример объявления и инициализации строки:** При использовании строкового литерала (текста в двойных кавычках) компилятор автоматически добавляет `\0` в конец массива. .. code-block:: c char greeting[] = "Привет"; // Этот массив будет иметь размер 7 символов: // 'П', 'р', 'и', 'в', 'е', 'т', '\0' **Важно: Ошибка инициализации без `\0`:** Если вы инициализируете `char` массив вручную символами, вы должны явно добавить `\0`, иначе это не будет считаться корректной строкой в C. .. code-block:: c char word[5] = {'H', 'e', 'l', 'l', 'o'}; // НЕ является строкой в C, так как отсутствует '\0'. // Попытка использовать ее со строковыми функциями // приведет к неопределенному поведению. **Правильный ввод строк с клавиатуры:** Функция `scanf("%s", ...)` опасна для чтения строк, так как она останавливается на первом пробеле и **не проверяет размер буфера**, что может привести к **переполнению буфера**. .. code-block:: c char name[20]; printf("Введите ваше имя: "); scanf("%s", name); // ОПАСНО! Останавливается на пробеле и не проверяет границы массива. printf("Привет, %s!\n", name); **Для безопасного ввода строк с пробелами используйте `fgets()`:** `fgets()` является более безопасной функцией, так как она позволяет указать максимальное количество символов для чтения, предотвращая переполнение буфера. .. code-block:: c // fgets(буфер, размер_буфера, источник) fgets(name, sizeof(name), stdin); // Важно: fgets() читает символ новой строки '\n' (если он есть) и включает его в буфер. // Его часто нужно удалять вручную, как показано в Примере 7.2. --- Работа со строками (``) --------------------------------- Для эффективной работы со строками в C используется стандартная библиотека ``. Чтобы использовать её функции, необходимо подключить заголовочный файл: .. code-block:: c #include Часто используемые функции из ``: Sure thing! My apologies for the oversight. Here's that updated table in **reStructuredText (RST) format**, with the improved descriptions for `strncpy` and `strncat` as discussed: --- .. list-table:: Функции для работы со строками в C (``) :widths: 25 75 :header-rows: 1 * - Функция - Описание * - ``strlen(const char *s)`` - Возвращает **длину строки** ``s`` (количество символов **без** завершающего нулевого символа ``\0``). * - ``strcpy(char *dest, const char *src)`` - **Копирует** строку ``src`` (включая ``\0``) в ``dest``. **Внимание**: ``dest`` должен быть достаточно большим, чтобы вместить всю строку ``src``, иначе возможно **переполнение буфера**. Используйте ``strncpy`` для безопасной копии. * - ``strncpy(char *dest, const char *src, size_t n)`` - **Безопасная копия**: Копирует не более ``n`` символов из ``src`` в ``dest``. Если ``src`` короче ``n``, ``dest`` дополняется нулями. **Важно**: Если ``src`` длиннее или равен ``n``, ``strncpy`` **не гарантирует добавление завершающего ``\0``**. Часто нужно добавлять его вручную: ``dest[n-1] = '\0';``. * - ``strcmp(const char *s1, const char *s2)`` - **Сравнивает** строки ``s1`` и ``s2`` лексикографически (по символам). Возвращает ``0``, если строки идентичны. Возвращает отрицательное значение, если ``s1`` "меньше" ``s2``; положительное, если ``s1`` "больше" ``s2``. * - ``strcat(char *dest, const char *src)`` - **Конкатенирует** (добавляет) строку ``src`` (включая ``\0``) в конец строки ``dest``. **Внимание**: ``dest`` должен иметь достаточно места для размещения обеих строк, иначе возможно **переполнение буфера**. Используйте ``strncat`` для безопасной конкатенации. * - ``strncat(char *dest, const char *src, size_t n)`` - **Безопасная конкатенация**: Добавляет не более ``n`` символов из ``src`` в конец ``dest``. После добавления символов и ``\0`` (если ``n`` не превышает длину ``src``), ``dest`` всегда будет корректно завершен ``\0``. * - ``strcspn(const char *s, const char *reject)`` - Возвращает длину начального сегмента строки ``s``, который не содержит ни одного символа из строки ``reject``. Очень часто используется для **удаления символа новой строки (``\n``)** от ``fgets()``: ``string[strcspn(string, "\\n")] = '\\0';``. --- Let me know if you need anything else! ### Пример 7.2: Расширенная Работа со Строками Этот пример демонстрирует чтение строк с помощью `fgets()` с последующей очисткой от `\n`, а также использование `strlen()`, `strcpy()`, `strcmp()` и `strcat()`. .. code-block:: c // string_manipulation.c #include // Для функций ввода-вывода, таких как printf и fgets. #include // Для строковых функций, таких как strlen и strcspn. int main() { // Объявляем символьный массив 'name' размером 50 символов. // Этого достаточно для хранения строки до 49 символов плюс завершающий нулевой символ '\0'. char name[50]; printf("Введите ваше имя: "); // Запрос ввода у пользователя. // fgets() - безопасная функция для чтения строки. // Она считывает строку из стандартного ввода (stdin) // до символа новой строки '\n' или до (sizeof(name) - 1) символов, // записывая их в массив 'name'. // Важно: fgets() включает символ '\n' (если он был введен) в строку. fgets(name, sizeof(name), stdin); // Удаляем символ новой строки ('\n'), который мог быть прочитан fgets(). // strcspn(name, "\n") находит индекс первого вхождения символа '\n' в строке 'name'. // Если '\n' найден, мы заменяем его на нулевой символ '\0', // что корректно завершает строку в этом месте. // Это критически важно для корректной работы других строковых функций и вывода. name[strcspn(name, "\n")] = 0; // Выводим приветствие с введенным именем. // %s - спецификатор формата для вывода строки. printf("Привет, %s!\n", name); // strlen(name) - функция из , которая возвращает длину строки, // исключая завершающий нулевой символ '\0'. // %lu - спецификатор формата для вывода значения типа size_t, // который возвращает strlen. printf("Длина строки: %lu\n", strlen(name)); // --- Демонстрация других строковых функций (дополнительно) --- // strcpy - копирование строки. char copied_name[50]; // strcpy(назначение, источник) // Копирует содержимое 'name' в 'copied_name', включая '\0'. strcpy(copied_name, name); printf("Скопированное имя: %s\n", copied_name); // strcmp - сравнение строк. char another_name[50]; strcpy(another_name, "Alice"); // Задаем другую строку для сравнения // strcmp возвращает 0, если строки идентичны. // Отрицательное значение, если s1 < s2. // Положительное значение, если s1 > s2. if (strcmp(name, another_name) == 0) { printf("Имена одинаковые.\n"); } else { printf("Имена разные.\n"); } // strcat - конкатенация (объединение) строк. char full_message[100]; strcpy(full_message, "Hello, "); // Инициализируем начальную часть // strcat(назначение, источник) // Добавляет содержимое 'name' в конец 'full_message'. strcat(full_message, name); strcat(full_message, "!"); // Добавляем восклицательный знак printf("Полное сообщение: %s\n", full_message); // Возвращаем 0 для успешного завершения. return 0; } --- Строковые литералы и указатели ------------------------------ В C можно объявлять строковые литералы, используя указатели: .. code-block:: c char *msg = "Hello, world!"; **Важно**: Строковый литерал (текст в двойных кавычках) часто хранится в памяти, предназначенной **только для чтения**. Присвоение его указателю (`char *`) означает, что `msg` указывает на эту область памяти. **Попытка изменить содержимое такой строки через указатель (`msg[0] = 'X';`) приведет к неопределенному поведению или, что чаще, к краху программы (segmentation fault)**, так как вы пытаетесь записать в область памяти, защищенную от записи. Если вам нужна изменяемая строка, всегда используйте объявление массива `char[]`. --- Практический Пример: Подсчёт гласных ------------------------------------ Этот пример демонстрирует более сложную работу со строками: итерацию по символам и условную логику для анализа содержимого. ### Пример 7.3: Подсчитать количество гласных в строке .. code-block:: c // vowel_counter.c #include // Для функций ввода-вывода (printf, fgets). #include // Для строковых функций (strcspn). #include // Для функции tolower() - для упрощения проверки регистра. int main() { // Объявляем символьный массив 'str' для хранения введенной строки. char str[100]; // Объявляем целочисленную переменную для подсчета гласных, инициализируем нулем. int count = 0; printf("Введите строку: "); // Запрос ввода у пользователя. // Безопасное чтение строки с использованием fgets(). fgets(str, sizeof(str), stdin); // Удаляем символ новой строки '\n', если он был прочитан fgets(). str[strcspn(str, "\n")] = 0; // Цикл for для итерации по каждому символу строки. // Условие str[i] != '\0' гарантирует, что цикл продолжается до конца строки. for (int i = 0; str[i] != '\0'; i++) { // Получаем текущий символ строки. char c = str[i]; // Преобразование символа в нижний регистр для упрощения проверки. // tolower() из преобразует букву в нижний регистр; // если это не буква, символ возвращается без изменений. c = tolower(c); // Проверяем, является ли текущий символ гласной (a, e, i, o, u). if (c == 'a' || c == 'e' || c == 'i' || c == 'o' || c == 'u') { count++; // Если символ является гласной, увеличиваем счетчик. } } // Выводим общее количество найденных гласных. printf("Количество гласных: %d\n", count); // Возвращаем 0 для успешного завершения программы. return 0; } --- Полезные советы и распространённые ошибки ----------------------------------------- * **Индексирование с 0**: Всегда помните, что массивы в C начинаются с индекса `0`. * **Завершающий `\0`**: Убедитесь, что все ваши строки в C корректно завершаются нулевым символом (`\0`). Без него строковые функции будут читать за пределами выделенной памяти, что приведёт к ошибкам. * **Используйте `fgets()` вместо `scanf("%s", ...)`**: `fgets()` гораздо безопаснее, так как позволяет указать размер буфера и предотвращает переполнение. * **Избегайте выхода за границы массива**: Попытка доступа к элементу массива по индексу, который находится за его объявленными границами (например, `data[5]` в массиве `data[5]`), приведёт к **неопределенному поведению** (Undefined Behavior), что может выражаться в крахе программы, некорректных данных или уязвимостях безопасности. Это одна из самых частых и сложных для отладки ошибок в C. * **Помните о `const` для строковых литералов**: Если вы инициализируете `char *` строковым литералом (`"Hello"`), помните, что это данные только для чтения. --- Компиляция и Запуск Примеров ---------------------------- Для компиляции и запуска этих программ вам понадобится компилятор C (например, GCC). В терминале, находясь в директории с исходными файлами: .. code-block:: bash # Компиляция Примера 7.1 gcc array_example.c -o array_example # Компиляция Примера 7.2 gcc string_manipulation.c -o string_manipulation # Компиляция Примера 7.3 gcc vowel_counter.c -o vowel_counter Запуск исполняемых файлов: .. code-block:: bash ./array_example ./string_manipulation # Потребуется ввод имени ./vowel_counter # Потребуется ввод строки --- Дополнительные Ресурсы ---------------------- * `man strlen`, `man strcpy`, `man strcmp`, `man strcat` — Используйте эти команды в терминале Linux/macOS для получения подробной справки по функциям из ``. * `man fgets`, `man scanf` — Справка по функциям ввода. * `man tolower` — Справка по функции из ``. * `C Arrays (W3Schools) `_ * `C Strings (TutorialsPoint) `_ * `C String Functions (GeeksforGeeks) `_