Заполнение массива с клавиатуры в C: от статических до динамических массивов

При попытке ввести элементы массива с клавиатуры в языке C многие сталкиваются с ошибками компиляции или некорректным сохранением данных — особенно когда речь идёт о динамическом выделении памяти или работе с указателями. Проблема часто кроется в неправильном использовании функции scanf(), отсутствии проверки на переполнение буфера или неверном синтаксисе циклов. Например, если вы видите в консоли бесконечный ввод или "мусорные" значения после первого элемента, это верный признак того, что адрес ячейки массива передаётся в scanf без оператора & или с ошибкой в арифметике указателей.

В этой статье разберём все способы заполнения массивов в C — от простейших статических массивов до динамических с использованием malloc(). Особое внимание уделим типичным ошибкам, таким как выход за границы массива или утечки памяти, и покажем, как их избежать. Все примеры кода протестированы в GCC 11.3 и Clang 14.0 под Linux и Windows (MinGW).

1. Базовый ввод статического массива через scanf

Начнём с самого простого случая — заполнения статического массива фиксированного размера. Предположим, у вас есть массив из 5 целых чисел, и вы хотите ввести их с клавиатуры. Ключевой момент здесь — правильно передать адрес каждого элемента в scanf.

Пример кода для массива из 5 элементов:

#include <stdio.h>

#define SIZE 5

int main() {

int arr[SIZE];

printf("Введите %d целых чисел:\n", SIZE);

for (int i = 0; i < SIZE; i++) {

scanf("%d", &arr[i]); // Важно: & перед arr[i]!

}

printf("Введённый массив:\n");

for (int i = 0; i < SIZE; i++) {

printf("%d ", arr[i]);

}

return 0;

}

Обратите внимание на оператор & перед arr[i]. Без него scanf интерпретирует значение элемента как адрес, что приводит к неопределённому поведению (чаще всего — к падению программы). Также не забывайте проверять возвращаемое значение scanf — оно должно равняться количеству успешно считанных элементов.

  • 🔹 Используйте #define или const int для размера массива — это упрощает изменение кода.
  • 🔹 Всегда проверяйте результат scanf: if (scanf("%d", &arr[i]) != 1) { / ошибка / }.
  • 🔹 Для чисел с плавающей точкой используйте %f (для float) или %lf (для double).
⚠️ Внимание: Если вы введёте символ вместо числа (например, букву), scanf вернёт 0, а оставшийся символ останется в буфере. Это может привести к зацикливанию программы при последующих вызовах scanf. Решение — очищать буфер с помощью while (getchar() != '\n');.

2. Ввод массива символов (строки) с клавиатуры

Работа со строками в C требует особого подхода, так как они представляют собой массивы символов, завершающиеся нулевым байтом (\0). Для ввода строки можно использовать scanf с форматированием %s, но это чревато переполнением буфера, если пользователь введёт больше символов, чем выделено памяти.

Безопасный способ ввода строки с ограничением длины:

#include <stdio.h>

#define MAX_LEN 50

int main() {

char str[MAX_LEN];

printf("Введите строку (макс. %d символов):\n", MAX_LEN - 1);

if (scanf("%49s", str) != 1) { // 49 = MAX_LEN - 1 (место для \0)

printf("Ошибка ввода!\n");

return 1;

}

printf("Вы ввели: %s\n", str);

return 0;

}

В этом примере %49s гарантирует, что scanf прочитает не более 49 символов, оставляя место для терминального нуля. Однако scanf остановится при первом пробеле. Чтобы считать строку с пробелами, используйте fgets:

fgets(str, MAX_LEN, stdin);
  • 📌 fgets безопаснее scanf для строк, так как всегда ограничивает длину.
  • 📌 Чтобы удалить символ новой строки (\n) после fgets, добавьте: str[strcspn(str, "\n")] = '\0';.
  • 📌 Для динамического выделения памяти под строку используйте getline (POSIX).
📊 Какой способ ввода строк вы используете чаще?
scanf с %s
fgets
getline
Свой вариант

3. Динамическое выделение памяти для массива

Если размер массива заранее неизвестен, его можно создать динамически с помощью функций malloc или calloc. Это особенно полезно для больших массивов или когда размер определяется во время выполнения программы.

Пример динамического ввода массива:

#include <stdio.h>

#include <stdlib.h>

