Включите исполнение JavaScript в браузере, чтобы запустить приложение.

Двумерные массивы в языке С

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

Что такое двумерный массив

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

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

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

Как объявить и инициализировать двумерный массив

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

elements_type  array_name[number_of_rows][number_of_columns];
c

Пример объявления:

int table[3][4];

/* Будет создан двумерный массив table, состоящий из 3 рядов и 4 столбцов и способный хранить целые числа*/
c

Инициализацию можно выполнить при объявлении — тогда содержимое указывается после знака равно в фигурных скобках (построчно):

int table[2][3] = {

    {1, 2, 3},

    {4, 5, 6}

}; 
c

table можно представить так:

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

То же самое можно сделать, указав все значения подряд. Однако такой способ удобен не всем и может повысить риск ошибки:

int table[2][3] = {1, 2, 3, 4, 5, 6};  /* Записи в памяти располагаются в виде такой последовательности*/
c

Также можно выполнить частичную инициализацию — в таком случае недостающие элементы заполняются нулями (так как рассматривается пример с числами):

int table[2][3] = {

    {1, 2},

    {3}

};
c

Результат:

Столбец 0Столбец 1Столбец 2
Строка 0120
Строка 1300

Далее рассмотрим другие способы инициализации.

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

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

#include <stdio.h>

int main() {

    int matrix[3][3];

    int value = 1;  /* Начальное значение*/

    for (int i = 0; i < 3; i++) {  /* Внешний цикл обходит ряды */

        for (int j = 0; j < 3; j++) {  /* Вложенный цикл обходит столбцы в каждом ряду */

            matrix[i][j] = value++;  /* Массив заполняется последовательностью чисел, начиная с 1*/

        }

    }

    return 0;

}

/* Итог:

1 2 3 

4 5 6 

7 8 9 

*/
c

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

#include <stdio.h>

int main() {

    int matrix[2][3];

    printf("Введите элементы:\n");

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

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

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

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

        }

    }

    return 0;

}

/* Допустим, пользователь ввел 1 5 4 5 5 2*/

/* Итог:

1 5 4 

5 5 2 

*/
c

Программист может реализовать и собственную логику заполнения, например, так:

#include <stdio.h>

int main() {

    int matrix[3][3];

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

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

            matrix[i][j] = (i + 1) * (j + 1);

        }

    }

    return 0;

}

/* Итог:

1 2 3 

2 4 6 

3 6 9 

*/
c

Чтобы заполнить массив случайными числами, можно воспользоваться такой программой:

#include <stdio.h>

#include <stdlib.h>

#include <time.h>

int main() {

    int matrix[2][3];

    srand(time(NULL));  /* Функция srand используется для инициализации генератора случайных чисел*/

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

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

            matrix[i][j] = rand() % 100;  /* Заполнение числами от 0 до 99*/

        }

    }

    return 0;

}
c

Также можно записать данные из файла:

#include <stdio.h>

int main() {

    int matrix[2][3];

    FILE *file = fopen("data.txt", "r");

    if (file == NULL) {

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

        return 1;

    }

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

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

            fscanf(file, "%d", &matrix[i][j]);  /* Функция fscanf для чтения файла */

        }

    }

    fclose(file);  /* Закрытие файла*/

    return 0;

}
c

В случае с текстовыми данными первым числом указывается количество рядов, а вторым — количество символов в каждом из них. Так как каждая строка в C должна заканчиваться символом NULL (\0), ко второму индексу прибавляется единица:

#include <stdio.h>

int main() {

    /* Каждая строка может содержать до 19 символов. Сейчас символов меньше, но при изменении

    значений можно будет указать строки длиннее*/

    char texts[2][20] = {

        "Привет",

        "Мир"

    };

    return 0;

}

/* Итог:

Привет 

Мир

*/
c

Также в программе можно использовать указатели — тогда длину записи ограничивать не нужно:

#include <stdio.h>

int main() {

    /* Массив указателей на строки */

    const char *texts[3] = {

        "Привет",

        "Мир",

        "Программирования"

    };

    return 0;

}

/* Итог:

Привет 

Мир

Программирования

*/
c

Как работать с элементами

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

#include <stdio.h>

int main() {

    int matrix[2][3] = {

        {10, 20, 30},

        {40, 50, 60}

    };

    /* Для доступа к конкретным значениям используются их индексы — получим первую, вторую и

    последнюю записи*/

    int first = matrix[0][0];  

    int second = matrix[0][1]; 

    int last = matrix[1][2];   

    /* Вывод на экран*/

    printf("Первый элемент: %d\n", first);

    printf("Второй элемент: %d\n", second);

    printf("Последний элемент: %d\n", last);

    /* Чтобы вывести сразу все записи, используем цикл*/

    printf("Все элементы:\n");

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

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

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

        }

    }

    return 0;

}

/* Вывод:

Первый элемент: 10

Второй элемент: 20

Последний элемент: 60

Все элементы:

matrix[0][0] = 10

matrix[0][1] = 20

matrix[0][2] = 30

matrix[1][0] = 40

matrix[1][1] = 50

matrix[1][2] = 60

*/
c

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

#include <stdio.h>

