Тема: «Разработка программ с использованием перегрузки»




ПРАКТИЧЕСКОЕ ЗАНЯТИЕ 17

Цели:

- определить понятие перегрузки;

- определить ситуации, в котопых необходима перегрузка;

- изучить отличия перегрузки функций, перегрузки операторов и перегрузки конструкторов в языке С++;

- приобрести основные навыки использования перегрузки в С++.

 

Характер занятия: репродуктивный.

Форма организации: индивидуальная.

Обеспечение занятия: тетрадь с лекциями, тетрадь для практических работ, компьютер.

 

Требования к знаниям студентов

Перед выполнением практической работы студент должен

знать:

- понятия класс и объект;

- основные особенности создания и использования классов в языке С++;

после выполнения практической работы студент должен

уметь:

- создавать программы, с использованием перегрузки операторов, функций и конструкторов на языке С++;

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

 

Теоретический материал

Перегрузка операций

Перегрузка операций — это одна из самых захватывающих возможностей ООП. Она может превратить сложный и малопонятный листинг программы в интуи­тивно понятный. Например, строки

d3.addobjects С dl. d2);

похожие, но одинаково непонятные

d3 = dl.addobjects (d2);

можно заменить на более читаемую d3 = dl + d2:

Довольно непривлекательный термин «перегрузка операций» дается обыч­ным операциям С++, таким, как +, *, <= или +=, в случае их применения с опре­деленными пользователем типами данных.

Обычно

а = b + с:

работают только с основными типами данных, такими, как int или float, и попыт­ка использования обычных операторов, когда a, b и с являются объектами опре­деленного пользователем класса, приведет к протестам компилятора. Однако, используя перегрузку, вы можете сделать эту строку правильной даже в том случае, если a, b и с являются объектами определенного пользователем класса.

На самом деле перегрузка операций дает вам возможность переопределить язык С++. Если вы считаете, что ограничены стандартными возможностями операций, то вы можете назначить их выполнять нужные вам действия. Исполь­ зуя классы для создания новых видов переменных и перегрузку для определе­ния новых операций, вы можете, расширив С++, создать новый, свой собствен­ный язык.

Другой вид действий, преобразование типов, тесно связан с перегрузкой опе­раций. С++ совершает преобразование простых типов, таких, как int или float, автоматически; но преобразование, включающее в себя созданные пользовате­лем типы, требует немного работы со стороны программиста.

 

Перегрузка унарных операций

Давайте начнем с перегрузки унарных операций. Унарные операции имеют только один операнд (операнд — это переменная, на которую действует операция). Примером унарной операции могут служить опе­рации уменьшения и увеличения ++ и — и унарный минус, например -33.

В примере COUNTER (Практическая работа №9) мы создали класс Counter для хранения значения счетчика. Объект этого класса мы увеличивали вызовом метода:

cl.inc_count ();

Он выполнял свою работу, но листинг программы станет более понятным, если мы применим вместо этой записи операцию увеличения ++.

++cl:

Давайте перепишем программу COUNTER, чтобы сделать это действие возмож­ным. Приведем листинг программы C0UNTPP11.

// countppl.cpp

// увеличение переменной операцией ++

#include <iostream>

using namespace std;

///////////////////////////////////////////////////////////

class Counter

{

private:

unsigned int count; // значение счетчика

public:

Counter (): count (0) // конструктор

{ }

unsigned int get_count () // получить значение

{ return count; }

void operator++ () // увеличить значение

{ ++count; }

};

///////////////////////////////////////////////////////////

int main () {

Counter cl, c2; // определяем переменные

cout << "\ncl = " << cl.get_count (); // выводим на экран

cout << "\nc2 = " << c2.get_count ();

++cl; // увеличиваем cl

++c2; // увеличиваем c2

++c2; // увеличиваем c2

cout << "\ncl = " << cl.get_count (); // снова показываем значения

cout << "\nc2 = " << c2.get_count () << endl;

return 0;

}

В этой программе мы создали два объекта класса Counter: cl и с2. Значения счетчиков выведены на дисплей, сначала они равны 0. Затем, используя пере­грузку операции ++, мы увеличиваем cl на единицу, а с2 на два и выводим полу­ченные значения. Результат работы программы:

cl = 0 - изначально обе переменные равны нулю
с2 = О

cl = 1 - после однократного увеличения

