Скидки до 60% и 3 курса в подарок 2 дня 13 :45 :02 Выбрать курс
Код
#статьи

Массивы языка C: что это и как их использовать

Разбираемся в основах работы с ними на практике.

Иллюстрация: Polina Vari для Skillbox Media

Массивы — один из основных инструментов для работы с данными в языке C. Их используют, когда нужно хранить и обрабатывать множество значений: например, результаты измерений, оценки студентов или координаты объектов. При этом работа с массивами требует внимательности: некоторые ошибки язык C не обнаруживает автоматически, и они могут привести к некорректной работе программы.

В этой статье мы разберём, что такое массивы в языке C, как их создавать и использовать, а также каких ошибок стоит избегать при работе с ними.

Содержание


Что такое массив

Массив (array) — это структура данных, которая позволяет хранить несколько элементов одного типа. Например, в массив можно записать пять целых чисел или десять символов. Все элементы располагаются в памяти последовательно, друг за другом.

У каждого элемента массива есть порядковый номер — индекс. С его помощью можно обратиться к конкретному элементу, чтобы прочитать или изменить его значение. В языке C индексация начинается с нуля. Поэтому в массиве из пяти элементов индексы будут от 0 до 4.

В языке C все элементы одного массива должны иметь одинаковый тип. Это могут быть целые и дробные числа, символы или структуры.

Тип данныхЧто хранитПример элемента
intЦелые числа25
floatДробные числа99.99f
charОтдельные символы'A'
structСтруктуры, объединяющие несколько полейstruct Point { int x; int y; };

Рассмотрим массив из пяти целых чисел:

int values[5] = {10, 20, 30, 40, 50}

Здесь int — тип элементов, values — имя массива, [5] — количество элементов. В фигурных скобках перечислены значения, которые записываются в массив при его создании.

Посмотрим, как устроен этот массив.

dd
Элементы массива1020304050
Индекс[0][1][2][3][4]

Все пять элементов занимают один непрерывный участок памяти. Например, чтобы получить значение первого элемента, нужно обратиться к values[0], а последнего — к values[4].

Как объявить и инициализировать массив

Чтобы создать массив в языке C, требуется указать тип данных, имя массива и количество элементов (в квадратных скобках). Например, объявим массив number из четырёх целых чисел:

#include <stdio.h>

int main(void) {
    int number[4];
    return 0;
}

В этом примере мы выделили память для четырёх элементов, но не задали их значения. Если объявить локальный массив без инициализации, его элементы будут иметь неопределённые начальные значения. Читать их до присваивания значений нельзя — это может привести к непредсказуемому поведению программы.

Такой способ объявления подходит, если вы планируете заполнить массив позже, например с помощью цикла или пользовательского ввода.

Если попробовать прочесть значения неинициализированного массива, то это приведёт к выводу случайных чисел
Скриншот: OnlineGDB / Skillbox Media

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

int number[] = {10, 20, 30};

В этом примере мы не указали размер массива в квадратных скобках. Компилятор определит его автоматически по количеству значений. В результате массив будет состоять из трёх элементов. Проверим это.

Скриншот: OnlineGDB / Skillbox Media

Также можно указать размер массива, но инициализировать только часть элементов.

int number[5] = {10, 20};

В этом случае первые два элемента получат значения 10 и 20, а остальные автоматически заполнятся нулями. Проверим код полностью.

Первые два элемента равны 10 и 20, а остальные равны нулю
Скриншот: OnlineGDB / Skillbox Media

Если нужно сразу заполнить весь массив нулями, достаточно указать один ноль в фигурных скобках:

int number[5] = {0};

Проверим, как это работает.

Все элементы массива автоматически были заполнены нулями
Скриншот: OnlineGDB / Skillbox Media

Как обращаться к элементам и обходить массив

В языке C к любому элементу массива можно обратиться по его индексу, чтобы прочитать или изменить значение. Если нужно обработать сразу все элементы, потребуется использовать цикл. Например, с помощью цикла можно вывести содержимое массива на экран, найти нужное значение или вычислить сумму элементов.

