Конструктор с инициализацией членов класса по умолчанию — КиберПедия 

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

Семя – орган полового размножения и расселения растений: наружи у семян имеется плотный покров – кожура...

Конструктор с инициализацией членов класса по умолчанию

2021-12-07 31
Конструктор с инициализацией членов класса по умолчанию 0.00 из 5.00 0 оценок
Заказать работу

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

Men(char n="***",int a=0){name=n;age=a;}

Если такой конструктор вызвать без аргументов, то данные-члены создаваемого объекта будут инициализированы указанными в конструкторе значениями (три звездочки на месте имени и возраст, равный 0). В то же время, вызов конструктора с конкретными значениями аргументов позволит по-прежнему инициализировать объект требуемым образом:

Men m1;// name =" *** ", age =0

Men m2("Собакин",41);// name =" Собакин ", age =41

Men m3("Львов");// name =" Львов ", age =0 (по умолчанию)

Men m4(32); // Недопустимый вызов

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

Mem(,32);

Необязательно иметь умолчание для всех переменных, инициализируемых с помощью конструктора. Однако при этом все параметры, не подлежащие инициализации, должны располагаться в начале списка параметров, а инициализируемые параметры – в конце этого списка. Так, можно образовать конструктор

Men(char n,int a=0){name=n;age=a;}// По умолчанию задается

                             // только значение возраста

однако недопустима конструкция

Men(char n="***",int a){name=n;age=a;}

Компилятор, встретив такое предложение, сообщит об ошибке отсутствия умолчания после параметра a.

Деструкторы

Деструктор, как и конструктор, имеет имя, совпадающее с именем класса, но перед ним ставится знак “~”. Для класса A деструктор должен иметь вид

~A(){ тело деструктора }

Деструктор не может иметь ни параметров, ни возвращаемого значения. Он автоматически вызывается при уничтожении объекта класса.

Основное назначение деструктора – выполнение завершающих действий (например, закрытие файлов или уничтожение таймеров). Однако чаще всего деструктор используется для освобождения памяти, динамически выделенной в процессе создания или использования объекта. Если, например, в конструкторе предусмотрено выделение памяти под массив данных с помощью оператора new (что может потребоваться, если размер этого массива заранее неизвестен и передается в конструктор через один из его параметров), то в деструкторе необходимо эту память освободить с помощью оператора delete []:

class A{

char* array;

public:

A(int size){

array=new char[size];

}

~A(){

delete[] array;

}

};

При таком определении класса A объект a 1, созданный предложением

A a1(10);

будет содержать в себе символьный массив длиной 10 байтов, а предложение

A a2(1000)

создаст объект a 2 с массивом длиной 1000 байтов.

При создании объекта класса A автоматически вызываемый конструктор выделит память под массив; при уничтожении объекта (оператором delete или при завершении программы) автоматически вызываемый деструктор освободит память массива, после чего будет уничтожена оставшаяся часть объекта.

Очень часто в завершающих действиях нет необходимости, и тогда надобность в явном определении деструктора отпадает. Таким образом, деструктор включается в состав класса, только если он нужен; в простых случаях можно обойтись без него.

Перегрузка

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

Перегрузка не является атрибутом именно объектно-ориентиро­ванного программирования и может использоваться и в традиционных программах. Однако для ООП перегрузка является одним из краеугольных камней; многие фундаментальные методики ООП целиком основаны на этом понятии.

Перегрузка функций

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

void Func(int);

void Func(int,int);

void Func(char*);

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

void Func(int);

int Func(int);

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

Каким образом компилятор отслеживает в тексте программы перегруженные функции? Для этого он использует кодирование, или декорирование имен функций, которое заключается в том, что функциям даются новые, дополнительные имена, в которые входят как имя класса, в котором объявлена функция (если функция объявлена в классе), так и список ее параметров. Если, например, приведенные выше три функции Func () объявлены в классе MyClass, то компилятор даст им следующие имена:

void Func(int);       @MyClass@Func$qi

void Func(int,int);   @MyClass@Func$qii

void Func(char*);     @MyClass@Func$qpzc