с2 = 2 - после двукратного увеличения

Выражения, соответствующие этим действиям:

++cl: ++C2; ++С2;

Операция ++ применена к cl и дважды применена к с2. В этом примере мы использовали префиксную версию операции ++, постфиксную мы объясним позднее.

 

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

Как использовать обычные операции с определенными пользователями тинами? В этом объявлении использовано ключевое слово operator для перегрузки опе­рации ++:

void operator++ ()

Сначала пишут возвращаемый тип (в нашем случае void), затем ключевое слово operator, затем саму операцию (++) и наконец список аргументов, заключен­ный в скобки (здесь он пуст). Такой синтаксис говорит компилятору о том, что если операнд принадлежит классу Counter, то нужно вызывать функцию с таким именем, встретив в тексте программы операцию ++.

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

++intvar:

то компилятор будет использовать свою встроенную процедуру для увеличения переменной типа int. Но если операнд является объектом класса Counter, то ком­пилятор будет использовать написанную программистом функцию operator++().

 

Аргументы операции

В функции main() операция ++ применена к объекту, как в выражении ++cl. До сих пор функция operator++() не имела аргументов. Что же увеличивает эта операция? Она увеличивает переменную count объекта, к которому применяется. Так как методы класса всегда имеют доступ к объектам класса, для которых они были вызваны, то для этой операции не требуются аргументы.

 

Значения, возвращаемые операцией

Функция operator++() программы C0UNTPP1 имеет небольшой дефект. Вы можете его выявить, если используете в функции main() строку, похожую на эту:

C1 = ++С2;

Компилятор будет протестовать. Почему? Просто потому, что мы определи­ли тип void для возвращаемого значения функции operator++(). А в нашем выра­жении присваивания будет запрошена переменная типа Counter. То-есть компи­лятор запросит значение переменной с2, после того как она будет обработана операцией ++, и присвоит ее значение переменной cl. Но, при данном нами оп­ределении в C0UNTPP1, мы не можем использовать ++ для увеличения объекта Counter в выражении присваивания: с таким операндом может быть использо­вана только операция ++. Конечно, обыкновенная операция ++, примененная к данным таких типов, как int, не столкнется с этими проблемами.

Для того чтобы иметь возможность использовать написанный нами operator++() в выражениях присваивания, нам необходимо правильно определить тип его воз­вращаемого значения. Это сделано в следующей нашей программе C0UNTPP2.

// countpp2.cpp

// операция ++. возвращающий значение

#include <iostream>

using namespace std;

///////////////////////////////////////////////////////////

class Counter

{

private:

unsigned int count;

public:

Counter (): count (0) { }

unsigned int get_count ()

{ return count; }

Counter operator++ ()

{

++count;

Counter temp;

temp.count = count;

return temp;

}

};

///////////////////////////////////////////////////////////

int main ()

{

Counter cl, c2; // определяем переменные

cout << "\ncl = " << cl.get_count (); // выводим на экран

cout << "\nc2 = " << c2.get_count ();

++cl; // увеличиваем cl

c2 = ++cl; // cl=2, c2=2

cout << "\ncl = " << cl.get_count (); // снова показываем значения

cout << "\nc2 = " << c2.get_count () << endl;

return 0;

}

Здесь функция operator++() создает новый объект класса Counter, назван­ный temp, для использования его в качестве возвращаемого значения. Она сна­чала увеличивает переменную count в своем объекте, а затем создает объект temp и присваивает ему значение count, то же значение, что и в собственном объекте. В конце функция возвращает объект temp. Получаем ожидаемый эф­фект. Выражение типа

++cl

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

с2 = ++cl:

как показано в функции main(), где значение, возвращаемое cl++, присваивает­ся с2. Результат работы этой программы будет следующим:

cl = О с2 - О cl - 2

с2 = 2

 

Временные безымянные объекты

В примере C0UNTPP2 мы создали временный объект temp типа Counter, чьей един­ственной целью было хранение возвращаемого значения для операции ++. Реа­лизация этого объекта потребовала трех строк программы:

Counter temp; // временный объект Counter

temp.count = count: // присваиваем ему значение count
return temp: // возвращаем объект

Существует много путей возвращения временного объекта из функции или перегруженной операции. Давайте рассмотрим еще один из способов в програм­ме C0UNTPP3:

// countpp3.cpp

// операция ++ с использованием недекларированной переменной

#include <iostream>

using namespace std;

///////////////////////////////////////////////////////////

class Counter

{

private:

unsigned int count;

public:

Counter (): count (0) { }

Counter (int с): count (с) { }

unsigned int get_count ()

{ return count; }

Counter operator++ ()

{

++count;

return Counter (count);

}

};

///////////////////////////////////////////////////////////

int main ()

{

Counter cl, c2; // определяем переменные

cout << "\ncl = " << cl.get_count (); // выводим на зкран

cout << "\nc2 = " << c2.get_count ();

++cl; // увеличиваем cl

c2 = ++cl; // cl-2. c2=2

cout << "\ncl = " << cl.get_count ();// снова показываем значения

cout << "\nс2 = " << c2.get_count () << endl;

return 0;

}

В строке этой программы return Counter (count):

происходят все те же действия, которые в программе C0UNTPP2 занимали три строки. Здесь создается объект типа Counter. Он не имеет имени, так как оно ни­где не будет использоваться. Этот объект инициализируется значением, полу­ченным в виде параметра count.

Но постойте: требуется ли здесь конструктор с одним аргументом? Да, тре­буется. Поэтому мы вставили этот конструктор в список методов класса в про­грамме C0UNTPP3.

Counter (int с): count (с) { }

Объект, инициализированный значением count, может быть возвращен функ­цией. Результат работы этой программы тот же, что и программы C0UNTPP2.

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

 

Постфиксные операции

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

++cl

А как можно использовать постфиксную запись, где переменная увеличива­ется после того, как ее значение было использовано в выражении?

cl++

Чтобы иметь возможность работать с двумя версиями операции, мы опреде­лим два варианта перегрузки операции ++. Это показано в программе POSTFIX.

// postfix.срр

// префиксная и постфиксная операции ++ для нашего класса

#include <iostream>

using namespace std;

///////////////////////////////////////////////////////////

class Counter

{

private:

unsigned int count;

public:

Counter (): count (0) { }

Counter (int с): count (с)

{}

unsigned int get_count ()

{ return count; }

Counter operator++ ()

{

return Counter (++count);

}

Counter operator++ (int) {

return Counter (count++);

}

};

///////////////////////////////////////////////////////////

int main ()

{

Counter cl, c2; // определяем переменные

cout << "\ncl = " << cl.get_count (); // выводим на экран

cout << "\nc2 = " << c2.get_count ();

++cl; // увеличиваем cl

c2 = ++cl; // cl=2, c2=2

cout << "\ncl = " << cl.get_count (); // снова показываем значения

cout << "\nc2 = " << c2.get_count ();

c2 = cl++;

cout << "\ncl = " << cl.get_count (); //и снова

cout << "\nc2 = " << c2.get_count () << endl;

return 0;

}

Теперь у нас есть два типа объявления функции operator++. С одной из них, для префиксной операции, мы уже были знакомы ранее:

Counter operator++ ():

Для реализации постфиксной записи операции ++ используем новую функцию:

Counter operator++ (int):

Различие между этими функциями только в том, что в скобках стоит int. Здесь int не играет роли аргумента и не означает целое число. Это просто сигнал для компилятора, чтобы использовалась постфиксная версия операции. Разработ­чики С++ нашли полезным повторное использование существующих операций и ключевых слов; в данном случае int предназначена также и для обозначения постфиксной операции. (А сможем ли мы использовать лучший синтаксис?) Результат работы программы будет таким:

cl = О с2 - О cl = 2 с2 - 2 cl = 3 с2 = 2

Первые четыре строки мы уже видели в программах C0UNTPP2 и C0UNTPP3. А в последних двух строках мы видим результаты, полученные после обработки выражения

с2 = cl++:

Здесь cl увеличивается до 3, но переменной с2 значение переменной cl было присвоено до ее увеличения. Поэтому переменная с2 имеет значение 2.

Конечно же, мы можем использовать различные варианты записи операции и для operator—.

 

Перегрузка бинарных операций

Бинарные операции могут быть перегружены так же, как и унарные операции. Мы рассмотрим примеры перегрузки арифметических операций, операций срав­нения и операции присваивания.

 

Арифметические операции

В программе ENGLCON мы рассматривали два объекта класса Distance, которые складывались с помощью метода add_dist():

