Программа должна после запуска на исполнение выводить информацию об авторе, назначении программы (приводится лабораторное задание полностью), перед запросом ввода данных с клавиатуры обязательно должно быть сообщение о типе и количестве вводимых данных
Вид материала | Программа |
СодержаниеТема №7. Двумерные массивы. Файловый (бинарный) ввод-вывод. New_Item(c, i, range1, range2) Примеры функций для работы с двумерным массивом Часть первая Часть вторая |
- Задание для вариантов 1-11, 100.34kb.
- 11. 09. 2008 Практическая работа №1 ms access. Основные приемы работы с данным Задание, 795.97kb.
- Инструкция по эксплуатации и меры предосторожности, 148.38kb.
- Инструкция по эксплуатации и меры предосторожности, 251.98kb.
- Инструкция по эксплуатации и меры предосторожности, 277.51kb.
- Инструкция по эксплуатации и меры предосторожности, 825.93kb.
- Разработка реляционной структуры данных, 255.43kb.
- Р. С. Енгалычев Научный руководитель А. А. Малюк, к т. н., профессор, 26.91kb.
- Любая программа для обработки данных должна выполнять три основных функции: ввод новых, 298.05kb.
- Базы данных, 55.38kb.
Тема №7. Двумерные массивы. Файловый (бинарный) ввод-вывод.
Задание: Задание состоит из нескольких пунктов:
- Заполнить двумерный массив (каждая строка массива заполняется в соответствии с заданием по теме №5).
- Распечатать содержимое массива в виде прямоугольной матрицы.
- Сохранить двумерный массив в бинарный файл.
- Считать содержимое бинарного файла в другой двумерный массив.
- Распечатать содержимое нового массива в виде прямоугольной матрицы.
- Выполнить над каждой строкой нового двумерного массива действия, предусмотренные по теме №5.
- Распечатать преобразованный двумерный массив.
Краткая теоретическая справка
Так же, как и во многих других языках программирования, двумерный массив – это одномерный массив одномерных массивов элементов любого типа. Как и для одномерного массива указывается количество элементов в массиве, например,
int x[5][7]; // Двумерный массив целых чисел (5 строк по 7 элементов)
Для использования одного элемента двумерного массива необходимо указать оба индекса (индекс строки и индекс столбца), например,
x[2][4] = 5; // Четвертому элементу второй строки присваивается 5
Особенным для языка Си является то, что можно получить указатель на одномерный массив, входящий в состав двумерного массива. Например, адрес начала второго массива будет выглядеть так:
x[2].
Тот же самый адрес второго массива можно представить в виде
(x+2).
Здесь x – адрес начала двумерного, а смещение на 2 относительно этого начала дает увеличение адреса на 14 байтов (размер одномерного массива равен 7, а после умножения на 2 и получаем число 14).
Используя оператор разыменования, по адресу массива можно получить обращение ко второй строке двумерного массива
*(x+2).
Ну а теперь, если вспомнить тему № 5, легко получить другой вариант записи присваивания четвертому элементу второй строки значения 5
*(*(x+2)+4) = 5// Четвертому элементу второй строки присваивается 5.
В этой форме записи присутствуют два оператора разыменования. Поэтому часто в отношении переменной x (двумерного массива) говорят, что это указатель на указатель, хотя это утверждение требует некоторых пояснений, которые будут даны ниже, при рассмотрении примеров обработки двумерных массивов.
Для использования всего того, что сделано в работе по теме №5, можно просто копировать функции из файла решения задачи по теме №5, а можно использовать другой прием. Алгоритм следующий:
- Создадим новый проект, например, с именем 7.
- Копируем в рабочий каталог проекта 7 файл с решением задачи по теме №5, но с именем 5.h.
- Редактируем файл 5.h, оставляя только заголовки функций, например, получится следующее содержимое
// 1. Инициализация генератора случайных чисел
void Randomize();
//2. Генератор случайного числа в диапазоне от 0 до range
int Random(int range);
// 3. Проверка повторения случайного числа
// c - указатель начала массива, n - индекс нового элемента
int Test_Repetition(int *c, int n);
// 4. Добавить новый элемент
// с - указатель начала массива, n - номер нового элемента
// range1, range2 – левая и правая границы диапазона
void New_Item(int *c, int n, int range1, int range2);
// 5. Заполнение одномерного массива
// c - массив, n - количество элементов
void Filling (int *c, int n, int range1, int range2);
// 6. Распечатка одномерного массива
void Print(int *c, int n);
// 7. Поиск минимального значения
// с – указатель на начало одномерного массива,
// n - количество элементов массива,
// index – указатель на индекс минимума
int Min(int *c, int n, int *index);
// 8. Создание дубликата массива в динамической памяти
int * Copy (int *c, int n);
// 9. Обмен элементов местами в массиве
void Exchange (int *c, int n, int k);
// 10 .Сортировка методом прямого поиска
void SearchSort ( int *c, int n );
- Копируем в рабочий каталог проекта 7 файл с решением задачи по теме №5, но с именем 5.c.
- Редактируем файл 5.c и получаем
#include
#include
#include
#include
// 1. Инициализация генератора случайных чисел
void Randomize()
{
srand(time(0));
}
//2. Генератор случайного числа в диапазоне от 0 до range
int Random(int range)
{
return (rand() % range);
}
// 3. Проверка повторения случайного числа
// c - указатель начала массива, n - индекс нового элемента
int Test_Repetition(int *c, int n)
{ int x, j;
x = 0; // Считаем, что значение новое
// Цикл сравнения со всеми предыдущими
for (j=0; j
if (*(c+n)==*(c+j))
{
x = 1; // Элементы совпали
break;
}
return x;
}
// 4. Добавить новый элемент
// с - указатель начала массива, n - номер нового элемента
// range1, range2 – левая и правая границы диапазона
void New_Item(int *c, int n, int range1, int range2)
{ int x = 0; // Элементы массива разные
do
{
c[n] = Random(range2 - range1) + range1; // Новое значение
x =Test_Repetition(c,n); // Проверка на повторение
}
while (x==1); // Повторять, когда элементы совпали
}
// 5. Заполнение одномерного массива
// c - массив, n - количество элементов
void Filling (int *c, int n, int range1, int range2)
{ int i;
c[0]=Random (range2 - range1) + range1; // Элемент с индексом 0.
// Цикл заполнения массива
for (i=1;i
New_Item(c, i, range1, range2);
}
// 6. Распечатка одномерного массива
void Print(int *c, int n)
{ int i;
for (i=0; i
printf("%4d",c[i]);
puts(""); // Переход на новую строку
}
// 7. Поиск минимального значения
// с – указатель на начало одномерного массива,
// n - количество элементов массива,
// index – указатель на индекс минимума
int Min(int *c, int n, int *index)
{ int i, min;
min = c[0]; // Начальное значение минимума
(*index) = 0; // Начальное значение индекса минимума
for ( i=1; i
if (c[i]
{
min = c[i];
(*index) = i;
}
return min;
}
// 8. Создание дубликата массива в динамической памяти
int * Copy (int *c, int n)
{ int *m, i;
// Резервирование памяти под дубликат массива
m = (int*)malloc(sizeof(int)*n);
// Копирование массива
for ( i = 0; i
// Функция возвращает адрес нового массива
return m;
}
// 9. Обмен элементов местами в массиве
void Exchange (int *c, int n, int k)
{ int tmp; //Переменная для временного хранения данных
tmp = c[n];
c[n] = c[k];
c[k] = tmp;
}
// 10 .Сортировка методом прямого поиска
void SearchSort ( int *c, int n )
{ int i, min, indexMin; int *p;
for ( i=0; i
{ p = (c+i); // Задать адрес начала массива
min = Min (p, n-i, &indexMin); // Найти минимум в массиве
// Обменять местами минимальный элемент с первым
Exchange ( p, 0, indexMin ); }
}
- Включаем файлы 5.h и 5.c в проект.
- Копируем в рабочий каталог проекта 7 файл с решением задачи по теме №5, но с именем main.c. Редактируем файл к следующему виду (обратите внимание на стрку #include “5.h”)
#include
#include
#include
#include "5.h"
#define N 15
inline void Rus() // Русификация вывода в консольное окно
{
setlocale( LC_CTYPE, ".1251" );
}
void Title()
{
puts("Лабораторная работа №5");
puts("Задание:");
puts("1. Заполнить массив неповторяющимися числами");
puts(" в диапазоне от -30 до +70.");
puts("2. Распечатать одномерный массив.");
puts("3. Найти минимальное значение в массиве и его индекс.");
puts("4. Сделать дубликат массива в динамической памяти.");
puts("5. Провести сортировку массива-дубликата методом");
puts(" прямого поиска");
puts("6. Распечатать результат сортировки.");
puts("7. Удалить дубликат из динамической памяти.\n");
}
// Главная функция
int main()
{
int m[N];
int min, IndexMin;
int *Duplicate;
Rus();
Randomize();
Title();
puts("\nЗаполнение массива");
Filling(m,N,-30,70);
puts("\nРаспечатка массива\n");
Print (m,N);
puts("\nМинимальное значение массива\n");
min = Min(m,N,&IndexMin);
printf("min = %d его индекс = %d \n", min, IndexMin);
puts("\nСоздание дубликата массива\n");
Duplicate = Copy(m,N);
puts("\nРаспечатка дубликата массива\n");
Print (Duplicate,N);
puts("\nСортировка дубликата методом прямого поиска");
SearchSort(Duplicate,N);
puts("\nРаспечатка дубликата массива после сортировки\n");
Print (Duplicate,N);
puts("\nУдаление дубликата");
free(Duplicate);
return 0;
}
- Компилируем и запускаем эту программу на исполнение, чтобы убедиться, что все преобразования сделаны правильно.
Теперь в любой программе можем использовать функции обработки одномерных массивов, подключив в программу подготовленные файлы 5.h и 5.c, что и сделаем в следующих примерах.
Примеры функций для работы с двумерным массивом
Примеры функций рассмотрим в двух частях: в первой части будем использовать «традиционную» форму обращения к элементу двумерного массива (используя квадратные скобки), а во второй части – «комбинированную» (обращение через адреса и традиционно).
Часть первая («традиционная» форма использования обращения к элементу двумерного массива).
Двумерный массив декларируем в виде
int x[R][C];
где константами R и C обозначим количество строк и столбцов массива, соответственно.
// 1. Заполнение двумерного массива
// m – адрес начала двумерного массива,
// Nr - количество строк в двумерном массиве
void Filling_2 ( int m[][C], int Nr, int range1, int range2)
{ int i;
for ( i = 0; i
{ //… заполняем одномерный массив
Filling(m[i], C, range1, range2);
}
}
Обратите внимание на то, что при передаче в качестве параметра массива m количество строк не указано. Но при этом обязательным будет указание размера одно строки ([C]). Другая особенность данного примера состоит в том, что при вызове на исполнение функции заполнения одномерного массива, в нее в качестве параметра передается одна строка двумерного массива (m[i]).
// 2. Распечатка двумерного массива
// m - адрес начала двумерного массива,
//Nr - окличество строк
void Print_2 ( int m[][C], int Nr)
{ int i;
for ( i = 0; i
{
Print (m[i],С);
printf("\n");
}
}
Распечатка двумерного массива дополнительных комментариев не требует, так как все особенности описаны в комментариях к предыдущей функции.
// 3. Сохранение массива в файл
// m - адрес начала двумерного массива,
// Nr - окличество строк
// Name - имя файла
void Save( int m[][C], int Nr, char Name[])
{ FILE *out; int i;
// Открыть файл для записи.
out = fopen(Name,"wb");
// Проверить: файл открыт?
if (out != NULL)
{ // Для каждой строки двуменого массива...
for (i=0; i
// ... записать строку массива в файл.
fwrite ( m[i], 1, C*sizeof(int), out );
// Закрыть файл.
fclose(out);
}
else puts("\nОшибка открытия файла для записи");
}
Для сохранения массива в файл потребуется файловая переменная out, являющаяся указателем на библиотечную структуру FILE.
Файл открывается для записи функцией fopen, в которую в качестве параметров передаются две строки: первая – имя файла, вторая – режим доступа к файлу. В строке режима доступа к файлу две буквы: w – означает запись (write), b – тип файла (бинарный, то есть двоичный – binary). Функция fopen возвращает указатель на структуру типа FILE, если открытие файла прошло успешно, или NULL, если произошла ошибка.
Запись в файл проводится функцией fwrite. Эта функция принимает четыре параметра: первый – нетипированный указатель на блок данных (в качестве фактического параметра может быть указатель любого типа, в данном примере – это адрес начала одномерного массива), второй параметр – количество блоков данных для записи, третий – размер одного блока данных (в примере вычисляется по размеру одномерного массива), четвертый – файловая переменная.
Обязательным действием по завершении записи в файл всех блоков данных служит закрытие файла функцией fclose, которая в качестве параметра принимает файловую переменную.
// 4. Загрузка массива из файла
// m - адрес начала двумерного массива,
// Nr - окличество строк
// Name - имя файла
void Load( int m[][C], int Nr, char Name[])
{ FILE *in; int i;
// Открыть файл для записи.
in = fopen(Name,"rb");
// Проверить: файл открыт?
if (in!=NULL)
{ // Для каждой строки двуменого массива...
for (i=0; i
// ... записать строку массива в файл.
fread(m[i], 1, C*sizeof(int), in);
// Закрыть файл.
fclose(in);
}
else puts("\nОшибка открытия файла для чтения");
}
При чтении данных из файла меняется режим доступа к файлу (rb) и используется функция чтения из файла fread, параметры которой полностью совпадают с параметрами функции fwrite.
Для проверки работоспособности приведенных функций была использована приведенная ниже программа. Не забудьте после главной функции добавить представленные ранее функции Filling_2, Print_2, Save и Load.
#include
#include
#include
#include "5.h"
#define R 5
#define C 7
// Предварительное описание функций
void Filling_2(int m[][C], int Nr, int range1, int range2);
void Print_2(int m[][C], int Nr);
void Save( int m[][C], int Nr, char Name[]);
void Load( int m[][C], int Nr, char Name[]);
// Главная функция
int main()
{
int m[R][C], x[R][C];
Randomize();
Title();
puts("\nЗаполнение двумерного массива");
Filling_2(m,R,-30,70);
puts("\nРаспечатка двумерного массива\n");
Print_2 (m,R);
puts("\nСохранить содержимое массива в файл\n");
Save(m, R, "7.dat");
puts("\nЗагрузить данные из файла в новый массив\n");
Load(x, R, "7.dat");
puts("\nРаспечатка нового двумерного массива\n");
Print_2 (x,R);
return 0;
}
Обратите внимание на то, что функции Filling_2, Print_2, Save и Load могут быть использованы для двумерных массивов с произвольным количеством строк, но каждая строка должна быть строго определенной длины. В этом состоит основной недостаток «традиционной» формы обращения к двумерному массиву.
Часть вторая (обращение к двумерному массиву через указатели).
Двумерный массив декларируем как указатель на указатель
int **x;
Так массив задан через указатель, то необходимо его инициализировать, то есть зарезервировать место под массив в динамической области оперативной памяти.
// 1. Инициализация массива
// Nr - количество строк, Nc - количество столбцов
int ** Init(int Nr, int Nc)
{ int i; int **x;
// Задать массив указателей на строки
x = (int**)calloc ( Nr, sizeof(int*));
// Задать указатели на элементы в строке
for ( i = 0; i
x[i]=(int *)calloc(Nc,sizeof(int));
return x;
}
Инициализация проводится функцией calloc, в которой первым параметром указывается количество элементов, а вторым – размер одного элемента. Как видно из текста программы, сначала задается массив указателей на строки, а потом в каждый из указателей помещаются адреса элементов массива.
// 2. Заполнение массива
// Nr - количество строк, Nc - количество столбцов
void Fill(int **x, int Nr, int Nc)
{ int i,j; static int k=1;
for ( i=0; i
{
for ( j=0; j
x[i][j]=k++;
}
}
Заполнение массива проводим целыми числами, начиная с единицы. Обратите внимание на то, что для переменной k выбран статический класс памяти (инициализация значением 1 будет сделана только при первом вызове функции на исполнение), а также на то, что к элементам массива, на который указывает x, можно обращаться «традиционным» способом.
Ниже приведены функции для распечатки двумерного массива и работы с бинарным файлом. Распечатка проводится циклом в цикле, а сохранение и загрузка данных при работе с файлом – построчно.
// 3. Распечатка массива
// Nr - количество строк, Nc - количество столбцов
void Print (int **x, int Nr, int Nc)
{ int i,j;
printf("\n");
for ( i=0; i
{
for ( j=0; j
printf("%4d",x[i][j]);
printf("\n");
printf("\n");
}
printf("\n");
}
// 4. Сохранение массива в файл
// Nr - количество строк, Nc - количество столбцов
// Name - имя файла
void Save( int **x, int Nr, int Nc, char Name[])
{ FILE *out; int i;
out = fopen(Name,"wb");
if (out != NULL)
{
for ( i=0; i
fwrite (x[i], 1, Nc*sizeof(int), out);
fclose(out);
}
else puts("\nОшибка открытия файла для записи");
}
// 5. Загрузка массива из файла
// Nr - количество строк, Nc - количество столбцов
// Name - имя файла
void Load( int **x, int Nr, int Nc, char Name[])
{ FILE *in; int i;
in = fopen (Name,"rb");
if (in != NULL)
{
for ( i=0; i
fread ( x[i], 1, Nc*sizeof(int), in);
fclose(in);
}
else puts("\nОшибка открытия файла для чтения");
}
Проверка работоспособности функций проводилась приведенной ниже программой. Можете заметить, что функции применимы как для массива размером 4х5 (массивы x и x1), так и для массива размером 2х3 (массивы y и y1).
#include
#include
#include
int ** Init(int Nr, int Nc);
void Fill(int **x, int Nr, int Nc);
void Print (int **x, int Nr, int Nc);
void Save( int **x, int Nr, int Nc, char Name[]);
void Load( int **x, int Nr, int Nc, char Name[]);
inline void Rus() // Русификация вывода в консольное окно
{ setlocale( LC_CTYPE, ".1251" ); }
int main()
{ int **x,**y,**x1,**y1;
Rus();
puts("\nИнициализация массива x\n");
x = Init (4, 5);
puts("\nЗаполнение массива x\n");
Fill(x,4,5);
puts("\nРаспечатка массива x\n");
Print(x,4,5);
puts("\nИнициализация массива y\n");
y = Init (2, 3);
puts("\nЗаполнение массива y\n");
Fill(y,2,3);
puts("\nРаспечатка массива y\n");
Print(y,2,3);
puts("Сохранение массива x в файл");
Save(x,4,5,"x.dat");
puts("\nИнициализация массива x1\n");
x1 = Init (4, 5);
puts("Загрузка массива x1 из файла");
Load(x1,4,5,"x.dat");
puts("\nРаспечатка массива x1\n");
Print(x1,4,5);
puts("Сохранение массива y в файл");
Save(y,2,3,"y.dat");
puts("\nИнициализация массива y1\n");
y1 = Init (2, 3);
puts("Загрузка массива y1 из файла");
Load(y1,2,3,"y.dat");
puts("\nРаспечатка массива y1\n");
Print(y1,2,3);
return 0;
}
Естественно, что при использовании приведенных функций нельзя путать размерность массивов. То есть, если массив x был инициализирован размером 4х5, то в нем нельзя использовать количество строк более 4 и столбцов более 5.
Чтобы было меньше ошибок можно ввести свой тип данных, содержащий в одной структуре не только указатель на указатель, например, такой
typedef struct {int **x; int Nr; int Nc;} TArray;
В этой записи слово typedef зарезервировано для обозначения того, что после его будет описание тип. Введенный тип данных представляет собой структуру (обозначено зарезервированным словом struct), а в фигурных скобках указано, что структура содержит три поля x – указатель на указатель на данные типа int, Nr и Nc – поля целого типа, для хранения информации о количестве строк и столбцов двумерного массива соответственно. TArray – имя типа данных.
Теперь, если задать переменную типа TArray, например,
TArray m;
то можно обратиться отдельно к каждому из полей (x,Nr,Nc) структуры типа TArray, записывая после имени переменной (в данном примере m) знак точка, а потом идентификатор поля (x,Nr,Nc), напрмер,
m.Nr = 4;
m.Nc = 5;
m.x = (int**) calloc ( m.Nr, sizeof(int*) );
Теперь функции для работы с двумерным массивом можно переписать к следующему виду:
// 1. Инициализация массива
// r - количество строк, c - количество столбцов
void Init ( TArray *m, int r, int c)
{ int i;
// Определить размеры массива
(*m).Nr = r; (*m).Nc = c;
// Задать массив указателей на строки
(*m).x = (int**)calloc ( r, sizeof(int*));
// Задать указатели на элементы в строке
for ( i = 0; i
(*m).x[i]=(int *)calloc ( c, sizeof(int));
}
Обратите внимание на то, что параметр m типа (TArray *) – это параметр –переменная (передается в функцию адресом). Поэтому при обращении к полям структуры используется разыменование, например, (*m).Nr = r. Аналогично используется параметр-переменная при заполнении массива и загрузке массива из файла. При распечатке и сохранении массива в файл можно использовать параметр-значение.
// 2. Заполнение массива
// Nr - количество строк, Nc - количество столбцов
void Fill ( TArray *m)
{ int i, j; static int k=1;
for ( i=0; i<(*m).Nr; i++)
{
for ( j=0; j<(*m).Nc; j++)
(*m).x[i][j] = k++;
}
}
// 3. Распечатка массива
// Nr - количество строк, Nc - количество столбцов
void Print (TArray m)
{ int i, j;
printf("\n");
for ( i=0; i
{
for ( j=0; j
printf("%4d",m.x[i][j]);
printf("\n");
printf("\n");
}
printf("\n");
}
// 4. Сохранение массива в файл
// Nr - количество строк, Nc - количество столбцов
// Name - имя файла
void Save( TArray m, char Name[])
{ FILE *out; int i;
out = fopen(Name,"wb");
if (out != NULL)
{
for ( i=0; i
fwrite (m.x[i], 1, m.Nc*sizeof(int), out);
fclose(out);
}
else puts("\nОшибка открытия файла для записи");
}
// 5. Загрузка массива из файла
// Nr - количество строк, Nc - количество столбцов
// Name - имя файла
void Load( TArray *m, char Name[])
{ FILE *in; int i;
in = fopen (Name,"rb");
if (in != NULL)
{
for ( i=0; i<(*m).Nr; i++)
fread ( (*m).x[i], 1, (*m).Nc*sizeof(int), in);
fclose(in);
}
else puts("\nОшибка открытия файла для чтения");
}
В главной части программы размер двумерного массива задается только при инициализации. Не забудьте также в функции Init, Fill и Load передавать в качестве фактического параметра адрес размещения переменной в оперативной памяти.
#include
#include
#include
typedef struct {int **x; int Nr; int Nc;} TArray;
void Init(TArray *m, int r, int c);
void Fill(TArray *m);
void Print (TArray m);
void Save( TArray m, char Name[]);
void Load( TArray *m, char Name[]);
inline void Rus() // Русификация вывода в консольное окно
{ setlocale( LC_CTYPE, ".1251" ); }
int main()
{ TArray x, y, x1, y1;
Rus();
puts("\nИнициализация массива x\n");
Init (&x,4, 5);
puts("\nЗаполнение массива x\n");
Fill(&x);
puts("\nРаспечатка массива x\n");
Print(x);
puts("\nИнициализация массива y\n");
Init (&y,2, 3);
puts("\nЗаполнение массива y\n");
Fill(&y);
puts("\nРаспечатка массива y\n");
Print(y);
puts("Сохранение массива x в файл");
Save(x,"x.dat");
puts("\nИнициализация массива x1\n");
Init (&x1,4, 5);
puts("Загрузка массива x1 из файла");
Load(&x1,"x.dat");
puts("\nРаспечатка массива x1\n");
Print(x1);
puts("Сохранение массива y в файл");
Save(y,"y.dat");
puts("\nИнициализация массива y1\n");
Init (&y1,2, 3);
puts("Загрузка массива y1 из файла");
Load(&y1,"y.dat");
puts("\nРаспечатка массива y1\n");
Print(y1);
return 0;
}