int main() {

int n;

printf("Введите количество элементов: ");

if (scanf("%d", &n) != 1 || n <= 0) {

printf("Некорректный ввод!\n");

return 1;

}

int arr = (int)malloc(n * sizeof(int));

if (arr == NULL) {

printf("Ошибка выделения памяти!\n");

return 1;

}

printf("Введите %d элементов:\n", n);

for (int i = 0; i < n; i++) {

if (scanf("%d", &arr[i]) != 1) {

printf("Ошибка ввода элемента %d!\n", i);

free(arr);

return 1;

}

}

// Работа с массивом...

printf("Массив: ");

for (int i = 0; i < n; i++) {

printf("%d ", arr[i]);

}

free(arr); // Освобождение памяти

return 0;

}

Ключевые моменты:

  • 🔧 Всегда проверяйте результат malloc на NULL.
  • 🔧 Освобождайте память с помощью free после использования.
  • 🔧 Для инициализации памяти нулями используйте calloc вместо malloc.
⚠️ Внимание: Если вы измените размер массива с помощью realloc, не забывайте проверять возвращаемое значение. Пример:
int new_arr = realloc(arr, new_size  sizeof(int));

if (new_arr == NULL) { / обработка ошибки / }

arr = new_arr;

Иначе при неудаче вы потеряете указатель на старую память!

1. Проверить, что размер массива (n) > 0

2. Убедиться, что malloc вернул ненулевой указатель

3. Освободить память с free после использования

4. Обработать ошибки ввода в цикле scanf-->

4. Ввод двумерного массива (матрицы)

Для работы с матрицами в C можно использовать как статические, так и динамические массивы. Статический подход проще, но менее гибкий:

Пример статической матрицы 3x3:

#include <stdio.h>

#define ROWS 3

#define COLS 3

int main() {

int matrix[ROWS][COLS];

printf("Введите элементы матрицы %dx%d:\n", ROWS, COLS);

for (int i = 0; i < ROWS; i++) {

for (int j = 0; j < COLS; j++) {

scanf("%d", &matrix[i][j]);

}

}

// Вывод матрицы

for (int i = 0; i < ROWS; i++) {

for (int j = 0; j < COLS; j++) {

printf("%d ", matrix[i][j]);

}

printf("\n");

}

return 0;

}

Для динамического выделения памяти под матрицу используйте массив указателей:

int matrix = (int)malloc(rows  sizeof(int));

for (int i = 0; i < rows; i++) {

matrix[i] = (int*)malloc(cols * sizeof(int));

}

Способ хранения Плюсы Минусы
Статический массив Простота, скорость доступа Фиксированный размер
Динамический (указатели на строки) Гибкость, произвольный размер Сложнее освобождать память
Одномерный массив с индексацией Один malloc/free, кэш-дружелюбный Сложнее чтение кода

5. Типичные ошибки и как их избежать

Даже опытные программисты иногда допускают ошибки при вводе массивов. Вот наиболее распространённые:

  1. Отсутствие & в scanf:

    Ошибка: scanf("%d", arr[i]);

    Последствия: Неопределённое поведение, падение программы.

  2. Выход за границы массива:

    Ошибка: Цикл for (i = 0; i <= SIZE; i++) вместо i < SIZE.

    Последствия: Перезапись соседних ячеек памяти.

  3. Утечки памяти:

    Ошибка: Забыли free после malloc.

    Последствия: Программа потребляет всё больше памяти.

  4. Неочищенный буфер ввода:

    Ошибка: После scanf("%d", &n) остаётся \n в буфере.

    Последствия: Следующий fgets вернёт пустую строку.

Чтобы избежать проблем с буфером, используйте эту функцию для очистки:

void clear_input_buffer() {

int c;

while ((c = getchar()) != '\n' && c != EOF) {}

}

6. Альтернативные способы ввода: fread и файлы

Иногда данные для массива удобнее считывать не с клавиатуры, а из файла. Для этого используйте функции fopen, fread или fscanf. Пример чтения массива из бинарного файла:

#include <stdio.h>

#include <stdlib.h>

int main() {

FILE *file = fopen("data.bin", "rb");

if (file == NULL) {

perror("Ошибка открытия файла");

return 1;

}

int arr[10];

size_t read_count = fread(arr, sizeof(int), 10, file);

if (read_count != 10) {

printf("Ошибка чтения или неверный размер файла!\n");

fclose(file);

return 1;

}

fclose(file);

// Работа с массивом arr

return 0;

}

Для текстового файла подойдёт fscanf:

while (fscanf(file, "%d", &arr[i]) == 1) {

i++;

if (i >= MAX_SIZE) break;

}

  • 📄 Для больших массивов предпочтительнее бинарный ввод (fread) — он быстрее.
  • 📄 Всегда проверяйте результат fopen и fread.
  • 📄 Закрывайте файл с fclose даже при ошибке.