dist3.add_dist (distl. dist2):

Используя перегрузку операции +, мы можем отказаться от этого непригляд­ного выражения и воспользоваться таким:

dist 3 - distl + dist2;

В листинге программы ENGLPLUS реализована эта возможность:

// engplus.cpp

// перегрузка операции + для сложения переменных типа Distances

#include <iostream>

using namespace std;

///////////////////////////////////////////////////////////

class Distance // класс английских мер длины

{

private:

int feet;

float inches;

public:

// конструктор без параметров

Distance (): feet (0), inches (0.0)

{ }

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

Distance (int ft, float in): feet (ft), inches (in)

{ }

// получение информации or пользователя

void getdist () {

cout << "\nВведите футы: ";

cin >> feet;

cout << "Введите дюймы: ";

cin >> inches;

}

// показ информации

void showdist ()

{ cout << feet << "\'-" << inches << '\"'; } // сложение двух длин

Distance operator+(Distance) const;

};

/////////////////////////////////////////////////////////// // сложение двух длин

Distance Distance::operator+ (Distance d2) const {

int f = feet + d2.feet; // складываем футы

float i = inches + d2.inches; // складываем дюймы

if (i >= 12.0) // если дюймов стало больше 12

{

i -= 12.0; // то уменьшаем дюймы на 12

f++; // и увеличиваем футы на 1

}

return Distance (f,i); // создаем и возвращаем временную переменную

}

///////////////////////////////////////////////////////////

int main ()

{

Distance dist1, dist3, dist4; // определяем переменные

dist1.getdist (); // получаем информацию

Distance dist2(11, 6.25); // определяем переменную с конкретным значением

dist3 = dist1 + dist2; // складываем две переменные

dist4 = dist1 + dist2 + dist3; // складываем несколько переменных

// показываем, что же у нас получилось

cout << "distl = ";

dist1.showdist ();

cout << endl;

cout << "dist2 = ";

dist2.showdist ();

cout << endl;

cout << "dist3 = ";

dist3.showdist ();

cout << endl;

cout << "dist4 = ";

dist4.showdist ();

cout << endl;

return 0;

}

Для того чтобы показать, что результат сложения может быть использован в других операциях, мы выполнили в функции main() следующие действия: сло­жили distl, dist2 и dist3, чтобы получить dist4 (значение которой представляет собой удвоенное значение переменной dist3), в строке:

dist4 = distl + dist2 + dist3:

Вот результат работы программы:

Введите футы: 10 Введите дюймы: 6.5

distl - 10'-6.5" - ввод пользователя

dist2 = 1Г-6.25" - предопределено в программе

?sZ3 = 22'-0.75" - distl+dist2

rst4 - 44'-1.5" - distl+dist2+dist3

Объявление метода operator+() в классе Distance выглядит следующим образом: Distance operator*- (Distance);

Эта операция возвращает значение типа Distance и принимает один аргумент типа Distance. В выражении

dist3 - distl + dist2;

важно понимать, к каким объектам будут относиться аргументы и возвраща­емые значения. Когда компилятор встречает это выражение, то он просматри­вает типы аргументов. Обнаружив только аргументы типа Distance, он выполняет операции выражения, используя метод класса Distance operator+(). Но какой из объектов используется в качестве аргумента этой операции — distl или dist2? И не нужно ли нам использовать два аргумента, так как мы складываем два объекта?

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

В функции operator+() к левому операнду мы имеем прямой доступ, исполь­зуя feet и inches, так как это объект, вызывающий функцию. К правому операн­ду мы имеем доступ как к аргументу функции, то есть как d2.feet и d2.inches.

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

Для вычисления значения функции operator+() мы сначала складываем зна­чения feet и inches обоих операндов (корректируя результат, если это необходи­мо). Полученные значения f и i мы затем используем при инициализации безы­мянного объекта класса Distance, который затем будет возвращен в выражение.

return Distance (f, i);

Это похоже на расположение, используемое в C0UNTPP3, за исключением то­го, что конструктор принимает два аргумента вместо одного. В строке

d1st3 - distl + d1st2;

функции main() затем присваивается значение безымянного объекта класса Distance переменной dist3. Сравните это интуитивно-понятное выражение с ис­пользованием вызова функции для выполнения той же задачи, как это было сде­лано в программе ENGLCON из главы 6.

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

 

