Что такое двумерный массив
Двумерный массив в C — это структура данных, которая хранит элементы одного типа в табличной форме (ее иногда называют формой многомерных массивов). Соответственно, доступ к содержимому осуществляется через два параметра: индекс строки и индекс столбца. Эту структуру данных можно назвать массивом массивов.
Несмотря на табличную форму, записи в памяти хранятся последовательно: двумерный массив преобразуется в одномерный, в котором каждая строка исходного массива располагается друг за другом.
Тип данных и длина массива определяются в момент его объявления и не могут быть изменены впоследствии. В стандарте C99 реализована возможность задания переменной длины, однако при ее использовании стоит помнить о риске переполнения стека. Размер каждого элемента одинаков для конкретного массива, так как определяется его типом данных.
Как объявить и инициализировать двумерный массив
Чтобы объявить двумерный массив в C, необходимо указать тип данных, который он будет хранить, имя, количество рядов и столбцов (два целых положительных числа, которые указываются в квадратных скобках). Ниже представлен синтаксис:
elements_type array_name[number_of_rows][number_of_columns];
Пример объявления:
int table[3][4];
/* Будет создан двумерный массив table, состоящий из 3 рядов и 4 столбцов и способный хранить целые числа*/
Инициализацию можно выполнить при объявлении — тогда содержимое указывается после знака равно в фигурных скобках (построчно):
int table[2][3] = {
{1, 2, 3},
{4, 5, 6}
};
table можно представить так:
Столбец 0 | Столбец 1 | Столбец 2 | |
Строка 0 | 1 | 2 | 3 |
Строка 1 | 4 | 5 | 6 |
То же самое можно сделать, указав все значения подряд. Однако такой способ удобен не всем и может повысить риск ошибки:
int table[2][3] = {1, 2, 3, 4, 5, 6}; /* Записи в памяти располагаются в виде такой последовательности*/
Также можно выполнить частичную инициализацию — в таком случае недостающие элементы заполняются нулями (так как рассматривается пример с числами):
int table[2][3] = {
{1, 2},
{3}
};
Результат:
Столбец 0 | Столбец 1 | Столбец 2 | |
Строка 0 | 1 | 2 | 0 |
Строка 1 | 3 | 0 | 0 |
Далее рассмотрим другие способы инициализации.
Как заполнить двумерный массив данными
Помимо уже описанных способов, двумерный массив можно заполнить с помощью программы с циклом:
#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
*/
Для заполнения данными, введенными пользователем, подойдет такая программа:
#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
*/
Программист может реализовать и собственную логику заполнения, например, так:
#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
*/
Чтобы заполнить массив случайными числами, можно воспользоваться такой программой:
#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;
}
Также можно записать данные из файла:
#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 должна заканчиваться символом NULL (\0), ко второму индексу прибавляется единица:
#include <stdio.h>
int main() {
/* Каждая строка может содержать до 19 символов. Сейчас символов меньше, но при изменении
значений можно будет указать строки длиннее*/
char texts[2][20] = {
"Привет",
"Мир"
};
return 0;
}
/* Итог:
Привет
Мир
*/
Также в программе можно использовать указатели — тогда длину записи ограничивать не нужно:
#include <stdio.h>
int main() {
/* Массив указателей на строки */
const char *texts[3] = {
"Привет",
"Мир",
"Программирования"
};
return 0;
}
/* Итог:
Привет
Мир
Программирования
*/
Как работать с элементами
Сначала посмотрим, как читать содержимое и выводить его на экран:
#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
*/
Чтобы изменить элемент, к нему также нужно обратиться по индексу, затем через знак равенства указать его новое значение:
#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 есть полезный оператор 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
*/
Доступ к текстовым массивам и изменение их значений несколько отличаются:
#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
*/
Примеры использования двумерных массивов
Двумерные массивы применяются для хранения информации в форме таблиц, например, это может быть расписание:
#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: Физическая культура Математика Информатика Русский язык
*/
Еще один пример — выполнение различных операций над матрицами, например, сложение:
#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
*/
Также можно хранить игровые поля, например:
#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.