Void PROB::FuncIn(int i)




{iii = i;}

Void PROB::FuncOut()

{

printf(“%d”,iii);

getch();

}

 

Синтаксис задания функций-членов понятен из текста. Сначала указывается тип возвращаемого значения – в нашем случае это void. Далее пишется имя класса. Затем, после двух двоеточий, обычное описание функции.

С функцией FuncIn все достаточно ясно. Поскольку это метод класса, он имеет прямой доступ к свойству iii. В теле функции свойство получает значение, равное аргументу функции. Второй метод должен вывести значение переменной iii в стандартный инструмент консольного вывода printf. Кроме того, здесь предусмотрен известный нам прием остановки программы до нажатия любой клавиши на клавиатуре. В этой связи в начале фрагмента производится подключение соответствующих заголовочных файлов.

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

 

#include "Unit2.h"

Void main()

{

PROB cl1;

cl1.FuncIn(3);

cl1.FuncOut();

}

Обращаем внимание на то, что необходимо сделать подключение заголовочного файла Unit2.h с помощью директивы include.

Что же происходит при запуске программы? В первой строке создается объект класса. Назван он cl1. Во второй строке вызывается метод FuncIn с аргументом, равным 3. Как можно видеть, при вызове метода сначала указывается имя объекта и затем, после разделителя. имя функции. В результате свойство iii становится рамным 3. Наконец функция FuncOut произведет вывод значения этого самого свойства на экран.

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

Применительно к классу, описанному выше, может понадобиться, например, начальное обнуление переменной iii. В заголовочном файле Unit2.h в описании класса теперь поместим строку:

 

PROB();

 

В файле реализации Unit2.cpp опишем конструктор:

 

PROB::PROB()

{iii = 0.0;}

 

Теперь в вызывающем модуле Unit1.cpp при создании объекта в строке:

 

PROB cl1;

 

сразу произойдет присвоение значения 0 свойству iii.

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

 

PROB(int);

PROB::PROB(int x)

{iii = x;}

PROB cl2(5);

 

Понятно, что теперь необходимость в методе FuncIn отпала.

Из представленных ниже текстов файлов Unit2.h, Unit2.cpp и Unit1.cpp будет понятен синтаксис использования рассматриваемых конструкторов и операторов.

 

//----------------------Unit2.h

Class PROB

{

int iii;

public:

void FuncOut();

PROB();

PROB(int);

}

//----------------------Unit2.cpp

#include "Unit2.h

#include<conio.h>

#include<stdio.h>

Void PROB::FuncOut()

{

printf("%d",iii);

getch();

}

PROB::PROB(int x)

{ iii=x; }

PROB::PROB()

{iii = 0;}

//---------------------- Unit1.cpp

 

#include "Unit2.h"

Void main()

{

PROB cl1,cl2(5);

cl1.FuncOut();

cl2.FuncOut();

}

 

Итак, в первой строке вызывающей программы создается объект cl1 класса PROB с использованием конструктора без параметров и объект cl2 с использованием инициализирующего конструктора с параметром.. При этом обнуляется свойство iii объекта cl1, а переменная-член iii в cl2 получает значение 5. Далее свойства объектов выводятся на экран.

Удаление объекта класса также сопровождается выполнением специального метода, называемого деструктором. Вызов деструктора в явном виде в приложении не делается. Он производится автоматически либо при выходе из блока (для локальных объектов) либо при завершении приложения (для глобальных объектов). Сразу отметим, что деструкторы, как и конструкторы, не обязательно и явно декларировать – всегда, когда необходимо, будут срабатывать конструкторы и деструкторы по умолчанию. Однако, если при удалении объекта необходимо выполнить какие-то действия, то «самодельный» деструктора может быть полезным. Имя функции-деструктора совпадает с именем класса, перед которым пишется символ ~ (тильда). Этот метод не имеет типа и не возвращает никакого значения.

Для демонстрации работы деструктора добавим в файл Unit2.h строку:

 

~PROB();

 

В файле реализации дадим описание метода:

 

PROB::~PROB()

{ printf("FINISH"); }

 

Как нетрудно догадаться, выполнение деструктора заключается в выводе сообщения FINISH. Больше ничего в программе делать не надо. По окончанию работы приложения вы увидите на экране это самое сообщение.

 

Перегрузка функций и операторов

 

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

