Введение в массивы и строки

Последнее обновление: 21.01.2024

Нередко возникает необходимость работы не с одиночными данными, а с наборами данных. И для этого в языке Си применяются массивы. Массив представляет набор однотипных значений. Объявление массива выглядит следующим образом:

тип_переменной название_массива [длина_массива]

После типа переменной идет название массива, а затем в квадратных скобках его размер. Например, определим массив из 4 чисел:

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

Используя номера элементов массиве, которые называются индексами, мы можем обратиться к отдельным элементам. Например:

#include <stdio.h>

int main(void)
{
	int numbers[4];
	numbers[0] = 1;
	numbers[1] = 2;
	numbers[2] = 3;
	numbers[3] = 4;
	
	printf("numbers[0] = %d \n", numbers[0]);	// 1 - первый элемент
	printf("numbers[2] = %d \n", numbers[2]);	// 3 - третий элемент
	return 0;
}

Индексы указываются в квадратных скобках после названия массива и начинаются с нуля, поэтому для обращения к первому элементу необходимо использовать выражение numbers[0].

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

int numbers[4] = { 1, 2, 3, 5 };		// инициализация массива
printf("numbers[2] = %d", numbers[2]);	// 3

То есть в данном случае у нас будет следующее соответствие между значениями элементов и их индексами:

Значение1235
Индекс0123

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

int numbers[] = { 1, 2, 3, 5 };

При этом необязательно инициализировать массив значениями для всех его элементов:

int numbers[5] = { 10, 12};	// 10, 12, 0, 0, 0

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

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

int numbers[5] = { [1]=11, [3] = 13 };

В данном случае инициализируются только два элемента - с индексами 1 и 3. Остальные получают значение по умолчанию - 0. То есть в итоге подобный массив будет анологичен следующему:

int numbers[5] = { 0, 11, 0, 13, 0 };

Размер и количество элементов массива

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

#include <stdio.h>

int main(void)
{
    int numbers[] = { 5, 6, 7};
	size_t size = sizeof(numbers);
    printf("numbers size: %zu \n", size);     // numbers size: 12
    return 0;
}

В этом примере оператор sizeof() для массива { 5, 6, 7} возвращает 12 байт (так как массив содержит 3 значения типа int, которое обычно занимает 4 байта). Тип результата оператора sizeof - size_t фактически является псевдонимом для типа unsigned long long, то есть 64-разрядное положительное число. Для его вывода на консоль применяется спецификатор %zu.

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

#include <stdio.h>

int main(void)
{
    int numbers[] = { 5, 6, 7};
    size_t size = sizeof(numbers);
    size_t count = sizeof(numbers) / sizeof(int);
    printf("numbers size: %zu \n", size);     	// numbers size: 12
    printf("numbers count: %zu \n", count);     	// numbers count: 3
    return 0;
}

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

#include <stdio.h>

int main(void)
{
    int numbers[] = { 5, 6, 7};
	size_t size = sizeof(numbers);
	size_t count = sizeof(numbers) / sizeof(numbers[0]);
    printf("numbers size: %zu \n", size);     	// numbers size: 12
    printf("numbers count: %zu \n", count);     	// numbers count: 3
    return 0;
}

Перебор элементов массива

Используя циклические конструкции, можно перебрать массив:

#include <stdio.h>

int main(void)
{
	int numbers[] = { 10, 12, 13, 54, 43 };
    size_t count = sizeof(numbers) / sizeof(numbers[0]);
	for(size_t i =0; i < count; i++)
	{
		printf("numbers[%zu] = %d \n", i, numbers[i]);
	}
	return 0;
}

Стоит отметить, что в качестве индекса используется значение типа size_t. В реальности часто встречается и мы могли бы использовать тип int или unsigned int:

#include <stdio.h>
 
