Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Лабораторная работа 9.doc
Скачиваний:
7
Добавлен:
22.07.2019
Размер:
2.66 Mб
Скачать

Лабораторная работа №9 Разработка и отладка алгоритмов и программ c использованием указателей.

Цель: научиться использовать указатели при необходимости работы с адресами переменных.

Теоретические сведения

Основные сведения

В языках C/C++ активно используются указатели – переменные, содержащие адреса ячеек некоторой области памяти, которая распределяется для размещения значений определенного типа (фактически значений других переменных). Указателю может быть присвоен нулевой адрес (NULL). Значение нулевого адреса не является реальным адресом и используется только для обозначения того, что указатель в данный момент не может использоваться для обращения ни к какой ячейке памяти.

Указатели помогают эффективно работать с динамически распределяемой памятью, массивами, строками, широко используются при передаче параметров функциям и др.

Определение указателя имеет следующий вид:

тип-данных *id1, *id2,…, *idn

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

Пример.

int *p;

float *p, *q;

Звездочка относится к переменной, а не к типу, поэтому

int *p, a;

p является указателем на тип int, а a – переменной типа int.

Для получения адреса переменной в языке C используется операция взятия адреса &.

int *z = &a;

Возможна инициализация указателя при его объявлении:

int *p = (int*)0x0012FF64;

Обратите внимание, что при инициализации во время объявления указателю будет присвоен некоторый адрес ячейки памяти (в шестнадцатеричной системе счисления). Следует также обратить внимание также на переопределение типов.

Возможна инициализация указателя нулевым указателем:

int *p = NULL;

Операция разыменования указателя * используется для доступа к переменной, адрес которой содержится в указателе:

*z=5;

Таким образом, в отличии от объявления указателя, при его разыменовании обращение происходит не к переменной-указателю (содержащей адрес), а к значению, которое содержится в ячейки памяти по данному адресу.

Если существует необходимость, чтобы в ячейках памяти содержались значения произвольного типа, то необходимо использовать указатель на void.

void *p;

В языках C/С++ существует тесная связь между указателями и массивами. В частности, имя массива без указания квадратных скобок рассматривается как адрес его нулевого элемента, и, наоборот, при использовании квадратных скобок с указателем, он рассматривается как одномерный массив.

S одно и то же, что и &S[0]

z[0] одно и то же, что и *z

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

Изменение указателя на единицу соответствует переходу к следующему (предыдущему) элементу одномерного массива:

z++;

z--;

Аналогичные действия выполняются, если к указателю прибавить (отнять) целое число:

S+5 // адрес элемента S[5]

*(S+5) // сам элемент S[5]

С указателями возможно использование и операций отношения, сравнивающих соответствующие адреса объектов в памяти. Например, элементу массива с большим индексом соответствует больший адрес в памяти, а нулевой указатель меньше любого из указателей.

Пример1:

int sum=0, *u=A+N;

while (A<u--) sum+=*u;

Пример2:

int *max=A, *u=A+N;

while (A<u--) if (*max<*u) max=u;

В данных примерах массивы просматриваются с помощью указателя u,

начальное значение которого равно адресу элемента массива, следующего за последним. Благодаря операциии уменьшения на 1, при проверке условия выполнения цикла указатель сдвигается на предыдущий элемент массива, значение которого затем суммируются (в первом примере) или сравниваются (во втором)

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

Указатели используются при создании и обработке динамических объектов. Динамические объекты, в отличие от статических, создаются в процессе выполнения программы. В отличие от заранее определенных (статических) объектов, число динамических объектов не фиксировано тем, что записано в тексте программы, - по желанию динамические объекты могут создаваться и уничтожаться в процессе ее выполнения. Динамические объекты, в отличие от заранее определенных, не имеют имен, и «ссылка» на них выполняется с помощью указателей. К динамическому распределению пямяти прибегают тогда, когда невозможно заранее определить количество и размер переменных, с которыми должна работать программа.

Значение 0 может быть присвоено указателям любого типа. Это значение показывает, что данный указатель не содержит «ссылки» на какой-либо объект. Попытка использовать это значение для обращения к объекту может привести к ошибке. По соглашению, для обозначения константы с нулевым значением используется идентификатор NULL, описание которого находится в библиотеке stddef.h и является системозависимым.

Функции для выделения и освобождения динамически распределяемой памяти описаны в заголовочном файле stdlib.h

void *malloc(size_t size) – выделяет область памяти для размещения динамической переменной размером size байт. Для определения размера памяти, занимаемой переменными, может быть использована операция sizeof.

void *calloc(size_t n, size_t size) – выделяет область памяти для размещения массива из n элементов по size байтов каждый.

void *realloc(void *pointer, size_t size) – изменяет размер области памяти , выделенной для хранения динамической переменной, расположенной по адресу pointer (при этом переменная может быть перенесена по другому адресу).

void free (void *pointer) – освобождает область памяти по адресу pointer, выделенную ранее вызовами malloc, calloc, realloc.

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

Пример.

int *a = (double *) malloc(sizeof(double));

int *b = (int *) calloc(N, sizeof(int));

b = (int *) realloc(b,2*N*sizeof(int));

Для работы с динамически распределяемой памятью в языке C++ было введено две специальные операции: new и delete.

int *p= new int // выделение памяти для указателя

//на тип int

int *q= new int(5) // памяти для указателя на тип

// int c занесением значения 5 в

// область памяти

int *q= new int[5] // выделение памяти для массива

// из 5 элементов типа int

delete p // освобождение памяти, занимаемой

//динамической переменной p

delete[] q // освобождение памяти, занимаемой

// массивом q

Пример динамического распределения памяти в языке C:

float *dynamic_array = malloc(number_of_elements * sizeof(float));

if(!dynamic_array)

{

/* обработка ошибки выделения памяти */

}

/* … работа с элементами массива … */

free(dynamic_array);

dynamic_array = NULL;

float *dynamic_array = calloc(number_of_elements, sizeof(float));

if(!dynamic_array)

{

/* обработка ошибки выделения памяти */

}

/* … работа с элементами массива … */

free(dynamic_array);

dynamic_array = NULL;

Пример динамического распределения памяти в языке C++:

int *p_var = new int;

int *p_array = new int[50];

*p_var = 5;

*(p_array +23) = 3;

delete[] p_array;

delete p_var;

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

Динамическое объявление многомерного массива.

Рассмотрим для примера двумерные массивы.

Двумерный массив в языках СИ/СИ++ реализуется как массив массивов. В связи с этим, необходимо динамически распределить память под каждый элемент массива, который в свою очередь тоже является массивом. Соответственно таким же образом нужно и освободить память. Иногда, можно не только вернуть распределенную память системе, но и «занулить» указатель (при освобождении распределенной памяти, указатель по прежнему содержит ее адрес)

Распределение и освобождение памяти с помощью функций calloc, free будет выглядеть так:

Распределение и освобождение памяти с помощью операций new, delete будет выглядеть так:

….