Объединение строк

Операция + не может быть использована для объединения строк. То есть мы не можем использовать выражение типа:

str3 = strl + str2:

где strl, str2 и str3 — строковые переменные (массивы типа char), и «кот» плюс «бегемот» равно «котбегемот». Однако если мы будем использовать свой собст­венный класс String из программы STR0BJ главы 6, то мы сможем перегрузить операцию + для объединения строк. Это же делает и стандартный класс string в С++, но будет проще пронаблюдать этот процесс на примере нашего более про­стого класса String. Перегрузка операции + для выполнения действий, которые являются не совсем сложением, будет являться нашим следующим примером переопределения языка С++. Приведем листинг программы STRPLUS.

// strplus.cpp

// перегрузка операции + для строк

#include <iostream>

using namespace std;

#include <string.h> // для функций strcpy. strcat

#include <stdlib.h> // для функции exit

///////////////////////////////////////////////////////////

class String // наш класс для строк

{

private:

enum { SZ = 80}; // максимальный размер строки

char str [ SZ ]; // массив для строки

public:

// конструктор без параметров

String ()

{ strcpy (str, ""); } // конструктор с одним параметром

String (char s [ ])

{ strcpy (str, s); } // показ строки

void display () const

{ cout << str; } // оператор сложения

String operator+ (String ss) const

{

String temp; // временная переменная

if (strlen (str) + strlen (ss.str) < SZ)

{

strcpy (temp.str, str); // копируем содержимое первой строки

strcat (temp.str, ss.str); // добавляем содержимое второй строки

}

else

{

cout << "\nПереполнение!";

exit (1);

}

return temp; // возвращаем результат

}

};

////////////////////////////////////////7//////////////////

int main ()

{

String s1= "\nC Рождеством! "; // используем конструктор с параметром

String s2 = "С Новым годом!"; // используем конструктор с параметром

String s3; // используем конструктор без параметров

// показываем строки

s1.display ();

s2.display ();

s3.display ();

s3 = s1 + s2; // присваиваем строке s3 результат сложения строк si и s2

s3.display (); // показываем результат cout «endl:

return 0;

}

 

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

С Рождеством! С Новым годом! - si. s2 и S3 Спока пуста)

С Рождеством! С Новым годом! - s3 после сложения

Перегрузка операции + в этом случае похожа на то, что мы делали ранее. Объявление

String operator* (String ss);

показывает, что функция operator+() принимает один аргумент типа String и возвра­щает объект того же типа. При объединении строк с помощью функции operator+() создается временный объект типа String, в него копируется строка объекта, для которого вызвана функция. Затем, используя библиотечную функцию strcat(), мы присоединяем к ней строку из объекта аргумента и возвращаем полученную во временном объекте строку. Заметим, что мы не можем использовать

return String (string):

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

Вам нужно быть внимательными, чтобы не допустить переполнения длины строки, используемой в классе String. Для предупреждения такого случая мы в функции operator+() проверяем, не превышает ли общая длина двух объединяемых строк максимально возможную длину строки. Если превышает, то мы выводим сообщение об ошибке вместо выполнения операции объединения. (Вы можете сообщить об ошибке и другим путем, например возвратив программе 0 или ис­ключив эту операцию, как мы обсудим в главе 14 «Шаблоны и исключения».)

Напомним, что при использовании enum для установки константы значение SZ временно фиксировано. Так как все компиляторы работают по стандартам С++, то вы можете изменить значение SZ

static const int SZ - 80:

 

Множественная перегрузка

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

 

Операции сравнения

Давайте рассмотрим перегрузку операций другого типа: операций сравнения.

Сравнение объектов класса Distance

В нашем первом примере мы перегрузим операцию «меньше чем» (<) в классе Distance для того, чтобы иметь возможность сравнивать объекты этого класса. Приведем листинг программы ENGLESS.

// engless.срр

// перегрузка операции < для сравнения длин

#include <iostream>

using namespace std;

///////////////////////////////////////////////////////////

class Distance // класс английских мер длины

{

private:

int feet;

float inches;

public:

// конструктор без параметров

Distance (): feet (0), inches (0.0) { }

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

Distance (int ft, float in): feet (ft), inches (in) { }

// получение информации от пользователя

void getdist () {

cout << "\nВведите футы: ";

cin >> feet;

cout << "Введите дюймы: ";

cin >> inches;

}

// показ информации

void showdist ()

{ cout << feet << "\"-" << inches << '\"'; } // сравнение двух длин

bool operator< (Distance) const;

};