Как считать массив из CSV-файла?

Для CSV используйте fgets + strtok:

char line[256];

while (fgets(line, sizeof(line), file)) {

char *token = strtok(line, ",");

while (token != NULL) {

arr[i++] = atoi(token);

token = strtok(NULL, ",");

}

}

Не забудьте проверить i на выход за границы массива!

7. Оптимизация ввода для больших массивов

При работе с массивами размером более 10 000 элементов стандартный scanf может работать медленно. Ускорить ввод можно следующими способами:

  1. Используйте fread для блокового чтения:

    Считайте большие порции данных в буфер, а затем парсите их вручную.

  2. Отключите синхронизацию с stdio (для C++):

    В начале программы добавьте:

    ios_base::sync_with_stdio(false);
    

    cin.tie(NULL);

    Это ускорит cin/cout, но не применяется к чистому C.

  3. Используйте getchar_unlocked (непереносимо):

    В некоторых системах (например, Linux) эта функция работает быстрее getchar, но не является стандартной.

Пример оптимизированного ввода для числового массива:

#include <stdio.h>

#include <ctype.h>

int read_int() {

int n = 0, c;

while (isspace(c = getchar())) {} // Пропуск пробелов

do {

n = n * 10 + (c - '0');

} while (isdigit(c = getchar()));

return n;

}

int main() {

int arr[10000];

for (int i = 0; i < 10000; i++) {

arr[i] = read_int();

}

return 0;

}

FAQ: Частые вопросы по вводу массивов в C

Можно ли ввести массив строк с пробелами?

Да, но для этого нельзя использовать scanf("%s", ...), так как он останавливается на пробеле. Вместо этого:

  1. Используйте fgets для чтения всей строки, включая пробелы.
  2. Удалите символ новой строки (\n) вручную, если он есть.
  3. Для массива строк выделяйте память динамически или используйте двумерный массив фиксированного размера:
    char strings[10][100];  // 10 строк по 100 символов
    

    for (int i = 0; i < 10; i++) {

    fgets(strings[i], 100, stdin);

    strings[i][strcspn(strings[i], "\n")] = '\0'; // Удаление \n

    }

Почему после ввода числа с клавиатуры программа пропускает следующий ввод строки?

Это классическая проблема с буфером ввода. После scanf("%d", &n) в буфере остаётся символ новой строки (\n), который сразу считывается следующим fgets или scanf("%s", ...) как пустая строка.

Решения:

  • Добавляйте пробел перед %s в scanf: scanf(" %s", str); (пробел пропускает все пробельные символы).
  • Очищайте буфер вручную: while (getchar() != '\n'); после scanf.
  • Используйте fgets для всех вводов, включая числа (затем парсите строку с sscanf или strtol).
Как ввести массив структур с клавиатуры?

Для массива структур используйте тот же подход, что и для обычных массивов, но обращайтесь к полям структуры через оператор . (точка). Пример:

#include <stdio.h>

typedef struct {

int id;

char name[50];

float price;

} Product;

int main() {

Product products[5];

for (int i = 0; i < 5; i++) {

printf("Введите ID, название и цену товара %d:\n", i + 1);

scanf("%d %49s %f", &products[i].id, products[i].name, &products[i].price);

}

return 0;

}

Обратите внимание на ограничение %49s для поля name, чтобы избежать переполнения.

Чем scanf отличается от scanf_s?

scanf_s — это "безопасная" версия scanf, введённая в стандарте C11. Она требует указывать размер буфера для строковых данных, что помогает избежать переполнения. Пример:

char str[10];

scanf_s("%9s", str, sizeof(str)); // Макс. 9 символов + \0

Однако scanf_s не является частью стандартной библиотеки GCC/Clang (она доступна в MSVC). Для кроссплатформенного кода лучше использовать fgets + sscanf.

Как проверить, что пользователь ввёл корректные данные?

Проверка корректности ввода включает:

  1. Проверку возвращаемого значения scanf:
    if (scanf("%d", &n) != 1) {
    

    printf("Ошибка: ожидалось число!\n");

    while (getchar() != '\n'); // Очистка буфера

    continue;

    }

  2. Проверку диапазона значений:
    if (n < 1 || n > 100) {
    

    printf("Число должно быть от 1 до 100!\n");

    }

  3. Для строк — проверку длины и содержимого (например, с strspn или регулярными выражениями).