Что возвращает new. Динамические объекты с подсчётом ссылок. Динамические объекты со стандартным управлением памятью

15.8. Операторы new и delete

По умолчанию выделение объекта класса из хипа и освобождение занятой им памяти выполняются с помощью глобальных операторов new() и delete(), определенных в стандартной библиотеке C++. (Мы рассматривали эти операторы в разделе 8.4.) Но класс может реализовать и собственную стратегию управления памятью, предоставив одноименные операторы-члены. Если они определены в классе, то вызываются вместо глобальных операторов с целью выделения и освобождения памяти для объектов этого класса.

Определим операторы new() и delete() в нашем классе Screen.

Оператор-член new() должен возвращать значение типа void* и принимать в качестве первого параметра значение типа size_t, где size_t – это typedef, определенный в системном заголовочном файле. Вот его объявление:

void *operator new(size_t);

Когда для создания объекта типа класса используется new(), компилятор проверяет, определен ли в этом классе такой оператор. Если да, то для выделения памяти под объект вызывается именно он, в противном случае – глобальный оператор new(). Например, следующая инструкция

Screen *ps = new Screen;

создает объект Screen в хипе, а поскольку в этом классе есть оператор new(), то вызывается он. Параметр size_t оператора автоматически инициализируется значением, равным размеру Screen в байтах.

Добавление оператора new() в класс или его удаление оттуда не отражаются на пользовательском коде. Вызов new выглядит одинаково как для глобального оператора, так и для оператора-члена. Если бы в классе Screen не было собственного new(), то обращение осталось бы правильным, только вместо оператора-члена вызывался бы глобальный оператор.

С помощью оператора разрешения глобальной области видимости можно вызвать глобальный new(), даже если в классе Screen определена собственная версия:

Screen *ps = ::new Screen;

void operator delete(void *);

Когда операндом delete служит указатель на объект типа класса, компилятор проверяет, определен ли в этом классе оператор delete(). Если да, то для освобождения памяти вызывается именно он, в противном случае – глобальная версия оператора. Следующая инструкция

освобождает память, занятую объектом класса Screen, на который указывает ps. Поскольку в Screen есть оператор-член delete(), то применяется именно он. Параметр оператора типа void* автоматически инициализируется значением ps. Добавление delete() в класс или его удаление оттуда никак не сказываются на пользовательском коде. Вызов delete выглядит одинаково как для глобального оператора, так и для оператора-члена. Если бы в классе Screen не было собственного оператора delete(), то обращение осталось бы правильным, только вместо оператора-члена вызывался бы глобальный оператор.

С помощью оператора разрешения глобальной области видимости можно вызвать глобальный delete(), даже если в Screen определена собственная версия:

В общем случае используемый оператор delete() должен соответствовать тому оператору new(), с помощью которого была выделена память. Например, если ps указывает на область памяти, выделенную глобальным new(), то для ее освобождения следует использовать глобальный же delete().

Оператор delete(), определенный для типа класса, может содержать два параметра вместо одного. Первый параметр по-прежнему должен иметь тип void*, а второй – предопределенный тип size_t (не забудьте включить заголовочный файл):

// заменяет

// void operator delete(void *);

Если второй параметр есть, компилятор автоматически инициализирует его значением, равным размеру адресованного первым параметром объекта в байтах. (Этот параметр важен в иерархии классов, когда оператор delete() может наследоваться производным классом. Подробнее наследование обсуждается в главе 17.)

Рассмотрим реализацию операторов new() и delete() в классе Screen более детально. В основе нашей стратегии распределения памяти будет лежать связанный список объектов Screen, на начало которого указывает член freeStore. При каждом обращении к оператору-члену new() возвращается следующий объект из списка. При вызове delete() объект возвращается в список. Если при создании нового объекта список, адресованный freeStore, пуст, то вызывается глобальный оператор new(), чтобы получить блок памяти, достаточный для хранения screenChunk объектов класса Screen.

Как screenChunk, так и freeStore представляют интерес только для Screen, поэтому мы сделаем их закрытыми членами. Кроме того, для всех создаваемых объектов нашего класса значения этих членов должны быть одинаковыми, а следовательно, нужно объявить их статическими. Чтобы поддержать структуру связанного списка объектов Screen, нам понадобится третий член next:

void *operator new(size_t);

void operator delete(void *, size_t);

static Screen *freeStore;

static const int screenChunk;

Вот одна из возможных реализаций оператора new() для класса Screen:

#include "Screen.h"

#include cstddef

// статические члены инициализируются

// в исходных файлах программы, а не в заголовочных файлах

Screen *Screen::freeStore = 0;

const int Screen::screenChunk = 24;

void *Screen::operator new(size_t size)

if (!freeStore) {

// связанный список пуст: получить новый блок

// вызывается глобальный оператор new

size_t chunk = screenChunk * size;

reinterpret_cast Screen* (new char[ chunk ]);

// включить полученный блок в список

p != &freeStore[ screenChunk - 1 ];

freeStore = freeStore-next;

А вот реализация оператора delete():

void Screen::operator delete(void *p, size_t)

// вставить "удаленный" объект назад,

// в список свободных

(static_cast Screen* (p))-next = freeStore;

freeStore = static_cast Screen* (p);

Оператор new() можно объявить в классе и без соответствующего delete(). В таком случае объекты освобождаются с помощью одноименного глобального оператора. Разрешается также объявить и оператор delete() без new(): объекты будут создаваться с помощью одноименного глобального оператора. Однако обычно эти операторы реализуются одновременно, как в примере выше, поскольку разработчику класса, как правило, нужны оба.

Они являются статическими членами класса, даже если программист явно не объявит их таковыми, и подчиняются обычным ограничениями для подобных функций-членов: им не передается указатель this, а следовательно, напрямую они могут получить доступ только к статическим членам. (См. обсуждение статических функций-членов в разделе 13.5.) Причина, по которой эти операторы делаются статическими, заключается в том, что они вызываются либо перед конструированием объекта класса (new()), либо после его уничтожения (delete()).

Выделение памяти с помощью оператора new(), например:

Screen *ptr = new Screen(10, 20);

// Псевдокод на C++

ptr = Screen::operator new(sizeof(Screen));

Screen::Screen(ptr, 10, 20);

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

Освобождение памяти с помощью оператора delete(), например:

эквивалентно последовательному выполнению таких инструкций:

// Псевдокод на C++

Screen::~Screen(ptr);

Screen::operator delete(ptr, sizeof(*ptr));

Таким образом, при уничтожении объекта сначала вызывается деструктор класса, а затем определенный в классе оператор delete() для освобождения памяти. Если значение ptr равно 0, то ни деструктор, ни delete() не вызываются.

15.8.1. Операторы new и delete

Оператор new(), определенный в предыдущем подразделе, вызывается только при выделении памяти для единичного объекта. Так, в данной инструкции вызывается new() класса Screen:

Screen *ps = new Screen(24, 80);

тогда как ниже вызывается глобальный оператор new() для выделения из хипа памяти под массив объектов типа Screen:

// вызывается Screen::operator new()

Screen *psa = new Screen;

В классе можно объявить также операторы new() и delete() для работы с массивами.

Оператор-член new() должен возвращать значение типа void* и принимать в качестве первого параметра значение типа size_t. Вот его объявление для Screen:

void *operator new(size_t);

Когда с помощью new создается массив объектов типа класса, компилятор проверяет, определен ли в классе оператор new(). Если да, то для выделения памяти под массив вызывается именно он, в противном случае – глобальный new(). В следующей инструкции в хипе создается массив из десяти объектов Screen:

Screen *ps = new Screen;

В этом классе есть оператор new(), поэтому он и вызывается для выделения памяти. Его параметр size_t автоматически инициализируется значением, равным объему памяти в байтах, необходимому для размещения десяти объектов Screen.

Даже если в классе имеется оператор-член new(), программист может вызвать для создания массива глобальный new(), воспользовавшись оператором разрешения глобальной области видимости:

Screen *ps = ::new Screen;

Оператор delete(), являющийся членом класса, должен иметь тип void, а в качестве первого параметра принимать void*. Вот как выглядит его объявление для Screen:

void operator delete(void *);

Чтобы удалить массив объектов класса, delete должен вызываться следующим образом:

Когда операндом delete является указатель на объект типа класса, компилятор проверяет, определен ли в этом классе оператор delete(). Если да, то для освобождения памяти вызывается именно он, в противном случае – его глобальная версия. Параметр типа void* автоматически инициализируется значением адреса начала области памяти, в которой размещен массив.

Даже если в классе имеется оператор-член delete(), программист может вызвать глобальный delete(), воспользовавшись оператором разрешения глобальной области видимости:

Добавление операторов new() или delete() в класс или удаление их оттуда не отражаются на пользовательском коде: вызовы как глобальных операторов, так и операторов-членов выглядят одинаково.

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

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

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

У оператора-члена delete() может быть не один, а два параметра, при этом второй должен иметь тип size_t:

// заменяет

// void operator delete(void*);

void operator delete(void*, size_t);

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

Из книги Справочное руководство по C++ автора Страустрап Бьярн

R.5.3.4 Операция delete Операция delete уничтожает объект, созданный с помощью new.выражение-освобождения: ::opt delete выражение-приведения::opt delete выражение-приведенияРезультат имеет тип void. Операндом delete должен быть указатель, который возвращает new. Эффект применения операции delete

Из книги Microsoft Visual C++ и MFC. Программирование для Windows 95 и Windows NT автора Фролов Александр Вячеславович

Операторы new и delete Оператор new создает объект заданного типа. При этом он выделяет память, необходимую для хранения объекта и возвращает указатель, указывающий на него. Если по каким-либо причинам получить память не удается, оператор возвращает нулевое значение. Оператор

Из книги Эффективное использование C++. 55 верных способов улучшить структуру и код ваших программ автора Мейерс Скотт

Правило 16: Используйте одинаковые формы new и delete Что неправильно в следующем фрагменте?std::string *stringArray = new std::string;...delete stringArray;На первый взгляд, все в полном порядкеиспользованию new соответствует применение delete, но кое-что здесь совершенно неверно. Поведение программы

Из книги Windows Script Host для Windows 2000/XP автора Попов Андрей Владимирович

Глава 8 Настройка new и delete В наши дни, когда вычислительные среды снабжены встроенной поддержкой «сборки мусора» (как, например, Java и. NET), ручной подход C++ к управлению памятью может показаться несколько устаревшим. Однако многие разработчики, создающие требовательные к

Из книги Стандарты программирования на С++. 101 правило и рекомендация автора Александреску Андрей

Метод Delete Если параметр force равен false или не указан, то с помощью метода Delete будет нельзя удалить каталог с атрибутом "только для чтения" (read-only). Установка для force значения true позволит сразу удалять такие каталоги.При использовании метода Delete неважно, является ли заданный

Из книги Справочник по Flash автора Коллектив авторов

Метод Delete Если параметр force равен false или не указан, то с помощью метода Delete будет нельзя удалить файл с атрибутом "только для чтения" (read-only). Установка для force значения true позволит сразу удалять такие файлы. Замечание Вместо метода Delete можно использовать метод DeleteFile

Из книги Firebird РУКОВОДСТВО РАЗРАБОТЧИКА БАЗ ДАННЫХ автора Борри Хелен

Операторы отношения и логические операторы Операторы отношения используются для сравнения значений двух переменных. Эти операторы, описанные в табл. П2.11, могут возвращать только логические значения true или false.Таблица П2.11. Операторы отношения Оператор Условие, при

Из книги Linux и UNIX: программирование в shell. Руководство разработчика. автора Тейнсли Дэвид

45. new и delete всегда должны разрабатываться вместе РезюмеКаждая перегрузка void* operator new(parms) в классе должна сопровождаться соответствующей перегрузкой оператора void operator delete(void* , parms), где parms - список типов дополнительных параметров (первый из которых всегда std::size_t). То же

Из книги Справка по SQL автора

delete - Удаление объекта, элемента массива или переменной delete(Оператор)Этот оператор используется для удаления из сценария объекта, свойства объекта, элемента массива или переменных.Синтаксис:delete identifier;Аргументы:Описание:Оператор delete уничтожает объект или переменную, имя

Из книги Понимание SQL автора Грубер Мартин

Оператор DELETE Запрос DELETE используется для удаления целых строк таблицы. SQL не дает возможности одному оператору DELETE удалять строки более чем из одной таблицы. Запрос DELETE, который изменяет только одну текущую строку курсора, называется позиционированным удалением.

Из книги автора

15.8. Операторы new и delete По умолчанию выделение объекта класса из хипа и освобождение занятой им памяти выполняются с помощью глобальных операторов new() и delete(), определенных в стандартной библиотеке C++. (Мы рассматривали эти операторы в разделе 8.4.) Но класс может реализовать

Из книги автора

15.8.1. Операторы new и delete Оператор new(), определенный в предыдущем подразделе, вызывается только при выделении памяти для единичного объекта. Так, в данной инструкции вызывается new() класса Screen:// вызывается Screen::operator new()Screen *ps = new Screen(24, 80);тогда как ниже вызывается

Массивы и указатели на самом деле тесно связаны. Имя массива является указателем-константой , значением которой служит адрес первого элемента массива (&arr). Следовательно, имя массива может являться инициализатором указателя к которому будут применимы все правила адресной арифметики, связанной с указателями. Пример программы:
Программа 11.1

#include using namespace std; int main() { const int k = 10; int arr[k]; int *p = arr; // указатель указывает на первый элемент массива for (int i = 0; i < 10; i++){ *p = i; p++; // указатель указывает на следующий элемент } p = arr; // возвращаем указатель на первый элемент for (int i = 0; i < 10; i++){ cout << *p++ << " "; } cout << endl; // аналогично: for (int i = 0; i < 10; i++){ cout << *(arr + i) << " "; } cout << endl; p = arr; // выводим адреса элементов: for (int i = 0; i < 10; i++){ cout << "arr[" << i << "] => " << p++ << endl; } return 0; }

Вывод программы:

0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 arr => 0xbffc8f00 arr => 0xbffc8f04 arr => 0xbffc8f08 arr => 0xbffc8f0c arr => 0xbffc8f10 arr => 0xbffc8f14 arr => 0xbffc8f18 arr => 0xbffc8f1c arr => 0xbffc8f20 arr => 0xbffc8f24

Выражение arr[i] – обращение к элементу по индексу соответствует выражению *(arr + i) , которое называется указателем-смещением (строка 22). Это выражение более наглядно иллюстрирует, как C++ на самом деле работает с элементами массива. Переменная-счетчик i указывает на сколько элементов необходимо сместиться от первого элемента . В строке 17 значение элемента массива выводится после разыменования указателя.

Что означает выражение *p++ ? Оператор * имеет более низкий приоритет, в тоже время постфиксный инкремент ассоциативен слева-направо. Следовательно, в этом сложном выражении сначала будет выполняться косвенная адресация (получение доступа к значению элемента массива), а затем инкрементация указателя. Иначе это выражение можно было бы представить так: cout Примечание . Оператор sizeof() , применяемый к имени массива, вернет размер всего массива (а не первого элемента).
Примечание . Оператор взятия адреса (&) для элементов массива используется также, как и для обычных переменных (элементы массива иногда называют индексированными переменными). Например, &arr . Поэтому можно всегда получить указатель на любой элемент массива. Однако, операция &arr (где arr - имя массива) вернет адрес всего массива и такая, например, операция (&arr + 1) будет означать шаг размером с массив, т. е. получение указателя на элемент, следующий за последним.

Преимущества использования указателей при работе с элементами массива

Рассмотрим два примера программ приводящих к одинаковому результату: элементам массива присваиваются новые значения от 0 до 1999999 и осуществляется их вывод.
Программа 11.2

#include using namespace std; int main() { const int n = 2000000; int mass[n] {}; for (int i = 0; i < n; i++) { mass[i] = i; cout << mass[i]; } return 0; }

Программа 11.3

#include using namespace std; int main() { const int n = 2000000; int mass[n] {}; int *p = mass; for (int i = 0; i < n; i++) { *p = i; cout << *p++; } return 0; }

Программа 11.3 будет выполняться быстрее, чем программа 11.2 (с ростом количества элементов эффективность программы 11.3 будет возрастать)! Причина заключается в том, что в программе 11.2 каждый раз пересчитывается местоположение (адрес) текущего элемента массива относительно первого (11.2, строки 12 и 13). В программе 11.3 обращение к адресу первого элемента происходит один раз в момент инициализации указателя (11.3, строка 11).

Выход за границы массива

Отметим еще одну важный аспект работы с С-массивами в С++. В языке С++ отсутствует контроль соблюдения выхода за границы С-массива . Т. о. ответственность за соблюдение режима обработки элементов в пределах границ массива лежит целиком на разработчике алгоритма. Рассмотрим пример.
Программа 11.4

#include #include #include using namespace std; int main() { int mas; default_random_engine rnd(time(0)); uniform_int_distribution < 10; i++) mas[i] = d(rnd); cout << "Элементы массива:" << endl; for (int i = 0; i < 10; i++) cout << mas[i] << endl; return 0; }

Программа выведет приблизительно следующее:

Элементы массива: 21 58 38 91 23 5 38 -1219324996 -1074960992 0

В программе 11.4 умышленно допущена ошибка. Но компилятор не сообщит об ошибке: в массиве объявлено пять элементов, а в циклах подразумевается, что элементов 10! В итоге, правильно проинициализированы будут только пять элементов (далее возможно повреждение данных), они же и будут выведены вместе с "мусором". С++ предоставляет возможность контроля границ с помощью библиотечных функций begin() и end() (необходимо подключить заголовочный файл iterator). Модифицируем программу 11.4
Программа 11.5

#include #include #include #include using namespace std; int main() { int mas; int *first = begin(mas); int *last = end(mas); default_random_engine rnd(time(0)); uniform_int_distribution d(10, 99); while(first != last) { *first = d(rnd); first++; } first = begin(mas); cout << "Элементы массива:" << endl; while(first != last) { cout << *first++ << " "; } return 0; }

Функции begin() и end() возвращают . Понятие итераторов мы раскроем позже, а пока скажем, что они ведут себя как указатели, указывающие на первый элемент (first) и элемент, следующий за последним (last). В программе 11.5 мы, для компактности и удобства, заменили цикл for на while (поскольку счетчик нам уже здесь не нужен - мы используем арифметику указателей). Имея два указателя мы легко можем сформулировать условие выхода из цикла, так как на каждом шаге цикла указатель first инкрементируется.
Еще одним способом сделать обход элементов массива более безопасным основан на применении цикла range-based for , упомянутого нами в теме ()

Операции new и delete

До момента знакомства с указателями вам был известен единственный способ записи изменяемых данных в память посредством переменных. Переменная - это поименованная область памяти. Блоки памяти для соответствующих переменных выделяются в момент запуска программы и используются до прекращения ее работы. С помощью указателей можно создавать неименованные блоки памяти определенного типа и размера (а также освобождать их) в процессе работы самой программы. В этом проявляется замечательная особенность указателей, наиболее полно раскрывающаяся в объектно-ориентированном программировании при создании классов.
Динамическое выделение памяти осуществляется с помощью операции new . Синтаксис:

Тип_данных *имя_указателя = new тип_данных;

Например:

Int *a = new int; // Объявление указателя типа int int *b = new int(5); // Инициализация указателя

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

#include using namespace std; int main() { int *a = new int(5); int *b = new int(4); int *c = new int; *c = *a + *b; cout << *c << endl; delete a; delete b; delete c; return 0; }

После выполнения работы с выделенной памятью ее необходимо освободить (вернуть, сделать доступной для других данных) с помощью операции delete . Контроль над расходованием памяти - важная сторона разработки приложений. Ошибки, при которых память не освобождается, приводят к "утечкам памяти ", что, в свою очередь, может привести к аварийному завершению программы. Операция delete может применяться к нулевому указателю (nullptr) или созданному с помощью new (т. о. new и delete используются в паре).

Динамические массивы

Динамический массив - это массив, размер которого определяется в процессе работы программы. Строго говоря C-массив не является динамическим в C++. То есть, можно определять только размер массива, а изменение размера массива, в процессе работы программы, по-прежнему невозможно. Для получения массива нужного размера необходимо выделять память под новый массив и копировать в него данные из исходного, а затем освобождать память выделенную ранее под исходный массив. Подлинно динамическим массивом в C++ является тип , который мы рассмотрим позднее. Для выделения памяти под массив используется операция new . Синтаксис выделения памяти для массива имеет вид:
указатель = new тип[размер] . Например:

Int n = 10; int *arr = new int[n];

Освобождение памяти производится с помощью оператора delete:

Delete arr;

При этом размер массива не указывается.
Пример программы. Заполнить динамический целочисленный массив arr1 случайными числами. Показать исходный массив. Переписать в новый динамический целочисленный массив arr2 все элементы с нечетными порядковыми номерами (1, 3, ...). Вывести содержимое массива arr2 .
Программа 11.7

#include #include #include using namespace std; int main() { int n; cout << "n = "; cin >> n; int *arr1 = new int[n]; default_random_engine rnd(time(0)); uniform_int_distribution d(10, 99); for (int i = 0; i < n; i++) { arr1[i] = d(rnd); cout << arr1[i] << " "; } cout << endl; int *arr2 = new int; for (int i = 0; i < n / 2; i++) { arr2[i] = arr1; cout << arr2[i] << " "; } delete arr1; delete arr2; return 0; } n = 10 73 94 17 52 11 76 22 70 57 68 94 52 76 70 68

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

Int **arr = new int *[m];

где m - количество таких массивов (строк двумерного массива).
Пример задачи. Заполнить случайными числами и вывести элементы двумерного динамического массива.
Программа 11.8

#include #include #include #include using namespace std; int main() { int n, m; default_random_engine rnd(time(0)); uniform_int_distribution d(10, 99); cout << "Введите количество строк:" << endl; cout << "m = "; cin >> m; cout << "введите количество столбцов:" << endl; cout << "n = "; cin >> n; int **arr = new int *[m]; // заполнение массива: for (int i = 0; i < m; i++) { arr[i] = new int[n]; for (int j = 0; j < n; j++) { arr[i][j] = d(rnd); } } // вывод массива: for (int i = 0; i < m; i++) { for (int j = 0; j < n; j++) { cout << arr[i][j] << setw(3); } cout << "\n"; } // освобождение памяти выделенной для каждой // строки: for (int i = 0; i < m; i++) delete arr[i]; // освобождение памяти выделенной под массив: delete arr; return 0; } Введите количество строк: m = 5 введите количество столбцов: n = 10 66 99 17 47 90 70 74 37 97 39 28 67 60 15 76 64 42 65 87 75 17 38 40 81 66 36 15 67 82 48 73 10 47 42 47 90 64 22 79 61 13 98 28 25 13 94 41 98 21 28

Вопросы
  1. В чем заключается связь указателей и массивов?
  2. Почему использование указателей при переборе элементов массива более эффективно, нежели использование операции обращения по индексу ?
  3. В чем суть понятия "утечка памяти"?
  4. Перечислите способы предупреждения выхода за границы массива?
  5. Что такое динамический массив? Почему в С++ С-массив не является динамическим по существу?
  6. Опишите процесс создания динамического двумерного массива
Презентация к уроку
Домашнее задание

Используя динамические массивы решить следующую задачу: Дан целочисленный массив A размера N . Переписать в новый целочисленный массив B все четные числа из исходного массива (в том же порядке) и вывести размер полученного массива B и его содержимое.

Учебник

§62 (10) §40 (11)

Литература
  1. Лафоре Р. Объектно-ориентированное программирование в C++ (4-е изд.). Питер: 2004
  2. Прата, Стивен. Язык программирования C++. Лекции и упражнения, 6-е изд.: Пер. с англ. - М.: ООО «И.Д. Вильяме», 2012
  3. Липпман Б. Стенли, Жози Лажойе, Барбара Э. Му. Язык программирования С++. Базовый курс. Изд. 5-е. М: ООО "И. Д. Вильямс", 2014
  4. Эллайн А. C++. От ламера до программера. СПб.: Питер, 2015
  5. Шилдт Г. С++: Базовый курс, 3-изд. М.: Вильямс, 2010

Как известно, в языке С для динамического выделения и освобождения памяти используются фун­кции malloc() и free(). Вместе с тем С++ содержит два оператора, выполняющих выделение и освобождение памяти более эффективно и более просто. Этими операторами являются new и delete. Их общая форма имеет вид:

переменная_указатель = new тип_переменной;

delete переменная_указатель;

Здесь переменная_указaтель является указателем типа тип_переменной. Оператор new выделяет память для хранения значения типа тип_переменной и возвращает ее адрес. С помощью new могут быть размещены любые типы данных. Оператор delete освобождает память, на которую указывает указатель переменная_указатель.

Если операция выделения памяти не может быть выполнена, то оператор new генерирует ис­ключение типа xalloc. Если программа не перехватит это исключение, тогда она будет снята с выполнения. Хотя для коротких программ такое поведение по умолчанию является удовлетвори­тельным, для реальных прикладных программ обычно требуется перехватить исключение и обра­ботать его соответствующим образом. Для того чтобы отследить это исключение, необходимо вклю­чить заголовочный файл except.h.

Оператор delete следует использовать только для указателей на память, выделенную с исполь­зованием оператора new. Использование оператора delete с другими типами адресов может по­родить серьезные проблемы.

Есть ряд преимуществ использования new перед использованием malloc(). Во-первых, оператор new автоматически вычисляет размер необходимой памяти. Нет необходимости в использовании оператора sizeof(). Более важно то, что он предотвращает случайное выделение неправильного количества памяти. Во-вторых, оператор new автоматически возвращает указатель требуемого типа, так что нет необходимости в использовании оператора преобразования типа. В-третьих, как ско­ро будет описано, имеется возможность инициализации объекта при использовании оператора new. И наконец, имеется возможность перегрузить оператор new и оператор delete глобально или по отношению к тому классу, который создается.

Ниже приведен простой пример использования операторов new и delete. Следует обратить вни­мание на использование блока try/catch для отслеживания ошибок выделения памяти.

#include
#include
int main()
{
int *p;
try {
p = new int; // выделение памяти для int
} catch (xalloc xa) {
cout << "Allocation failure.\n";
return 1;
}
*p = 20; // присвоение данному участку памяти значения 20
cout << *р; // демонстрация работы путем вывода значения
delete р; // освобождение памяти
return 0;
}

Эта программа присваивает переменной р адрес блока памяти, имеющего достаточный размер для того, чтобы содержать число целого типа. Далее этой памяти присваивается значение и содер­жимое памяти выводится на экран. Наконец, динамически выделенная память освобождается.

Как отмечалось, можно инициализировать память с использованием оператора new. Для этого надо указать инициализирующее значение в скобках после имени типа. Например, в следующем примере память, на которую указывает указатель р, инициализируется значением 99:

#include
#include
int main()
{
int *p;
try {
p = new int (99); // инициализация 99-ю
} catch (xalloc xa) {
cout << "Allocation failure.\n";
return 1;
}
cout << *p;
delete p;
return 0;
}

С помощью new можно размещать массивы. Общая форма для одномерного массива имеет вид:

переменная_указатель = new тип_переменной [размер];

Здесь размер определяет число элементов в массиве. Необходимо запомнить важное ограничение при размещении массива: его нельзя инициализировать.

Для освобождения динамически размещенного массива необходимо использовать следующую форму оператора delete:

delete переменная_указатель;

Здесь скобки информируют оператор delete, что необходимо освободить память, выделенную для массива.

В следующей программе выделяется память для массива из 10 элементов типа float. Элементам массива присваиваются значения от 100 до 109, а затем содержимое массива выводится на экран:

#include
#include
int main()
{
float *p;
int i;
try {
p = new float ; // получение десятого элемента массива
} catch(xalloc xa) {
cout << "Allocation failure.\n";
return 1;
}
// присвоение значений от 100 до 109
for (i=0; i<10; i + +) p[i] = 100.00 + i;
// вывод содержимого массива
for (i=0; i<10; i++) cout << p[i] << " ";
delete p; // удаление всего массива
return 0;
}

  • Сергей Савенков

    какой то “куцый” обзор… как будто спешили куда то