В результате функции, имеющие с нашей точки зрения одно и то же имя, для компилятора представляются различными.

Рассмотрим пример перегрузки функций-членов класса, воспользовавшись нашим простым классом Men:

class Men{

char* name;

int age;

public:

Men(char n,int a){name=n;age=a;}// Конструктор

void Age(int a){age=a;}// Запись возраста

int Age(){return age;}// Чтение возраста

};

Теперь и для инициализации, и для чтения переменной age мы используем один идентификатор Age (), хотя, в зависимости от состава используемых аргументов, он обозначает разные функции:

Men* pm = new Men("Орлов",18);// Вызов конструктора

pm->Age(19);// Коррекция возраста

int a = pm->Age();// Чтение возраста

Во втором предложении этого фрагмента функция Age () вызывается с целочисленным аргументом (типа int), и компилятор обращается к функции для записи возраста; в третьем предложении функция Age () вызывается без аргумента, и компилятор подставляет в программу вызов функции для чтения возраста.

Перегрузка конструкторов

Перегрузка конструкторов приобретает особое значение из-за того, что конструктору нельзя назначить произвольное имя – оно всегда должно совпадать с именем класса. В то же время в классе, как правило, приходится определять несколько конструкторов с разными сигнатурами. Наличие нескольких функций-конструкто­ров, имеющих одно имя, но разный состав параметров, возможно благодаря реализации в языке средства перегрузки функций.

Рассмотрим пример класса с перегруженными конструкторами. Пусть класс с именем Rect описывает прямоугольник, для которого можно задавать координаты (x и y) и длины сторон (w и h). Таким классом можно воспользоваться в приложении, формирующем на экране блок-схему программы или процесса. Координаты и размеры можно задавать парами целых чисел, однако в некоторых случаях может оказаться удобнее использовать структурные переменные типа POINT. Для реализации этой возможности в класс следует включить перегруженные конструкторы:

class Rect{

int x,y,w,h;// координаты и размеры

public:

Rect(int xx,int yy,int ww,int hh){// 4 аргумента типа int

x=xx; y=yy; w=ww; h=hh;

}

Rect(POINT ptCoords,POINT ptDims){// 2 аргумента типа POINT

x=ptCoords.x; y=ptCoords.y; w=ptDims.x; h=ptDims.y;

}

Rect(int xx,int yy){// Только координаты; размеры фиксированы

x=xx;y=yy;w=50;h=30;

}

};

Первый конструктор требует четыре целочисленных аргумента; при вызове второго конструктора необходимо указать две структурных переменных типа POINT; третий конструктор создает объект с задаваемыми координатами (в виде двух целых чисел), но с фиксированными размерами:

Rect r1(20,10,200,100);// x =20, y =10, w =200, h =100

POINT pc={250,50};

POINT pd={180,60};

Rect r2(pc,pd);// x =250, y =50, w =180, h =60

Rect r3(5,300);// x =5, y =300, w =50, h =30

Довольно часто встречается ситуация, когда при создании объекта неизвестны значения его данных и, соответственно, нельзя воспользоваться конструктором-инициализатором. Например, некоторый объект должен быть заполнен данными с диска; в этом случае необходимо сначала создать “пустой” объект, а затем прочитать в него значения данных, записанные на диске. В таких случаях в составе класса среди прочих конструкторов предусматривают перегруженный “конструктор по умолчанию”, который выделяет память под объект, но больше ничего не делает:

class Men{

char* name;

int age;

public:

Men(char*n,int a){name=n;age=a;}// Обычный конструктор -

                             // инициализатор

Men(){}// Конструктор по умолчанию, создающий “пустой” объект

void SetName(char*n) {name=n;}

void SetAge(int a) {age=a;}

};

Заметьте, что при наличии конструктора по умолчанию в классе должна быть предусмотрена возможность инициализировать данные уже после создания объекта. В приведенном выше примере для этого в класс включены функции SetName () и SetAge (). Теперь создавать объекты класса можно двояко:

Men m1(”Громыко”,25);// Объект создается и сразу же

     // инициализируется конструктором-инициализатором

