- •Введение
- •1. ТИПЫ ДАННЫХ И ОПЕРАТОРЫ
- •1.1. Переменные и базовые типы данных
- •1.2. Операции и выражения
- •1.3. Символические константы
- •1.5. Несколько слов о функции main()
- •2. ВВОД И ВЫВОД В СИ
- •2.2. Форматный ввод-вывод
- •3. ЦИКЛЫ И ОПЕРАТОРЫ СРАВНЕНИЯ
- •3.1. Условный оператор
- •3.2. Оператор выбора switch
- •3.3. Операторы цикла
- •3.4. Операторы break и continue
- •3.5. Примеры
- •3.6. Вычисление значений элементарных функций
- •3.7. Задачи
- •4. ОБРАБОТКА ПОСЛЕДОВАТЕЛЬНОСТЕЙ
- •4.1. Примеры
- •4.2. Задачи
- •5. ОДНОМЕРНЫЕ МАССИВЫ
- •5.1. Начальные сведения о массивах
- •5.2. Примеры работы с массивами
- •5.3. Задачи
- •6. МНОГОМЕРНЫЕ МАССИВЫ
- •6.1. Определение и инициализация двумерных массивов
- •6.2. Примеры с двумерными массивами
- •6.3. Задачи
- •7. УКАЗАТЕЛИ И МАССИВЫ
- •7.1. Указатели и адреса
- •7.2. Указатели и аргументы функций
- •7.3. Указатели и массивы
- •7.4. Операции с указателями
- •7.5. Указатели с типом void
- •7.6. Модификатор const
- •7.7. Массивы переменного размера
- •7.8. Массивы указателей
- •7.9. Двумерные массивы переменного размера
- •8. СИМВОЛЫ И СТРОКИ
- •8.1. Представление символьной информации в ЭВМ
- •8.2. Библиотека обработки символов
- •8.3. Строки в языке Си
- •8.4. Функции обработки строк
- •8.5. Функции преобразования строк
- •8.6. Примеры работы со строками
- •8.7. Разбиение строки на лексемы
- •8.8. Задачи
- •9. СТРУКТУРЫ
- •9.1. Основные сведения о структурах
- •9.2. Объединения
- •10. ДИРЕКТИВЫ ПРЕПРОЦЕССОРА
- •10.1. Директива #include
- •10.2. Директива #define
- •10.3. Директива #undef
- •10.4. Условная компиляция
- •11. ФУНКЦИИ
- •11.1. Основные сведения о функциях
- •11.2. Прототипы функций
- •11.3. Классы памяти
- •11.4. Указатели на функции
- •11.5. Рекурсия
- •11.6. Примеры с использованием рекурсии
- •11.7. Метод «разделяй и властвуй»
- •11.8. Задачи на применение рекурсии
- •12. РАБОТА С БИТАМИ ПАМЯТИ
- •12.1. Битовые операции
- •12.2. Примеры с использованием битовых операций
- •12.3. Задачи
- •13. РАБОТА С ФАЙЛАМИ
- •13.1. Файлы и потоки
- •13.2. Текстовые файлы
- •13.3. Двоичные файлы
- •13.4. Шифрование файлов
- •13.5. Задачи на текстовые файлы
- •13.6. Задачи на двоичные файлы
- •14. СТРУКТУРЫ ДАННЫХ
- •14.1. Односвязные списки
- •14.2. Примеры работы с односвязными списками
- •14.3. Задачи на односвязные списки
- •14.4. Стеки, очереди
- •14.5. Задачи на стеки и очереди
- •14.6. Двусвязные списки
- •14.7. Задачи на двусвязные списки
- •14.8. Бинарные деревья
- •14.9. Примеры с использованием бинарных деревьев
- •14.10. Задачи на бинарные деревья
- •Приложение 1. АЛГОРИТМЫ ПОИСКА
- •1. Линейный поиск
- •2. Поиск с барьером
- •3. Двоичный поиск
- •Приложение 2. АЛГОРИТМЫ СОРТИРОВКИ
- •Несколько слов о сложности алгоритмов
- •1. Метод прямого выбора
- •2. Метод прямого включения
- •3. Пузырьковая сортировка
- •4. Шейкерная сортировка
- •5. Быстрая сортировка
- •6. Сортировка подсчетом
- •Приложение 3. СОРТИРОВКА ИНДЕКСОВ И УКАЗАТЕЛЕЙ
- •1. Сортировка индексов на основе метода прямого выбора
- •2. Сортировка индексов на основе пузырьковой сортировки
- •3. Сортировка индексов на основе быстрой сортировки
- •4. Сортировка двумерных массивов
- •5. Сортировка строк
- •Приложение 4. СОРТИРОВКА ФАЙЛОВ И СПИСКОВ
- •1. Сортировка двоичных файлов
- •2. Сортировка линейных списков
- •Приложение 5. СОРТИРОВКА С УСЛОВИЕМ
- •1. Сортировка с условием на базе пузырьковой сортировки
- •2. Сортировка с условием на базе быстрой сортировки
- •3. Сортировка с условием двоичных файлов
- •4. Сортировка с условием линейного списка на базе пузырьковой сортировки
- •5. Сортировка с условием линейного списка на базе быстрой сортировки
- •ЛИТЕРАТУРА
6.МНОГОМЕРНЫЕ МАССИВЫ
6.1.Определение и инициализация двумерных массивов
Вязыке Си имеется возможность создавать n-мерные массивы. Стандарт ANSI предусматривает, что значение n может быть по крайней мере любым числом в пределах от 1 до 12.
Вданном разделе будут рассматриваться двумерные массивы. Такие массивы описываются следующим образом:
int a[10][20];
То есть тем самым определен двумерный массив целых чисел a, состоящий из 10 строк и 20 столбцов. Каждый элемент двумерного массива однозначно определяется парой индексов i и j: a[i][j], где i
– номер строки, а j – номер столбца. Как и в случае одномерных массивов, нумерация по переменным i и j начинается с нуля: a[0][0] – элемент, находящийся на пересечении нулевой строки и нулевого столбца… a[9][19] – элемент, находящийся на пересечении девятой строки и девятнадцатого столбца.
Двумерный массив можно инициализировать при его объявлении:
int a[3][2] = {{1, 2}, {3, 4}, {5, 6}};
При этом значения в фигурных скобках группируются по строкам. Если дополнительные фигурные скобки не присутствуют, то числа распределяются по строкам: сначала заполняется нулевая строка, затем первая и т.д. Например, массив a можно инициализировать и так:
int a[3][2] = {1, 2, 3, 4, 5, 6};
Если для какой-либо строки указано недостаточно значений, то оставшиеся значения данной строки инициализируются нулями. Например, если матрицу a инициализировать таким образом:
int a[3][2] = {{1}, {3}, {5}};
тогда второй столбец данной матрицы будет содержать нулевые значения:
1 |
0 |
3 |
0 |
5 |
0 |
72
Если необходимо все элементы матрицы инициализировать нулями, то для этого инициализацию достаточно просто записать следующим образом:
int a[10][20] = {0};
6.2. Примеры с двумерными массивами
Пример 1. Инициализация и вывод двумерного массива на экран.
#include<stdio.h>
#include<stdlib.h>
#include<time.h>
#define M 5 /* Число строк матрицы */ #define N 10 /* Число столбцов матрицы */
/* заполнение матрицы случайными числами от 0 до 99 */ void Init(int a[][N], int m, int n)
{
int i, j;
for (i = 0; i < m; i++) for (j = 0; j < n; j++)
a[i][j] = rand()%100;
}
/* вывод матрицы на экран */ void Print(int a[][N], int m, int n)
{
int i, j;
for (i = 0; i < m; i++)
{
for (j = 0; j < n; j++) printf("%5d", a[i][j]);
printf("\n");
}
}
73
int main() |
|
{ |
|
int a[M][N]; |
/* матрица размера M на N */ |
srand(time(NULL)); |
|
Init(a, M, N); |
/* инициализация элементов матрицы */ |
Print(a, M, N); |
/* вывод матрицы на экран */ |
return 0; |
|
} |
|
При этом заметим очень важный момент. Массив int a[M][N] является одномерным массивом размера M, каждый элемент которого является целочисленным массивом размерности N, то есть каждый из M элементов массива a имеет тип int a[N]. В соответствии с данной логикой элемент a[0] является одномерным массивом, представляющим первую строку матрицы a. Поэтому a[i] – одномерный массив, представляющий i-ую строку матрицы a.
Двумерное представление это всего лишь удобный способ работы с массивом, используя два индекса – номер строки и номер столбца. В памяти компьютера такой двумерный массив хранится последовательно в виде одномерного массива: сначала следует первая строка матрицы, затем вторая и т.д.
Таким образом, с каждой строкой матрицы a можно работать как с обычным одномерным массивом, что иллюстрируется в следующей программе.
#include<stdio.h>
#include<stdlib.h>
#define M 5 /* Число строк матрицы */ #define N 10 /* Число столбцов матрицы */
/* заполнение одномерного массива a случайными числами */ void Init(int *a, int n)
{
int i;
for (i = 0; i < n; i++) a[i] = rand()%100;
}
74
/* вывод одномерного массива a на экран */ void Print(int *a, int n)
{
int i;
for(i = 0; i < n; i++) printf("%5d", a[i]);
putchar('\n');
}
int main()
{
int i, a[M][N]; /* матрица размера M на N */
/* инициализация каждой строки матрицы: */ for (i = 0; i < M; i++)
Init(a[i], N);
/* вывод элементов матрицы построчно: */ for (i = 0; i < M; i++)
Print(a[i], N); return 0;
}
Пример 2. Пусть имеется двумерный массив int a[M][N] и одномерный массив int b[MN]. Требуется элементы массива b построчно записать в матрицу a.
Для решения данной задачи учтем важное замечание из предыдущего примера. Если представить двумерный массив a[M][N] в виде последовательно расположенных одномерных массивом размера N, то k-ый элемент (0 ≤ k ≤ MN-1) полученного ‘большого’ одномерного массива соответствует a[k/N][k%N] элементу матрицы, что следует из однозначного разложения числа k по модулю числа N:
k = i*N + j, 0 ≤ j < N.
Поэтому решить задачу можно с помощью такого алгоритма: int k;
for(k = 0; k < M*N; k++) a[k/N][k%N] = b[k];
75
Заметим, что данный алгоритм является действенным, но не практичным, так как используется операция деления в каждом витке итерации. Намного практичнее использовать адресную арифметику в этом случае, о которой речь пойдет в следующей главе. С использованием адресной арифметики алгоритм будет иметь такой вид:
for(k = 0; k < M*N; k++) *(*a + k) = b[k];
где *a – адрес первого элемента матрицы a, *a + k – адрес k-го элемента матрицы, *(*a + k) – значение k-го элемента.
Если же требуется, наоборот, в массив b записать последовательно строки матрицы a, то это делается таким образом:
for (i = 0; i < M; i++) for(j = 0; j < N; j++)
b[i*N + j] = a[i][j];
Если учесть замечание, связанное с эффективностью использования адресной арифметики, то данный алгоритм можно записать в такой форме:
for(k = 0; k < M*N; k++) b[k] = *(*a + k);
Пример 3. Подсчитать количество строк целочисленной матрицы, упорядоченных по возрастанию.
#include<stdio.h>
#include<stdlib.h> #define M 5 #define N 10
/* проверка одномерного массива на упорядоченность */ int IsSort(int *a, int n)
{
int i;
for(i = 0; i < n && a[i-1] <= a[i]; i++)
;
return (i >= n);
}
76
int main()
{
int a[M][N], i, count;
Init(a, M, N); /* функция из первого примера */ for(i = count = 0; i < M; i++)
if (IsSort(a[i], N)) count++;
printf("count = %d\n", count); return 0;
}
Пример 4. Пусть имеется некоторая квадратная матрица a размера n. Требуется вывести на экран все подматрицы данной матрицы порядка 1, 2,…,n.
#include<stdio.h> #define N 5
/* вывод подматрицы порядка size, левая верхняя вершина которой имеет координаты (i0, j0) в матрице a:
*/
void Print(int a[][N], int i0, int j0, int size)
{
int i, j;
for (i = i0; i < i0 + size; i++)
{
for (j = j0; j < j0 + size; j++) printf("%5d", a[i][j]);
printf("\n");
}
printf("\n");
}
/* функция рассматривает все подматрицы в матрице a */ void SubMatrix(int a[][N], int n)
{
77