Напишем код, который будет увеличивать каждый элемент массива на единицу, а затем посчитаем сумму полученных значений.

#include <stdio.h>
#include <stddef.h>

int main(void) {
    int values[] = {10, 20, 30, 40, 50};
    size_t length = sizeof values / sizeof values[0];
    int sum = 0;

    /* ---> Начало обхода массива ( перебираем индексы от 0 до length - 1) */
    for (size_t i = 0; i < length; i++) {
        
        /* [Обращение к элементу] по индексу i для изменения его значения */
        values[i] += 1; 
        
        /* [Обращение к элементу] по индексу i для чтения и добавления к сумме */
        sum += values[i]; 
    }
    /* <--- Конец обхода массива */

    printf("Сумма: %d\n", sum);
    return 0;
}

Разберём, как работает этот код.

Сначала мы создаём массив values из пяти целых чисел. Затем с помощью выражения sizeof values / sizeof values[0] определяем количество элементов: делим размер всего массива в байтах на размер одного элемента. Результат сохраняем в переменной length.

Далее цикл for последовательно перебирает элементы массива. Переменная i принимает значения от 0 до length — 1. На каждой итерации выполняются две операции:

  • values[i] += 1 — увеличивает значение текущего элемента на единицу;
  • sum += values[i] — прибавляет обновлённое значение элемента к общей сумме.

После завершения цикла программа выводит результат.

После завершения цикла мы получаем сумму: 155
Скриншот: OnlineGDB / Skillbox Media

При обходе массива важно не выходить за его границы. В нашем примере условие i < length гарантирует, что цикл переберёт только допустимые индексы — от 0 до 4.

Если заменить условие на i <= length, цикл выполнит ещё одну итерацию и попытается обратиться к values[5]. Но такого элемента в массиве нет.

В языке C обращение за границы массива приводит к неопределённому поведению. Программа может завершиться с ошибкой, выдать неправильный результат или продолжить работу без видимых проблем. Поэтому при работе с массивами необходимо следить за допустимыми значениями индексов.

При использовании ​​i <= length выведенная сумма многократно превышает ожидаемый результат
Скриншот: OnlineGDB / Skillbox Media

Как заполнить массив и вывести его

Массив можно заполнить значениями сразу при создании или во время выполнения программы. Второй способ используют, когда данные заранее неизвестны: например, если их вводит пользователь или программа получает их из файла.

Рассмотрим пример, в котором программа запрашивает у пользователя пять целых чисел, сохраняет их в массив и выводит на экран.

#include <stdio.h>
#include <stddef.h>

int main(void) {
    int values[5];
    size_t length = sizeof values / sizeof values[0];

    for (size_t i = 0; i < length; i++) {
        printf("Введите число %zu: ", i + 1);

        if (scanf("%d", &values[i]) != 1) {
            printf("Некорректный ввод.\n");
            return 1;
        }
    }

    for (size_t i = 0; i < length; i++) {
        printf("%d ", values[i]);
    }

    printf("\n");
    return 0;
}

Разберём, как работает программа.

Сначала мы создаём массив values из пяти элементов. Затем первый цикл for последовательно запрашивает у пользователя числа. Функция scanf() считывает введённое значение, а выражение & values[i] указывает адрес элемента массива, в который его нужно записать.

Условие scanf («%d», & values[i])! = 1 проверяет, удалось ли считать число. Если пользователь введёт некорректные данные, например букву вместо числа, программа выведет сообщение об ошибке и завершит работу.

После заполнения массива запускается второй цикл for. Он последовательно обращается к каждому элементу и выводит его значение с помощью printf(). В результате на экране появятся все пять чисел, которые ввёл пользователь.

Скриншот: OnlineGDB / Skillbox Media

Важно! При использовании scanf() всегда проверяйте результат считывания. Если ввод завершится ошибкой, значение соответствующего элемента может остаться неопределённым. Попытка использовать его в дальнейших вычислениях может привести к некорректной работе программы.

Как определить количество элементов через sizeof