Men m2;// Пустой объект (создается конструктором по умолчанию)

m2.SetName(”Крымов”);// Инициализация пустого объекта

m2.SetAge(38);// вызовом соответствующих функций

Следует заметить, что реальные классы (как прикладные, так библиотечные) практически всегда содержат несколько, а иногда и много (до 10 – 15) перегруженных конструкторов.

Перегрузка операторов

Перегрузку операторов приходится применять во всех случаях, когда смысл привычных для нас операций, таких как сложение, умножение или присваивание, оказывается для объектов конкретного класса отличным от общепринятого. Например, сложение комплексных чисел заключается в почленном сложении как действительных, так и мнимых составляющих чисел-слагаемых. Однако обычный (глобальный) оператор “ + ” умеет складывать только два числа, а не две пары чисел. Таким образом, для класса комплексных чисел этому оператору следует придать другой смысл или, лучше сказать, другую реализацию, для чего и используется перегрузка. Часто перегрузка изменяет реализацию оператора, но его интуитивный смысл остается без изменения (как в приведенном примере с оператором “+”); в других случаях перегрузка совершенно изменяет назначение оператора. Например, глобальные операторы языка C++ << и >> служат для сдвига операнда влево или вправо на заданное число битов; однако при использовании с потоковыми объектами эти операторы приобретают смысл “вставить в поток” и “извлечь из потока”, о чем будет идти речь в одном из последующих разделов пособия.

Перегрузить можно почти любой оператор языка (=, +, *, ^, <, << и т. д.); как видно из этого перечня, перегружаются как одноместные операторы, применяемые к одному объекту (например, ++ или!), так и двуместные, требующие двух операндов (операторы сложения или умножения, логических операций и другие).

Перегрузка операторов осуществляется с помощью специальной своеобразной функции, имеющей имя operator (ее называют операторной функцией). Рассмотрим ее применение на примере перегрузки нескольких операторов для класса Point 3 D, описывающего координаты точки в трехмерном пространстве.

Будем (довольно естественно) считать, что равенство двух точек обозначает равенство их соответствующих координат, а сложение заключается в образовании точки, у которой каждая координата вычисляется как сумма соответствующих координат точек-операндов. В простейшем случае класс Point 3 D выглядит следующим образом:

class Point3D{

int x,y,z;// Данные-члены (координаты точки)

public:

Point3D(int xx,int yy,int zz){// Конструктор-инициализатор

x=xx; y=yy; z=zz;

}

Point3D(){};// Конструктор для создания “пустого” объекта

bool operator == (Point3D&);// Прототип функции перегрузки ==

Point3D operator+ (Point3D&);// Прототип функции перегрузки +

Point3D operator= (Point3D&);// Прототип функции перегрузки =

};

Как видно из приведенного фрагмента, все функции operator принимают в качестве аргумента объект класса; при этом функции перегрузки операторов + и =, которые должны образовывать новые объекты, возвращают объекты класса, а функция перегрузки оператора сравнения возвращает результат сравнения, т. е. булево значение.

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

bool operator == (Point3D);// Аргумент передается по значению

Определение функции operator для операции сравнения на равенство выглядит следующим образом:

bool Point3D::operator == (Point3D obj&){

if((x==obj.x)&&(y==obj.y)&&(z==obj.z)) return true;

else return false;

}

Для выяснения того, как работает эта функция, рассмотрим сначала строки программы, в которых используется оператор сравнения:

Point3D p1(5,10,15), p2(5,10,15);// Два равных объекта

if(p1==p2)...

Оператор == сравнивает объекты p 1 и p 2 (в данном случае равные). Как эти два объекта поступают в функцию operator?

Общее правило здесь таково: оператор == (или любой другой двуместный оператор) вызывается для первого операнда. Второй операнд поступает в функцию в качестве ее аргумента. Другими словами, функция operator вызывается таким образом:

                              

Обозначение операторной функции,       Аргумент операторной

вызываемой для объекта p 1                       функции (объект p 2)

