Лабораторная работа №8

Программирование с использованием указателей.

Динамическое распределение памяти

8.1.Объявление указателей

Указатель - это переменная, которая содержит адрес памяти, по которому можно найти значение другой переменной. Объявление указателя:

<тип> *<идентификатор>

При объявлении указателя необходимо указывать тип переменной, на которую он будет указывать. Каждая переменная, объявляемая как указатель, должна иметь перед своим именем символ “*”. Например:

int *pA, B;           // *pA - указатель на элемент целого типа;

                            // B - переменная целого типа

double *pC;      // *C - указатель на переменную действительного типа

void *pD;      // *D - указатель на любой тип данных (перед применением

                   // необходимо инициировать конкретным типом данных

Указатель обязательно должен быть инициирован либо при объявлении, либо во время выполнения программы (до его первого использования). В качестве значения указатель может получить либо конкретный адрес, либо 0 или NULL, последнее указывает на то, что указатель ни на что не указывает.

Для указателей определены 2 операции:

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

& - операция адресации. Применяется для получения адреса какой-либо переменной.

Пример:

int A=9, *pB;

pB=&A;

*pB=44;

         Memo1->Lines->Add("A= "+IntToStr(A));

A=28;

         Memo1->Lines->Add("*pB= "+IntToStr(*pB));

Результат:

A= 44

 *pB= 28

8.1.Указатели на массив

Массивы и указатели в C++ тесно связаны и могут использоваться одинаковым образом. Имя массива является указателем на его первый элемент. Указатель также содержит адрес первого элемента массива. Указатели могут бить использованы при любых операциях с массивами. Например имеется объявление:

int A[5] = [1,2,3,4,5], *pmas;

Тогда для того чтобы pmas указывал на массив, надо записать: pmas=A; или pmas=&A[0];

Обращение, например, к третьему элементу массива можно записать:

A[2]  или *(pmas+2), или *(A+2)

Указатели можно индексировать так же, как и массивы. Все операции, которые можно проводить с массивами, можно применить к массивам указателей. Массивы указателей, как правило, применяются для работы с массивами данных.

Пример. Отсортировать массив по возрастанию, не перемещая элементы в памяти компьютера.

int mas[]={1,8,6,3,9}; int *pmasmin[5], *px;

for (int i=0; i<5; i++) pmasmin[i] = &mas[i];

for (int i=0; i<4; i++)

for (int j=i+1; j<5; j++)

if (*pmasmin[i]> *pmasmin[j])

{

px=pmasmin[i];

pmasmin[i]=pmasmin[j];

pmasmin[j]=px;

}

for (int i=0; i<5; i++) Memo1->Lines->Add(IntToStr(*pmasmin[i]));

Результат: 1 3 6 8 9.

Для двухмерных массивов используются указатели на указатели. Например int mas[5][5];

int **pmas;

Обращение может проводиться: *(*(mas+i)+j).

С помощью массивов указателей также удобно работать с массивами строк. Например:

char *pmas[]={"One","Save","Men","Write","BuNder"};

 char *px;

for (int i=0; i<4; i++)    

for (int j=i+1; j<5; j++)

if (*pmas[i]> *pmas[j])

{

px=pmas[i];

pmas[i]=pmas[j];

pmas[j]=px;

}

for (int i=0; i<5; i++)

Memo1->Lines->Add(pmas[i]);

Результат: Builder Men One Save Write.

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

8.3.Особенности применения указателей

При работе с указателями можно использовать следующие операции: сложение, вычитание, операции сравнения и отношения. Однако имеются особенности их применения.

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

Операции сравнения. Так же как и операции сложения и вычитания имеют смысл только для однотипных массивов.

Операции отношения (== и !=). Имеют смысл для любых указателей. Если два указателя равны между собой, то они указывают на одну и ту же переменную.

Указатели можно присваивать друг другу только в случае, если они имеют одинаковый тип (исключение - указатель на void).

8.4.Динамическое размещение данных

Динамическое размещение данных используется в случаях, когда число необходимых для работы объектов заранее неизвестно и задается непосредственно во время выполнения программы, либо когда некоторые объекты используются только в определенном месте программы и далее становятся ненужными. При этом достигается значительная экономия вычислительных ресурсов.

Для динамически размещаемых данных выделяется специальная область памяти - heap. Имеются следующие функции для работы с динамической памятью (находятся в файлах stdlib.h alloc.h):