int main() {

    int matrix[2][3] = {

        {1, 2, 3},

        {4, 5, 6}

    };

    printf("Значение до изменения:\n");

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

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

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

        }

        printf("\n");

    }

    /* Меняем значение первой записи на второй строке*/

    matrix[1][0] = 10;

    printf("Значение после изменения:\n");

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

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

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

        }

        printf("\n");

    }

    return 0;

}

/* Вывод:

Значение до изменения:

1 2 3

4 5 6

Значение после изменения:

1 2 3

10 5 6

*/
c

Также в C есть полезный оператор sizeof. Он выводит размер массива в байтах:

#include <stdio.h>

int main() {

    int matrix[2][3] = {

        {1, 2, 3},

        {4, 5, 6}

    };

    /* Выводим размер matrix*/

    printf("matrix: %zu байт\n", sizeof(matrix));

    /* Выводим размер одного ряда*/

    printf("Одна строка: %zu байт\n", sizeof(matrix[0]));

    /* Выводим размер одной записи */

    printf("Один элемент: %zu байт\n", sizeof(matrix[0][0]));

    /* Выводим количество рядов: для этого общий размер делим на размер одной строки */

    printf("Количество строк: %zu\n", sizeof(matrix) / sizeof(matrix[0]));

    /* Вычисляем количество столбцов: для этого размер строки делим на размер одного элемента */

    printf("Количество столбцов: %zu\n", sizeof(matrix[0]) / sizeof(matrix[0][0]));

    /* Выводим количество записей в matrix: для этого общий размер делим на размер одного

    элемента */

    printf("Общее количество элементов: %zu\n", sizeof(matrix) / sizeof(matrix[0][0]));

    return 0;

}

/* Вывод:

matrix: 24 байт

Одна строка: 12 байт

Один элемент: 4 байт

Количество строк: 2

Количество столбцов: 3

Общее количество элементов: 6

*/
c

Доступ к текстовым массивам и изменение их значений несколько отличаются:

#include <stdio.h>

int main() {

    char words[3][10] = {"Hello", "World", "GitVerse"};

    /* Для доступа используются индексы ряда и символа*/

    printf("Первый символ первой строки: %c\n", words[0][0]);

    printf("Второй символ первой строки: %c\n", words[0][1]);

    printf("Первый символ второй строки: %c\n", words[1][0]);

    /* Изменяются символы аналогичным образом */

    words[0][0] = 'h';

    printf("После изменения: %s\n", words[0]);

    /* Чтобы изменить всю строку с фиксированной длиной, необходимо использовать snprintf (нужно для ее копирования)*/

    snprintf(words[1], 10, "Program"); 

    printf("После замены строки: %s\n", words[1]);

    return 0;

}

/* Вывод:

Первый символ первой строки: H

Второй символ первой строки: e

Первый символ второй строки: W

После изменения: hello

После замены строки: Program

*/
c

Примеры использования двумерных массивов

Двумерные массивы применяются для хранения информации в форме таблиц, например, это может быть расписание:

#include <stdio.h>

int main() {

    /* Используется указатель */

    const char *schedule[5][4] = {

        {"Математика", "Физическая культура", "История", "Литература"},

        {"Биология", "Химия", "Русский язык", "Английский язык"},

        {"География", "Математика", "Физика", "Информатика"},

        {"Английский язык", "История", "Литература", "Обществознание"},

        {"Физическая культура", "Математика", "Информатика", "Русский язык"}

    };

    printf("Расписание уроков:\n");

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

        printf("День %d: ", i + 1);

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

            printf("%s ", schedule[i][j]);

        }

        printf("\n");

    }

    return 0;

}

/* Вывод:

Расписание уроков:

День 1: Математика Физическая культура История Литература 

День 2: Биология Химия Русский язык Английский язык 

День 3: География Математика Физика Информатика 

День 4: Английский язык История Литература Обществознание 

День 5: Физическая культура Математика Информатика Русский язык 

*/
c

Еще один пример — выполнение различных операций над матрицами, например, сложение:

#include <stdio.h>

int main() {

    int matrix1[2][2] = {

        {1, 2},

        {3, 4}

    };

    int matrix2[2][2] = {

        {5, 6},

        {7, 8}

    };

    int result[2][2];

    /* Цикл для сложения */

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

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

            result[i][j] = matrix1[i][j] + matrix2[i][j];

        }

    }

    printf("Результат сложения:\n");

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

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

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

        }

        printf("\n");

    }

    return 0;

}

/* Вывод:

Результат сложения:

6 8 

10 12 

*/
c

Также можно хранить игровые поля, например:

#include <stdio.h>

int main() {

    /* Поле для игры в крестики-нолики */

    char board[3][3] = {

        {'X', 'O', 'X'},

        {'O', 'X', 'O'},

        {'X', ' ', ' '}

    };

    printf("Игровое поле:\n");

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

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

            printf("%c ", board[i][j]);

        }

        printf("\n");

    }

    return 0;

}

/* Вывод:

Игровое поле:

X O X 

O X O 

X          

*/
c

Подведем итоги

Двумерные массивы удобны для хранения информации в формате таблиц и выполнения операций над ними. При этом программисту важно помнить о возможности выхода за пределы массива (как по строкам, так и по столбцам), а также об отсутствии встроенных методов для обработки этой структуры данных в C.