/////////////////////////////////////////////////////////// // сравнение двух длин

bool Distance::operator< (Distance d2) const {

float bf1= feet + inches /12;

float bf2 = d2.feet + d2.inches / 12;

return (bf1 < bf2)? true: false;

}

//////////////////////////////////////////////////////////

int main ()

{

Distance dist1; // определяем переменную

dist1.getdist (); // получаем длину от пользователя

Distance dist2 (6, 2.5); // определяем и инициализируем переменную

// показываем длины

cout << "\ndistl = ";

dist1.showdist ();

cout << "\ndist2 = ";

dist2.showdist ();

// используем наш оператор

if (dist1 < dist2)

cout << "\ndist1 меньше чем dist2";

else

cout << "\ndist1 больше или равно чем dist2";

cout << endl;

return 0;

}

 

Эта программа сравнивает интервал, заданный пользователем, с интерва­лом 6'-2.5", определенным в программе. В зависимости от полученного резуль­тата затем выводится одно из двух предложений. Пример возможного вывода:

Введите футы: 5 Введите дюймы: 11.5 distl - 5"-11.5"

<J1st2 - 6'-2.5"

distl меньше чем dist2

Подход, используемый для функции operator<() в программе ENGLESS, похож на перегрузку операции + в программе ENGPLUS, за исключением того, что здесь функция operator<() имеет возвращаемое значение типа boot. Возвращаемым значением может быть false или true, в зависимости от результата сравнения двух интервалов. При сравнении мы конвертируем оба интервала в значения футов с плавающей точкой и затем сравниваем их, используя обычную опера­цию <. Помним, что здесь использована операция условия:

return (bfl < bf2)? true: false:

это то же самое, что

if (bfl < bf2)

return true: else

return false:

Сравнение строк

Рассмотрим другой пример перегрузки оператора, теперь это будет оператор сравнения (==). Мы будем использовать его для сравнения объектов класса String, возвращая true, если они одинаковы, и false, если они различны. Листинг про­граммы STREQUAL:

// strequal.срр

// перегрузка операции —

#include <iostream>

using namespace std;

#include <string.h> // для функции strcmp

//////////////////////////////////////////////

class String // наш класс для строк

{

private:

enum { SZ = 80}; // максимальный размер строки

char str [ SZ ]; // массив для строки

public:

// конструктор без параметров

String ()

{ strcpy (str, " "); } // конструктор с одним параметром

String (char s [ ])

{ strcpy (str, s); } // показ строки

void display () const

{ cout << str; } // получение строки

void getdist ()

{ cin.get (str,SZ); } // оператор сравнения

bool operator== (String ss) const

{

return (strcmp (str, ss.str) == 0)? true: false;

}};

///////////////////////////

int main () {

String s1 = "да";

String s2 = "нет";

String s3;

cout << "\nВведите 'да' или 'нет': ";

s3.getdist (); // получаем строку от пользователя

if (s3 == s1) // сравниваем со строкой 'да'

cout << "Вы ввели 'да'\n";

else

if (s3 == s2) // сравниваем со строкой 'нет'

cout << "Вы ввели 'нет'\n";

else

cout << "Следуйте инструкциям!\n";

return 0;

}

В функции main() мы используем операцию = = дважды: в первый раз, когда определяем, введена ли строка «да», а во второй раз — введена ли строка «нет». Вот результат работы программы, когда пользователь печатает слово «да»:

Введите 'да' или 'нет': да Вы ввели 'да'

Функция operator==() использует библиотечную функцию strcmp() для срав­нения двух строк. Эта функция возвращает 0, если строки равны, отрицательное число, если первая строка меньше второй, и положительное число, если первая строка больше второй строки. Здесь «меньше чем» и «больше чем» использо­ваны в их лексикографическом смысле для определения того, что стоит раньше в алфавитном списке — первая строка или вторая.

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

 

Операции арифметического присваивания

