Программирование на языке C в Microsoft Visual Studio 2010

Числовые массивы в языке программирования С

Разбить на страницы
Показывать лекцию целиком

Теоретическая часть

В языке программирования С заложены средства для задания последовательностей упорядоченных данных [5.1]. Такие последовательности называются массивами. В массивах должны быть упорядочены данные одного и того же типа. В данной лабораторной работе будут рассматриваться массивы с целыми и вещественными типами данных, т.е. типы int, float или double.

Массивы данных могут быть одномерными (векторами размера 1 $$\times$$ n или n $$\times$$ 1), двухмерными (матрицами размера n $$\times$$ m) или многомерными (размера n $$\times$$ m $$\times$$ p...). В частности, для векторов и матриц в приведенной записи первый индекс означает количество строк, а второй (число или буква) – это количество столбцов. Для названия массива может быть использована переменная, состоящая из букв (буквы), букв с цифрами, букв с цифрами и знаком подчеркивания и т.д. в соответствии с правилами объявления переменных, принятых в языке С. Если размерность массива меньше, чем требуется, то компилятор не выдаст сообщения об ошибке. Выход за границы массивов должен следить только сам программист.

5.1. Одномерные массивы

Одномерный массив – это список связанных однотипных переменных.

Общая форма записи одномерного массива [5.2]:

тип  имя_массива[размер];

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

В языке С индексация массива начинается с нуля. Например, если размер массива определен величиной 9, то в массиве можно хранить 10 элементов с индексацией 0, 1, 2, 3, 4, 5, 6, 7, 8, 9.

Доступ к отдельному элементу массива осуществляется с помощью индекса. Индекс описывает позицию элемента внутри массива.

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

Для одномерных массивов общий размер массива в байтах вычисляется по формуле:

всего байт = размер типа в байтах * количество элементов

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

5.2. Двухмерные массивы, матрицы

Двухмерный массив представляет собой список одномерных массивов.

Общая форма записи двухмерного массива:

тип  имя_массива[размер1] [размер2];

В приведенной записи размер1 означает количество строк двухмерного массива, а размер2 – количество столбцов.

В двухмерном массиве позиция любого элемента определяется двумя индексами. Индексы каждого из размеров массива начинаются с 0 (с нуля).

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

Для двухмерных массивов общий размер массива в байтах вычисляется по формуле:

всего байт = число строк * число столбцов * размер типа в байтах

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

Общая форма записи многомерного массива:

тип  имя_массива[размер1] [размер2]... [размерN];

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

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

Для многомерных массивов общий размер многомерного массива в байтах вычисляется по формуле:

всего байт = размер1* размер2*...* размерN *размер типа в байтах

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

Для определения размера типа в байтах применяется функция sizeof(), которая возвращает целое число. Например, sizeof(float).

5.4. Инициализация массивов

В языке С массивы при объявлении можно инициализировать [5.2].

Общая форма инициализации массива:

тип  имя_массива[размер1] * [размерN] = {список_значений};

В список_значений входят константы, разделенных запятыми. Типы констант должны быть совместимыми с типом массива.

Пример инициализации одномерного массива:

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

При этом A[0] = 1, A[1] = 2 и т.д.

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

Пример инициализации двухмерного массива:

int  MN[3][4] = {
{1, 2, 3, 4},
{5, 6, 7, 8},
{9, 10, 11, 12}
};

Массив MN[3][4] – это матрица, у которой 3 строки и 4 столбца.

Для многомерных массивов инициализацию можно также проводить с указанием номера инициализируемого элемента.

Пример инициализации трехмерного массива:

int  XYZ[2][3][4] = {
{ {1, 2, 3, 4}, {5, 6, 7, 8}, {9, 10, 11, 12} },
{ {13, 14, 15, 16}, {17, 18, 19, 20}, {21, 22, 23, 24} }
};

Как видно, массив XYZ содержит два блока, каждый из которых есть матрица размера 3 $$\times$$ 4, т.е. 3 строки и 4 столбца.

В языке С возможна инициализация безразмерных массивов. Например, для одномерного массива:

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

В многомерном массиве размер самого левого измерения также можно не указывать. В частности, для инициализации массива MN[3][4] допустима следующая запись:

int  MN[][4] = {
{1, 2, 3, 4},
{5, 6, 7, 8},
{9, 10, 11, 12}
};

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

int  XYZ[][3][4] = {
{
{1, 2, 3, 4},
{5, 6, 7, 8},
{9, 10, 11, 12}
},
{
{13, 14, 15, 16},
{17, 18, 19, 20},
{21, 22, 23, 24}
}
};

Вывод трехмерного массива на консоль (дисплей) можно выполнить по следующей программе:

#include <stdio.h>
#include <conio.h>

int main (void) {
	int i, j, k;
	int  XYZ[][3][4] = {
{ {1, 2, 3, 4}, {5, 6, 7, 8}, {9, 10, 11, 12} }, // 1-й 
{ {13, 14, 15, 16}, {17, 18, 19, 20}, {21, 22, 23, 24} } }; // 2-й 
	for (i = 0; i < 2; ++i) { printf("\n");
		for (j = 0; j < 3; ++j) { printf("\n");
			for (k = 0; k < 4; ++k)
				printf(" %3d", XYZ[i][j][k]);
		}
	}
	printf("\n\n Press any key: ");
	_getch();
	return 0;
 }

Практическая часть

Пример 1. Напишите программу заполнения одномерного массива случайными числами из интервала от 1 до 15 по случайному равномерному закону. Отсортировать массив случайных чисел по возрастанию.

