Си++: различия между версиями

Содержимое удалено Содержимое добавлено
Строка 196:
TODO: главное отличие перечислений Си++ от Си.
 
=== Операторы управления динамической памятью, инициализация массивов ===
 
При написании серьезных проектов всегда возникает необходимость выделить допонительный кусок памяти. Динамическая память - это отнюдь не "барство дикое", а необходимый инструмент. Просто зачастую (например, если мы описываем деревья или списки) изначально нам неизвестно, сколько ячеек памяти может понадобиться. На самом деле, такая пробема существует на всем протяжении существования науки/искусства программирования, поэтому неудивительно, что ещё в Си былы функции для динамической работой с памятью.
 
==== Как это делалось в старом добром Си ====
Вектор из 15-ти элементов можно проинициализировать
так
int vec[15];
так (си)
int *vec=(int *)malloc(15*sizeof(int));
и так (си++)
int *vec = (int *)new 15*sizeof(int);
но обычно делают так(С++):
int *vec = new int[15];
TODO (пропущено)
 
В Си для этого было две главных функции: одна называлась <tt>malloc()</tt> и выделяла непрерывный кусок памяти, другая, <tt>free()</tt>, этот кусок освобождала. Вот как выгядит код на Си для работы с динамической памятью (она на жаргоне называется <i>кучей</i>, <i>heap</i>):
NOTE: интересно, что здесь имели в виду под «инициализацией массивов»?
<font color=blue>int</font> *Piece;
Piece = malloc(<font color="blue">sizeof</font>(<font color="blue">int</font>)); <font color="gray">/* аргумент функции malloc() - число байт, которые надо выделить */</font>
<font color="blue">if</font> (Piece == NULL) <font color="gray">/* malloc() возвращает NULL, если не может выделить память */</font>
{
printf(<font color=green>"Ошибка выделения памяти: видимо, недостаточно места в ОЗУ\n"</font>)
<font color=blue>return</font>;
}
. . .
free(Piece); <font color="gray">/* аргумент free() - указатель на уже ненужный кусок памяти */</font>
Если возникала необходимость выделить память под несколько переменных одного типа, расположенных рядом (то есть под массив), аргумент <tt>malloc()</tt>'а просто домножали на нужное количество ячеек массива:
<font color="blue">int</font> *Piece = malloc(<font color="green">15</font> * <font color="blue">sizeof</font>(<font color="blue">int</font>));
Был, правда, у <tt>malloc()</tt>'а один недостаток: выделяя память, он не изменял содержимое ячеек, поэтому там могло оказаться совершенно произвольное значение. С этим боролись либо с помощью специальной функции <tt>memset(ptr, c, n)</tt> (она заполняет <i>n</i> байт памяти начиная с места, на которое указывает <i>ptr</i>, значением <i>c</i>), либо с помощью <tt>calloc()</tt>'а. Функция <tt>calloc()</tt> принимает два параметра: число ячеек массива, под которые надо выделить память, и размер этой ячейки в байтах; делает эта функция следующее: выделяет нужное количество памяти (непрерывный кусок) и обнуляет все значения в нём. Таким образом такой код:
<font color="blue">int</font> *Piece = malloc(<font color="green">15</font> * <font color="blue">sizeof</font>(<font color="blue">int</font>));
memset(Piece, <font color="green">0</font>, <font color="green">15</font>);
эквивалентен такому:
<font color="blue">int</font> *Piece = calloc(<font color="green">15</font>, <font color="blue">sizeof</font>(<font color="blue">int</font>));
 
==== Операторы new и delete ====
 
Идеология языка C++ предполагает, что каждый объект создаётся (объявляется) именно в том месте, где он нужен, и является работоспособным сразу после создания. Для этого каждый класс имеет определёный набор конструкторов - функций, которые должны автоматически запускаться при создании объекта (экземпляра данного класса) и инициализировать его члены (<i>data members</i>). Конструкторы одного класса отличаются только количеством и типом передаваемых параметров, то есть явяются перегруженными функциями. Однако, к сожалению, функции <tt>malloc()</tt> и <tt>сalloc()</tt> не умеют автоматически запускать конструкторы, и потому непригодны для динамического создания объектов. В языке C++ им имеется адекватная замена - оператор <tt>new</tt>. Рассмотрим пример:
MyClass *mc = <font color="blue">new</font> MyClass(<font color="green">5</font>);
В данном случае создаётся экземпляр класса <tt>MyClass</tt> с помощью его конструктора, принимающего в качестве параметра целое число (в данном случае, число 5). Адрес вновь созданного объекта присваивается указателю <i>mc</i>.
Если для класса определён конструктор по умолчанию, после имени класса допускается не указывать пустые скобки. Писать их или нет - это, как говорится, дело вкуса:
<font color="blue">new</font> MyClass(); <font color="gray">// эти две строки кода</font>
<font color="blue">new</font> MyClass; <font color="gray">// абсолютно эквивалентны</font>
Естественно, ничто не мешает использовать оператор <tt>new</tt> для простых скалярных переменных (например, целых чисел или других указателей).
 
Важное отличие оператора <tt>new</tt> от функции <tt>malloc()</tt> заключается в том, что он возвращает значение типа <i>"указатель-на-объект"</i> (то есть <tt>MyClass *</tt>), в то время как функция <tt>malloc()</tt> - <i>"указатель-на-что-угодно"</i> (<tt>void *</tt>). Подобная типизация в C++ - не редкость, она строже, чем та, что используется в Си, и, следовательно, менее ошибкоопасна. То есть в C++ нельзя скомпилировать код, где указатель на один класс приводится к указателю на другой класс, никак не связанный с первым.
 
Для каждого класса, помимо конструкторов, определён ещё и деструктор, то есть функция, отвечающая за корректное уничтожение объекта. Деструктор никогда никаких параметров не принимает, и потому не может быть перегружен. Проблема с деструктором возникает та же, что и с конструктором: функция <tt>free()</tt> не умеет его вызывать. Поэтому в C++ введён ещё один оператор - <tt>delete</tt>. Синтаксис его очень прост:
<font color="blue">delete</font> mc;
где <i>mc</i> - указатель на класс. Именно для этого класса и вызовется деструктор, поэтому, если Вы объявили его как <i>"указатель-на-что-угодно"</i>, деструктор не будет вызван вообще. Собственно, именно поэтому <tt>void *</tt> не рекомендуется использовать. Другой пример:
<font color="blue">class</font> Base
{
. . .
}
<font color="blue">class</font> Derived : <font color="blue">public</font> Base
{
. . .
}
<font color="blue">int</font> main( <font color="blue">void</font> )
{
Base *ptr = <font color="blue">new</font> Derived; <font color="gray">// присваивать указателю на предка адрес потомка - можно</font>
. . .
<font color="blue">delete</font> ptr;
<font color="blue">return</font> <font color="green">0</font>;
}
В этом случае оператором <tt>delete</tt> вызовется деструктор базового класса <tt>Base</tt>, хотя требуется вызвать деструктор класса-потомка <tt>Derived</tt>. Казалось бы, применение RTTI (<i>Run-Time Type Info</i>) в среде Microsoft Visual Studio позволило бы спастись от этой напасти, но увы и ах... В принципе, гибким решением этой пробемы является применение виртуальных деструкторов. Вообще же, как мы видим, при использовании <tt>delete</tt> надо проявлять особую осторожность.
 
==== Диномассивы на C++ ====
 
Вы, возможно, уже заметили, что при создании объектов с помощью оператора <tt>new</tt> мы выделяем память ровно под один экземпляр класса, в то время как используя <tt>malloc()</tt> или <tt>calloc()</tt> имеем возможность создать целый массив из произвольного числа элементов. На самом деле, синтаксис языка C++ позволяет использовать для этих целей конструкцию, аналогичную оператору <tt>new</tt>:
MyClass *mc = <font color="blue">new</font> MyClass[<font color="green">15</font>];
С формальной точки зрения, эта конструкция (называемая оператором <tt>new[]</tt> (разница в скобках)) не есть оператор <tt>new</tt>, описанный ранее. В частности, при перегрузке оба эти оператора описываются отдельно друг от друга и вообще никак не связываются.
 
Итак, в нашем примере будет выделена память под массив из 15 объектов класса <tt>MyClass</tt>, и каждый из них будет инициализирован с помощью конструктора по умолчанию. Если такой конструктор не определён, то попытка использовать <tt>new[]</tt> приведёт к ошибке. Применить к элементам созданного динамического массива какой-либо другой конструктор, увы, нельзя; поэтому такая запись вызовет легкое недоумение компилятора:
MyClass *mc1 = <font color="blue">new</font> MyClass(<font color="green">"hello, world!"</font>)[<font color="green">134</font>];
MyClass *mc2 = <font color="blue">new</font> MyClass()[<font color="green">2</font>]; <font color="gray">// это тоже ошибка, нельзя комбинировать два типа скобок в одном new</font>
В пару к оператору <tt>new[]</tt> введён оператор <tt>delete[]</tt>. Система сама помнит, сколько памяти было выделено под этот динамический массив, поэтому указывать число элементов не требуется. Просто напишите:
<font color="blue">delete</font>[] mc;
и компьютер сделает всё за Вас. Предостережение при использовании <tt>delete[]</tt> такие же, как и для <tt>delete</tt>: если хотите избежать утечек памяти (<i>memory leak</i>), следите за тем, чтобы вызывались "правильные" деструкторы.
 
TODO : перегрузка операторов <tt>new</tt>, <tt>delete</tt>, <tt>new[]</tt> и <tt>delete[]</tt> в классах
 
=== Структура программы, раздельная компиляция и особенности использования статической памяти ===