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

Вид материалаПрограмма

Содержание


Тема №7. Двумерные массивы. Файловый (бинарный) ввод-вывод.
New_Item(c, i, range1, range2)
Примеры функций для работы с двумерным массивом
Часть первая
Часть вторая
Подобный материал:
1   2   3   4   5   6   7

Тема №7. Двумерные массивы. Файловый (бинарный) ввод-вывод.


Задание: Задание состоит из нескольких пунктов:
  1. Заполнить двумерный массив (каждая строка массива заполняется в соответствии с заданием по теме №5).
  2. Распечатать содержимое массива в виде прямоугольной матрицы.
  3. Сохранить двумерный массив в бинарный файл.
  4. Считать содержимое бинарного файла в другой двумерный массив.
  5. Распечатать содержимое нового массива в виде прямоугольной матрицы.
  6. Выполнить над каждой строкой нового двумерного массива действия, предусмотренные по теме №5.
  7. Распечатать преобразованный двумерный массив.

Краткая теоретическая справка

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

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, а можно использовать другой прием. Алгоритм следующий:
  1. Создадим новый проект, например, с именем 7.
  2. Копируем в рабочий каталог проекта 7 файл с решением задачи по теме №5, но с именем 5.h.
  3. Редактируем файл 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 );
  1. Копируем в рабочий каталог проекта 7 файл с решением задачи по теме №5, но с именем 5.c.
  2. Редактируем файл 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 ); }

}
  1. Включаем файлы 5.h и 5.c в проект.
  2. Копируем в рабочий каталог проекта 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;

}
  1. Компилируем и запускаем эту программу на исполнение, чтобы убедиться, что все преобразования сделаны правильно.



Теперь в любой программе можем использовать функции обработки одномерных массивов, подключив в программу подготовленные файлы 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;

}