7.3. Краткие итоговые сведения
Отметим отличия и особенности хорошего стиля работы с рассмотренными циклическими операторами.
Цикл с предусловием (пока условие истинно) |
Цикл с постусловием (до истинности условия) |
|
|
|
|
|
|
|
|
|
|
|
|
Цикл со счетчиком (с параметром) For |
|
|
|
|
|
|
|
|
|
|
32. Подпрограммы
См вопрос номер 20
33. Работа с динамическими переменными и указателями
34. Статические и динамические переменные. Основные ошибки при работе с динамическими переменными и указателями.
Ошибки при работе с динамическими переменными и указателями.
1. Утечка памяти
При использовании динамически распределяемых переменных часто возникает общая проблема, называемая утечкой динамической памяти. Утечка памяти – это ситуация, когда пространство выделяется в динамически распределяемой памяти и затем теряется – по каким-то причинам ваш указатель не указывает больше на распределенную область, так что вы не можете освободить пространство. Общей причиной утечек памяти является переприсваивание динамических переменных без освобождения предыдущих. Простейшим случаем является следующий (на Паскале):
var IntPointer :^ Integer;
begin
New (IntPointer);
New (IntPointer);
end.
При первом вызове New в динамически распределяемой памяти выделяется 2 байта, и на них устанавливается указатель IntPointer. Второй вызов New выделяет другие 2 байта, и IntPointer устанавливается на них. Теперь у вас нет указателя, ссылающегося на первые 2 байта, поэтому вы не можете их освободить. В программе эти байты будут потеряны. В итоге программе (а, зачастую, и вовсе даже не той, которая память бездумно «потребила») может просто не хватить памяти.
2. Несоответствие типов при присваивании указателей (только для С++)
Значение одного указателя можно присвоить другому. Если указатели одного типа, то один можно присваивать другому с помощью обычной операции присваивания (см. в теме «Типы переменных»).
Если указатели ссылаются на различные типы, то при присваивании значения одного указателя другому, необходимо использовать преобразование типов. Без преобразования можно присваивать любому указателю указатель void. Преобразование типов для статических переменных рассматривалось ранее (информацию высылала в электронном виде).
Рассмотрим примеры работы с указателями различных типов.
Пример №1
#include <iostream.h>
#include <math.h>
int main()
{ float PI=3.14159,*p1;
double *p2;
p1=&PI; //В переменную p1 записываем адрес PI
p2=(double *)p1; //указателю на double присваиваем значение, которое ссылается на тип float.
Cout<<"По адресу p1=”<<p1<<”хранится значение“<<*p1;
Cout<<"По адресу p2=”<<p2<<”хранится значение“<<*p2; }
Результатом этой программы будет:
По адресу p1=0012FF7C хранится 3.14159
По адресу p2=0012FF7C хранится 2.642140e-308
Почему? В указателях p1 и p2 хранится один и тот же адрес, но значения, на которые они ссылаются, оказываются разными. Это связано с тем, указатель типа *float адресует 4 байта, а указатель *double – 8 байт. После присваивания p2=(double *)p1; при обращении к *p2 происходит следующее: к переменной, хранящейся по адресу p1, дописывается еще 4 байта из памяти. Что в них хранится – неизвестно, а число разрядов, отводимых на смещенную экспоненту и мантиссу будет браться для типа double. В результате значение *p2 не совпадает со значением *p1.
Пример №2
#include <stdio.h>
#include <math.h>
int main()
{
double PI=3.14159,*p1;
float *p2;
p1=&PI;
p2=(float *)p1;
cout<<"По адресу p1=”<<p1<<” хранится значение ”<<*p1);
cout<<"По адресу p2=”<<p2<<” хранится значение ”<<*p2); }
После присваивания p2=(double *)p1; при обращении к *p2 происходит следующее: из переменной, хранящейся по адресу p1, выделяется только 4 байта. В результате и в этом случае значение *p2 не совпадает со значением *p1.
ВЫВОД. При преобразовании указателей разного типа приведение типов разрешает только синтаксическую проблему присваивания. Следует помнить, что операция * над указателями различного типа, ссылающимися на один и тот же адрес, возвращает различные значения.
3. Неинициализированный указатель (для С++)
Каждый раз при работе с указателями необходимо выполнять их инициализацию, т.е. задавать адрес на выделенную область памяти. Так, в Паскале при объявлении переменной-указателя ей автоматически присваивается значение nil. В С++ при объявлении переменной-указателя они указывают на произвольную область памяти, с которой СРАЗУ можно работать как с обычной переменной.
Пример
int* ptr;
*ptr = 10;
В результате в произвольную область памяти будет записано два байта со значениями 10 и 0. Это может привести к необратимым последствиям в работе программы и к ее ошибочному завершению. Поэтому перед использованием указателей всегда нужно быть уверенным, что они предварительно были инициализированы.
35. Указатели. Применение указателей. Преобразование типов указателей.
См 33 вопрос.