7. Глава 7: Массивы и Строки — Работа с Коллекциями Данных и Текстом
Массивы и строки — это фундаментальные конструкции языка C, которые позволяют эффективно работать с упорядоченными наборами данных и текстом. Понимание того, как они функционируют на низком уровне, является ключом к эффективному программированию на C.
В этой главе мы подробно рассмотрим:
Одномерные массивы: Как хранить коллекции однотипных данных.
Строки (`char[]`): Особенность строк в C как массивов символов, завершающихся нулевым байтом.
Основные функции для работы со строками из библиотеки <string.h>.
Важные советы по безопасному и эффективному использованию массивов и строк.
—
7.1. Одномерные массивы
Массив — это коллекция элементов одного и того же типа, хранящихся в непрерывной области памяти. Каждый элемент массива доступен по своему индексу.
Объявление массива:
При объявлении массива необходимо указать тип его элементов и желаемый размер (количество элементов).
int numbers[5]; // Объявляет массив 'numbers', который может хранить 5 целых чисел.
char letters[10]; // Объявляет массив 'letters', который может хранить 10 символов.
Важно: Элементы массива индексируются от `0` до `n - 1`, где n — это размер массива. То есть, первый элемент находится по индексу 0, а последний — по индексу n-1.
Инициализация массива:
Массивы можно инициализировать при их объявлении, присваивая значения его элементам.
// Инициализация массива 'a' с явным указанием размера и всех элементов.
int a[3] = {1, 2, 3};
// Инициализация массива 'vowels' символьными литералами.
char vowels[5] = {'a', 'e', 'i', 'o', 'u'};
Если вы не указываете размер массива при инициализации, компилятор автоматически определяет его по количеству предоставленных элементов:
// Размер массива 'nums' будет автоматически установлен в 3, так как предоставлено 3 элемента.
int nums[] = {10, 20, 30};
### Пример 7.1: Вывод Элементов Массива
Этот пример демонстрирует, как объявить, инициализировать и затем итерировать по элементам массива, выводя их значения.
// array_example.c
#include <stdio.h> // Подключаем стандартную библиотеку ввода-вывода для функции 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
—
7.2. Строки в стиле C (char[])
В языке C строка — это особый вид массива символов (`char[]`), который обязательно завершается специальным нулевым символом (0). Этот нулевой символ служит маркером конца строки, позволяя функциям, работающим со строками, знать, где заканчивается текст.
Пример объявления и инициализации строки:
При использовании строкового литерала (текста в двойных кавычках) компилятор автоматически добавляет 0 в конец массива.
char greeting[] = "Привет"; // Этот массив будет иметь размер 7 символов:
// 'П', 'р', 'и', 'в', 'е', 'т', '\0'
Важно: Ошибка инициализации без `0`:
Если вы инициализируете char массив вручную символами, вы должны явно добавить 0, иначе это не будет считаться корректной строкой в C.
char word[5] = {'H', 'e', 'l', 'l', 'o'}; // НЕ является строкой в C, так как отсутствует '\0'.
// Попытка использовать ее со строковыми функциями
// приведет к неопределенному поведению.
Правильный ввод строк с клавиатуры:
Функция scanf(«%s», …) опасна для чтения строк, так как она останавливается на первом пробеле и не проверяет размер буфера, что может привести к переполнению буфера.
char name[20];
printf("Введите ваше имя: ");
scanf("%s", name); // ОПАСНО! Останавливается на пробеле и не проверяет границы массива.
printf("Привет, %s!\n", name);
Для безопасного ввода строк с пробелами используйте `fgets()`:
fgets() является более безопасной функцией, так как она позволяет указать максимальное количество символов для чтения, предотвращая переполнение буфера.
// fgets(буфер, размер_буфера, источник)
fgets(name, sizeof(name), stdin);
// Важно: fgets() читает символ новой строки '\n' (если он есть) и включает его в буфер.
// Его часто нужно удалять вручную, как показано в Примере 7.2.
—
7.3. Работа со строками (<string.h>)
Для эффективной работы со строками в C используется стандартная библиотека <string.h>. Чтобы использовать её функции, необходимо подключить заголовочный файл:
#include <string.h>
Часто используемые функции из <string.h>:
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:
—
Функция |
Описание |
---|---|
|
Возвращает длину строки |
|
Копирует строку |
|
Безопасная копия: Копирует не более |
|
Сравнивает строки |
|
Конкатенирует (добавляет) строку |
|
Безопасная конкатенация: Добавляет не более |
|
Возвращает длину начального сегмента строки |
—
Let me know if you need anything else!
### Пример 7.2: Расширенная Работа со Строками
Этот пример демонстрирует чтение строк с помощью fgets() с последующей очисткой от n, а также использование strlen(), strcpy(), strcmp() и strcat().
// string_manipulation.c
#include <stdio.h> // Для функций ввода-вывода, таких как printf и fgets.
#include <string.h> // Для строковых функций, таких как 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) - функция из <string.h>, которая возвращает длину строки,
// исключая завершающий нулевой символ '\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;
}
—
7.4. Строковые литералы и указатели
В C можно объявлять строковые литералы, используя указатели:
char *msg = "Hello, world!";
Важно: Строковый литерал (текст в двойных кавычках) часто хранится в памяти, предназначенной только для чтения. Присвоение его указателю (char *) означает, что msg указывает на эту область памяти.
Попытка изменить содержимое такой строки через указатель (`msg[0] = „X“;`) приведет к неопределенному поведению или, что чаще, к краху программы (segmentation fault), так как вы пытаетесь записать в область памяти, защищенную от записи. Если вам нужна изменяемая строка, всегда используйте объявление массива char[].
—
7.5. Практический Пример: Подсчёт гласных
Этот пример демонстрирует более сложную работу со строками: итерацию по символам и условную логику для анализа содержимого.
### Пример 7.3: Подсчитать количество гласных в строке
// vowel_counter.c
#include <stdio.h> // Для функций ввода-вывода (printf, fgets).
#include <string.h> // Для строковых функций (strcspn).
#include <ctype.h> // Для функции 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() из <ctype.h> преобразует букву в нижний регистр;
// если это не буква, символ возвращается без изменений.
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;
}
—
7.6. Полезные советы и распространённые ошибки
Индексирование с 0: Всегда помните, что массивы в C начинаются с индекса 0.
Завершающий `0`: Убедитесь, что все ваши строки в C корректно завершаются нулевым символом (0). Без него строковые функции будут читать за пределами выделенной памяти, что приведёт к ошибкам.
Используйте `fgets()` вместо `scanf(«%s», …)`: fgets() гораздо безопаснее, так как позволяет указать размер буфера и предотвращает переполнение.
Избегайте выхода за границы массива: Попытка доступа к элементу массива по индексу, который находится за его объявленными границами (например, data[5] в массиве data[5]), приведёт к неопределенному поведению (Undefined Behavior), что может выражаться в крахе программы, некорректных данных или уязвимостях безопасности. Это одна из самых частых и сложных для отладки ошибок в C.
Помните о `const` для строковых литералов: Если вы инициализируете char * строковым литералом («Hello»), помните, что это данные только для чтения.
—
7.7. Компиляция и Запуск Примеров
Для компиляции и запуска этих программ вам понадобится компилятор C (например, GCC).
В терминале, находясь в директории с исходными файлами:
# Компиляция Примера 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
Запуск исполняемых файлов:
./array_example
./string_manipulation # Потребуется ввод имени
./vowel_counter # Потребуется ввод строки
—
7.8. Дополнительные Ресурсы
man strlen, man strcpy, man strcmp, man strcat — Используйте эти команды в терминале Linux/macOS для получения подробной справки по функциям из <string.h>.
man fgets, man scanf — Справка по функциям ввода.
man tolower — Справка по функции из <ctype.h>.