В языке C количество элементов массива можно определить с помощью оператора sizeof. Он возвращает размер объекта или типа данных в байтах. Чтобы узнать длину массива, нужно разделить его общий размер на размер одного элемента.

Рассмотрим пример.

#include <stdio.h>
#include <stddef.h>

int main() {
    int a[5] = {10, 20, 30, 40, 50};
    const size_t count = sizeof a / sizeof a[0];

    printf("Количество элементов в массиве: %zu\n", count);

    return 0;
}

Здесь sizeof a возвращает размер всего массива в байтах, а sizeof a[0] — размер его первого элемента. Поскольку все элементы имеют одинаковый тип, результат деления будет равен их количеству. В нашем примере — пяти.

Полученное значение сохраняется в переменной count типа size_t. Этот тип используют для представления размеров объектов в памяти. Для вывода значения size_t с помощью printf() применяют спецификатор %zu.

Важно! Такой способ работает, только когда sizeof применяется к самому массиву. Если передать массив в функцию, её параметр, объявленный как массив, фактически будет указателем на первый элемент. В этом случае sizeof вернёт размер указателя, а не всего массива. Поэтому длину массива обычно передают в функцию отдельно.

На 64-битной системе размер указателя sizeof arr составляет 8 байт, а размер одного int равен 4 байтам
Скриншот: OnlineGDB / Skillbox Media

Как передать массив в функцию

Массив можно передать в функцию, чтобы обработать его элементы отдельно от основного кода программы. Например, одна функция может заполнить массив числами, а другая — вывести их на экран.

При передаче массива в функцию его элементы не копируются. Вместо этого функция получает указатель на первый элемент и может обращаться к остальным по индексам. При этом количество элементов нужно передать отдельно.

Рассмотрим пример.

#include <stdio.h>
#include <stddef.h>

// Функция заполнения
void fill_array(int a[], size_t n) {
    for (size_t i = 0; i < n; i++) {
        a[i] = (int)i * 2;
    }
}

// Функция чтения
void print_array(const int *a, size_t n) {
    printf("Элементы массива: ");
    for (size_t i = 0; i < n; i++) {
        printf("%d ", a[i]); // Читаем и выводим каждый элемент
    }
    printf("\n");
}

int main() {
    int values[5];
    
    size_t count = sizeof values / sizeof values[0];
    
    fill_array(values, count);
    print_array(values, count);
    
    return 0;
}

Разберём, как работает программа.

Сначала мы создаём массив values из пяти элементов и определяем его длину. Затем передаём массив и количество элементов в две функции:

  • fill_array() заполняет массив числами 0, 2, 4, 6 и 8. Поскольку функция работает с исходным массивом, внесённые изменения сохраняются после её завершения.
  • print_array() последовательно читает элементы массива и выводит их на экран. В параметре const int *a ключевое слово const запрещает изменять значения элементов. Это помогает избежать случайного изменения данных.

В результате программа выведет:

Элементы массива: 0 2 4 6 8

Почему функция не узнаёт длину массива автоматически

В параметрах функции записи int a[] и int *a эквивалентны: в обоих случаях функция получает указатель на первый элемент массива.

Поэтому внутри функции оператор sizeof a возвращает размер указателя, а не всего массива. Вычислить количество элементов с помощью выражения sizeof a / sizeof a[0] в таком случае не получится.

Чтобы функция знала, сколько элементов нужно обработать, длину массива передают отдельным параметром. Её можно определить в main(), где переменная values ещё является массивом.

int values[5];
size_t count = sizeof values / sizeof values[0];

fill_array(values, count);
print_array(values, count);

Проверим код полностью.

Скриншот: OnlineGDB / Skillbox Media

Двумерные и многомерные массивы

В языке C можно создавать массивы с несколькими измерениями. Например, двумерный массив удобно представить в виде таблицы со строками и столбцами. Чтобы обратиться к его элементу, нужно указать два индекса: номер строки и номер столбца.

Массивы с тремя и более измерениями называют многомерными. Их используют, например, для хранения наборов таблиц или данных с несколькими координатами.

Как создать двумерный массив

Создадим массив matrix из двух строк и трёх столбцов:

#include <stdio.h>

int main() {
    int matrix[2][3] = {
        {1, 2, 3},
        {4, 5, 6}
    };

Здесь [2] обозначает количество строк, а [3] — количество столбцов. В фигурных скобках мы задаём значения для каждой строки.

Полученный массив можно представить в виде таблицы.

Столбец 0Столбец 1Столбец 2
Строка 0123
Строка 1456

Чтобы получить значение конкретного элемента, нужно указать два индекса. Например, matrix[0][0] содержит число 1, а matrix[1][2] — число 6.

Проверим это на практике:

#include <stdio.h>

int main() {

    int matrix[2][3] = {
        {1, 2, 3},  
        {4, 5, 6}  
    };
    printf("matrix[0][0] содержит: %d\n", matrix[0][0]); 
    printf("matrix[1][2] содержит: %d\n", matrix[1][2]); 
    
    return 0;
}

Программа выведет:

matrix[0][0] содержит: 1
matrix[1][2] содержит: 6

Как двумерные массивы хранятся в памяти

Хотя двумерный массив удобно представлять в виде таблицы, в памяти его элементы располагаются последовательно, строка за строкой. Такой способ хранения называют row-major order.

Например, после последнего элемента первой строки matrix[0][2] в памяти идёт первый элемент второй строки matrix[1][0].

МассивСтрока 0Строка 1
Индексы[0][0][0][1][0][2]
[1][0][1][1][1][2]
Значения123456

Таким образом, все шесть элементов занимают один непрерывный участок памяти. Аналогичный принцип действует и для массивов с большим количеством измерений.

Как обойти элементы двумерного массива

Для обработки элементов двумерного массива используют два вложенных цикла for. Внешний перебирает строки, а внутренний — столбцы внутри каждой строки.

Рассмотрим пример, в котором программа увеличивает каждый элемент массива в два раза, а затем выводит результат:

#include <stdio.h>
#include <stddef.h>

int main(void) {
    // 1. Объявляем и инициализируем двумерный массив 2x3
    int matrix[2][3] = {
        {1, 2, 3},  // Строка 0
        {4, 5, 6}   // Строка 1
    };

    // 2. Обход массива (внутри функции main)
    for (size_t row = 0; row < 2; row++) {
        for (size_t col = 0; col < 3; col++) {
            matrix[row][col] *= 2;
        }
    }

    // 3. Выводим результат, чтобы проверить работу
    for (size_t row = 0; row < 2; row++) {
        for (size_t col = 0; col < 3; col++) {
            printf("%d ", matrix[row][col]);
        }
        printf("\n");
    }

    return 0;
}

Сначала внешний цикл выбирает первую строку, а внутренний последовательно обращается к трём её элементам. Операция matrix[row][col] *= 2 умножает значение каждого элемента на два. Затем программа переходит ко второй строке и повторяет действия.

После этого ещё два вложенных цикла выводят обновлённые значения. Команда printf («\n») добавляет перенос строки, чтобы результат отображался в виде таблицы:

2 4 6
8 10 12

Как передать двумерный массив в функцию

Двумерный массив, как и одномерный, можно передать в функцию для обработки. При этом в объявлении параметра нужно указать количество столбцов, чтобы компилятор мог правильно обращаться к элементам.

Например, создадим функцию, которая увеличивает все значения двумерного массива в два раза:

void process_matrix(int m[][3], size_t rows) {
    for (size_t row = 0; row < rows; row++) {
        for (size_t col = 0; col < 3; col++) {
            m[row][col] *= 2; 
        }
    }
}

Здесь int m[][3] обозначает параметр, через который функция получает доступ к строкам из трёх целых чисел. Параметр rows содержит количество строк, которые нужно обработать.

Вызвать функцию можно следующим образом:

int matrix[2][3] = {
    {1, 2, 3},
    {4, 5, 6}
};

process_matrix(matrix, 2);

После вызова process_matrix() значения исходного массива изменятся. Его первая строка будет содержать числа 2, 4, 6, а вторая — 8, 10, 12.

Скриншот: OnlineGDB / Skillbox Media

Важно! Количество столбцов в типе параметра должно соответствовать размеру строк передаваемого массива. Иначе функция не сможет правильно вычислить расположение элементов в памяти.

Типичные ошибки при работе с массивами

При работе с массивами в языке C важно следить за индексами, правильно управлять памятью и проверять вводимые данные. Рассмотрим распространённые ошибки и способы их избежать.

Выход за границы массива. В массиве из пяти элементов допустимые индексы — от 0 до 4. Если обратиться к values[5], программа попытается получить доступ к памяти за пределами массива. Это приводит к неопределённому поведению: программа может выдать неправильный результат, аварийно завершиться или продолжить работу без видимых ошибок.

int values[5];
values[5] = 10; /* Выход за границы */

Ошибка в условии цикла. Если при обходе массива использовать условие i <= length, цикл выполнит лишнюю итерацию и обратится к элементу с индексом length, которого не существует. Чтобы избежать ошибки, используйте условие i < length. Тогда цикл обработает только существующие элементы массива.

Чтение неинициализированных элементов. Если объявить локальный массив int values[5]; без инициализации, его элементы будут иметь неопределённые начальные значения. Использовать их до присваивания нельзя. Чтобы сразу заполнить массив нулями, укажите начальное значение при объявлении:

int values[5] = {0};

Неправильное определение длины массива через sizeof. При передаче массива в функцию её параметр, объявленный как int values[], фактически является указателем. Поэтому sizeof values внутри такой функции вернёт размер указателя, а не всего массива. Чтобы правильно обрабатывать элементы, определяйте длину массива до передачи в функцию и передавайте её отдельным параметром.

Ошибка при использовании scanf(). Для записи числа в элемент массива функция scanf() должна получить его адрес. Если передать values[i] вместо & values[i], функция получит значение элемента вместо адреса. Это приводит к неопределённому поведению.

Правильная запись:

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

Возврат указателя на локальный массив. Локальный массив с автоматическим временем хранения перестаёт существовать после выхода из функции. Если вернуть указатель на его первый элемент, этот указатель станет недействительным. Обращение к данным через него приведёт к неопределённому поведению.

Чтобы избежать ошибки, можно создать массив в вызывающей функции и передать его для заполнения. Другой вариант — выделить память динамически с помощью malloc() и освободить её через free(), когда данные больше не понадобятся.

Что следует запомнить

Мы разобрали, как устроены массивы в языке C, как с ними работать и каких ошибок стоит избегать. Повторим главное:

  • Массив — это последовательность элементов одного типа, которые располагаются в памяти друг за другом.
  • Индексация начинается с нуля. В массиве из пяти элементов допустимые индексы — от 0 до 4.
  • Массив можно заполнить при создании или во время выполнения программы. Например, с помощью цикла или пользовательского ввода.
  • Для обхода массива используют циклы. Они позволяют читать, изменять и обрабатывать элементы.
  • Количество элементов можно определить с помощью sizeof. Для этого размер всего массива делят на размер одного элемента.
  • При передаче массива в функцию его длину указывают отдельно. Функция получает указатель на первый элемент, а не копию массива.
  • В языке C есть многомерные массивы. Например, двумерный массив позволяет хранить данные в виде таблицы.
  • Для работы со строками используют массивы char. Строка заканчивается специальным символом '\0'.
  • Язык C не проверяет границы массивов автоматически. Поэтому важно следить за индексами, инициализировать элементы перед чтением и проверять корректность ввода.
Онлайн-магистратура по ИТ с государственным дипломом
Реклама. Скилфэктори ООО. ИНН: 9702009530. Erid: 2VtzquYeuiJ

Бесплатный курс по разработке на Python ➞
Пройдите бесплатный курс по Python и создайте с нуля телеграм-бот, веб-парсер и сайт. Спикер — руководитель отдела разработки в «Сбере».
Пройти курс
Понравилась статья?
Да

Пользуясь нашим сайтом, вы соглашаетесь с тем, что мы используем cookies 🍪

Ссылка скопирована