void *malloc(size) - выделяет блок памяти размером size байт. Если выделение прошло удачно, то функция возвращает указатель на выделенную область, иначе - возвращается NULL.

void *calloc(n, size) - выделяет n блоков памяти, каждый размером size байт. Если выделение прошло удачно, то функция возвращает указатель на выделенную область, иначе - возвращается NULL.

void *realloc(*bl, size) - изменяет размер ранее выделенного блока памяти с адресом *bl на новый размер, равный size байт. Если изменение прошло удачно, то возвращает указатель на выделенную область, иначе - возвращается NULL. Если *bl равен NULL, то функция работает так же, как и функция malloc. Если size равен нулю, то выделенный по адресу *bl блок памяти освобождается, и функция возвращает NULL.

void free(*bl) - освобождает ранее выделенный блока памяти с адресом *bl. Пример динамического выделения освобождения памяти для двухмерного массива:

int **pA;

pA=(int **) calloc (N,sizeof(int*));  for (i=0; i<N; i++)

pA[i]=(int *) calloc (M,sizeof(int));

for (i=0; i<N; i++) free(pA[i]); free(pA);

Другим, более предпочтительным подходом к динамическому распределению памяти является использование операций new и delete:

void *объект new void - возвращает указатель на динамически размещенный объект;

delete объект - освобождение памяти.

Например:

double *M = new double;

double К = *new double;

double *A = new ouble[100];

delete M;

delete K;

delete [] A;

8.5.Порядок выполнения задания

Задание: написать программу, которая определяет, является ли заданная матрица N-го порядка магическим квадратом. Магический квадрат - это матрица, в которой сумма элементов во всех столбцах и во всех строках одинакова. Элементы матрицы необходимо разместить в памяти динамически.

Панель диалога приведена на рис. 8.1.

 

Текст программы:

 #include <vcl.h>

#pragma hdrstop

#include "lr7.h"

#pragma package(smart_init)     

 #pragma resource "*.dfm" TForm1 *Form1;

int **pA;

int N,M;

//---------------------------------------------------------------------------

___fastcall TForm1::TForm1(TComponent* Owner)

: TForm(Owner)

{

}

//--------------------------------------------------------------------

void___fastcall TForm1::FormCreate(TObject *Sender)

{

N=3; M=3;

Edit1->Text="3";

Edit2->Text="3";

StringGrid1->RowCount=N+1;

StringGrid1->ColCount=M+1;

for (int i=1; <=N; i++){

StringGrid1->Cells[i][0]="j="+IntToStr(i);

StringGrid1->Cells[0][i]="i="+IntToStr(i);

}

Label4->Caption="";

}

//---------------------------------------------------------------------

void___fastcall TForm1::Button1Click(TObject *Sender)

{

N=StrToInt(Edit1->Text);

M=StrToInt(Edit2->Text);

StringGrid1->RowCount=N+1;

StringGrid1->ColCount=M+1;

for (int i=1;i<=N;i++) StringGrid1->Cells[0][i]="i="+IntToStr(i);

for (int i=1;i<=M;i++) StringGrid1->Cells[i][0]="j="+IntToStr(i);

}

//---------------------------------------------------------------------

void  fastcall TForm1::Button2Click(TObject *Sender)

{

int i,j,s1=0, sn;

pA = new int*[N]; for (i=0; i<N; i++)

pA[i] = new int[M];

for (i=0;i<N;i++)

for (j=0;j<M;j++)

pA[i][j]=StrToInt(StringGrid1->Cells[j+1][i+1]);

 bool met=True;

for (i=0;i<N;i++)

s1+=pA[i][1];

for (i=0;i<N && met;i++) {

sn=0;

for (j=0; j<M; j++) sn+=pA[i][j];

 if (s1!=sn) met=False;

}

for (j=0; j<M && met; j++) {

sn=0;

for (i=0; i<N; i++)

sn+=pA[i][j];

 if (s1!=sn) met=False;

}

if (met) Label4->Caption="Магический  квадрат";

else Label4->Caption="Немагический  квадрат";

for (i=0;i<N;i++)

delete[] pA[i];

delete [] pA;

}

8.6.Индивидуальные задания

По указанию преподавателя выберите вариант задания из темы 4. Для размещения массива использовать динамическое выделение памяти.