1 Перечисления Рассмотрим примеры: band = blue; // правильно, blue это перечислитель band = 2000; // неправильно, 2000 это не перечислитель Таким образом,

Презентация:



Advertisements
Похожие презентации
Практическое занятие 6. Функции. Большинство языков программирования используют понятия функции и процедуры. C++ формально не поддерживает понятие процедуры,
Advertisements

Основы информатики Массивы. Указатели. Заикин Олег Сергеевич
УКАЗАТЕЛИ. Переменная - это именованная область памяти с заданным типом. [=значение]; int a; //Переменная типа integer с именем a int b=2;// Переменная.
Основы информатики Лекция. Массивы. Указатели. Заикин Олег Сергеевич
Основы информатики Классы Заикин Олег Сергеевич zaikin.all24.org
МАССИВЫ 4 Определение 4 Описание 4 Обращение к элементам массива 4 Связь массивов с указателями 4 Примеры программ.
Распределение памяти. Динамическое выделение памяти.
Объектно-ориентированный язык программирования. Переменная - эта поименованная ячейка памяти, хранящая какое-либо одно значение (одно число, один фрагмент.
Циклы в C++. Иногда необходимо повторять одно и то же действие несколько раз подряд. Для этого используют циклы. В этом уроке мы научимся программировать.
Работа с файлами Сазонов Д.О. ПМиЭММ Часть 2. Тема занятия: Работа с файлами через потоки Для реализации файлового ввода/вывода, необходимо включить в.
Основные виды ресурсов и возможности их разделения.
Что нужно знать: динамическое программирование – это способ решения сложных задач путем сведения их к более простым задачам того же типа динамическое.
Лекция 6. Способы адресации в микропроцессорных системах.
Полиморфизм Полиморфизм (polymorphism) - последний из трех "китов", на которых держится объектно-ориентированное программирование Слово это можно перевести.
Полиморфизм. Полиморфизм – это свойство системы использовать объекты с одинаковым интерфейсом без информации о типе и внутренней структуре объекта.
Теперь, когда вы постигли азы программирования, будем учиться писать программы, которые позволяют вести диалог между компьютером и человеком (пользователем).
План-конспект урока (информатика и икт, 9 класс) по теме: Переменные:тип, имя, значение
Тип, имя и значение переменной.. Переменные. В объектно-ориентированных языках программирования, и в частности в языке Visual Basic, переменные играют.
Лекция 8 Область видимости Время жизни. Область видимости Область видимости – характеристика именованного объекта Область видимости - часть текста программы,
Pascal 1 Основы языка Паскаль Информатика и ИКТ Смирнов М.В. МКО ООШ п. Климковка Белохолуницкого района Кировской области.
Транксрипт:

1 Перечисления Рассмотрим примеры: band = blue; // правильно, blue это перечислитель band = 2000; // неправильно, 2000 это не перечислитель Таким образом, переменная типа spectrum ограничена только восемью возможными значениями. Некоторые компиляторы выдают ошибку при попытке назначить недопустимое значение, тогда как другие выводят предупреждение. Чтобы обеспечить максимальную переносимость, нужно трактовать присвоение не входящего в перечисление значения переменной типа enum как ошибку.

2 Перечисления Для перечислений определен только оператор присваивания. Арифметические действия, в частности, для них не определены: band = orange; // допустимо ++band; // недопустимо band = orange + red; // недопустимо

3 Перечисления Перечислители имеют целочисленный тип и могут быть преобразованы в тип int, но элементы типа int не преобразовываются автоматически в тип enum: int color = blue; // правильно, тип spectrum преобразуется в тип int band =3; // неправильно, int не преобразуется в spectrum color = 3 + red; // правильно, red преобразуется в int Заметим, что, хотя значение 3 соответствует перечислителю green, присвоение этого значения переменной band вызовет ошибку использования типа. Но присвоение значения green переменной band проходит нормально, поскольку они оба принадлежат к типу spectrum.

4 Перечисления Значение типа int может быть присвоено перечислению при условии, что это значение допустимо, и используется явное приведение типов: band = spectrum (3); // приведение значения 3 к типу spectrum Что будет при попытке приведения типов для недопустимого значения? Результат окажется неопределенным, другими словами, полагаться на его правильность нельзя: band = spectrum(40003); // результат не определен

5 Перечисления Заметим, что правила, регламентирующие работу с перечислениями, довольно строги. Практически перечисления используются чаще как способ определения связанных символических констант, чем как способ определения нового типа данных. Например, можно было бы применить перечисление, чтобы определить символические константы для оператора switch. Если планируется использовать только константы и не создавать переменных типа enum, имя типа перечисления можно опустить: enum {red, orange, yellow, green, blue, violet, indigo, ultraviolet};

6 Установка значений перечислите лей Значения перечислите лей можно устанавливать явно, используя оператор присвоения: enum bits{one = 1, two = 2, four = 4, eight = 8}; Присваиваемые значения должны быть целочисленными; явно задавать можно не все, а только некоторые из перечислите лей: enum bigstep{first, second = 100, third}; В этом случае элемент first по умолчанию равен 0, а последующие неинициализированные перечислите ли будут больше предшествующих им на единицу; так, third будет иметь значение 101.

7 Установка значений перечислите лей Наконец, одно и то же значение можно присвоить более чем одному перечислителю: enum {zero, null = 0, one, numero_uno = 1} Здесь оба значения и zero, и null, равны 0, a one и numero_uno 1. В более ранних версиях C++ можно было присваивать перечислителям только значения типа int (или значения, которые могут быть повышены до типа int), но это ограничение отменено, с тем чтобы можно было использовать значения типа long.

8 Диапазоны значений для перечислений Первоначально единственно правильными значениями для перечисления были те, которые назначались при объявлении. Однако теперь C++ поддерживает усовершенствованную концепцию, в соответствии с которой можно вполне законно присваивать значения путем приведения типа к типу переменной перечисления. Каждое перечисление имеет диапазон, и можно присваивать любое целочисленное значение из диапазона, даже если оно не является значением перечислителя, выполняя приведение типа к переменной перечисления.

9 Диапазоны значений для перечислений Предположим, что переменная myflag и перечисление bits определены следующим образом: enum bits{one = 1, two = 2, four = 4, eight = 8}; bits myflag; Тогда следующее выражение является допустимым: myflag = bits(6); // правильно, поскольку 6 находится в диапазоне bits Здесь 6 не одно из перечислений, однако оно находится в диапазоне, который определяют перечислите ли.

10 Указатели и свободная память Мы упоминали три фундаментальных свойства, которые должна учитывать компьютерная программа при сохранении данных. Перечислим их здесь еще раз: где хранится информация, какое значение там хранится и вид хранящейся информации. Для реализации этих свойств использовался один из возможных методов определение простой переменной. Оператор объявления определяет тип и символическое имя значения, а также заставляет программу выделить область памяти для значения и внутренними средствами отслеживать ее адрес.

11 Указатели и свободная память Теперь рассмотрим второй метод. Он приобретает особое значение при разработке классов C++. Этот метод основан на указателях, которые являются переменными, сохраняющими адреса значений вместо непосредственно самих значений. Но прежде чем рассматривать указатели, обратимся к способу явного определения адресов обычных переменных. Этот способ заключается в применении к переменной операции определения адреса, представленной знаком &. Например, если home переменная, &home является ее адресом. Применение этого оператора продемонстрировано в листинге 4.9:

12 Указатели и свободная память

13 Указатели и свободная память Ниже приведен результат выполнения программы в одной из операционных систем: donuts value = 6 and donuts address = 0x0065fd40 cups value = 4.5 and cups address = 0x0065fd44 При отображении адресов объект cout использует шестнадцатеричное представление, потому что оно обычно применяется для записи адресов памяти. В нашей реализации значение переменной donuts хранится в ячейке памяти с меньшей величиной адреса по сравнению с переменной cups. Разность между двумя адресами составляет 0x0065fd40 0x0065fd44, или 4. Это имеет смысл, поскольку переменная donuts имеет тип int, размерность которого составляет четыре байта.

14 Указатели и свободная память Используя в таком случае обыкновенные переменные, можно обработать значение как именованную величину, а его адрес как производную величину. Теперь ознакомимся с принципом использования указателей, который занимает важное место в философии языка C++ относительно программного управления памятью.

15 Указатели и свободная память Объектно-ориентированное программирование отличается от традиционного процедурного тем, что решения принимаются во время выполнения, а не во время компиляции ("во время выполнения" значит, в то время, когда программа работает, а "во время компиляции" когда компилятор собирает программу).

16 Указатели и свободная память Принятие решения во время выполнения обеспечивает гибкость, что позволяет приспосабливаться к текущим обстоятельствам. Рассмотрим, например, процесс распределения памяти для массива. Традиционный способ задания массива состоит в том, чтобы объявить массив, а объявление массива в C++ сопряжено с необходимостью привязки к определенному размеру массива. Таким образом, размер массива устанавливается во время компиляции программы. Это можно назвать принятием решения во время компиляции. В ООП предпринимается попытка сделать программу более гибкой за счет того, что подобные решения откладываются до времени выполнения.

17 Указатели и свободная память Чтобы сделать такой подход возможным, язык должен обеспечить возможность создавать массив или его эквивалент во время выполнения программы. Как мы вскоре увидим, принятая в C++ методика предусматривает использование ключевого слова new для запрашивания корректного объема памяти, и указателей для отслеживания расположения зарезервированной памяти.

18 Указатели и свободная память В соответствии с новой стратегией обработки хранящихся в памяти данных местоположение значения трактуется как именованная величина, а значение как производная (порожденная) величина. Специальный тип переменной указатель содержит Имя указателя представляет местонахождение значения в памяти. Применяя операцию *, называемую косвенным значением или операцией разыменования, получаем значение, хранящееся по данному адресу. Предположим, что manly - это указатель. Тогда выражение manly представляет собой адрес, a *manly значение по данному адресу. Комбинация *manly становится эквивалентом обычной переменной типа int.

19 Указатели и свободная память

20 Указатели и свободная память

21 Указатели и свободная память Очевидно, что переменная updates типа int и переменная- указатель p_updates являются всего лишь двумя сторонами одной и той же медали. Переменная updates представляет прежде всего значение, а операция & используется для получения адреса, тогда как переменная p_updates представляет адрес, а операция * служит для получения значения(рис.4.8). Поскольку p_updates указывает на значение updates, выражения *p_updates и updates совершенно эквивалентны. Выражение *p_updates можно использовать точно так же, как переменную типа int.

22 Указатели и свободная память

23 Объявление и инициализация указателей Рассмотрим процесс объявления указателей. Компьютеру необходимо отслеживать тип значения, на которое ссылается указатель. Например, адрес переменной типа char выглядит так же, как и адрес переменной типа double, но для типов char и double используется различное количество байтов и различные внутренние форматы для хранения значений. Поэтому при объявлении указателя необходимо точно определять, на какой тип данных он указывает. Последний пример содержит следующее объявление: int * p_updates; Это выражение указывает, что комбинация * p_updates принадлежит к типу int.

24 Объявление и инициализация указателей Это выражение указывает, что комбинация *p_updates принадлежит к типу int. Поскольку операция * выполняется по отношению к указателю, переменная p_updates сама должна быть указателем. Мы говорим, что p_updates указывает на значение типа int. Мы также говорим, что типом для p_updates должен быть указатель на int или, более кратко, int*. Повторим: p_updates является указателем (адресом), a *p_updates переменной типа int, но не указателем.

25 Объявление и инициализация указателей

26 Объявление и инициализация указателей Отделять пробелами знак операции * не обязательно. Программисты, работающие на С, традиционно применяют следующую форму записи: int *ptr; При использовании данной формы записи подчеркивается то, что комбинация *ptr имеет значение типа int. Широко распространена и другая форма этого выражения: int* ptr; В ней акцентируется внимание на том, что int* это тип "указатель на int". Компилятору же совершенно безразлично, где будут размещены пробелы. Однако не следует упускать из виду, что, например, объявление int* pi, р 2; создает один указатель (pi) и одну обычную переменную типа int (p2), другими словами, необходимо использовать знак * для каждого имени объявляемой переменной-указателя.

27 Объявление и инициализация указателей Для объявления указателей на другие типы переменных используется аналогичный синтаксис: double * tax_ptr; // tax_ptr указывает на тип double char * str; // str указывает на тип char Поскольку tax_ptr объявляется как указатель на тип double, компилятор определяет, что значение *tax_ptr принадлежит к типу double. Другими словами, он распознает, что *tax_ptr представляет собой величину, которая хранится в формате с плавающей точкой и занимает восемь байтов. Переменная-указатель никогда не бывает просто указателем. Она всегда указывает на определенный тип данных.

28 Объявление и инициализация указателей Для инициализации указателя можно использовать оператор объявления. В этом случае инициализируется указатель, а не указываемое значение. Таким образом, следующие операторы: int higgens = 5; int * pt = &higgens; присваивают указателю pt (но не *pt) значение &higgens.

29 Объявление и инициализация указателей В листинге 4.11 демонстрируется инициализация указателя с присвоением ему адреса.

30 Объявление и инициализация указателей Результаты выполнения программы: Value of higgens = 5; Address of higgens = 0068FDF0 Value of *pi = 5; Value of pi = 0068FDF0 Можно видеть, что программа присваивает адрес переменной higgens указателю pi, но не значению *pi.

31 Объявление и инициализация указателей Неосторожное использование указателей может привести к нежелательным последствиям. При создании указателя в C++ компьютер выделяет память для хранения адреса, но не для хранения данных, на которые указывает этот адрес. Чтобы выделить места для данных, требуется дополнительное действие. Если вы такое действие не выполните (как в приведенном ниже примере), то окажетесь на прямом пути к краху: long * fellow; // создание указателя на переменную типа long *fellow = ; // помещение значения по неопределенному адресу Конечно, переменная fellow является указателем. Но на что она указывает? Программа не присвоила ей адрес. Куда же будет помещено значение ? Неизвестно. Поскольку переменная fellow не была инициализирована, она может принять любое значение. Какое бы значение она ни имела, программа будет интерпретировать его как адрес, по которому нужно сохранить число Если fellow имеет значение 1200, то компьютер попытается поместить данные по адресу 1200, даже если окажется, что этот адрес находится в середине кода программы. Понятно одно: куда бы ни указывал указатель fellow, это не то место, куда вы намерены поместить число Ошибки подобного рода коварны. Кроме того, их сложно выявлять. Золотое правило для указателей: ВСЕГДА присваивайте указателю определенное и правильное значение адреса до применения к нему операции разыменования (*).

32 Указатели и числа Указатели не принадлежат к целочисленным типам, даже несмотря на то, что в компьютере, как правило, адреса хранятся как целочисленные значения. Более того, указатели принципиально отличаются от типа целочисленных значений. Целочисленные значения являются числами, с которыми можно производить сложение, вычитание, деление и т.д. Указатель же описывает местонахождение, и поэтому, например, перемножение двух адресов лишено смысла.

33 Указатели и числа Различие между указателями и целочисленными значениями проявляется и операциях, в которых участвуют данные обоих типов; нельзя просто присвоить целочисленное значение переменной-указателю: int * pt; pt = 0 хВ ; // несоответствие типов Здесь левая часть является указателем на тип int, поэтому ей можно присвоить значение адреса, а вот правая просто целочисленное значение. Вы, возможно, и знаете, что 0xB представляет собой комбинированный адрес области видеопамяти, состоящий из сегмента и смещения, но программа к такому выводу прийти никак не сможет: ведь ничто в операторе прямо не указывает на то, что данное значение является адресом.

34 Указатели и числа Язык С позволяет выполнять подобные присвоения, однако в C++ принято более строгое соблюдение типов данных, и компилятор выдаст сообщение об ошибке несовпадения типов. Чтобы использовать числовое значение как адрес, необходимо выполнить приведение типов для преобразования числа в приемлемый для значения адреса тип: int * pt; pt = (int *) 0 хВ ; // сейчас типы совпадают Теперь обе части оператора присваивания представляют собой адреса целочисленных значений, поэтому присвоение возможно. Заметим, что переменная pt сама по себе не принадлежит к типу int на том основании, что она является адресом значения типа int.

35 Выделение памяти с помощью оператора new До сих пор мы присваивали указателям адреса переменных. Переменная является именованной областью памяти, которая резервируется во время компиляции, а указатели всего лишь служат псевдонимами для областей памяти, к которым можно обратиться напрямую по имени. Истинная ценность указателей проявляется, когда для хранения данных выделяется неименованная область памяти во время выполнения программы. В этом случае указатели становятся единственным способом доступа к этим областям памяти. В C++ есть эффективное средство оператор new.

36 Выделение памяти с помощью оператора new Попробуем использовать эту новую технологию создадим неименованное хранилище значений типа int во время исполнения программы и будем обращаться к этим значениям с помощью указателя. Здесь ключевое значение имеет оператор new. Мы указываем оператору new, для какого типа данных выделяется память. Оператор new ищет блок памяти нужного размера и возвращает адрес этого блока. Присваивая этот адрес указателю, мы получаем желаемое. Ниже приводится пример реализации описанной технологии: int * pn = new int;

37 Выделение памяти с помощью оператора new Выражение new int сообщает программе о том, что необходимо некоторое новое хранилище, подходящее для размещения значения типа int. Оператор new анализирует тип для вычисления необходимого количества байтов. Затем он отыскивает область памяти и возвращает адрес. Далее присваиваем адрес переменной рn, которая объявляется как указатель на тип int. Теперь рn является адресом, а *рn значением, хранящимся по этому адресу. Сравним это с присваиванием указателю адреса переменной: int higgens; int * pt = &higgens;

38 Выделение памяти с помощью оператора new В обоих примерах (рn и pt) указателю присваивается адрес переменной типа int. Во втором случае можно также обращаться к переменной типа int по имени: higgens. В первом же случае обращение возможно исключительно через указатель. Возникает вопрос: так как область памяти, на которую указывает рn, не имеет имени, как же к ней обращаться? Мы говорим, что рn указывает на объект данных. "Объект данных", под которым понимается любой блок памяти, выделенный для хранения элементарной группы данных, является более общим понятием, чем "переменная". Поэтому переменная также является объектом данных, но память, на которую указывает рn, не является переменной. Способ управления объектами данных с помощью указателей может показаться на первый взгляд довольно неудобным, однако он обеспечивает большие возможности по управлению организацией памяти в программе.

39 Выделение памяти с помощью оператора new Общая форма записи для получения и назначения области памяти для одного объекта данных, которым может быть как структура, так и основной тип, имеет следующий вид: имя Типа имя_указателя = new имя Типа Тип данных используется дважды: один раз для указания вида запрашиваемой памяти и второй раз для объявления подходящего указателя.

40 Выделение памяти с помощью оператора new

41 Выделение памяти с помощью оператора new Результат выполнения программы: int value = 1001: location = 0x004301a8 double value = le+07: location = 0x004301d8 size of pt = 4: size of *pt = 4 size of pd = 4: size of *pd = 8

42 Выделение памяти с помощью оператора new Программа использует оператор new для выделения памяти объектам данных типа int и double. Это происходит при выполнении программы. Указатели pt и pd указывают на эти два объекта данных. Без них невозможно обращаться к выделенным участкам памяти. Благодаря указателям, можно использовать выражения *pt и *pd просто как переменные. Значения выражениям *pt и *pd, присваиваются для того, чтобы присвоить значения новым объектам данных. Аналогично, выражения *pt и *pd используются для отображения этих значений с помощью объекта cout.

43 Выделение памяти с помощью оператора new Программа также демонстрирует одну из причин, по которой необходимо объявлять тип данных, на которые указывает указатель. Адрес сам по себе определяет только начало области памяти, в которой хранится объект, но не его тип или занимаемый размер. Но поскольку в программе use_new.cpp объявлены типы указателей, программа знает, что значение *pd относится к типу double с размерностью восемь байтов, в то время как значение *pt относится к типу int размерностью четыре байта. Когда программа use_new.cpp выводит значение *pd, конструкция cout определяет, сколько байтов нужно прочесть и как их представить.

44 Высвобождение памяти с помощью оператора delete Использование оператора new для запроса памяти представляет собой наиболее эффектную сторону механизма управления памятью в C++. Другой стороной является оператор delete, который позволяет высвободить память после того, как ее использование завершено. Память, которая возвращается, или высвобождается, может быть повторно использована другими модулями программы. В тексте программы за оператором delete должен следовать указатель на блок памяти, ранее зарезервированный с помощью оператора new: int * ps = new int; // выделение памяти с помощью оператора new... // использование памяти

45 Высвобождение памяти с помощью оператора delete delete ps; // очистка памяти с помощью оператора delete //no окончании выполнения программы В этом примере очищается область памяти, на которую указывает ps, но сам указатель ps не удаляется. Его можно использовать повторно, например, для указания на другую выделенную область памяти. Операторы new и delete всегда необходимо применять сбалансированно, иначе может произойти утечка памяти; другими словами, наступит момент, когда выделенную память нельзя будет больше использовать. Если утечка памяти чересчур велика, она может привести к останову программы при поиске дополнительной памяти.

46 Высвобождение памяти с помощью оператора delete Не следует пытаться повторно очистить блок памяти, который уже высвобожден. Результат такого действия не определен. Нельзя также использовать оператор delete для освобождения памяти, созданной при объявлении переменных: int * ps = new int; // допустимо delete ps; // допустимо delete ps; // уже недопустимо int jugs = 5; // допустимо int * pi = & jugs; // допустимо delete pi ; // недопустимо, память // не выделена оператором new

47 Высвобождение памяти с помощью оператора delete Отметим, что важным залогом корректности работы оператора delete является то, что он должен применяться по отношению к области памяти, выделенной с помощью оператора new. Это не значит, что нужно использовать тот же указатель, что и для new; важно, чтобы это был тот же самый адрес: int * ps = new int; // распределение памяти int * pq = ps; // второму указателю присваивается // адрес того же самого блока памяти delete pq; // применение оператора delete со // вторым указателем Обычно не требуется создавать два указателя на один и тот же блок памяти, поскольку возрастает вероятность того, что вы ошибочно попытаетесь очистить один и тот же блок памяти дважды.

48 Использование оператора new для создания динамических массивов Предположим, что создается программа, для которой массив может либо потребоваться, либо нет, в зависимости от информации, получаемой в ходе выполнения программы. Если массив создается путем объявления, часть памяти будет отведена под него уже при компиляции программы. Независимо от того, использует программа созданный массив или нет, память он занимать будет в любом случае. Выделение памяти для массива в процессе компиляции называется статическим связыванием. Это означает, что массив будет встроен в программу во время компиляции.

49 Использование оператора new для создания динамических массивов Однако оператор new позволяет либо создавать массив во время выполнения программы, если возникает необходимость, либо не создавать, если такой необходимости не возникнет. Можно также выбирать размер массива в процессе выполнения программы. Процесс создания массива в ходе выполнения программы называется динамическим связыванием. Такой массив называется динамическим.

50 Использование оператора new для создания динамических массивов При статическом связывании необходимо определить размер массива во время создания программы, а при динамическом связывании программа сама принимает решение о размере создаваемого массива в ходе своего выполнения. Теперь рассмотрим два главных вопроса, касающихся динамических массивов: как использовать оператор языка C++ new для создания массива и указатель для доступа к элементам массива.

51 Создание динамического массива с помощью оператора new Необходимо указать оператору new тип элементов массива и их количество. Например, если нужно задать массив из десяти переменных типа int, необходимо использовать следующее выражение: int * psome = new int [10]; // блок из 10 значений типа int Оператор new возвращает адрес первого элемента выделенного блока. В приведенном примере это значение присваивается указателю psome.

52 Создание динамического массива с помощью оператора new Количество вызовов оператора new необходимо согласовать с количеством вызовов оператора delete, чтобы высвободить блок памяти, когда программа завершит его использование. Если для создания массива используется оператор new, следует употребить другую форму оператора delete, которая указывает, что высвобождается именно память, отведенная для массива: delete [ ] psome; // высвобождается память, отведенная для // динамического массива

53 Создание динамического массива с помощью оператора new Квадратные скобки указывают, что необходимо высвободить память, отведенную для целого массива, а не только для элемента, на который указывает указатель. Заметим, что квадратные скобки располагаются между оператором delete и именем указателя. Если оператор new используется без квадратных скобок, то и оператор delete должен быть без квадратных скобок; то же справедливо и в случае оператора new с квадратными скобками. int * pi = new int; short * ps = new short [500] ; delete [ ] pi;// результат не определен, не делайте так delete ps;// результат не определен, не делайте так

54 Создание динамического массива с помощью оператора new Кратко перечислим правила, которые необходимо соблюдать при применении операторов new и delete: Не используйте оператор delete для высвобождения памяти, которая не выделялась с помощью оператора new. Не используйте оператор delete для освобождения одного и того же блока памяти два раза подряд. Если для выделения памяти массиву был применен оператор new [], следует использовать оператор delete []. Если для выделения памяти единственному элементу был применен оператор new, следует использовать оператор delete (без квадратных скобок). К нулевому указателю оператор delete применять безопасно (ничего не происходит).

55 Создание динамического массива с помощью оператора new psome является указателем на один элемент типа int, первый элемент блока. А следить за количеством элементов блока уже ваша забота. Компилятор не отслеживает то, что psome указывает на первые десять целочисленных элементов, поэтому необходимо реализовывать проверку количества элементов в программе. Обычно программа отслеживает объем выделяемой памяти, благодаря чему впоследствии обеспечивается возможность корректно ее высвободить с помощью оператора delete [ ]. Но эта информация не является общедоступной нельзя применить оператор sizeof, например, для определения количества байтов в динамическом массиве.

56 Создание динамического массива с помощью оператора new Общая форма выделения и присвоения области памяти для массива имеет следующий вид: Type_name pointer_name = new type_name [num_elements] ; В результате вызова оператора new резервируется блок памяти, достаточный для хранения элементов num_eiements типа type_name, а указатель pointer_name устанавливается на первый элемент блока. Как можно видеть, pointer__name во многих случаях можно использовать как имя массива.

57 Использование динамического массива Сначала рассмотрим эту проблему на понятийном уровне. Оператор int * psome = new int [10]; // получаем группу из 10 целых чисел создает указатель psome, который указывает на первый элемент группы из десяти переменных типа int.

58 Использование динамического массива Теперь рассмотрим проблему с практической точки зрения. Как выполняется обращение к какому-либо из этих элементов? С первым элементом нет никаких проблем. Поскольку psome указывает на первый элемент массива, выражение *psome является значением первого элемента. Остается еще девять элементов, к которым надо получить доступ. Есть очень простой способ: нужно просто применять указатель так, как будто он является именем массива. Иными словами, для первого элемента можно использовать обозначение psome[0] вместо *psome, для второго psome [1] и т.д.

59 Использование динамического массива Причина в том, что языки С и C++ обрабатывают массивы внутренним образом, в любом случае используя указатели. Такое близкое подобие массивов и указателей является одним из преимуществ языков С и C++. Сейчас мы рассмотрим это подобие более подробно. Для начала взгляните на листинг 4.13, который демонстрирует использование оператора new для создания динамического массива, а затем применение синтаксиса обращения к массиву для доступа к его элементам. В этом листинге также проиллюстрировано фундаментальное различие между указателем и настоящим именем массива.

60 Использование динамического массива

61 Использование динамического массива Программа arraynew.cpp использует указатель рЗ, как будто он является именем массива, первый элемент которого р 3[0] и т.д. Принципиальная разница между именем массива и указателем проявляется в следующей строке: рЗ = рЗ + 1; // правильно для указателей, неправильно для имен массивов

62 Использование динамического массива Значение имени массива изменять нельзя, однако указатель является переменной, следовательно, его значение можно изменять. Обратите внимание на результат прибавления единицы к рЗ. Теперь выражение рЗ[0] ссылается на тот элемент массива, который раньше был вторым. Таким образом, после прибавления единицы к рЗ он указывает на второй элемент вместо первого. При вычитании единицы указатель возвращается к своему первоначальному значению, в результате чего программа может выполнить операцию delete [] с правильным адресом. Обратите внимание на то, что разница между реальными адресами последовательных чисел типа int обычно составляет 2 или 4 байта, а в предыдущем листинге адрес следующего элемента получается при прибавлении единицы к рЗ. Это свидетельствует о том, что арифметические операции с указателями имеют свои особенности.

63 Указатели, массивы и арифметика указателей Корни близкого подобия указателей и имен массивов следует искать в арифметике указателей, а также в особенностях внутренней обработки массивов в языке C++. Для начала обратимся к арифметике. В результате прибавления единицы к целой переменной ее значение увеличивается на единицу. Однако, прибавив единицу к переменной-указателю, мы увеличим ее значение на число байтов, соответствующее размерности типа, на который она указывает. Если прибавить единицу к указателю на тип double, то числовое значение указателя в системах с 8-байтовыми типами double увеличится на 8. А если прибавить единицу к указателю на тип short, то значение указателя увеличится на 2 при условии, что размерность типа short составляет 2 байта. В листинге 4.14 имя массива интерпретируется как адрес.

64 Указатели, массивы и арифметика указателей

65 Указатели, массивы и арифметика указателей

66 Указатели, массивы и арифметика указателей Примечания к программе Чаще всего в C++ имя массива интерпретируется как адрес его первого элемента. Так, оператор double * pw = wages; делает pw указателем на тип double и затем инициализирует pw с присвоением значения wages, что является адресом первого элемента массива wages. Для wages, равно как и для любого другого массива, можно записать следующее равенство: wages = &wages[0] = адрес первого элемента массива

67 Указатели, массивы и арифметика указателей С целью показать, что это не пустые слова, в программе явно используется операция адресации в выражении &stacks [0], чтобы присвоить указателю ps начальное значение, равное адресу первого элемента массива. Далее программа проверяет значения выражений pw и *pw. Первое является адресом, а второе значением, находящимся по этому адресу. Поскольку pw указывает на первый элемент, выражение *pw отображается как значение первого элемента, т.е Если прибавить 1 к переменной-указателю, его значение увеличивается на число, соответствующее размерности в байтах того типа, на который он указывает.

68 Указатели, массивы и арифметика указателей

69 Указатели, массивы и арифметика указателей Теперь рассмотрим выражение stacks [1]. Компилятор C++ обрабатывает это выражение так, как будто вы написали его как * (stacks + 1). Второе выражение означает вычисление адреса второго элемента массива, а затем поиск значения, которое хранится по этому адресу. В результате получается в точности значение элемента stacks [1]. Результат выполнения программы показывает, что * (stacks + 1) и stacks [1] - это одно и то же. Аналогично * (stacks + 2) то же самое, что и stacks [2]. В общем, когда бы вы ни использовали синтаксис обращений к массивам, среда C++ выполняет следующее преобразование: Имя Массива[i] преобразовывается в *(Имя Массива + i)

70 Указатели, массивы и арифметика указателей Если же вместо имени массива используется указатель, C++ выполняет такое же преобразование: Имя Укаэателя[i] преобразовывается в * (имя Указателя + i) Таким образом, во многих отношениях можно одинаково применять имена указателей и имена массивов. В обоих случаях допустимо использование квадратных скобок. Kак к массивам, так и к указателям можно применять операцию разыменования (*). В большинстве выражений любой из этих объектов представляет собой адрес. Единственное различие состоит в том, что значение указателя можно изменять, в то время как имя массива является константой.

71 Указатели, массивы и арифметика указателей Второе различие заключается в том, что, применяя оператор sizeof к имени массива, мы получаем размер массива, а применяя sizeof к указателю, получаем размер указателя, даже если он указывает на массив. Например, в листинге 4.15 оба указателя, pw и wages, указывают на один и тот же массив. Но применение к ним оператора sizeof дает следующие результаты: 24 = size of wages array // отображение размера массива wages 4 = size of pw pointer // отображение размера указателя pw Одним словом, очень просто использовать оператор new для создания массива, а указатель для обращения к различным элементам: достаточно всего лишь интерпретировать его как имя массива.

72 Основные сведения об указателях Объявление указателей. Чтобы объявить указатель на какой-либо тип, используйте следующую форму записи: имя Типа * имя Указателя; Примеры: double * рn; // рn указывает на значение double char * рс; // рс указывает на значение char Здесь рn и рс указатели, a double * и char * обозначения для типов "указатель на double" и "указатель на char" в языке C++.

73 Основные сведения об указателях Присвоение значений указателям. Следует присвоить указателю адрес области памяти. Можно применить операцию & к имени переменной, чтобы получить адрес именованной области памяти, при этом оператор new возвращает адрес безымянной записи. Примеры: double bubble = 3.2; рn = &bubble; // присваивает указателю рn адрес bubble рс = new char; // присваивает указателю рс адрес только что зарезервированной области памяти char

74 Основные сведения об указателях Разыменование указателей. Разыменование указателей означает обращение к значению, на которое указывает указатель. Чтобы разыменовать указатель, примените к нему операцию разыменования (*). Так, если рn это указатель на bubble, как в предыдущем примере, то *рn это значение, на которое указывает рn (в данном случае 3.2). Примеры: cout « *рn; // выводит значение bubble *рс = 'S';// помещает 'S' в ячейку памяти, адресом которой является рс Применение синтаксиса обращения к массиву второй способ разыменования указателя. Например, выражения рn[0] и *рn эквивалентны. Никогда не разыменовывайте указатель, которому не был присвоен соответствующий адрес.

75 Основные сведения об указателях Различие между указателем и значением, на которое он указывает. Запомните, если pt это указатель на значение типа int, то *pt это не указатель на int, а полный эквивалент переменной типа int. Именно pt является указателем. Примеры: int * pt = new int; // присваивает адрес указателю pt *pt = 5; //сохраняет значение 5 по этому адресу Имена массивов. В большинстве контекстов C++ интерпретирует имя массива как эквивалент адреса его первого элемента. Пример: int tacos[10]/ // теперь tacos то же самое, что и &tacos[0] Единственное исключение это когда имя массива используется вместе с оператором sizeof. В таком случае оператор sizeof возвращает размер всего массива в байтах.

76 Основные сведения об указателях Арифметика указателей. Язык C++ позволяет прибавлять целые числа к указателям. Результат прибавления единицы равносилен прибавлению значения, равного количеству байтов в объекте, на который указывает указатель, к исходному значению адреса. Кроме того, можно вычитать целые числа из указателей, а также один указатель из другого. Последняя операция, результатом которой является целое число, является значимой, только если оба указателя указывают на один и тот же массив. В результате выполнения этой операции возвращается разница значений адресов двух элементов. Примеры: int tacos[10] = {5,2,8,4,1,2,2,4,6,8}; int * pt = tacos; // предположим, что pf и fog это адрес 3000 pt = pt + 1; // теперь pt равно 3004, если int занимает четыре байта int * pe = & tacos [9]; // ре равно 3036, если int занимает четыре байта ре = ре -1; // теперь ре равно 3032, адрес facos[8] int diff = ре pt; // diff равен 7, смещение адресов facos[8] и facos[1]

77 Основные сведения об указателях Динамическое и статическое связывание для массивов. Используйте объявление массива для создания массива со статическим связыванием, т.е. такого массива, размер которого устанавливается во время компиляции: int tacos[10]; // статическое связывание, // размер устанавливается во время компиляции Используйте оператор new [] для создания динамического массива. После завершения использования массива высвободить память с помощью оператора delete []: int size; cin » size; int * pz = new int [size]; // динамическое связывание, размер // устанавливается во время выполнения … delete [ ] pz; // освобождение памяти по завершении ее использования

78 Основные сведения об указателях Синтаксис обращения к массивам и указателям. Использование скобок в записи массива эквивалентно разыменованию указателя: tacos[0] равносильно *tacos и равносильно значению по адресу tacos tacos[3] равносильно *(tacos + 3) и равносильно значению по адресу tacos + з Это справедливо и для имен массивов, и для переменных-указателей, поэтому можно использовать как обозначение указателя, так и обозначение массива совместно с именами указателей и массивов. Примеры: int * pi = new int [10] ; //pi указывает на группу из 10 целых чисел *pi =5; // присваивает нулевому элементу значение 5 pi[0] = б; // переустанавливает элемент zero с присвоением значения б pi[9] =44; // присваивает элементу tenth значение 44 int tacos[10] ; *(tacos + 4) = 12; // присваивает элементу tacos[4] значение 12

79 Указатели и строки Особая связь между массивами и указателями распространяется также и на использование строк. Рассмотрим следующий код: char flower[10] = "rose"; cout « flower « "s are red\n"; Имя массива это адрес его первого элемента, поэтому flower в операторе cout это адрес элемента char, который содержит символ r. Объект cout предполагает, что адрес массива типа char это адрес строки, поэтому он выводит символ по этому адресу и затем продолжает вывод символов, пока не достигнет нулевого символа (\0). Одним словом, если присвоить оператору cout адрес символа, он выведет все символы, начиная с данного и заканчивая первым нулевым символом, который встретится за ним. Решающим здесь является не то, что flower это имя массива, а то, что flower выступает адресом элемента char. Это подразумевает, что можно использовать переменную-указатель на char в качестве аргумента оператора cout, так как она тоже является адресом элемента char.

80 Указатели и строки Давайте разберемся с завершающей частью оператора cout из предыдущего примера. Если flower является адресом первого символа строки, что же означает выражение "s are red\n"? В соответствии с тем, как оператор cout обрабатывает вывод строки, эта строка в кавычках тоже должна быть адресом. В действительности так оно и есть, поскольку в C++ строка в кавычках так же, как и имя массива, является адресом своего первого элемента. Рассматриваемый код на самом деле передает в cout не собственно строку, а ее адрес.

81 Указатели и строки Это значит, что строки в массиве, строковые константы в кавычках и строки, описанные указателями, обрабатываются эквивалентно. На самом деле каждый элемент передается как адрес, и при этом, безусловно, выполняется меньше работы, чем при обработке каждого символа в строке. Имя массива элементов типа char, указатель на элемент char и строковая константа в кавычках интерпретируются как адрес первого символа строки.

82 Указатели и строки Листинг 4.15 иллюстрирует использование различных видов строк. В нем применяются две функции из библиотеки строк. Функция strlen(), которую мы уже использовали ранее, возвращает длину строки. Функция strcpy() копирует строку из одного места в другое. У обеих функций есть прототипы в заголовочном файле cstring.

83 Указатели и строки

84 Указатели и строки

85 Указатели и строки

86 Указатели и строки Примечания к программе Программа, представленная в листинге 4.15, создает один массив типа char (animal) и две переменных-указателя на char (bird и ps). Программа начинается с присваивания массиву animal строки "bear", точно так же, как мы ранее присваивали значения массивам. Затем программа делает кое-что новое. Она присваивает строку указателю на char: const char * bird = "wren"; // bird содержит адрес строки На самом деле "wren" представляет собой адрес строки, поэтому этот оператор назначает адрес "wren" указателю bird. Это значит, что указатель bird можно использовать подобно строке "wren", как в следующем примере: cout « "A concerned " « bird « " speaks\n"

87 Указатели и строки Строковые литералы являются константами, поэтому в коде при их объявлении используется ключевое слово const. Употребление const означает, что указатель bird можно использовать для обращения к строке, но не для ее изменения. Наконец, указателю ps не было присвоено никакого адреса, поэтому он не указывает ни на одну строку. Из программы видно, что в операторе cout можно использовать имя массива animal и указатель bird равноценно. Они оба являются адресами строк. Поэтому объект cout отображает две строки ("bear" и "wren"), которые хранятся по этим адресам. Когда в коде допускается ошибка, которая заключена в попытке отобразить значение ps, возможен вывод пустой строки либо отображение "мусора", а иногда и сбой программы.

88 Указатели и строки При вводе ситуация иная. Массив animal можно использовать для ввода при условии, что вводимые строки будут достаточно короткими, чтобы уместиться в массиве. Однако было бы некорректно использовать для ввода указатель bird. Причины этого следующие: Некоторые компиляторы обрабатывают строковые литералы как константы, предназначенные только для чтения, в результате чего возникает ошибка времени выполнения при попытке их перезаписи новыми данными. В соответствии с требованиями стандарта в языке C++ строковые литералы должны быть константами, однако в некоторых компиляторах и по сей день реализован устаревший подход к интерпретации строковых литералов. В некоторых компиляторах используется единственная копия строкового литерала для представления всех его вхождений в программу.

89 Указатели и строки Рассмотрим второе положение подробнее. В языке C++ не гарантируется уникальное хранение строковых литералов. Другими словами, если строковый литерал "wren" используется в программе несколько раз, компилятор может хранить либо несколько копий строки, либо только одну копию. В последнем случае присваивание строки bird указателю на одну из строк "wren" приводит к тому, что он указывает на единственную копию этой строки. Считывание значения в одну строку могло бы затронуть все вхождения строки, которая казалась вам независимой от остального текста. В любом случае, поскольку указатель bird объявлен со спецификатором const, компилятор предотвращает любые попытки изменить содержимое того адреса, на который указывает bird. Еще хуже пытаться считывать информацию в область памяти, на которую указывает ps. Поскольку указателю ps не присвоено никакого адреса, вы не знаете, куда будет помещена информация. Возможна перезапись каких-либо данных, сохраненных ранее в памяти. К счастью, этих проблем очень легко избежать просто используйте достаточно большой массив типа char для вводимых данных. Не следует применять для приема вводимых данных строковые константы или указатели, которые не инициализированы.

90 Указатели и строки При считывании строки в программу нужно всегда использовать адрес, предварительно зарезервированный в памяти. Такой адрес может быть представлен в виде имени массива или указателя, который был инициализирован с помощью оператора new. Рассмотрим следующий фрагмент кода: ps = animal; // указатель ps указывает на строку cout « animal « " at " « (int *) animal « endl; cout « ps « " at " « (int *) ps « endl; Он выводит на экран следующие данные: fox at 0065fd30

91 Указатели и строки Обычно если в операторе cout используется указатель, выводится адрес. Но если тип указателя char *, оператор cout отображает строку, на которую ссылается указатель. Чтобы увидеть адрес строки, следует использовать приведение типа указателя к указателю другого типа, такого как int *, что и выполняет данный код. Поэтому указатель ps выводит строку "fox", а выражение (int *) ps адрес, по которому эта строка хранится. Обратите внимание: при присвоении указателя animal указателю ps копируется не строка, а ее адрес. В результате получаем два указателя (animal и ps) на один и тот же адрес в памяти и строку. Получить копию строки несколько сложнее. Во-первых, необходимо зарезервировать память для сохранения строки. Это можно сделать путем объявления второго массива или использования оператора new. Второй подход позволяет выделить для строки область памяти подходящего размера: ps = new char[strlen(animal) +1]; // получение новой области хранения

92 Указатели и строки Строка "fox" не заполняет массив animal полностью, поэтому имеет место перерасход памяти. В этом фрагменте кода используется функция strlen() для определения длины строки. Единица добавляется, чтобы учесть нулевой (завершающий) символ. Затем программа использует оператор new для резервирования ровно такого объема памяти, которого достаточно для хранения этой строки. Далее потребуется способ, позволяющий скопировать строку из массива animal во вновь выделенную область памяти. Если присвоить массив animal указателю ps, это только изменит адрес, который хранится в ps, и, таким образом, программа утратит единственную возможность обращаться ко вновь выделенной области памяти. Следовательно, вместо присваивания необходимо использовать библиотечную функцию strcpy(): strcpy(ps, animal); // копирует строку в новую область хранения

93 Указатели и строки Функция strcpy() принимает два аргумента. Первый из них это адрес назначения, второй адрес строки, которую требуется скопировать. Программисту необходимо позаботиться о том, чтобы по адресу назначения действительно находилась выделенная область памяти достаточного размера для хранения копии. В данном примере с этой целью была применена функция strlen() для определения правильного размераи оператор new для получения свободной памяти. Обратите внимание на то, что с помощью функции strcpy() и оператора new получены две отдельные копии строки "fox": fox at 0x0065fd30 fox at 0x004301c8 Отметим также, что оператор new обнаружил область свободной памяти, которая значительно удалена от области, занятой массивом animal.

94 Указатели и строки Часто бывает необходимо поместить строку в массив. Для инициализации массива следует использовать оператор присвоения (=) либо функцию strcpy() или stmcpy(). С функцией strcpy() читатель уже знаком; она работает следующим образом: char food[20] = "carrots"; // присвоение значения strcpy(food, "flan"); // другой способ Заметим, что конструкция, подобная следующей strcpy(food, "a picnic basket filled with many goodies"); может вызвать ошибку, поскольку размер массива food меньше размера строки. В данном случае функция скопирует остаток строки в байты памяти непосредственно сразу после массива, в результате чего могут быть перезаписаны данные, используемые программой.

95 Указатели и строки Во избежание этого следует применять функцию strncpy (). Для нее нужно указывать еще один, третий аргумент максимальное количество символов, которые требуется скопировать. Однако, если выделенная для этой функции память будет израсходована прежде, чем функция достигнет конца строки, нулевой символ не добавится. Поэтому функцию нужно использовать следующим образом: strncpy(food, "a picnic basket filled with many goodies", 19); food [19] = '\0'; Здесь происходит копирование до 19 символов в массив и затем последнему элементу присваивается нулевое значение. Если строка короче 19 символов, функция strncpy () добавляет нулевой символ раньше, для того чтобы отметить истинный конец строки.

96 Использование оператора new для создания динамических структур Преимущества создания массивов во время выполнения программы, а не во время компиляции читателю уже известны. То же самое можно сказать и о структурах. Если требуется выделять область памяти только для такого количества структур, которое необходимо программе в определенный момент, снова приходит на помощь оператор new. Он позволяет создавать динамические структуры. Слово "динамические" означает то, что память выделяется во время выполнения программы, а не во время компиляции. Кроме того, рассмотренная выше техника работы со структурами применима к классам, так как классы во многом похожи на структуры.

97 Использование оператора new для создания динамических структур Использование оператора new при работе со структурами происходит в два этапа: создание структуры и доступ к ее элементам. Чтобы создать структуру, следует указать ее тип с оператором new. Например, создать безымянную структуру типа inflatable и присвоить ее адрес подходящему указателю можно следующим образом: inflatable * ps = new inflatable; Здесь указателю ps присваивается адрес части свободной памяти, достаточной для хранения структуры типа inflatable.

98 Использование оператора new для создания динамических структур Получить доступ к элементам структуры сложнее. При создании динамической структуры нельзя применять оператор принадлежности (в виде точки) к имени структуры, поскольку у структуры нет имени. Все, что у вас есть, это ее адрес. В языке C++ имеется оператор, специально предназначенный для этого случая, оператор принадлежности в виде стрелки (->). Этот оператор, который образован дефисом и знаком "больше", играет для указателей на структуры ту же роль, что и оператор в виде точки для имен структур. Например, если ps указывает на структуру типа inflatable, то ps->price это элемент price указанной структуры.

99 Использование оператора new для создания динамических структур

100 Использование оператора new для создания динамических структур Второй, менее изящный подход основан на том факте, что если ps указатель на структуру, то *ps представляет значение, на которое он указывает, т.е. саму структуру. Далее, поскольку ps - это структура, то (*ps).price это элемент price структуры. В соответствии с правилами приоритета операторов в языке C++ в этой конструкции следует использовать круглые скобки. В листинге 4.16 используется оператор new для создания безымянной структуры, а также демонстрируются оба способа записи указателей для доступа к элементам структуры.

101 Использование оператора new для создания динамических структур

102 Использование оператора new для создания динамических структур

103 Использование оператора new для создания динамических структур Результаты выполнения программы из листинга 4.16: Enter name of inflatable item: Fabulous Frodo Enter volume in cubic feet: 1.4 Enter price: $17.99 Name: Fabulous Frodo Volume: 1.4 cubic feet Price: $17.99

104 Пример использования операторов new и delete Рассмотрим пример использования операторов new и delete для управления хранением в памяти данных, введенных с клавиатуры. В листинге 4.17 определяется функция, которая возвращает указатель на введенную строку. Эта функция считывает вводимые данные во временный массив большого размера и затем использует оператор new [] для создания блока памяти, размер которого точно подходит для сохранения введенной строки. После этого функция возвращает указатель на этот блок. Такой подход позволяет эффективно экономить память в программах.

105 Пример использования операторов new и delete Предположим, что в программе необходимо выполнить чтение 1000 строк, при этом самая длинная строка содержит 79 символов, но большинство строк намного короче. Если бы для хранения строк использовались массивы типа char, потребовалось бы задействовать 1000 массивов, каждый по 80 символов. Это составляет 80 тыс. байтов, в то время как большая часть такого блока памяти в итоге не используется. В качестве альтернативного варианта можно создать массив из 1000 указателей на тип char и затем использовать оператор new для резервирования только того объема памяти, который требуется для каждой строки. Так можно сэкономить десятки тысяч байтов, поскольку размер памяти настраивается соответственно вводимым данным. В листинге 4.17 показано несколько приемов использования данного подхода. Кроме того, демонстрируется применение оператора delete для высвобождения памяти в целях повторного использования.

106 Пример использования операторов new и delete

107 Пример использования операторов new и delete

108 Пример использования операторов new и delete Примечания к программе Во-первых, рассмотрим функцию getname(). В ней используется объект cin для помещения вводимого слова в массив temp, а затем оператор new, чтобы выделить память для сохранения слова. С учетом нулевого символа программе необходима память размером в strlen (temp) + 1 символ для сохранения строки. Это и есть значение, которое передается оператору new. После того как область памяти становится доступной, функция getname() использует стандартную библиотечную функцию strcpy() для копирования строки из временного массива temp в новый блок. Эта функция не проверяет, достаточно ли места для хранения данной строки. Вместо этого она запрашивает требуемое количество байтов с помощью оператора new. В результате функция возвращает указатель рn адрес копии строки.

109 Пример использования операторов new и delete В функции main () возвращенное значение (адрес) присваивается указателю name. Этот указатель определен в main(), но указывает на блок памяти, зарезервированный функцией getname(). Затем программа выводит строку и ее адрес. После высвобождения блока памяти, на который указывает name, функция main() вызывает getname () второй раз. В C++ не гарантируется, что только что освобожденный блок памяти будет обязательно задействован при ближайшем использовании оператора new, это может быть совершенно другая область памяти, что и подтверждается в данном примере выполнения программы. Отметим, что в рассмотренном примере функция getname() выделяет память, а функция main () высвобождает ее. Обычно нецелесообразно помещать операторы new и delete в разные функции, поскольку так скорее можно забыть использовать оператор delete. В этом примере операторы отделены с единственной целью продемонстрировать, что такое разделение возможно.

110 Автоматическое, статическое и динамическое выделение памяти В языке C++ существует три способа управления сохранением данных в памяти, которые зависят от метода выделения памяти автоматического, статического и динамического. Последний метод иногда называют выделением свободной области (free store) или кучи (heap). Объекты данных, для которых память выделяется каким-либо из этих трех методов, отличаются друг от друга своим временем существования. Мы кратко рассмотрим каждый вид данных.

111 Автоматические переменные Обычные переменные, определенные внутри функции, называются автоматическими. Они начинают свое существование автоматически, когда вызвана функция, которая их содержит, а прекращают существование, когда функция завершает работу. Например, массив temp в листинге 4.17 существует только до тех пор, пока функция getname() активна. Когда управление возвращается функции main(), память, используемая для temp, очищается автоматически. Если бы функция getname() возвращала адрес temp, указатель name в функции main () указывал бы на область памяти, которая в скором времени была использована повторно.

112 Статическая область хранения Статическая область хранения существует в течение всего времени выполнения программы. Существует два способа, позволяющие сделать переменную статической. Первый внешнее определение, т.е. вне функции. Второй использовать ключевое слово static при объявлении переменной: static double fee = 56.50; Главный вывод, который можно сделать сейчас об автоматической и статической памяти, состоит в том, что эти методы выделения памяти жестко определяют время существования переменной. Переменная существует либо во время выполнения всей программы (статическая переменная), либо только во время действия какой-либо из функций (автоматическая переменная).

113 Динамическая область хранения В то же время операторы new и delete обеспечивают более гибкий подход. Они управляют некоторой областью памяти, к которой C++ обращается как к свободной памяти. Эта область отделена от памяти, используемой для статических и автоматических переменных. Как видно из листинга 4.17, для выделения и высвобождения памяти допускается использование операторов new и delete в разных функциях. Таким образом, время существования данных не привязано жестко ко времени работы программы или функции.

114 Динамическая область хранения Что произойдет, если не использовать оператор delete после создания переменной в свободной области (или куче) с помощью оператора new? Динамическая переменная или конструкция продолжит существование даже после очистки памяти в соответствии с правилом определения области действия или времени существования объекта. По сути, после исчезновения указателя нет возможности доступа к конструкции, сохраненной в свободной области. Таким образом получается утечка памяти. Этот объем утраченной памяти не может использоваться до завершения программы. Отмена выделения данной области памяти невозможна!

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

116 Резюме Три типа составных данных в языке C++ это массивы, структуры и указатели. В массиве может храниться несколько значений одного и того же типа. Для обращения к отдельным элементам массива применяется индекс. Структура может содержать несколько значений разных типов в одном объекте данных. Для обращения к отдельным элементам структуры применяется оператор принадлежности (.). Первым шагом в использовании структур является создание шаблона структуры, который определяет, какие элементы она содержит. Имя или дескриптор шаблона становится впоследствии идентификатором нового типа данных. Затем можно объявлять структурные переменные этого типа.

117 Резюме Объединение может одновременно содержать только одно значение, которое, однако, может принадлежать к нескольким типам. Имя элемента показывает, какой тип используется в данный момент. Указатели это переменные, в которых хранятся адреса. Мы говорим, что указатель указывает на адрес, который он содержит. При объявлении указателя необходимо указывать, на объект какого типа он указывает. Результатом применения операции разыменования (*) к указателю является значение по адресу, на который он указывает.

118 Резюме Строка это последовательность символов, которая заканчивается нулевым символом. Строка может быть представлена строковой константой в кавычках; в этом случае нулевой символ неявно подразумевается. Строку можно хранить в массиве типа char и представлять ее указателем на данные типа char, который указывает на эту строку. Функция strlen() возвращает длину строки без учета нулевого символа. Функция strcpy() копирует строку из одной области памяти в другую. При использовании этих функций включайте в программу файл заголовков cstring или string.h.

119 Резюме Оператор new позволяет запрашивать память для какого-либо из объектов данных во время выполнения программы. Этот оператор возвращает адрес полученной области памяти, который можно присвоить указателю. Единственный способ получения доступа к этому блоку памяти использование указателя. Если объект данных простая переменная, можно использовать операцию разыменования (*) для получения значения, хранимого по этому адресу. Если объект данных представляет собой массив, для доступа к его элементам можно использовать указатель так, будто он является именем массива. Если объектом данных является структура, для доступа к ее элементам можно использовать операцию разыменования указателей (->).

120 Резюме Указатели и массивы тесно связаны. Если, например, аг это имя массива, то выражение ar[i] интерпретируется как *(ar + i), а имя массива как адрес первого элемента массива. Имя массива играет ту же роль, что и указатель. Можно использовать имя указателя и синтаксис обозначения массива для обращения к элементам массива, созданного с помощью оператора new. Операторы new и delete дают возможность явно управлять выделением памяти для объектов данных и ее высвобождением.

121 Резюме Автоматические переменные, объявленные внутри функции, и статические переменные, определенные за пределами функции или с помощью ключевого слова static, не позволяют гибко управлять распределением памяти. Автоматические переменные начинают существование, когда управление передается содержащему их блоку и исчезают, когда управление передается за пределы блока. Статические переменные существуют в течение всего времени выполнения программы.