Партнерка на США и Канаду по недвижимости, выплаты в крипто

  • 30% recurring commission
  • Выплаты в USDT
  • Вывод каждую неделю
  • Комиссия до 5 лет за каждого referral

Константные функции-члены

Константная функция-член возвращает константный объект, который может использоваться как любой другой константный объект. Рассмотрим пример:

class ConstFunction

{

public:

const int f3();

};

const int ConstFunction::f3(){ return 3; };

void main()

{

ConstFuction a;

Const int i=s. f3(); // использование f3() для инициализации константы

int y=s.f3(); // использование f3() с переменной

};

Функции-члены inline

Подставляемые (inline) функции - распространённый вид функций-членов в С++. Они были разработаны для повышения эффективности вызовов функций-членов и предоставления языку объектного подхода, при минимальном расходе вычислительной мощности на вызовы функций. Подставляемые функции объявляются и определяются в объявлении класса без какого-либо специального ключевого слова.

Такие функции обрабатываются компилятором почти так же, как и макрос: вызов непосредственно заменяется телом функции. Для коротких функций такой подход очень эффективен, поскольку отсутствуют инструкции вызова и возврата из функции. Рассмотрим следующий код:

class InlineClass

{

public:

int value;

int getvalue(){ return value; };

};

void main()

{

InlineClass s;

Int i=s. getvalue();

};

При компиляции вызов функции s.getvalue() заменяется обращением к переменной s.value, что в результате приводит к устранению накладных расходов на вызов функции.

Каждый раз, когда вызывается подставляемая функция, её вызов заменяется телом функции. Это имеет смысл только в том случае, если подставляемые функции небольшие. Если же они занимают много места, то программа может стать необоснованно большой.

НЕ нашли? Не то? Что вы ищете?

Сложность подставляемых функций ограничена. Если компилятор посчитает, что функция слишком велика или сложна, то он будет считать её обыкновенной функцией-членом.

Функции-члены с константным указателем this

Программисты, работающие на Си, знакомы с ключевым словом const, которое используется для ограничения доступа к переменным. Модификатор const применим и к телу функции-члена класса для указания на огранияенность её возможностей. Функции-члены, объявленные таким образом с помощью const, являются новшеством языка С++. В данном случае ключевое слово const не относится к возвращаемому функцией значению, а касается указателя this, используемого в функции. Например:

class Example

{

int value;

public:

int GetValue() const; // обыкновенная функция

int ReadValue() const { return value; }; // подставляемая функция

};

int Example::GetValue() const{ return value; };

Объявление функции с const касается типа указателя this на объект класса Example, передаваемого функции-члену. С++ всегда передаёт нестатическим функциям-членам невидимый указатель this. Функции-члены могут таким образом получить доступ к членам данных объекта. Функция-член класса Example, объявленная без ключевого слова const, имела бы следующее объявление для this:

// обычно подразумеваемый this

Example *const this;

Этот оператор объявляет константный указатель this на объект класса Example. Если мы могли бы видеть невидимый указатель, который передаётся в обычную функцию-член Normal(), то её объявление выглядело бы следующим образом:

class Example

{

public:

int Normal(Example *const this);

};

Размещение const перед телом функции позволяет изменить тип указателя this, передаваемого функции. Ключевое слово const заставляет this принять следующее невидимое объявление:

// модифицированный this

const Example *const this;

Теперь this определён как указатель на константный объект Example. Значение такого определения состоит в том, что функции-члену запрещено вносить любые изменения в члены данных класса.

Объявление статической функции-члена с ключевым словом const не имеет смысла, потому что такой функции при вызове не передаётся указателя this, в то время как задача ключевого слова const - модифицировать тип указателя this.

Специальные функции класса

Классы имеют любое количество функций - членов, но две из них занимают особое положение. Это конструктор и деструктор. Хотя они и не являются обязательными, но часто выполняют такие действия, без которых нельзя обойтись. Конструктор и деструктор отличаются от других функций класса тем, что их вызывает компиллятор. Такое встроенное поведение освобождает программиста от запоминания определённых деталей и тем самым снижает вероятность ошибок.

Конструкторы

Как следует из названия, конструктор - это функция, которая строит объект данного класса. С одной стороны, конструктор вовсе не обязателен, с другой стороны, можно определить несколько конструкторов (а не один). С++ гарантирует, что, если класс имеет конструктор, то он будет вызван до выполнения любой другой функции класса. Рассмотри определение объекта, где класс Counter был описан раньше:

void main()

{

Counter counter;

}

Что делает компиллятор? Прежде всего, определяется автоматическая переменная, которой отводится память в стеке точно также, как если бы была объявлена структура. При этом компиллятор ищет в обявлении класса Counter конструктор. Если компиллятор находит конструктор класса, не требующий при вызове аргументов, то генерируется код, вызывающий конструктор. Хотя конструкторы написаны для создания новых объектов, само создание может проходить по одному из трёх сценариев:

·  создание объектов с инициализацией по умолчанию

·  создание объектов со специальной инициализацией

·  создание объектов путём копирования других объектов

Конструктор имеет специальное зарезервированное имя, совпадающее с именем класса, к которому принадлежит конструктор. Итак, конструктор для класса Counter объявляется следующим образом:

class Counter

{

public:

Counter(); // объявление конструктора

};

Различные виды конструкторов объявляются по-разному, а объединяет их то, что они не имеют возвращаемого значения. Нельзя объявить конструктор, возвращающий какое-либо значение, даже void. Задача конструктора - построить в памяти объект, что не влечёт возвращения значения.

Конструкторы по умолчанию

Конструктор, объявляемый без аргументов, называется конструктором по умолчанию (defaul constructor). Рассмотрим модифицированное объявление класса Counter, представленного ранее:

class Counter

{

long count;

public:

Counter();

void SetValue(long v);

long GetValue();

};

Конструктор Counter() - это конструктор по умолчанию. Поскольку такой конструктор не имеет аргументов, то каждый класс может иметь только один конструктор по умолчанию. Код конструктора обычно инициализирует данные, впоследствии используемые другими функциями - членами. Рассмотрим следующий пример:

// определение конструктора по умолчанию

Counter::Counter()

{

// извещение пользователя о создании нового объекта

printf("\nCreating a Counter object");

// инициализация каких-то внутренних данных

count=0;

}

Если конструктор по умолчанию не определён в описании класса, то он создаётся компилятором автоматически. Созданный таким образом конструктор просто выделяет память при создании объекта своего класса. Конструктор, имеющий аргументы, значение которых задано по умолчанию, похож на конструктор по умолчанию, поскольку может быть вызван без аргументов. Фактически конструктор по умолчанию и конструктор с аргументами, имеющими значения по умолчанию, так похожи, что могут стать причиной ошибки. Рассмотрим следующий пример:

class Counter

{

int value;

public:

Counter(int i=0){ value=i; };

Counter(){ value=0; };

};

void main()

{

Counter c; // здесь будет обнаружена ошибка компиляции

Counter c1(4); // всё нормально

}

Определение первого объекта в main() приведёт к ошибке компиляции, поскольку непонятно, какая именно функция должна быть вызвана. Если конструктор по умолчанию из объявлеия удалить, то всё заработает.

Конструкторы с аргументами

Большинство конструкторов в типичных классах С++ принимает аргументы. Основная задача конструктора - инициализировать объект перед использованием, в результате чего можно вызвать функции, динамически распределить память, присвоить переменным конкретные значения и т. д. Давайте рассмотрим, каким может быть конструктор для класса Counter:

// поместите этот код в объявление класса

Counter(long); // объявление конструктора

// определение конструктора

Counter:: Counter(long value){ count=value; }

Создание объекта Counter могло бы выглядеть следующим образом:

void main()

{

Counter object(5);

}

Следует обратить внимание на скобки после имени переменной, которые делают определение объекта похожим на вызов функции. И действительно, определение объекта - это и есть вызов функции и передача ей аргументов. Переменная object определяется и инициализируется в одной и той же строке. Учитывая объявление конструктора, ему нужно передать аргумент. Чтобы вызвать конструктор без аргументов, нужно использовать аргументы по умолчанию.

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

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

Counter(int = 0) // принимается целое или обнуляется по умолчанию

Counter(long); // принимает long

Counter(double); // принимает double

Counter(char*); // принимает символьную строку

Рассмотрим, как они определяются:

Counter:: Counter(int initial_value){ count = initial_value; }

Counter:: Counter(long initial_value){ count = initial_value; }

Counter:: Counter(double initial_value){ count = initial_value; }

Counter:: Counter(char* initial_value){ count = atol(initial_value); }

И, наконец, как они используются:

void main()

{

Counter oject1(5); // конструктор для int

Counter oject2(5L); // конструктор для long

Counter oject3(5.0); // конструктор для double

Counter oject4("5"); // конструктор для char*

Counter oject5; // конструктор для int по умолчанию

}

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

Конструкторы копирования

Часто, создавая объект, вовсе не обязательно инициализировать какие-то переменные. Нужен просто "такой же" объект, как и этот. Это подразумевает получение копии уже существующего объекта, что, в свою очередь, требует особого конструктора, обычно называемого инициализатором копии (copy initializer) или конструктором копирования (cope constructor). Следующий листинг демонстрирует использование такого конструктора.

// разместить этот фрагмент в объявлении класса

Counter(Counter&)

// Определение конструктора копирования

Counter:: Counter(Counter& reference)

{

count=reference. count;

}

void main

{

Counter object(5); // использование конструктора для int

Counter object1 = object; // использование конструктора копирования

}

Эта запись может сбить с толку. Знак равенства указывает на то, что происходит какое-то копирование между object и object1. Знак равенства - это только переключатель, предписывающий компилятору использовать конструктор копирования.

