logo
Metod_ukazanija C_1-8

Примеры функций для работы с двумерным массивом

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

Часть первая («традиционная» форма использования обращения к элементу двумерного массива).

Двумерный массив декларируем в виде

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<Nr; 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<Nr; 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<Nr; 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<Nr; i++)

// ... записать строку массива в файл.

fread(m[i], 1, C*sizeof(int), in);

// Закрыть файл.

fclose(in);

}

else puts("\nОшибка открытия файла для чтения");

}

При чтении данных из файла меняется режим доступа к файлу (rb) и используется функция чтения из файла fread, параметры которой полностью совпадают с параметрами функции fwrite.

Для проверки работоспособности приведенных функций была использована приведенная ниже программа. Не забудьте после главной функции добавить представленные ранее функции Filling_2, Print_2, Save и Load.

#include <stdio.h>

#include <stdlib.h>

#include <locale.h>

#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<Nr; 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<Nr; i++)

{

for ( j=0; j<Nc; 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<Nr; i++)

{

for ( j=0; j<Nc; 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<Nr; 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<Nr; i++)

fread ( x[i], 1, Nc*sizeof(int), in);

fclose(in);

}

else puts("\nОшибка открытия файла для чтения");

}

Проверка работоспособности функций проводилась приведенной ниже программой. Можете заметить, что функции применимы как для массива размером 4х5 (массивы x и x1), так и для массива размером 2х3 (массивы y и y1).

#include <stdio.h>

#include <stdlib.h>

#include <locale.h>

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<r; 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<m.Nr; i++)

{

for ( j=0; j<m.Nc; 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<m.Nr; 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 <stdio.h>

#include <stdlib.h>

#include <locale.h>

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;

}