int main(void)
{
	int numbers[] = { 10, 12, 13, 54, 43 };
    size_t count = sizeof(numbers) / sizeof(numbers[0]);
    // int в качестве индекса
	for(int i =0; i < count; i++)
	{
		printf("numbers[%d] = %d \n", i, numbers[i]);
	}
	return 0;
}
В целом мы получим тот же результат. Но в общем рекомендуемым способом все таки является использование size_t, поскольку этот тип допускает количество, которое может не вписаться в допустимый диапазон чисел типов int или unsigned int.

Динамическая установка размера массива

Размер массива можно установить динамически с помощью переменной/константы:

#include <stdio.h>

int main(void)
{
	int maxSize = 3;
    int array[maxSize];
    array[0] = 1;
    array[1] = 2;
    array[2] = 3;
    for (int i = 0; i < maxSize; i++)
    {
        printf("%d", array[i]);
    }
	return 0;
}

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

int maxSize = 3;
int array[maxSize] = {1, 2, 3};	// ! Ошибка, так нельзя

Константные массивы

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

#include <stdio.h>
 
int main(void)
{
    int numbers[3] = {11, 12, 13};
    numbers[1] = 22;        // изменяем второй элемент
    printf("numbers[1] = %d", numbers[1]);  // numbers[1] = 22
    return 0;
}

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

#include <stdio.h>
 
int main(void)
{
    const int numbers[3] = {11, 12, 13};
    // numbers[1] = 22; // Нельзя изменить - массив константный
    printf("numbers[1] = %d", numbers[1]);  // numbers[1] = 22
    return 0;
}

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

Многомерные массивы

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

int numbers[3][2] = { {1, 2}, {4, 5}, {7, 8} };

Здесь массив numbers имеет три элемента (3 строки), но каждый из этих элементов сам представляет массив из двух элементов (2 столбцов). Такой массив еще можно представить в виде таблицы:

12
45
78

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

int numbers[3][2] = { {1, 2}, {4, 5}, {7, 8} };
printf("numbers[1][0] = %d \n", numbers[1][0]);		// 4

другой пример, двухмерный массив с двумя строками и тремя столбцами:

int numbers[2][3] = { {1, 2, 4}, {5, 7, 8} };

Такой массив графически можно представить следующим образом:

124
578

Для перебора двухмерного массива применются вложенные циклы:

#include <stdio.h>

int main(void)
{
	int numbers[3][2] = { {1, 2}, {4, 5}, {7, 8} };
	// проходим по 3 строкам таблицы
	for(int i =0; i < 3; i++)
	{
		// проходим по 2 столбцам каждой строки
		for(int j =0; j<2; j++)
		{
			printf("numbers[%d][%d] = %d \n", i, j, numbers[i][j]);
		}
	}
	return 0;
}

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

#include <stdio.h>
 
int main(void)
{
    int numbers[3][2] = { {1, 2}, {4, 5}, {7, 8} };
    size_t rows_count = sizeof(numbers) / sizeof(numbers[0]);           // 3
    size_t columns_count = sizeof(numbers[0]) / sizeof(numbers[0][0]);  // 2
    printf("rows count = %zu \n", rows_count);
    printf("columns count = %zu \n", columns_count);
    // проходим по 3 строкам таблицы
    for(size_t i =0; i < rows_count; i++)
    {
        // проходим по 2 столбцам каждой строки
        for(size_t j =0; j<columns_count; j++)
        {
            printf("numbers[%zu][%zu] = %d \n", i, j, numbers[i][j]);
        }
    }
    return 0;
}

Консольный вывод:

rows count = 3
columns count = 2
numbers[0][0] = 1
numbers[0][1] = 2
numbers[1][0] = 4
numbers[1][1] = 5
numbers[2][0] = 7
numbers[2][1] = 8

Строки

Выше рассматривались массивы чисел, но с массивами остальных типов данных все будет аналогично. Но отдельно стоит остановиться на массивах символов. В различных языках программирования есть специальные типы данных для представления строк. В языке программирования Си для представления строк используются массивы символов, ведь по сути строка - это и есть набор символов. Например, определим строку:

#include <stdio.h>

int main(void)
{
    char message[] = "Hello";
    printf("message: %s \n", message);     // message: Hello
    return 0;
}

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

Но стоит отметить, что кроме самих символов, которые заключены двойные кавычки, каждая строка в качестве завершающего символа содержит символ \0или нулевой символ (нулевой байт). Он же самый первый символ из таблицы ASCII. В Си нулевой байт служит признаком окончания строки. Поэтому в строке "Hello" на самом деле будет не 5 символов, а 6.

Hello\0

К примеру, переберем все символы строки и выведем их десятичный код ASCII:

char message[] = "Hello";
size_t length = sizeof(message)/sizeof(char);   // 6 символов
for(size_t i=0; i<length; i++)
{
	printf("%d ", message[i]);
}

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

72 101 108 108 111 0

Если бы мы определяли массив message не как строку, а именно как массив символов, то последним элементом должен был бы идти нулевой символ:

char message[] = {'H', 'e', 'l', 'l', 'o', '\0'};

Задачи с массивы

Рассмотрим работу с массивами на примере умножения матриц:

#include <stdio.h>
 
int main(void)
{
    const int r1 = 3, c1r2=2, c2=1;
    int matrix1[3][2] = {{1, 2},{3, 4},{5, 6}};
    int matrix2[2][1] = {{10},{20}};
    int matmult[r1][c2];

    // инициализируем результирующую матрицу
    for(int i=0;i<r1;i++)
    {
        for(int j=0;j<c2;j++)
        {
            matmult[i][j]=0;
        }
    }

    for(int i=0;i<r1;i++)
    {
        for(int j=0;j<c2;j++)
        {
            for(int k=0;k<c1r2;k++)
            {
                matmult[i][j] = matmult[i][j] + matrix1[i][k] * matrix2[k][j];
            }
        }
    }

    printf("Result \n");
    for(int i=0;i<r1;i++)
    {
        for(int j=0;j<c2;j++)
        {
            printf("%d ",matmult[i][j]);
        }
        printf("\n");
    }
}

Здесь у нас определены две матрицы. Матрица matrix1[3][2] имеет три строки и два столбца:

12
34
56

Вторая матрица фактически состоит из одно столбца:

10
20

Для хранения размера столбцов и строк определены переменные

const int r1 = 3,       // число строк в 1-й матрице
          c1r2=2,     // число столбцов в 1-й и число строк во 2-й матрице
          c2=1;       // число столбцов во 2-й матрице

Также определяем результирующую матрицу - результат произведения - matmul:

int matmult[r1][c2];

И инициализируем ее нулями.

При произведении матриц мы получаем матрицу, где количество строк равно количеству строк первой матрицы, а количество столбцов - количеству столбцов второй матрицы. А элемент результирующей матрицы на i-й строке j-м столбце равен сумме произведений элементов на i-й строке первой матрицы на соответствующие элементы j-го столбца второй матрицы.

c2,1 = a2,1 * b1,1 + a2,2 * b2,1

Соответственно при вычислении произведения в цикле по i проходим по всем строкам первой матрицы:

for(int i=0;i<r1;i++)

Далее в цикле по j проходим по всем столбцам второй матрицы:

for(int j=0;j<c2;j++)

В цикле по k умножаем значения из k-столбца первой матрицы на значения k-строки второй матрицы и прибавляем к результату:

for(int k=0;k<c1r2;k++)
{
    matmult[i][j] = matmult[i][j] + matrix1[i][k] * matrix2[k][j];
}

В результате мы получим матрицу из трех строк и одного столбца:

Result
50
110
170
Дополнительные материалы
Помощь сайту
Юмани:
410011174743222
Перевод на карту
Номер карты:
4048415020898850