Лабораторная работа №8
Программирование с использованием указателей.
Динамическое
распределение памяти
Указатель - это переменная, которая содержит адрес памяти, по которому можно найти значение другой переменной. Объявление указателя:
<тип> *<идентификатор>
При объявлении указателя необходимо указывать тип
переменной, на которую он будет указывать. Каждая переменная, объявляемая как
указатель, должна иметь перед своим именем символ “*”. Например:
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
Массивы и указатели в 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. Для размещения
массива использовать динамическое выделение памяти.