Начнем с перегруженных функций. Классическим примером здесь служит функция, вычисляющая модуль (абсолютную величину) числа. Стандартной функцией в С/С++ для нахождения модуля целого числа является функция int abs(int). Подчеркнем, что аргументом функции должно быть целое число, а возвращаемое число также целого типа. Применительно к вещественным числам то же действие выполняет функция float fabs(float). Логично было бы попробовать «научить» функцию abs работать с любыми аргументами – целыми, вещественными, комплексными и возвращать значения соответствующих типов. При этом имя функции одно для всех типов. Вот в этом и состоит смысл перегрузки функции. Как это делается практически?

Если бы мы решили создать собственную функцию abs, возвращающую модуль целого (типа int) числа, то это можно было бы сделать, например, так, как показано в следующем фрагменте:

 

Int abs(int x)

{

int y;

if(x<0) y = -x; else y=x;

return y;

}

Void main()

{

printf(abs(-2);

}

 

Прежде всего, можно убедиться в том, что компиляция программы выдаст ошибку, связанную с тем, что обнаруживается конфликт с уже существующей стандартной функцией abs. Дело в том, что мы пытаемся просто переписать функцию того же имени и того же типа, что и стандартная. Если заменить имя, например, на rabs, то все пройдет нормально, и программа выполнит необходимое действие. Однако мы хотим другого. Мы хотим, чтобы функция abs находила модуль как целого, так и вещественного числа. Для этого определение функции представим в виде:

 

Float abs(float x)

{

float y;

if(x<0) y=-x; else y=x;

return y;

}

 

В вызывающей функции можно, например, написать выражение:

 

printf(abs(-2.3));

 

Таким образом, мы выполнили перегрузку функции abs на тип float. Аналогичную процедуру можно было бы проделать и для типов double и long.

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

 

Условие? Выражение1: Выражение2;

 

Эта конструкция работает следующим образом. Если Условие имеет значение, не равное нулю (ИСТИНА), то конструкция возвращает значение Выражения1, в противном случае – значение Выражения2. Тогда определение функции можно записать, в частности, так:

 

Long abs(long x)

{

return x<0? –x: x;

}

 

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

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

Наряду с перегрузкой функций в С++ широко используется и перегрузка операторов. Под термином оператор в языке понимается очень многое. Знак операции сложения + является бинарным оператором, слева и справа от которого находятся операнды – слагаемые. Два знака ++ также являются оператором для унарной операции инкремента. Не столь очевидным является, например тот факт, что пара прямых скобок [ ] также является оператором индексирования, то есть оператором присвоения номера объекту. Очевидно, перегрузка операторов позволяет использовать один и тот же символ для сходных дествий над объектами различных типов. Большинство операторов в С++ могут быть перегружены, однако, в силу большого разнообразия операторов мы остановимся только на перегрузке простейших бинарных операций.

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

 

Тип Имя_класса::operator#(Аргументы)

{

//Исполняемые инструкции;

}

 

Здесь Тип – тип возвращаемого функцией значения. В большинстве случаев это будет тип класса, для которого делается перегрузка, но не обязательно. Далеe указывается имя класса и, после разделителя:: пишется служебное слово operator. Вместо символа # как раз и следует указать перегружаемый оператор, как то +, --, * и так далее.

При перегрузке бинарных операторов у функции-оператора будет единственный аргумент. Этот аргумент будет принимать значение объекта класса, стоящего справа от знака оператора. Тело функции должно обеспечить действие или действия, которые будут выполняться над этим аргументом и свойством класса, которое будет иметь значения операнда, стоящего слева. Результат действий должен быть возвращен инструкцией return.

Все сказанное, наверное, воспринимается пока слишком абстрактно. Что же, попробуем продемонстрировать ситуацию примером. Создадим «самодельный» класс комплексных чисел и перегрузим для него некоторые арифметические операции. Обратимся к тексту заголовочного файла с описанием класса.

 

Class COMP

{

public:

double a,b;

COMP(double, double);

COMP();

COMP operator+(COMP);

COMP operator-(COMP);

COMP operator*(COMP);

};

 

Итак, организован класс с именем COMP, открытыми свойствами которого являются два вещественных числа a и b, которые соответствуют действительной и мнимой частям комплексного числа. Здесь же объявлены два конструктора – с параметрами и без параметров. Последние три объявления – это как раз и есть декларации операторов-функций для перегрузки сложения, вычитания и умножения.

Реализация представлена в следующем фрагменте.

 



Поделиться:




Поиск по сайту

©2015-2024 poisk-ru.ru
Все права принадлежать их авторам. Данный сайт не претендует на авторства, а предоставляет бесплатное использование.
Дата создания страницы: 2016-02-16 Нарушение авторских прав и Нарушение персональных данных


Поиск по сайту: