Глава 9. Динамическое выделение памяти

Динамическое выделение памяти

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

Пределы динамического выделения памяти ограничены только объемом доступной физической или виртуальной памяти в системах с виртуальной памятью. Операции 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();


 



Понравилась статья? Добавь ее в закладку (CTRL+D) и не забудь поделиться с друзьями:  



double arrow
Сейчас читают про: