Динамическими массивами
Цель работы: закрепить практические навыки работы с системой программирования языка С++ на примере реализации алгоритмов над динамическими многомерными массивами.
Основные теоретические сведения
Указатели
Указатель – это переменная, значение которой равно значению адреса памяти, по которому лежит значение некоторой другой переменной. В этом смысле имя этой другой переменной отсылает к ее значению прямо, а указатель – косвенно. Ссылка на значение посредством указателя называется косвенной адресацией.
Указатели в языке Си делятся на два вида: указатели на объекты и указатели на функции. Свойства и правила их использования различны.
Указатели, подобно любым другим переменным, перед своим использованием должны быть объявлены. Объявление указателя на объект имеет вид:
тип *имя_указателя;
где тип – один из предопределенных или определенных пользователем типов, а имя_указателя – указатель.
Например,
int *APtr;
объявляет переменную APtr типа int * (т.е указатель на целое число) и читается следующем образом: “ APtr является указателем на объект целочисленного типа”. Каждая переменная, объявляемая как указатель, должна иметь перед собой символ (*), который обозначает операцию косвенной адресации.
При объявлении указателя возможна его инициализация. Имеется две формы инициализации указателя:
1) тип *имя_указателя=инициализирующее_значение
2) тип *имя_указателя (инициализирующее_значение)
В качестве инициализирующего_значения может использоваться:
- явно заданный участок памяти
double *cc=0x1047;
- указатель, уже имеющий значение
float *ca;
…
float *cb=ca;
- выражение, позволяющее получить адрес объекта с помощью операции ссылки (&)
int a;
…
int *ce=&a;
Указатели должны инициализироваться либо при своем объявлении, либо с помощью оператора присваивания. Использовать указатель без присвоения ему какого-нибудь участка памяти нельзя. Указатель может получить в качестве начального значения 0 или NULL. Указатель с начальным значением 0 или NULL ни на что не указывает. NULL – это символическая константа, определенная специально для цели показать, что данный указатель ни на что не указывает. Пример объявления указателя с его инициализацией:
int *countPtr = NULL;
Для присваивания указателю адреса некоторой переменной используется операция адресации & (операция ссылки), которая возвращает адрес своего операнда. Например, если имеются объявления
int y = 5;
int *yPtr, x;
то оператор
yPtr = &y;
присваивает адрес переменной y указателю yPtr.
Присвоив указателю адрес конкретного участка памяти, можно с помощью операции разыменования не только получать, но и изменять содержимое этого участка памяти. Для этого используется операция разыменования * (операция косвенной адресации). Она возвращает значение объекта, на который указывает ее операнд (т.е. указатель).
Если присвоить указателю адрес конкретного объекта или значение уже инициализированного указателя, то это превратит запись *имя_указателя в синоним уже имеющегося имени объекта. Запись,
double z;
double *A=&z;
double *C, *D;
C=&z;
D=A;
превратит *A, *C, *D в синонимы переменной z. Изменяя значение, лежащее по адресу на которое указывают указатели, автоматически изменяется значение переменной, так как указатели A, C, D и переменная z связаны между собой одним участком памяти. Продолжая начатый пример
z=6;
double A1, C1, D1;
A1=*A;
C1=*C;
D1=*D;
присвоит переменным A1, C1, D1 значение 6, т.е. значение переменной z, на которую указывают указатели A, C, D.
Операции new и delete
Чтобы связать неинициализированный участок памяти, еще не занятым никаким объектом программы, используется оператор new:
указатель= new тип (инициализирующее_значение);
Операция возвращает указатель на динамически размещенный в памяти объект, т.е. предоставляет указателю память под объект заданного типа. Пример
double *A=new double;
int *B;
B=new int;
объявляет указатели и выделяет им свободное место, не занятое никаким объектом.
Инициализирующее_значение задает начальные значения создаваемого объекта. Запись
double *C=new double (-1.8);
предоставляет место указателю С под объект вещественного типа и инициализирует его, т.е. записывает начальное значение -1.8.
Если операция new возвратила нулевое значение адреса – NULL, то это значит, что операционная система не может выделить память под данный объект, поэтому после использования оператора new желательно всегда проверять адрес выделенного участка памяти
double *A;
A=new double;
if (A==NULL) …
Динамически распределенную память следует освобождать, когда отпадает необходимость в размещенных в ней объектах. Освобождение памяти осуществляется с помощью операции delete, которая имеет следующий синтаксис
delete имя_указателя;
Пример
int *B=new int;
…
delete B;
объявляет указатель, выделяя и освобождая место под объект в адресном пространстве.
Освобождение памяти уничтожает сам объект, а не указатель. В дальнейшем, указателю можно заново выделить участок памяти под новый объект и освободить эту память, что позволяет более гибко использовать оперативное адресное пространство компьютера. Поэтому, объекты созданные с помощью операции new называются динамическими.
long *P;
…
P=new long;
…
delete P;
…
P=new long;
…
delete P;
Операция delete освобождает память, но сама не задает указателю значение NULL, поэтому во избежание использования в дальнейшем неинициализированного указателя желательно это делать программно.
delete P;
P=NULL;
Операции над указателями
Указатели могут применяться как операнды в арифметических выражениях, выражениях присваивания и выражениях сравнения. Однако, не все операции, обычно используемые в этих выражениях, разрешены применительно к переменным указателям.
С указателями может выполняться ограниченное количество арифметических операций. Указатель можно увеличивать (++), уменьшать (--), складывать с указателем целые числа (+ или +=), вычитать из него целые числа (- или -=) или вычитать один указатель из другого.
Сложение указателей с целыми числами отличается от обычной арифметики. Прибавить к указателю 1 означает сдвинуть его на число байтов, содержащихся в переменной, на которую он указывал. Обычно подобные операции применяются к указателям на массивы. Например, запись
int *Pt;
…
Pt+=2;
увеличит значение Pt (т.е. адрес в памяти, на который указывает Pt), не на два, а на четыре байта, так как один объект целочисленного типа int в памяти занимает два байта.
Аналогичные правила действуют и при вычитании из указателя целого значения.
Однако при использовании арифметических операций над указателями нельзя полагать, чтоб две переменные – указатели одинакового типа будут находится в памяти вплотную друг к другу, если только они не соседствуют в массиве.
Сравнение указателей операциями >, <, >=, <= также имеют смысл только для указателей на один и тот же массив. Однако, операции отношения == и != имеют смысл для любых указателей. При этом указатели равны, если они указывают на один и тот же адрес в памяти.
Указатель можно присваивать другому указателю, если оба указателя имеют одинаковый тип. В противном случае нужно использовать операцию приведения типа, чтобы преобразовать значение указателя в правой части присваивания к типу указателя в левой части присваивания. Исключением из этого правила является указатель на void (т.е. void*), который является общим указателем, способным представлять указатели любого типа. Указателю на void можно присваивать все типы указателей без приведения типа. Однако указатель на void не может быть присвоен непосредственно указателю другого типа – указатель на void сначала должен быть приведен к типу соответствующего указателя.
Связь массивов и указателей
Массивы и указатели Си++ тесно связаны и могут быть использованы почти эквивалентно. При определении массива, имя массива является указателем константой, значением которой служит адрес первого элемента массива (с индексом 0), а запись
имя_массива[индекс]
является выражением с двумя операндами. Первый из них, т.е. имя_массива – константный указатель – адрес начала массива в основной памяти, а индекс – это выражение целого типа, определяющее смещение от начала массива.
Используя операцию обращения по адресу * (операция разыменования), действие бинарной операции можно представить следующим образом:
*(имя_массива + индекс)
т.е. операндами для операции [] служат имя_массива и индекс. Из этого становится очевидным, почему индекс первого элемента массива равен нулю. Таким образом, запись *имя_массива – обращение к первому элементу массива, *(имя_массива+1) – обращение ко второму, и т.д.
Поскольку сложение *(имя_массива+индекс) коммутативно, то возможна такая эквивалентная запись *(индекс+имя_массива) и, следовательно, индекс [имя_массива] именует тот же элемент массива, что имя_массива [индекс].
В некоторых конструкциях можно использовать выражение имя_массива [индекс] с отрицательным значением индекса. В этом случае имя_массива должен указывать не на начало массива, т.е. не на его нулевой элемент.
Ссылки
Ссылки – это специальный тип указателя, который позволяет работать с указателем как с объектом. Объявление ссылки делается с помощью операции ссылки, обозначаемой амперсантом (&) – тем же символом, который используется для адресации. Например, если имеется объявление
double*P = new double;
то можно создать ссылку на этот объект оператором:
double& Ref = *P;
Объявленная таким образом переменная Ref является ссылкой на объект вещественного типа. Она может рассматриваться как псевдоним объекта. Эта переменная является именно указателем, а не самим объектом. Но работа с ней производится как с объектом, в отличие от указателя.
*P=*P+0.5;
Ref=Ref+0.5;
Чаще всего ссылки используются при передаче в функции параметров по ссылке.
Дата: 2019-02-02, просмотров: 260.