Давайте закончим наше изучение перегрузки бинарных операций на примере арифметического присваивания: +=. Вспомним, что эта операция выполняет при­сваивание и сложение за один шаг. Мы будем использовать эту операцию для сложения интервалов, записывая результат в переменную, обозначающую пер­вый интервал. Это похоже на пример ENGLPLUS, представленный нами ранее, но здесь есть небольшое отличие. Приведем листинг программы ENGLPLEQ.

// engpleq.cpp

// перегрузка операции +=

#include <iostream>

using namespace std;

///////////////////////////////////////

class Distance // класс английских мер длины

{

private:

int feet;

float inches;

public:

// конструктор без параметров

Distance (): feet (0), inches (0.0) { }

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

Distance (int ft, float in): feet (ft), inches (in) { }

// получение информации от пользователя

void getdist () {

cout << "\nВведите футы: ";

cin >> feet;

cout << "Введите дюймы: ";

cin >> inches;

}

// показ информации

void showdist ()

{ cout << feet << "\'-" << inches << '\"'; } // сложение с присвоением

void operator+= (Distance);

};

////////////////////////////////////////////

void Distance::operator+=(Distance d2)

{

feet += d2.feet; // складываем футы

inches +=d2.inches; // складываем дюймы

if (inches >= 12.0) // если дюймов больше 12

{

inches -= 12.0; // то уменьшаем дюймы на 12

feet++; // увеличиваем футы на 1

}

}

///////////////////////////////////

int main () {

Distance dist1; // определяем переменную

dist1.getdist (); //и вводим информацию

cout << "\ndistl = ";

dist1.showdist ();

Distance dist2 (11, 6.25); // описываем и инициализируем другую переменную

cout << "\ndist2 = ";

dist2.showdist ();

dist1 += dist2; // distl = distl + dist2

cout << "\nПосле добавления:";

cout << "\ndist1 = ";

dist1.showdist ();

cout << endl;

return 0;

}

 

В этой программе мы получаем интервал от пользователя и складываем его со вторым интервалом, инициализированным в программе как 1Г-6.25". Пример простого взаимодействия с программой:

Введите футы: 3 Введите дюймы: 5.75 distl - 3'-5.75" dist2 - 11--6.25" После добавления: distl = 151-О"

В этой программе сложение выполняется в функции main() в строке

distl += dist2:

Это означает, что сумма интервалов distl и dist2 будет записана в перемен­ную distl.

Заметим, что существует различие между функцией operator+=(), использу­емой здесь, и функцией operator+() из программы ENGLPLUS. В функции operator+() создается новый объект типа Distance, который будет затем возвращен функци­ей, чтобы быть присвоенным третьему объекту типа Distance:

dist3 = distl + dist2;

В функции operator+=() программы ENGLPLEQ объектом, принимающим значе­ние суммы, является объект, вызывающий функцию operator+=(). Поэтому feet и inches являются заданными величинами, а не временными переменными, исполь­зуемыми только для возвращаемого объекта. Функция operator+-() не имеет воз­вращаемого значения: она возвращает тип void. Для операции += возвращаемое значение не требуется, так как мы не присваиваем ее результат никакой пере­менной. Эта операция используется без возвращаемого значения в выражениях, похожих на выражение из нашей программы:

distl += dist2:

Но если мы захотим использовать эту операцию в более сложных выражени­ях, таких, как:

dist3 - distl +- dist2:

то в этом случае нам понадобится возвращаемое значение. Мы можем ввести его, записав в конце определения функции operator+»() строку

return Distance (feet, inches):

в которой безымянный объект инициализируется тем же значением, что и объ­ект, вызывающий функцию, и затем возвращается.

 

Операция индексации массива ([])

Операция индексации, которая обычно используется для доступа к элементам массива, может быть перегружена. Это полезно в том случае, если вы хотите изменить способ работы С++ с массивами. Например, если вам понадобится создать «безопасный массив», в котором заложена автоматическая проверка ис­пользуемого для доступа к массиву индекса элемента. Она будет проверять, на­ходитесь ли вы в границах массива.

Для демонстрации перегрузки операции индексации массива мы должны вернуться к другой теме, с которой мы уже встречались в главе 5, — возвращению значений из функции по ссылке. Перегруженная операция индексации должна возвращать свои значения по ссылке. Чтобы показать, почему это так, мы рас­смотрим три программы, которые реализуют «безопасный» массив, каждая при этом использует свой подход для вставки и считывания элементов массива:

два отдельных метода put() и get();