Для решения поставленной задачи применим сортировку методом прямого выбора [5.3]. Алгоритм сортировки заключается в следующем:

  • В исходной последовательности из N элементов отыскивается элемент с наименьшим ключом.
  • Он меняется местами с первым элементом.
  • В оставшейся последовательности из ( N –1) элементов отыскивается минимальный элемент и меняется местами со вторым элементом и т.д., пока не останется один, самый большой элемент.
  • Программный код решения примера:

    #include <stdio.h>
    #include <conio.h>
    #include <time.h>
    #include <stdlib.h>
    #define Left 1
    #define Right 15
    #define N 10
    
    int main (void) {
    float R, r, min;
    float A[N];
    
    int i, j, k;	
    unsigned int some;
    long int L;
    
    L = (long) time(NULL); // Системное время
    some = (unsigned) L; // Приведение типов
    srand(some); // Задание исходного случайного числа для rand()
    printf("\n\t The initial array of random numbers in the interval [%d, %2d]\n", Left, Right);
    
    for (i = 0; i < N; ++i)
    {// Случайное число из интервала [0,1]
    r = (float) rand()/RAND_MAX;
    // Формирование случайного числа из заданного интервала
    R = Left + (Right - Left) * r;
    // Заполнение массива случайными числами
    A[i] = R; }
    
    // Печать элементов исходного массива
    for (i = 0; i < N; ++i)
    printf("\n\t %5d) %10.4f", i + 1, A[i]);
    
    // Сортировка методом выбора
    for (i = 0; i < (N - 1); ++i)
     {
     min = A[i]; k = i;
    for (j = i + 1; j < N; ++j)
    if (A[j] < min) { k = j; min = A[k]; }
    A[k] = A[i]; A[i] = min;
    }
    
    // Печать отсортированного массива по возрастанию
    printf("\n\n\t Sort an array:\n");
    for (i = 0; i < N; ++i)
    printf("\n\t %5d) %10.4f", i + 1, A[i]);
    
    	printf("\n\n Press any key: ");
    	_getch();
    	return 0;
    }

    Возможный результат выполнения программы показан на рис 5.1.

    (рис 5.1) Сортировка одномерного массива по возрастанию

    В программе использованы директивы препроцессора для задания левой границы ( #define Left 1 ), правой границы ( #define Right 15 ) и размера одномерного массива ( #define N 10 ). Включены дополнительные библиотеки time.h – для обращения к функциям системного времени, stdlib.h – для обращения к функциям генерации псевдослучайных чисел.

    Задание 1

  • Границы формирования случайных чисел и размерность массива задайте без препроцессорных директив.
  • Произведите сортировку массива случайных чисел по убыванию.
  • Введите границы интервала формирования случайных чисел по правилу: Left = X, Right = 3*X, где Х – номер компьютера, за которым выполняется лабораторная работа. Произвести сортировку по убыванию.
  • Найдите и изучите сортировку методом прямого обмена (сортировка методом пузырька). Напишите программу формирования одномерного массива случайными числами и их сортировки методом пузырька.
  • Пример 2. Напишите программу поиска максимального элемента в заданном одномерном массиве. Элементы массива являются целыми числами. Программный код решения примера:

    #include <stdio.h>
    #include <conio.h>
    
    int main (void){
    	int i, size, max;
    	int A[ ] = {3, 5, 2, 8, 12, 0, -7, -3, -21};
    size = sizeof(A)/sizeof(A[0]);
    printf("\n\t The dimention of the array A is equal to: %d\n", size);
         	max = A[0]; // Предполагаемый максимум
    	for (i = 0; i < size; ++i)
    		if (A[i] > max) max = A[i]; 
    
    printf("\n\t Maximum array element: %d\n", max);
    
    	printf("\n\n Press any key: ");
    	_getch();
    	return 0; }

    В программе использована инициализация безразмерного массива и определения его размерности с помощью функции sizeof().

    Результат выполнения программы показан на рис 5.2.

    (рис 5.2) Определение максимального элемента массива

    Задание 2

  • В программе предусмотрите вывод на консоль значений массива в виде строки.
  • Определите индекс максимального элемента.
  • Определите минимальный элемент массива и его индекс.
  • Напишите программу перемещения максимального и минимального элементов на края массива. Например, максимальный элемент поместить в конец массива, а минимальный – в начало массива. Выведите на консоль исходный массив данных и преобразованный массив в виде двух строк, следующих друг за другом.
  • Напишите программу ввода целых чисел массива с клавиатуры и определения максимального и минимального элементов в нем. Количество вводимых чисел задается по соотношению 5*Х, где Х – номер компьютера, за которым выполняется лабораторная работа.
  • Пример 3. Напишите программу циклической перестановки чисел заданного массива так, чтобы i -e число стало ( i +1)-м, а последнее число – первым. Выведите на дисплей исходный массив и преобразованный.

    Программный код решения примера:

    #include <stdio.h>
    #include <conio.h>
    #define N 55
    
    int main (void) {
    	int i, j, k;
    	double D[ ] = {1.23, 2.34, 3.45, 4.56, 5.67, 6.78};
    	double B[N]; // Заведомо больший размер, чем у массива D
    	// Обнуление массива и  выделение памяти для него
    	for (i = 0; i < N; ++i)
    		B[i] = 0.0;
    
    k = sizeof(D)/sizeof(D[0]);
    
    B[0] = D[k-1];
    for (i = 0; i < (k - 1); ++i)
    B[i+1] = D[i];
    
    printf("\n\t The original array:\n");
    for (i = 0; i < k; ++i)
    printf("%8.2f", D[i]); 
    
    printf("\n\n\t The reconfigured array:\n");
    for (j = 0; j < k; ++j)
    printf("%8.2f", B[j]);
    
    	printf("\n\n Press any key: ");
    	_getch();
    	return 0;
     }

    Результат выполнения программы показан на рис 5.3.

    (рис 5.3) Пример циклической перестановки элементов числового массива

    Задание 3

  • Выведите на дисплей (для проверки) два элемента массива В, номера которых превышают размерность массива D.
  • Программным путем подсчитайте сумму элементов массива и выведите ее на консоль.
  • Дополните программу формированием массива, в котором элементы расположены в обратном порядке, чем в исходном массиве.
  • Сформируйте массив кодов строчных букв латинского алфавита. Выведите на дисплей сами буквы и их коды, которые являются элементами массива.
  • Пример 4. В данном одномерном массиве вещественных чисел поменяйте местами элементы, стоящие на нечетных местах, с элементами, стоящими на четных местах. Предусмотрите четность и нечетность размерности массива.

    Для определения четности места в заданном массиве можно использовать операцию деления по модулю, т.е. %.

    Программный код решения примера:

    #include <stdio.h>
    #include <conio.h>
    // Размер массива
    #define n 7 
    
    int main (void) { 
    int i, k;
    // Пример массива
    float A[n] = {1.23F, 2.34F, 3.45F, 4.56F, 5.67F, 6.78F, 7.89F};
    float B[n];// Вспомогательный массив
    
    // Обнуление массива
    	for (i = 0; i < n; ++i)
    		B[i] = 0;
    
    // Распечатка заданного массива
    printf("\n\t\t The original array of dimention n = %d:\n", n);
    	printf("\t");
    	for (i = 0; i < n; ++i)
    		printf("%6.2f", A[i]);
    
    // Распечатка преобразованного массива
    	printf("\n\n\t\t The reconfigured array:\n");
    	for (i = 0; i < n; ++i) { 
    k = i % 2; // Для определения четности индекса массива
    if (k == 0  i < n - 1 )
     B[i] = A[i + 1]; 
    else if (k != 0  i > 0 )
    B[i] = A[i-1];
    else if (k == 0  i < n)
    B[i] = A[i]; }  printf("\t");
    	
         for (i = 0; i < n; ++i)
    		printf("%6.2f", B[i]);
    
    	printf("\n\n Press any key: ");
    	_getch();
    	return 0; }

    При инициализации массива каждый его элемент снабжен суффиксом F.

    Результат выполнения программы показан на рис 5.4.

    (рис 5.4) Смена четных и нечетных мест чисел массива

    Задание 4

  • Вывод на дисплей пользователя исходного массива и преобразованного выполните без дополнительного массива.
  • В программу внесите изменения, чтобы не использовать целочисленную переменную для определения остатка от деления
  • Проверьте программу для массива с четным количеством элементов.
  • Напишите программу без использования операции определения остатка от деления, т.е. без использования операции i % 2.
  • Напишите программу для двухмерного массива вещественных чисел. Смену мест элементов (четных с нечетными) предусмотрите в каждой строке матрицы. Размер матрицы примите равным n $$\times$$ m, где n = 2*X
  • Указание: Можно предусмотреть определение остатка от деления, как индекса строки, так и индекса столбца и суммы индексов строки и столбца.

    Пример 5. Напишите программу заполнения квадратной матрицы (заданного размера n > 2) по спирали натуральными числами начиная с левого верхнего угла (принимая его за номер 1) и двигаясь по часовой стрелке.

    Образец заполнения:

    Программный код решения примера:

    #include <stdio.h>
    #include <conio.h>
    #define n 13
    
    int main(void) {
    	int i = 1, j, k;
    	int p = n/2;
    	int A[n][n];
    
    	// Обнуление матрицы
    	for (j = 0; j < n; ++j)
    		for (k = 0; k < n; ++k)
    			A[j][k] = 0;
    
    printf("\n\t Spiral matrix of dimention (%d x %d):\n", n, n);
    
    	for (k = 1; k <= p; k++)  // Число спиралей
    	{
    		// Верхний горизонтальный столбец
    		for (j = (k-1); j < (n-k+1); j++)
    			A[(k-1)][j] = i++;
    
    		// Правый верхний столбец
    		for (j = k; j < (n-k+1); j++) 
    			A[j][n-k] = i++;
    
    		// Нижний горизонтальный столбец
    		for (j = (n-k-1); j >= (k-1); --j) 
    			A[n-k][j] = i++;
    
    		// Левый верхний столбец
    		for (j = (n-k-1); j >= k; j--) 
    			A[j][(k-1)] = i++;
    	}
    	if ( n % 2 ) 
    		A[p][p] = n*n;
    
    	// Распечатка матрицы
    	for (i = 0; i < n; ++i)
    		for (j = 0; j < n; ++j)
    		{
    			printf("%5d", A[i][j]);
    			if (j == (n-1))
    				printf("\n");
    		}
    
    		          printf("\n Press any key: ");
    		_getch();
    		return 0;
    }

    Результат выполнения программы показан на рис 5.5.

    (рис 5.5) Заполнение матрицы по спирали

    Задание 5

  • Дополните программу расчетом количества четных элементов выше главной диагонали матрицы и количества нечетных элементов ниже главной диагонали, не включая саму диагональ.
  • Протестировать программу при изменении размера матрицы от 3 до 12. Результаты вставьте в отчет лабораторной работы.
  • Напишите программу заполнения матрицы по спирали против часовой стрелки, начиная с верхнего левого угла (номер 1).
  • Напишите программу с вводом направления заполнения матрицы. Например, если будет введено число 1, то заполнение выполните по часовой стрелке, если введено число –1, – против часовой стрелки.
  • Напишите программу заполнения прямоугольной матрицы натуральными числами "змейкой" – построчно: слева – направо – справа – налево и т.д.
  • Пример 6. Каждый день производятся замеры некоторых величин (вещественных значений), причем значения этих величин сводятся в прямоугольную таблицу размера n $$\times$$ m. Составьте многомерный массив данных за 30 дней. Формирование данных произвести по случайному равномерному закону из интервала от –12 до 21.

    Этот пример относится к определению трехмерного массива данных. Программный код решения примера:

    #include <stdio.h>
    #include <conio.h>
    #include <time.h>
    #include <stdlib.h>
    
    #define n 6
    #define m 7
    #define N 30
    
    const int Left = -12; // Левая граница
    const int Right = 21; // Правая граница
    
    int main (void) 
    {
    	float R, r;
    	float A[N][n][m];
    
    	int i, j, k;	
    
    	// Инициализация генератора случайных чисел
    	srand((unsigned) time(NULL)); 
    
    	printf("\n\t The values of every 10 days from 30 days:");
    
    	// Формирование данных за 30 дней
    	for (k = 0; k < N; ++k)
    		for (i = 0; i < n; ++i)
    			for (j = 0; j < m; ++j)
    			{	r = (float) rand()/RAND_MAX;
    	R = Left + (Right - Left)*r;
    	A[k][i][j] = R;
    	}
    
    	// Печать данных за каждый 10-й день
    	for (k = 0; k < N; k += 10) { printf("\n");
    	for (i = 0; i < n; ++i) { printf("\n");
    	for (j = 0; j < m; ++j)
    		printf("%10.4f", A[k][i][j]);
    	}
    	}
    
    	printf("\n Press any key: ");
    	_getch();
    	return 0; 
    }

    В программе используется трехмерный массив размера 30 $$\times$$ 6 $$\times$$ 7. Это означает, что прямоугольная таблица (массив) данных размера 6 $$\times$$ 7 как бы скрепляется 30 раз – по заданному числу дней. Границы случайных чисел определены с помощью спецификатора const.

    Возможный результат выполнения программы показан на рис 5.6.

    (рис 5.6) Вывод данных за каждый 10-й день

    Задание 6

  • В программе сделайте предварительное обнуление трехмерного массива.
  • Измените тип вещественных данных float на тип double. Проверьте работу программы.
  • Найдите максимальный и минимальный элементы массивов.
  • Найдите абсолютные значения максимального и минимального элементов всего трехмерного массива.
  • Выполните заполнение трехмерного массива размера 3 $$\times$$ 5 $$\times$$ 7 натуральными числами и выведите на консоль полученный многомерный массив.
  • Пример 7. Напишите программу по перемножению двух матриц А и В с размерностями ( m $$\times$$ r ) и ( r $$\times$$ n ) соответственно. Матрицу А примите размером 4 $$\times$$ 5, матрицу В – размером 5 $$\times$$ 3 (обе целочисленные).

    Условием перемножения двух матриц А и В является равенство числа столбцов матрицы А и числа строк матрицы В. Если первая матрица А имеет размер m $$\times$$ r, то вторая матрица В должна иметь размер r $$\times$$ n. В результате перемножения получим матрицу С размера m $$\times$$ n. Приведем следующую схему по размерностям:

    Поэлементное перемножение двух матриц в стандартной математической форме имеет следующий вид:

    $$C_{ij}=\sum^{n}_{k=1}a_{il}*b_{kj}, i=1,2,\cdots,n, j=1,2,\cdots,m.$$

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

    $$C_{ij}=\sum^{n-1}_{k=1}a_{il}*b_{kj}, i=1,2,\cdots,n-1, j=0,1,2,\cdots,m-1.$$

    Программный код решения примера:

    #include <stdio.h>
    #include <conio.h>
    
    #define m 4
    #define r 5
    #define n 3
    
    int main (void) {
    	int i, j, k; // переменные циклов
    	
    	const int A[m][r] = {{1,2,3,4,5},
    	{2,3,4,5,6},
    	{2,2,2,2,2},
    	{3,3,3,3,3}};
    	
    	const int B[r][n] = {{9,8,7},
    	{1,2,3},
    	{4,5,6},
    	{7,8,9},
    	{1,1,1}};
    
    	// Массив под результат произведения двух матриц
    	int C[m][n];
    
    	// Обнуление результирующей матрицы
    	for (i = 0; i < m; i++)
    		for (j = 0; j < n; j++)
    			C[i][j] = 0;
    // Формирование результата произведения двух матриц
    	for (i = 0; i < m; i++)
    		for (j = 0; j < n; j++)
    			for (k = 0; k < r; k++)
    				C[i][j] = C[i][j] + A[i][k]*B[k][j];
    	
    // Распечатка результата произведения двух матриц
    	printf("\n 1) Index: \"ijk\".  Matrix (%dx%d):\n", m, n);
    	for (i = 0; i < m; i++) {
    		printf("\n");
    		for (j = 0; j < n; j++)
    			printf(" %4d", C[i][j]);
    	}
    			
    	printf("\n\n ... Press any key: ");
    	_getch();
    	return 0;
    }

    В программе используются три цикла по формированию произведения двух матриц. Первый цикл (переменная i ) связан с количеством строк первой матрицы (матрицы А ), второй цикл (переменная j ) связан с количеством столбцов второй матрицы (матрица В ), третий цикл (переменная k ) связан со смежной размерностью матриц, которая исчезает в результирующей матрице С. Матрицы А и В определены как неизменяемы типы ( const int ). Приведенный программный метод можно назвать как первый метод, метод "ijk".

    Результат выполнения программы показан на рис 5.7.

    (рис 5.7) Результат произведения двух матриц

    Задание 7

  • Измените программу, чтобы циклы были организованы в порядке "jki". Это можно назвать вторым методом программного перемножения двух матриц.
  • Измените программу, чтобы циклы были организованы в порядке "kji". Это можно назвать третьим методом программного перемножения двух матриц.
  • Примените тип float для заданных матриц. Предусмотрите форматированный вывод результата на консоль.
  • Контрольные вопросы

  • Как организуются многомерные числовые массивы в языке С?
  • Как организуется индексирование числовых массивов в языке С?
  • На кого или на что возлагается контроль границ числовых массивов в языке программирования С?
  • В какой очередности и как происходит заполнение многомерных числовых массивов в программах на языке С?
  • Для чего применяется начальная инициализация числовых массивов при дальнейшем их использовании?
  • Сколько потребуется операторов цикла для вывода на консоль двухмерного числового массива (матрицы чисел)?
  • Почему при определении размерности массива с помощью препроцессорной директивы define не используется точка с запятой после числового значения?
  • Страницы:

    Теоретическая часть

    В языке программирования С заложены средства для задания последовательностей упорядоченных данных [5.1]. Такие последовательности называются массивами. В массивах должны быть упорядочены данные одного и того же типа. В данной лабораторной работе будут рассматриваться массивы с целыми и вещественными типами данных, т.е. типы int, float или double.

    Массивы данных могут быть одномерными (векторами размера 1 $$\times$$ n или n $$\times$$ 1), двухмерными (матрицами размера n $$\times$$ m) или многомерными (размера n $$\times$$ m $$\times$$ p...). В частности, для векторов и матриц в приведенной записи первый индекс означает количество строк, а второй (число или буква) – это количество столбцов. Для названия массива может быть использована переменная, состоящая из букв (буквы), букв с цифрами, букв с цифрами и знаком подчеркивания и т.д. в соответствии с правилами объявления переменных, принятых в языке С. Если размерность массива меньше, чем требуется, то компилятор не выдаст сообщения об ошибке. Выход за границы массивов должен следить только сам программист.

    5.1. Одномерные массивы

    Одномерный массив – это список связанных однотипных переменных.

    Общая форма записи одномерного массива [5.2]:

    тип  имя_массива[размер];

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

    В языке С индексация массива начинается с нуля. Например, если размер массива определен величиной 9, то в массиве можно хранить 10 элементов с индексацией 0, 1, 2, 3, 4, 5, 6, 7, 8, 9.

    Доступ к отдельному элементу массива осуществляется с помощью индекса. Индекс описывает позицию элемента внутри массива.

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

    Для одномерных массивов общий размер массива в байтах вычисляется по формуле:

    всего байт = размер типа в байтах * количество элементов

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

    5.2. Двухмерные массивы, матрицы

    Двухмерный массив представляет собой список одномерных массивов.

    Общая форма записи двухмерного массива:

    тип  имя_массива[размер1] [размер2];

    В приведенной записи размер1 означает количество строк двухмерного массива, а размер2 – количество столбцов.

    В двухмерном массиве позиция любого элемента определяется двумя индексами. Индексы каждого из размеров массива начинаются с 0 (с нуля).

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

    Для двухмерных массивов общий размер массива в байтах вычисляется по формуле:

    всего байт = число строк * число столбцов * размер типа в байтах

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

    Общая форма записи многомерного массива:

    тип  имя_массива[размер1] [размер2]... [размерN];

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

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

    Для многомерных массивов общий размер многомерного массива в байтах вычисляется по формуле:

    всего байт = размер1* размер2*...* размерN *размер типа в байтах

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

    Для определения размера типа в байтах применяется функция sizeof(), которая возвращает целое число. Например, sizeof(float).

    5.4. Инициализация массивов

    В языке С массивы при объявлении можно инициализировать [5.2].

    Общая форма инициализации массива:

    тип  имя_массива[размер1] * [размерN] = {список_значений};

    В список_значений входят константы, разделенных запятыми. Типы констант должны быть совместимыми с типом массива.

    Пример инициализации одномерного массива:

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

    При этом A[0] = 1, A[1] = 2 и т.д.

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

    Пример инициализации двухмерного массива:

    int  MN[3][4] = {
    {1, 2, 3, 4},
    {5, 6, 7, 8},
    {9, 10, 11, 12}
    };

    Массив MN[3][4] – это матрица, у которой 3 строки и 4 столбца.

    Для многомерных массивов инициализацию можно также проводить с указанием номера инициализируемого элемента.

    Пример инициализации трехмерного массива:

    int  XYZ[2][3][4] = {
    { {1, 2, 3, 4}, {5, 6, 7, 8}, {9, 10, 11, 12} },
    { {13, 14, 15, 16}, {17, 18, 19, 20}, {21, 22, 23, 24} }
    };

    Как видно, массив XYZ содержит два блока, каждый из которых есть матрица размера 3 $$\times$$ 4, т.е. 3 строки и 4 столбца.

    В языке С возможна инициализация безразмерных массивов. Например, для одномерного массива:

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

    В многомерном массиве размер самого левого измерения также можно не указывать. В частности, для инициализации массива MN[3][4] допустима следующая запись:

    int  MN[][4] = {
    {1, 2, 3, 4},
    {5, 6, 7, 8},
    {9, 10, 11, 12}
    };

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

    int  XYZ[][3][4] = {
    {
    {1, 2, 3, 4},
    {5, 6, 7, 8},
    {9, 10, 11, 12}
    },
    {
    {13, 14, 15, 16},
    {17, 18, 19, 20},
    {21, 22, 23, 24}
    }
    };

    Вывод трехмерного массива на консоль (дисплей) можно выполнить по следующей программе:

    #include <stdio.h>
    #include <conio.h>
    
    int main (void) {
    	int i, j, k;
    	int  XYZ[][3][4] = {
    { {1, 2, 3, 4}, {5, 6, 7, 8}, {9, 10, 11, 12} }, // 1-й 
    { {13, 14, 15, 16}, {17, 18, 19, 20}, {21, 22, 23, 24} } }; // 2-й 
    	for (i = 0; i < 2; ++i) { printf("\n");
    		for (j = 0; j < 3; ++j) { printf("\n");
    			for (k = 0; k < 4; ++k)
    				printf(" %3d", XYZ[i][j][k]);
    		}
    	}
    	printf("\n\n Press any key: ");
    	_getch();
    	return 0;
     }

    Практическая часть

    Пример 1. Напишите программу заполнения одномерного массива случайными числами из интервала от 1 до 15 по случайному равномерному закону. Отсортировать массив случайных чисел по возрастанию.

    Для решения поставленной задачи применим сортировку методом прямого выбора [5.3]. Алгоритм сортировки заключается в следующем:

  • В исходной последовательности из N элементов отыскивается элемент с наименьшим ключом.
  • Он меняется местами с первым элементом.
  • В оставшейся последовательности из ( N –1) элементов отыскивается минимальный элемент и меняется местами со вторым элементом и т.д., пока не останется один, самый большой элемент.
  • Программный код решения примера:

    #include <stdio.h>
    #include <conio.h>
    #include <time.h>
    #include <stdlib.h>
    #define Left 1
    #define Right 15
    #define N 10
    
    int main (void) {
    float R, r, min;
    float A[N];
    
    int i, j, k;	
    unsigned int some;
    long int L;
    
    L = (long) time(NULL); // Системное время
    some = (unsigned) L; // Приведение типов
    srand(some); // Задание исходного случайного числа для rand()
    printf("\n\t The initial array of random numbers in the interval [%d, %2d]\n", Left, Right);
    
    for (i = 0; i < N; ++i)
    {// Случайное число из интервала [0,1]
    r = (float) rand()/RAND_MAX;
    // Формирование случайного числа из заданного интервала
    R = Left + (Right - Left) * r;
    // Заполнение массива случайными числами
    A[i] = R; }
    
    // Печать элементов исходного массива
    for (i = 0; i < N; ++i)
    printf("\n\t %5d) %10.4f", i + 1, A[i]);
    
    // Сортировка методом выбора
    for (i = 0; i < (N - 1); ++i)
     {
     min = A[i]; k = i;
    for (j = i + 1; j < N; ++j)
    if (A[j] < min) { k = j; min = A[k]; }
    A[k] = A[i]; A[i] = min;
    }
    
    // Печать отсортированного массива по возрастанию
    printf("\n\n\t Sort an array:\n");
    for (i = 0; i < N; ++i)
    printf("\n\t %5d) %10.4f", i + 1, A[i]);
    
    	printf("\n\n Press any key: ");
    	_getch();
    	return 0;
    }

    Возможный результат выполнения программы показан на рис 5.1.

    (рис 5.1) Сортировка одномерного массива по возрастанию

    В программе использованы директивы препроцессора для задания левой границы ( #define Left 1 ), правой границы ( #define Right 15 ) и размера одномерного массива ( #define N 10 ). Включены дополнительные библиотеки time.h – для обращения к функциям системного времени, stdlib.h – для обращения к функциям генерации псевдослучайных чисел.

    Задание 1

  • Границы формирования случайных чисел и размерность массива задайте без препроцессорных директив.
  • Произведите сортировку массива случайных чисел по убыванию.
  • Введите границы интервала формирования случайных чисел по правилу: Left = X, Right = 3*X, где Х – номер компьютера, за которым выполняется лабораторная работа. Произвести сортировку по убыванию.
  • Найдите и изучите сортировку методом прямого обмена (сортировка методом пузырька). Напишите программу формирования одномерного массива случайными числами и их сортировки методом пузырька.
  • Пример 2. Напишите программу поиска максимального элемента в заданном одномерном массиве. Элементы массива являются целыми числами. Программный код решения примера:

    #include <stdio.h>
    #include <conio.h>
    
    int main (void){
    	int i, size, max;
    	int A[ ] = {3, 5, 2, 8, 12, 0, -7, -3, -21};
    size = sizeof(A)/sizeof(A[0]);
    printf("\n\t The dimention of the array A is equal to: %d\n", size);
         	max = A[0]; // Предполагаемый максимум
    	for (i = 0; i < size; ++i)
    		if (A[i] > max) max = A[i]; 
    
    printf("\n\t Maximum array element: %d\n", max);
    
    	printf("\n\n Press any key: ");
    	_getch();
    	return 0; }

    В программе использована инициализация безразмерного массива и определения его размерности с помощью функции sizeof().

    Результат выполнения программы показан на рис 5.2.

    (рис 5.2) Определение максимального элемента массива

    Задание 2

  • В программе предусмотрите вывод на консоль значений массива в виде строки.
  • Определите индекс максимального элемента.
  • Определите минимальный элемент массива и его индекс.
  • Напишите программу перемещения максимального и минимального элементов на края массива. Например, максимальный элемент поместить в конец массива, а минимальный – в начало массива. Выведите на консоль исходный массив данных и преобразованный массив в виде двух строк, следующих друг за другом.
  • Напишите программу ввода целых чисел массива с клавиатуры и определения максимального и минимального элементов в нем. Количество вводимых чисел задается по соотношению 5*Х, где Х – номер компьютера, за которым выполняется лабораторная работа.
  • Пример 3. Напишите программу циклической перестановки чисел заданного массива так, чтобы i -e число стало ( i +1)-м, а последнее число – первым. Выведите на дисплей исходный массив и преобразованный.

    Программный код решения примера:

    #include <stdio.h>
    #include <conio.h>
    #define N 55
    
    int main (void) {
    	int i, j, k;
    	double D[ ] = {1.23, 2.34, 3.45, 4.56, 5.67, 6.78};
    	double B[N]; // Заведомо больший размер, чем у массива D
    	// Обнуление массива и  выделение памяти для него
    	for (i = 0; i < N; ++i)
    		B[i] = 0.0;
    
    k = sizeof(D)/sizeof(D[0]);
    
    B[0] = D[k-1];
    for (i = 0; i < (k - 1); ++i)
    B[i+1] = D[i];
    
    printf("\n\t The original array:\n");
    for (i = 0; i < k; ++i)
    printf("%8.2f", D[i]); 
    
    printf("\n\n\t The reconfigured array:\n");
    for (j = 0; j < k; ++j)
    printf("%8.2f", B[j]);
    
    	printf("\n\n Press any key: ");
    	_getch();
    	return 0;
     }

    Результат выполнения программы показан на рис 5.3.

    (рис 5.3) Пример циклической перестановки элементов числового массива

    Задание 3

  • Выведите на дисплей (для проверки) два элемента массива В, номера которых превышают размерность массива D.
  • Программным путем подсчитайте сумму элементов массива и выведите ее на консоль.
  • Дополните программу формированием массива, в котором элементы расположены в обратном порядке, чем в исходном массиве.
  • Сформируйте массив кодов строчных букв латинского алфавита. Выведите на дисплей сами буквы и их коды, которые являются элементами массива.
  • Пример 4. В данном одномерном массиве вещественных чисел поменяйте местами элементы, стоящие на нечетных местах, с элементами, стоящими на четных местах. Предусмотрите четность и нечетность размерности массива.

    Для определения четности места в заданном массиве можно использовать операцию деления по модулю, т.е. %.

    Программный код решения примера:

    #include <stdio.h>
    #include <conio.h>
    // Размер массива
    #define n 7 
    
    int main (void) { 
    int i, k;
    // Пример массива
    float A[n] = {1.23F, 2.34F, 3.45F, 4.56F, 5.67F, 6.78F, 7.89F};
    float B[n];// Вспомогательный массив
    
    // Обнуление массива
    	for (i = 0; i < n; ++i)
    		B[i] = 0;
    
    // Распечатка заданного массива
    printf("\n\t\t The original array of dimention n = %d:\n", n);
    	printf("\t");
    	for (i = 0; i < n; ++i)
    		printf("%6.2f", A[i]);
    
    // Распечатка преобразованного массива
    	printf("\n\n\t\t The reconfigured array:\n");
    	for (i = 0; i < n; ++i) { 
    k = i % 2; // Для определения четности индекса массива
    if (k == 0  i < n - 1 )
     B[i] = A[i + 1]; 
    else if (k != 0  i > 0 )
    B[i] = A[i-1];
    else if (k == 0  i < n)
    B[i] = A[i]; }  printf("\t");
    	
         for (i = 0; i < n; ++i)
    		printf("%6.2f", B[i]);
    
    	printf("\n\n Press any key: ");
    	_getch();
    	return 0; }

    При инициализации массива каждый его элемент снабжен суффиксом F.

    Результат выполнения программы показан на рис 5.4.

    (рис 5.4) Смена четных и нечетных мест чисел массива

    Задание 4

  • Вывод на дисплей пользователя исходного массива и преобразованного выполните без дополнительного массива.
  • В программу внесите изменения, чтобы не использовать целочисленную переменную для определения остатка от деления
  • Проверьте программу для массива с четным количеством элементов.
  • Напишите программу без использования операции определения остатка от деления, т.е. без использования операции i % 2.
  • Напишите программу для двухмерного массива вещественных чисел. Смену мест элементов (четных с нечетными) предусмотрите в каждой строке матрицы. Размер матрицы примите равным n $$\times$$ m, где n = 2*X
  • Указание: Можно предусмотреть определение остатка от деления, как индекса строки, так и индекса столбца и суммы индексов строки и столбца.

    Пример 5. Напишите программу заполнения квадратной матрицы (заданного размера n > 2) по спирали натуральными числами начиная с левого верхнего угла (принимая его за номер 1) и двигаясь по часовой стрелке.

    Образец заполнения:

    Программный код решения примера:

    #include <stdio.h>
    #include <conio.h>
    #define n 13
    
    int main(void) {
    	int i = 1, j, k;
    	int p = n/2;
    	int A[n][n];
    
    	// Обнуление матрицы
    	for (j = 0; j < n; ++j)
    		for (k = 0; k < n; ++k)
    			A[j][k] = 0;
    
    printf("\n\t Spiral matrix of dimention (%d x %d):\n", n, n);
    
    	for (k = 1; k <= p; k++)  // Число спиралей
    	{
    		// Верхний горизонтальный столбец
    		for (j = (k-1); j < (n-k+1); j++)
    			A[(k-1)][j] = i++;
    
    		// Правый верхний столбец
    		for (j = k; j < (n-k+1); j++) 
    			A[j][n-k] = i++;
    
    		// Нижний горизонтальный столбец
    		for (j = (n-k-1); j >= (k-1); --j) 
    			A[n-k][j] = i++;
    
    		// Левый верхний столбец
    		for (j = (n-k-1); j >= k; j--) 
    			A[j][(k-1)] = i++;
    	}
    	if ( n % 2 ) 
    		A[p][p] = n*n;
    
    	// Распечатка матрицы
    	for (i = 0; i < n; ++i)
    		for (j = 0; j < n; ++j)
    		{
    			printf("%5d", A[i][j]);
    			if (j == (n-1))
    				printf("\n");
    		}
    
    		          printf("\n Press any key: ");
    		_getch();
    		return 0;
    }

    Результат выполнения программы показан на рис 5.5.

    (рис 5.5) Заполнение матрицы по спирали

    Задание 5

  • Дополните программу расчетом количества четных элементов выше главной диагонали матрицы и количества нечетных элементов ниже главной диагонали, не включая саму диагональ.
  • Протестировать программу при изменении размера матрицы от 3 до 12. Результаты вставьте в отчет лабораторной работы.
  • Напишите программу заполнения матрицы по спирали против часовой стрелки, начиная с верхнего левого угла (номер 1).
  • Напишите программу с вводом направления заполнения матрицы. Например, если будет введено число 1, то заполнение выполните по часовой стрелке, если введено число –1, – против часовой стрелки.
  • Напишите программу заполнения прямоугольной матрицы натуральными числами "змейкой" – построчно: слева – направо – справа – налево и т.д.
  • Пример 6. Каждый день производятся замеры некоторых величин (вещественных значений), причем значения этих величин сводятся в прямоугольную таблицу размера n $$\times$$ m. Составьте многомерный массив данных за 30 дней. Формирование данных произвести по случайному равномерному закону из интервала от –12 до 21.

    Этот пример относится к определению трехмерного массива данных. Программный код решения примера:

    #include <stdio.h>
    #include <conio.h>
    #include <time.h>
    #include <stdlib.h>
    
    #define n 6
    #define m 7
    #define N 30
    
    const int Left = -12; // Левая граница
    const int Right = 21; // Правая граница
    
    int main (void) 
    {
    	float R, r;
    	float A[N][n][m];
    
    	int i, j, k;	
    
    	// Инициализация генератора случайных чисел
    	srand((unsigned) time(NULL)); 
    
    	printf("\n\t The values of every 10 days from 30 days:");
    
    	// Формирование данных за 30 дней
    	for (k = 0; k < N; ++k)
    		for (i = 0; i < n; ++i)
    			for (j = 0; j < m; ++j)
    			{	r = (float) rand()/RAND_MAX;
    	R = Left + (Right - Left)*r;
    	A[k][i][j] = R;
    	}
    
    	// Печать данных за каждый 10-й день
    	for (k = 0; k < N; k += 10) { printf("\n");
    	for (i = 0; i < n; ++i) { printf("\n");
    	for (j = 0; j < m; ++j)
    		printf("%10.4f", A[k][i][j]);
    	}
    	}
    
    	printf("\n Press any key: ");
    	_getch();
    	return 0; 
    }

    В программе используется трехмерный массив размера 30 $$\times$$ 6 $$\times$$ 7. Это означает, что прямоугольная таблица (массив) данных размера 6 $$\times$$ 7 как бы скрепляется 30 раз – по заданному числу дней. Границы случайных чисел определены с помощью спецификатора const.

    Возможный результат выполнения программы показан на рис 5.6.

    (рис 5.6) Вывод данных за каждый 10-й день

    Задание 6

  • В программе сделайте предварительное обнуление трехмерного массива.
  • Измените тип вещественных данных float на тип double. Проверьте работу программы.
  • Найдите максимальный и минимальный элементы массивов.
  • Найдите абсолютные значения максимального и минимального элементов всего трехмерного массива.
  • Выполните заполнение трехмерного массива размера 3 $$\times$$ 5 $$\times$$ 7 натуральными числами и выведите на консоль полученный многомерный массив.
  • Пример 7. Напишите программу по перемножению двух матриц А и В с размерностями ( m $$\times$$ r ) и ( r $$\times$$ n ) соответственно. Матрицу А примите размером 4 $$\times$$ 5, матрицу В – размером 5 $$\times$$ 3 (обе целочисленные).

    Условием перемножения двух матриц А и В является равенство числа столбцов матрицы А и числа строк матрицы В. Если первая матрица А имеет размер m $$\times$$ r, то вторая матрица В должна иметь размер r $$\times$$ n. В результате перемножения получим матрицу С размера m $$\times$$ n. Приведем следующую схему по размерностям:

    Поэлементное перемножение двух матриц в стандартной математической форме имеет следующий вид:

    $$C_{ij}=\sum^{n}_{k=1}a_{il}*b_{kj}, i=1,2,\cdots,n, j=1,2,\cdots,m.$$

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

    $$C_{ij}=\sum^{n-1}_{k=1}a_{il}*b_{kj}, i=1,2,\cdots,n-1, j=0,1,2,\cdots,m-1.$$

    Программный код решения примера:

    #include <stdio.h>
    #include <conio.h>
    
    #define m 4
    #define r 5
    #define n 3
    
    int main (void) {
    	int i, j, k; // переменные циклов
    	
    	const int A[m][r] = {{1,2,3,4,5},
    	{2,3,4,5,6},
    	{2,2,2,2,2},
    	{3,3,3,3,3}};
    	
    	const int B[r][n] = {{9,8,7},
    	{1,2,3},
    	{4,5,6},
    	{7,8,9},
    	{1,1,1}};
    
    	// Массив под результат произведения двух матриц
    	int C[m][n];
    
    	// Обнуление результирующей матрицы
    	for (i = 0; i < m; i++)
    		for (j = 0; j < n; j++)
    			C[i][j] = 0;
    // Формирование результата произведения двух матриц
    	for (i = 0; i < m; i++)
    		for (j = 0; j < n; j++)
    			for (k = 0; k < r; k++)
    				C[i][j] = C[i][j] + A[i][k]*B[k][j];
    	
    // Распечатка результата произведения двух матриц
    	printf("\n 1) Index: \"ijk\".  Matrix (%dx%d):\n", m, n);
    	for (i = 0; i < m; i++) {
    		printf("\n");
    		for (j = 0; j < n; j++)
    			printf(" %4d", C[i][j]);
    	}
    			
    	printf("\n\n ... Press any key: ");
    	_getch();
    	return 0;
    }

    В программе используются три цикла по формированию произведения двух матриц. Первый цикл (переменная i ) связан с количеством строк первой матрицы (матрицы А ), второй цикл (переменная j ) связан с количеством столбцов второй матрицы (матрица В ), третий цикл (переменная k ) связан со смежной размерностью матриц, которая исчезает в результирующей матрице С. Матрицы А и В определены как неизменяемы типы ( const int ). Приведенный программный метод можно назвать как первый метод, метод "ijk".

    Результат выполнения программы показан на рис 5.7.

    (рис 5.7) Результат произведения двух матриц

    Задание 7

  • Измените программу, чтобы циклы были организованы в порядке "jki". Это можно назвать вторым методом программного перемножения двух матриц.
  • Измените программу, чтобы циклы были организованы в порядке "kji". Это можно назвать третьим методом программного перемножения двух матриц.
  • Примените тип float для заданных матриц. Предусмотрите форматированный вывод результата на консоль.
  • Контрольные вопросы

  • Как организуются многомерные числовые массивы в языке С?
  • Как организуется индексирование числовых массивов в языке С?
  • На кого или на что возлагается контроль границ числовых массивов в языке программирования С?
  • В какой очередности и как происходит заполнение многомерных числовых массивов в программах на языке С?
  • Для чего применяется начальная инициализация числовых массивов при дальнейшем их использовании?
  • Сколько потребуется операторов цикла для вывода на консоль двухмерного числового массива (матрицы чисел)?
  • Почему при определении размерности массива с помощью препроцессорной директивы define не используется точка с запятой после числового значения?
  • Вернуться к учебному плану