Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
DOROGOVA.pdf
Скачиваний:
245
Добавлен:
05.06.2015
Размер:
853.4 Кб
Скачать

p[0]=1; /* Ошибка! Массива, на который указывал p, уже нет ! */ /* Создаем, используем и уничтожаем еще один массив */

p = malloc(2 * sizeof(int)); p[0] = 7;

free(p);

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

10.1. Универсальный указатель void

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

int *ip; double *dp;

dp = ip; // будет предупреждение транслятора

Предупреждение выдается не зря - подобные операции с указателями, если это сделано не осознанно, приводят к трудно выявляемым ошибкам. Поэтому и рекомендуют в подобных случаях явно приводить один тип указателя к другому. Ошибку в предыдущем примере можно исправить следующим образом: dp = (double*) ip;

Казалось бы, при таком внимательном отношении транслятора к типам указателей примеры, приведенные выше, должны пестреть от операторов приведения типов, например таких:

char *cp; double *dp;

cp = (char *) malloc(100);

dp= (double*)calloc(100, sizeof(double));

Однако во всех примерах работы с динамической памятью мы ни разу не пользовались операторами приведения типов! Почему это было возможно, и какой тип указателя используют функции calloc(), malloc(), realloc()? Для ответа на этот вопрос, рассмотрим уже извесный нам тип void.

Свойства типа void.

Переменных этого типа не существует.

Первое применение void - отсутствие значения, указание того, что функция ничего не возвращает или не содержит параметров.

Второе применение типа void - указатели.

Для указателя void не работает арифметика указателей, поскольку отсутствует само понятие размера элемента. Транслятор не позволит написать для такого указателя ptr[2] или ptr++.

Указатель void нужен для хранения значения указателей различных типов. По существу транслятор

позволяет использовать операции присваивания между указателем void и любыми другими типами указателей без явного приведения типов:

Пример: Присваивание между указателем типа void и любым другим типом указателя.

void *vp; char *cp; double *dp;

/* Несмотря на отсутствие оператора приведения типа, ни одна из следующих строк не даст предупреждения транслятора */

cp = vp; vp = cp; dp = vp; vp = dp;

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

Функции работы с динамической памятью - calloc(), malloc(), realloc() возвращают указатель типа void, который можно приравнять к указателю любого типа, а значит, при выделении динамической памяти указатели справа и слева от операции присваивания могут иметь разные типы.

Прототипы функций имеют следующий вид: void* malloc(int size);

void* сalloc (int qu, int size); void *free(void *p);

PDF created with FinePrint pdfFactory Pro trial version http://www.fineprint.com

10.2.Принципы работы с динамическими массивами

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

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

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

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

Почаще проверяйте значение указателя перед использованием, NULL должен сигнализировать о том, что указатель "свободен", то есть не указывает ни на один объект:

char *p = malloc(100); char c;

free(p);

p=NULL;

/* Так легко ошибиться, так как p не установлен! */ c = *p;

/*А так безопасно, так как программа проверила, что p содержит адрес переменной */ if (p!=NULL)

{ c = *p;

}

2. Всегда следует сохранять параметры выделенной динамической памяти:

Не теряйте длину выделенной области - иначе вы не сможете узнать, сколько же у вас элементов в динамическом массиве.

Не теряйте исходный адрес, который вам вернули malloc(), calloc() или realloc() - без него вы не сможете освободить выделенную область, а также организовать многопроходную работу с выделенной памятью.

Приведем пример, который иллюстрирует неправильную работу с динамической памятью.

char *p;

int size=k; // размер требуемой памяти p = malloc(size);

size=10;

p++;

После выполнения инструкции size=10 - потерян размер выделенной памяти, после инструкции p++ - потерян начальный адрес выделенной памяти. После этих действий невозможно работать с динамической памятью, так как неизвестен ни её начальный, ни конечный адрес, также невозможно будет вернуть память в кучу.

Следующий пример демонстрирует стандартные подходы, помогающие избегать перечисленных проблем.

Рассмотрим сортировку динамического массива. Алгоритм сортировки методом "пузырька" подробно рассмотрен в разделе "Функции", где мы сортировали статический массив, воспользуемся этим же алгоритмом для решения поставленной задачи. Обратите внимание, что определения функций swab() и mas_print() не изменились. Так как после выделения памяти из кучи, обращение к отдельным элементам массива ничем не отличается от обращения к статическому массиву через указатель.

В функции main() работа идет по следующему сценарию:

Для полноценной работы с массивом через указатели необходимо два указателя - один для хранения начала массива (begin), второй для текущей работы (tek). Следует отметить, что требование этого пункта относится равно как к статическим, так и к динамическим массивам, работающим с указателями.

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

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

Условие выполнения цикла for - tek<(begin+n) заключается в следующем: пока адрес в текущем указателе не превышает адреса последнего элемента массива (begin+n)

Переход к очередному элементу происходит по инструкции tek++ в заголовке цикла for.

PDF created with FinePrint pdfFactory Pro trial version http://www.fineprint.com

Пример. Создать динамический массив, заполнить его случайными числами и отсортировать по возрастанию.

#include <iostream.h>

#include <stdlib.h>

 

#include <time.h>

 

#define n 101

// размер массива

#define col 10

// печать по 10 элементов в строке

void swab (int *p1 , int *p2)

{/* создание локальной переменнрой f и записть в нее значение из переменной указателя p1 */ int f=*p1;

*p1=*p2; *p2 =f;

}

void mas_print (int *p_beg, int *p_end, int k)

{int i=0;// счетчик выведенных элементов for (i=0 ; p_beg < p_end ; i++)

{ cout<<*p_beg++;

if ((i+1)%k==0) cout<<'\n'; else cout<<'\t';

}

 

 

}

 

 

void main()

 

 

{ int *begin,*tek,i; //определение указателей

 

srand(time(0));

 

 

begin=(int*)malloc(n*sizeof(int)); // выделение памяти из кучи

 

// формирование исходного массива

 

for (tek=begin ; tek<(begin+n) ; tek++)

 

*tek=rand();

 

 

mas_print(begin , (begin+n) , col);// печать массива до сортировки

 

cout<<"\n!!! сортировка !!!!\n";

 

for(i=0 ; i<n ; i++) //______сортировка___

 

for( tek=begin ; tek<(begin+n-i-1) ; tek++)

 

if (*tek > *(tek+1))

 

 

swab ( tek , (tek+1) );

//__________________________|

mas_print(begin , begin+n,col);//печать отсортированного массива

 

free (begin);

// возвратить память в кучу

 

}

 

 

При выделении памяти из кучи некоторые компиляторы требуют явного приведения типов указателей правой и левой части равенства, что и сделано в примере: begin=(int*) malloc(n*sizeof(int));

PDF created with FinePrint pdfFactory Pro trial version http://www.fineprint.com

Вопросы для самопроверки

1.Чем отличаются статические переменные от динамических?

2.Какие программные объекты могут выступать в качестве динамических объектов?

3.Поясните термин "куча", для чего используется "куча"?

4.Какие функции динамического выделения памяти Вы знаете?

5.Приведите примеры динамического выделения памяти.

6.Можно ли изменить размер динамического массива?

7.Какая из функций calloc() или malloc() очищает память при создании динамического массива?

8.Какая из функций calloc() или malloc() не инициализирует (не очищает) память при создании динамического массива?

9.Какая функция уничтожает динамический массив? Для чего это нужно?

10.Универсальный указатель void? Для чего он нужен?

11.Приведите схему многопроходной работы с динамической памятью.

PDF created with FinePrint pdfFactory Pro trial version http://www.fineprint.com

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]