Функция operator == является членом нашего класса и ей, естественно, доступны все данные объекта p 1, для которого она вызывается (конкретно x, y и z). Объект p 2 поступает в операторную функцию в качестве аргумента и внутри нее принимает имя obj. Таким образом, x, y и z в теле операторной функции являются данными объекта p 1, а обозначения obj. x, obj. y и obj. z соответствуют данным x, y и z объекта p 2.

Из текста рассматриваемой функции видно, что если значения соответствующих данных-членов двух объектов-операндов совпадают, функция возвращает true; в противном случае – false.

Рассмотрим теперь перегрузку оператора +. Функция operator + для нашего класса будет иметь следующий вид:

Point3D Point3D::operator + (Point3D obj){

Point3D temp;// Вспомогательный временный объект

temp.x=x+obj.x;// Образуем во временном

temp.y=y+obj.y;// объекте temp суммы членов

temp.z=z+obj.z;// объектов-операндов

return temp;// Вернем объект temp с суммами членов

}

Сложение объектов класса Point 3 D:

Point3D p3 = p1 + p2;

(в предположении, что объекты p 1 и p 2 уже имеются).

Как и в предыдущем случае, функция operator + вызывается для первого операнда-слагаемого p 1, и обозначения x, y и z в операторной функции относятся к членам объекта p 1; второй операнд p 2 поступает в операторную функцию в качестве аргумента и принимает в ней имя obj.

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

Третий перегруженный оператор, оператор присваивания, как и оператор сравнения, не требует временного объекта temp:

Point3D Point3D::operator = (Point3D obj){

x=obj.x;

y=obj.y;

z=obj.z;

return *this;

}

Данным-членам первого операнда (стоящего до знака равенства) присваиваются значения членов второго операнда (поступающего в функцию через параметр obj). Вообще говоря, на этом операция присваивания завершается, и данная функция могла бы не возвращать никакого значения. Однако возврат текущего объекта (* this), в котором уже образовалась копия второго операнда, нужен для того, чтобы объекты класса могли участвовать в операциях множественного присваивания:

Point3D p(10,20,30);// Объект со значениями

Point3D p1,p2;// ”Пустые” пока объекты p1 и p2

p1 = p2 = p;// И p 1, и p 2 приравниваются объекту p

Здесь объекту p 1 присваивается значение выражения, стоящего справа от него, т. е. выражения (p 2 = p). Чтобы эта операция присваивания могла выполняться, надо не только придать объекту p 2 значение объекта p, но и сделать так, чтобы выражение p 2 = p образовывало объект. Другими словами, оператор присваивания должен возвращать объект – результат выполненной им операции присваивания.

Поясним смысл выражения * this. Каждый раз, когда вызывается функция-члена класса, она автоматически получает указатель с именем this на объект, для которого она вызвана. Указатель this является неявным параметром всех функций-членов классов, и его можно использовать в любой функции. Например, в операторной функции перегрузки оператора сложения предложение

temp.x=x+obj.x;

можно было бы записать таким образом:

temp.x=this->x+obj.x;

хотя в этом и нет необходимости. Однако функция перегрузки оператора присваивания, как уже отмечалось, должна возвращать объект, для которого она вызывается. Указатель на этот объект поступает в функцию в качестве неявного параметра и принимает имя this. Для того, чтобы, имея указатель, получить сам объект, надо снять ссылку с указателя, что и выполняется операцией * this.


Поделиться с друзьями:

Особенности сооружения опор в сложных условиях: Сооружение ВЛ в районах с суровыми климатическими и тяжелыми геологическими условиями...

Семя – орган полового размножения и расселения растений: наружи у семян имеется плотный покров – кожура...

Состав сооружений: решетки и песколовки: Решетки – это первое устройство в схеме очистных сооружений. Они представляют...

Индивидуальные очистные сооружения: К классу индивидуальных очистных сооружений относят сооружения, пропускная способность которых...



© cyberpedia.su 2017-2024 - Не является автором материалов. Исключительное право сохранено за автором текста.
Если вы не хотите, чтобы данный материал был у нас на сайте, перейдите по ссылке: Нарушение авторских прав. Мы поможем в написании вашей работы!

0.087 с.