Деструкторы

Деструкторы - из той же категории, что и конструкторы. Они используются для выполнения определённых операций при удалении объекта. Деструкторы всегда начинаются с символа "~"(тильда) и имеют название, совпадающее с именем класса. Обычно деструкторы выполняют операции, обратные тем, что выполняли соответствующие конструкторы. Если вы создали класс файла, то деструктор, вероятно, будет содержать код для закрытия файла. Если конструктор класса выделяет динамическую память для членов класса, то деструктор освобождает выделенную память. Однако между конструктором и дестуктором есть несколько важных различий.

·  Деструкторы могут быть виртуальными, а конструкторы нет

·  Деструкторам нельзя передавать аргументы

·  В каждом классе может быть объявлен только один деструктор

Общедоступные деструкторы

Обычно деструкторы объявляются общедоступными. Однако, в особых случаях они объявляются приватными или защищёнными. Деструкторы не имеют типов возвращаемых значений. Объявление, даже void, будет ошибкой. Рассмотрим пример деструктора:

// фрагмент из объявления класса

~Counter();

// определение

Counter::~Counter()

{

printf("\n Уничтожается объект Counter!");

}

Конструктор и деструктор похожи на скобки, обрамляющие серию операций и действующие как составное выражение в С++. Такое вложение называется функциональным замыканием, которое будет рассматривать несколько позже. Например, можно описать класс, выполняющий весь графический вывод какой-либо программы. Объявление такого класса может выглядеть следующим образом:

class Graphics

{

public:

Graphics();

void DrawCircle(int x, int y, int radius);

void DrawDot(int x, int y);

// другие графические функции

~Graphics();

};

При создании объекта Graphics конструктор вызывается компилятором автоматически. Конструктор инициализирует графическую систему, настраивает аппаратное обеспечение, инициализирует дисплей и т. д. Деструктор вызывается после использования графической подсистемы, возможно, в самом конце программы. Он также вызывается автоматически, так что забыть его вызвать нельзя. В деструкторе закрываются графические устройства, дисплей устанавливается в прежнее состояние, память, отведённая под объект, освобождается.

Приватные деструкторы

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

Любые объекты, динамически распределяемые в программе, должны быть разрушены до выхода в операционную систему. Чтобы уничтожить такие объекты, следует воспользоваться одним из следующих способов:

·  Объявить общедоступную функцию - член класса, разрушающую объект

·  Объявить дружественный класс и разрушить в нём объект

Приведём пример иллюстрирующий только первый случай.

class PrivateExample

{

private:

int* value;

~ PrivateExample(){ delete value; };

public:

PrivateExample(){ value = new int[10]; };

void Delete(){ delete this; };

};

static PrivateExample object1; // нельзя создать

PrivateExample object2; // нельзя создать

void main()

{

PrivateExample object3; // нельзя создать

static PrivateExample object4; // нельзя создать

// а это правильно

PrivateExample *object5=new PrivateExample; // нельзя создать

Delete object5; // так нельзя

Object5->Delete(); // так можно

}

Ключевое слово friend

Иногда нужно объявить два класса, которые так тесно связаны друг с другом концептуально, что хотелось бы обеспечить одному из них неограниченный доступ к членам второго. Рассмотрим, например, обработку связного списка. В данном случае необходим один класс для обработки каждого узла списка и другой - для обработкисписка в целом.

Проблемы подобного рода решаются через дружественный класс, который имеет доступ как к общедоступным, так и к приватным и защищённым членам другого класса. С ключевым словом friend может быть объявлен целый класс или отдельная функция. На необъявленный класс можно ссылаться как на дружественный. Например:

class Node

{

friend class ObjectList; // представление класса ObjectList в той же области видимости,

// что и класс Node

int value; // объявление приватных членов

Node *predecessor;

Node *successor;

public:

void Value(int i){ value=i }; // объявление общедоступного члена

};

class ObjectList // и наконец, объявление класса, который уже в области видимости

{

private:

Node *head; // этот класс просто обрабатывает объекты Node

Node *tail;

Node *current;

public:

void InsertNode(Node*);

void DeleteNode(Node*);

int CurrentObject(Node *node){ return node->value; };

};

Следующий оператор демонстрирует, что объявление friend эквивалентно неполному объявлению класса в том, что касается области видимости идентификатора ObjectList:

Class ObjectList;

Неполные объявления используются как ссылки наперёд, т. е. представляют идентификатор в области видимости до его полного объявления. Одна и та же функция или класс может быть объявлена дружественной в более чем одном классе. Можно объявить дружественной и обычную функцию до того, как идентификатор функции попадёт в область видимости. Например:

class Node

{

friend int GetObject(Node*);

int value;

Node *predecessor;

Node *successor;

public:

void Value(int i){ value=i; };

};

int GetObject(Node *n){ return n->value; };