Динамическое выделение памяти
Создание и поддержание динамических структур данных требует динамического распределения памяти: возможности в процессе выполнения программы изменения области памяти для хранения новых узлов и освобождения ресурсов памяти, в которых уже нет необходимости.
Пределы динамического выделения памяти ограничены только объемом доступной физической или виртуальной памяти в системах с виртуальной памятью. Операции new и delete – основные для работы с динамической памятью. Операция new принимает в качестве аргумента тип динамического размещения объекта и возвращает указатель на объект этого типа.
Пример 1
Node *newptr = new Node[10];
// выделяет в памяти sizeof(Node) байтов и сохраняет указатель на область памяти указателем Ptr. Число 10 – число размещенных объектов данных.
Пример 2
Main()
{
int* p = new int;
cout << "sizeof(int) = " << sizeof(int) "\n";
}
Для освобождения памяти используется оператор delete. Указатель newptr не удаляется, а исчезает область памяти, на которую указывает newptr.
|
|
Пример 3
delete[] newptr;
Пример 4
main() {
char *p = new char[100];
char *q = new char[100];
delete p;
delete q;
}
Типичные ошибки:
· может быть ссылка на область памяти, которая уже была освобождена
· оператором delete освобождать память, которая не была выделена new
· не осуществляется возвращение динамически выделенной памяти, когда эта память уже не требуется. Это может явиться причиной переполнения памяти (утечка памяти)
· предположение о том, что размер объекта класса является простой суммой объектов его элементов данных. Это не так по причине различных машинно-зависимых требований по выравниванию границ области памяти.
· необходимо проверять, не вернула функция new нулевой указатель
Пример 5
/* распределить память под 1-мерный массив указателей на беззнаковые целые размером degree
по адресу tab используя оператор new */
tab=new unsigned *(degree);
/* под каждый указатель полученного массива указателей распределить одномерный массив беззнаковых
целых из degree элементов, используя оператор new в цикле */
for (int i=0; i<degree;i++)
tab[i]=new unsigned(degree);
/* освободить память, распределенную под degree одномерных массивов беззнаковых целых (из degree элементов каждый)
по адресам от tab[0] до tab[degree-1] */
for (int i=0; i<degree; i++)
delete [degree](tab[i]);
/* освободить память, распределенную под 1-мерный массив указателей на беззнаковые целые,
состоящий из degree указателей по адресу tab */
delete [degree]tab;
Глава 10. Перегрузка операций
Перегрузка операций
Функция-оператор (операторная функция, функциональная операция) может быть определена как внутри описания класса, так и вне его. Различают два вида операторных функций:
|
|
· простую (определяется вне класса, может быть одноместной или двухместной)
· компонентную (определяется в классе. У нее первый аргумент представляет собой объект класса, заданный неявно. Она может быть одноместной – не имеет явных аргументов, либо двухместной – с одним аргументом)
Простая функция | Компонентная функция |
Одноместная: <тип результата> operator@(<аргумент>) | Одноместная: <тип результата> operator@() |
Двухместная: <тип результата> operator@(<аргумент 1>, <аргумент 2>) | Двухместная: <тип результата> operator@(<аргумент 1>, <аргумент 2>) |
<тип результата> - тип возвращаемого значения (имеет тот же тип, что и класс, но возможен и иной тип значения).
Следует помнить, что нельзя перегружать следующие операторы:.::.*? sizeof и операторы препроцессора # и ##
Перегрузка операций подчинена следующим правилам:
· при перегрузке операций сохраняется количество аргументов, приоритеты операций и правила ассоциации, используемые в стандартных типах данных
· для стандартных типов данных переопределить операции нельзя
· функциональные операции не могут иметь аргументов по умолчанию
· функциональные операции наследуются (кроме операции присваивания =)
· функциональные операции не могут определяться как static
Функциональную операцию можно определить тремя способами:
· как метод класса
· как дружественную функцию
· как обычную функцию
Если операторная функция определяется вторым и третьим способами, то она должна принимать хотя бы один аргумент, имеющий тип класса, либо указатель или ссылку на класс.
Особый случай операторной функции: когда первый параметр имеет стандартный тип. В этом случае она не определяется как метод класса.
Формы вызова операторной функции:
Стандартная форма | Операторная форма |
Простая функция: operator@(<аргумент>) | Простая функция: @(<аргумент>) |
operator@(<аргумент 1>, <аргумент 2>) | <аргумент1>@ <аргумент2> |
Компонентная функция:<аргумент>.operator@() | Компонентная функция: @(<аргумент>) |
<аргумент1>.operator@(<аргумент2>) | <аргумент1>@ <аргумент2> |
Перегрузка унарных операций
Унарная операторная функция может быть определена как внутри, так и вне класса. Унарная функциональная операция, определенная внутри класса, должна быть представлена с помощью нестатического метода. При этом операндом является вызывающий её объект.
Пример 1
Class monstr
{
monstr &operator ++()
{
++health;
return *this;
}
};
monstr Vasia;
cout<<(++Vasia).get_health();
Если унарная операция определена вне класса, она должна иметь один параметр - типа класса
Пример 2
Class monstr
{
friend monstr &operator ++(monstr &M);
};
monstr &operator ++ (monstr &M)
{
++M.health;
return M;
}
Если не описывать функцию внутри класса как дружественную функцию, нужно учитывать доступность полей. В данном примере поле health не доступно извне, т.к. описано как private. Для его изменения следует использовать специальные методы. Введем в класс monstr специальный метод change_health, который будет позволять менять значение health:
Пример 3
void change_health(int he) {health =he;}
При вводе этого метода можно пере+гружать оператор инкрементации с помощью обычной функции, описанной вне класса:
Пример 4
monstr &operator ++ (monstr &M)
{
int h=M.get_health();
h++;
M.change_health(h);
return M;
}
В случае использования оператора постфиксного инкремента и декремента дадим им первый параметр типа int. Он используется для того, чтобы отличить их от префиксной формы.
Пример 5
const monstr operator ++ (int)
{
monstr M(*this);
health++;
/* Лучше ++(*this); */
return M;
}
monstr Vasia;
cout << (Vasia++).get_health();