В этом уроке мы разберем массивы в C на двух примерах: массив чисел и строка как массив char. Это важный поворот после условий и циклов, потому что в C строка не прячется за удобным объектом. Она лежит в памяти как последовательность символов, а конец строки отмечается специальным нулевым символом \0
Цель не в том, чтобы выучить все функции для строк. Цель — увидеть, где находятся элементы, почему индексы начинаются с нуля и почему выход за границы массива в C опасен
- Что получится в конце
- Массив чисел: одна переменная, много ячеек
- Как не зашить размер массива везде
- Строка в C: char[] плюс
- strlen считает символы до конца строки
- Безопасный ввод строки
- Проверяем границы руками
- Мини-практика: максимум в массиве
- Мини-практика: заменить символ в строке
- Почему выход за границы особенно неприятен
- Массив в параметре функции
- Частые ошибки и порядок проверки
- Что может быть еще интересно по этой теме
- Что почитать дальше по C
Что получится в конце
Файл arrays_strings.c:
#include <stdio.h>
#include <string.h>
int main(void)
{
int scores[5] = {10, 20, 30, 40, 50};
int total = 0;
for (int i = 0; i < 5; i++) {
total += scores[i];
}
printf("Total: %d\n", total);
char name[16] = "Dinar";
printf("Name: %s\n", name);
printf("Length: %zu\n", strlen(name));
for (size_t i = 0; i < strlen(name); i++) {
printf("name[%zu] = %c\n", i, name[i]);
}
return 0;
}
Сборка:
gcc -Wall -Wextra -std=c17 arrays_strings.c -o arrays_strings
./arrays_strings
Массив чисел: одна переменная, много ячеек
Строка:
int scores[5] = {10, 20, 30, 40, 50};
создает массив из пяти int. Индексы идут от 0 до 4, а не от 1 до 5. Это сначала непривычно, но потом оказывается удобным: первый элемент лежит со смещением ноль от начала массива
Доступ к элементу:
scores[0]
scores[1]
scores[4]
А вот scores[5] уже не шестой «легальный» элемент, а выход за границы. В C компилятор не обязан ловить такую ошибку во время выполнения. Программа может вывести мусор, сломаться позже или выглядеть рабочей до первого неприятного случая
Именно поэтому цикл идет так:
for (int i = 0; i < 5; i++) {
total += scores[i];
}
Условие i < 5 останавливает цикл на последнем допустимом индексе 4
Как не зашить размер массива везде
В учебном примере число 5 видно дважды. Лучше вычислять размер:
size_t count = sizeof(scores) / sizeof(scores[0]);
sizeof(scores) дает размер всего массива в байтах, а sizeof(scores[0]) — размер одного элемента. Деление дает количество элементов
Тогда цикл можно написать так:
for (size_t i = 0; i < count; i++) {
total += scores[i];
}
Это надежнее: если вы измените массив на шесть элементов, цикл сам подстроится. Но важно понимать ограничение: такой прием работает именно там, где scores еще настоящий массив, а не параметр функции
Строка в C: char[] плюс \0
Строка:
char name[16] = "Dinar";
создает массив символов на 16 ячеек. Внутри лежит:
D i n a r \0
Символ \0 означает конец строки. Он не печатается как буква, но функции вроде printf("%s", name) и strlen(name) ищут именно его, чтобы понять, где строка заканчивается
Поэтому массив должен иметь место не только для видимых символов, но и для \0. Строка "Dinar" содержит пять букв, но занимает минимум шесть ячеек
strlen считает символы до конца строки
Функция:
strlen(name)
возвращает длину строки до \0. Она не возвращает размер массива. Для char name[16] = "Dinar"; результатом будет 5, а не 16
Это принципиальная разница:
sizeof(name) /* размер массива в байтах */
strlen(name) /* длина текста до \0 */
В первом случае вы спрашиваете про контейнер, во втором — про содержимое
Безопасный ввод строки
Плохой первый вариант:
char city[16];
scanf("%s", city);
Он опасен, потому что пользователь может ввести слишком длинное слово. Лучше поставить ограничение:
char city[16];
scanf("%15s", city);
Почему 15, если размер 16? Потому что одна ячейка нужна под завершающий \0. Это не мелочь, а базовая привычка для C
Для строк с пробелами удобнее fgets:
char line[64];
if (fgets(line, sizeof(line), stdin) != NULL) {
printf("Line: %s", line);
}
fgets читает строку с ограничением размера буфера, поэтому для реального ввода она обычно спокойнее, чем голый scanf("%s", ...)
Проверяем границы руками
Сделайте маленький эксперимент:
char word[6] = "Hello";
printf("%s\n", word);
Это работает, потому что пять букв плюс \0 помещаются в шесть ячеек
Теперь подумайте, почему такой массив слишком мал:
char word[5] = "Hello";
Пять видимых букв занимают все ячейки, а места для конца строки нет. Компилятор может предупредить, но главное здесь понять модель: строка в C всегда требует место под завершающий нулевой символ
Мини-практика: максимум в массиве
После суммы полезно найти максимальное значение. Эта задача показывает правильную работу с первым элементом и границами массива
#include <stdio.h>
int main(void)
{
int values[] = {14, 3, 29, 8, 21};
size_t count = sizeof(values) / sizeof(values[0]);
int max = values[0];
for (size_t i = 1; i < count; i++) {
if (values[i] > max) {
max = values[i];
}
}
printf("Max: %d\n", max);
return 0;
}
Мы начинаем max с values[0], а цикл запускаем с индекса 1. Это лучше, чем придумывать искусственное значение вроде 0. Если все числа в массиве отрицательные, начальный 0 даст неверный результат. Первый элемент массива — честная стартовая точка, если массив не пустой
В реальной функции нужно отдельно проверять, что массив содержит хотя бы один элемент. В учебном примере массив задан прямо в коде, поэтому мы видим, что он не пуст
Мини-практика: заменить символ в строке
Строка как char[] допускает изменение отдельных символов:
#include <stdio.h>
int main(void)
{
char word[] = "cat";
word[0] = 'b';
printf("%s\n", word);
return 0;
}
Вывод:
bat
Здесь word — массив, созданный из строкового литерала. Сам массив можно менять. Но если написать char *word = "cat";, это уже указатель на строковый литерал, и менять символы так нельзя. Для новичка безопасное правило простое: хотите редактировать строку — кладите ее в массив char word[]
Почему выход за границы особенно неприятен
В C выход за границы массива не всегда падает сразу. Программа может прочитать соседнюю память и вывести случайное значение. Может испортить другую переменную. Может сломаться только на другом компьютере или после небольшого изменения кода
Поэтому не ждите, что язык каждый раз остановит вас за руку. Границы нужно держать явно: размер массива, условие цикла, ограничение ввода, место под \0. Это звучит строго, но именно здесь C хорошо тренирует системное мышление
Хорошая проверка перед запуском цикла: проговорить последний индекс. Для массива из пяти элементов последний индекс — четыре. Значит условие должно остановить цикл до пяти, а не включительно на пяти
Массив в параметре функции
Когда массив передается в функцию, он фактически приходит как указатель на первый элемент. Поэтому функция должна получить размер отдельно:
void print_values(const int values[], size_t count)
{
for (size_t i = 0; i < count; i++) {
printf("%d\n", values[i]);
}
}
Не пытайтесь внутри такой функции вычислить количество элементов через sizeof(values) / sizeof(values[0]). В параметре values уже не полноценный массив из вызывающего кода, а адрес. Поэтому размер нужно передавать рядом и использовать его в каждом цикле
Это правило хорошо связывает массивы с будущими указателями: массив хранит элементы подряд, а функция видит начало этой последовательности и число элементов, с которыми можно безопасно работать
Частые ошибки и порядок проверки
Индекс на один больше нужного Если массив создан как int a[3], допустимые индексы: 0, 1, 2. Индекс 3 уже за пределами массива
Путаница между размером массива и длиной строки sizeof(name) и strlen(name) отвечают на разные вопросы. Первый про память, второй про текст до \0
Недостаточный размер char-массива Для строки из n символов нужен массив минимум на n + 1 ячеек, потому что последняя ячейка хранит \0
Повторный вызов strlen в условии цикла В маленьком примере это допустимо, но в более серьезном коде лучше сохранить длину в переменную: size_t len = strlen(name);
Что может быть еще интересно по этой теме
Почему строки в C такие неудобные? C дает прямую модель памяти и не добавляет отдельный безопасный строковый объект в базовый язык. Это сложнее, зато хорошо объясняет, как строки устроены на низком уровне
Можно ли менять символы внутри строки? Если строка лежит в массиве char name[] = "Dinar";, можно менять элементы массива. Но строковые литералы лучше считать неизменяемыми и не пытаться редактировать через указатель
Когда появятся указатели? Почти сразу после массивов. Имя массива во многих выражениях ведет себя как адрес первого элемента, поэтому указатели становятся естественным продолжением этой темы
Что делать с русскими буквами? Для первого блока C лучше работать с ASCII-строками. UTF-8 и многобайтовые символы требуют отдельного разговора, потому что один видимый символ может занимать несколько байтов
Что почитать дальше по C
- if, switch и циклы в C на простых задачах
- Указатели в C без паники: адрес, * и &
- malloc и free в C: память без утечек в первом примере
- Файлы в C: fopen, fgets, fprintf и проверка ошибок