метод access(), использующий возвращение по ссылке;

перегруженная операция [ ], использующая возвращение по ссылке.

Все три программы используют класс safearray, в котором определен массив, состоящий из 100 элементов типа int. И все три проверяют, находится ли эле­мент, к которому мы хотим получить доступ, в границах массива. Функция mainQ каждой из программ заполняет массив значениями (каждое из которых равно значению индекса массива, умноженному на 10) и затем выводит их все, чтобы показать, что пользователь сделал все правильно.

Два отдельных метода put() и get()

В первой программе мы используем для доступа к элементам массива два мето­да: putel() для вставки элемента в массив и getel() для получения значения нуж­ного нам элемента. Оба метода проверяют, чтобы значение индекса массива вхо­дило в рамки границ массива. То есть оно не должно быть меньше 0 и больше размера массива (минус 1). Приведем листинг программы ARR0VER1.

// arroverl.cpp

// демонстрация создания безопасного массива, проверяющего

// свои индексы при использовании

// используются отдельные функции для установки и получения значения

#include <iostream>

using namespace std;

#include <process.h> // для функции exit

const int LIMIT = 100; // размер массива

///////////////////////////////////////////////////////////

class safearray

{

private:

int arr [ LIMIT ];

public:

// установка значения элемента массива

void putel (int n, int elvalue) {

if (n < 0 || n >= LIMIT)

{ cout << "\n0шибочный индекс!";

exit (1); }

arr [ n ] = elvalue;

}

// получение значения элемента массива

int getel (int n) const {

if (n < 0 || n >= LIMIT)

{ cout << "\nОшибочный индекс!";

exit (1); }

return arr [ n ];

}

};

//////////////////////////////////////

int main ()

{

safearray sal;

// задаем значения элементов

for (int j = 0; j < LIMIT; j++)

sal.putel (j, j * 10);

// показываем элементы

for (int j = 0; j < LIMIT; j++)

{

int temp = sal.getel (j);

cout <<"Элемент " <<j << " равен " << temp << endl;

}

return 0;

}

Данные помещаются в массив с помощью метода putel() и выводятся на дис­плей с помощью метода getel(). Безопасность массива реализована с помощью вывода сообщения об ошибке при попытке использования индекса, не входяще­го в границы массива. Однако этот формат несколько груб.

Метод access (), использующий возвращение по ссылке

Как оказывается, мы можем использовать один метод для вставки и вывода эле­ментов массива. Секрет этого метода в использовании возвращения значения по ссылке. Это означает, что мы можем записать функцию с левой стороны знака равно, при этом переменной, стоящей справа, будет присвоено значение, возвра­щаемое функцией, как показано в главе 5. Приведем листинг программы ARR0VER2.

// arrover2.cpp

// демонстрация создания безопасного массива, проверяющего

// свои индексы при использовании

// используется общая функция для установки и получения значения

#include <iostream>

using namespace std;

#include <process.h> // для функции exit

const int LIMIT = 100; // размер массива

/////////////////////////////////////

class safearray

{

private:

int arr [ LIMIT ];

public:

// обратите внимание, что функция возвращает ссыпку!

int& access (int n) {

if (n < 0 || n >= LIMIT)

{ cout << "\nОшибочный индекс!";

exit (1); }

return arr [ n ];

}

};

///////////////////////////////////////////////////////////

int main ()

{

safearray sal;

// задаем значения элементов

for (int j = 0; j < LIMIT; j++)

sal.access (j) = j * 10; // используем функцию слева от знака -

// показываем элементы

for (int j = 0; j < LIMIT; j++)

{

int temp = sal.access (j); // используем функцию справа от знака =

cout << "Элемент " << j << " равен " << temp << endl;

}

return 0;

}

Строка sal.access (j) - j * 10:

означает, что значение j*10 будет помещено в элемент массива arrfj], ссылка на который возвращается методом.

Это использование одного метода для ввода и вывода элементов массива в общем случае немного более удобно, чем использование отдельных методов, на одно имя меньше. Но существует еще лучший способ, вовсе без имен.

Перегруженная операция [], использующая возвращение по ссылке

Мы перегрузим операцию индексации [] в классе safearray для использования стандартной записи С++ для доступа к элементам массива. Однако, так как эта операция обычно используется слева



Поделиться:




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

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


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