Перегрузка операторов отношения и логических операторов



 

Операторы отношений (например, "==" или "<" ) и логические операторы (например, "&&" или "||" ) также можно перегружать, причем делать это совсем нетрудно. Как правило, перегруженная операторная функция отношения возвращает объект класса, для которого она перегружается. А перегруженный оператор отношения или логический оператор возвращает одно из двух возможных значений: true или false . Это соответствует обычному применению этих операторов и позволяет использовать их в условных выражениях.

Рассмотрим пример перегрузки оператора "==" для уже знакомого нам класса three_d .

 

 

Если считать, что операторная функция operator==() уже реализована, следующий фрагмент кода совершенно корректен.

 

 

Поскольку операторная функция operator==() возвращает результат типа bool , ее можно использовать для управления инструкцией if . В качестве упражнения попробуйте реализовать и другие операторы отношений и логические операторы для класса three_d .

 

Подробнее об операторе присваивания

 

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

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

Чтобы до конца понять суть описанной проблемы, рассмотрим следующую (некорректную) программу.

 

 

Возможные результаты выполнения этой программы выглядят так.

 

 

В зависимости от используемого компилятора, вы можете увидеть "мусор" или нет. Программа может также сгенерировать ошибку во время выполнения. В любом случае ошибки не миновать. И вот почему.

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

Однако ошибки не миновать, если объект, возвращаемый функцией, присваивается объекту ob , поскольку при выполнении присваивания по умолчанию создается побитовая копия. В данном случае временный объект, возвращаемый функцией input() , копируется в объект ob . В результате член ob.s указывает на ту же самую область памяти, что и член s временного объекта. Но после присваивания в процессе разрушения временного объекта эта память освобождается. Следовательно, член ob.s теперь будет указывать на уже освобожденную память! Более того, память, адресуемая членом ob.s , должна быть освобождена и по завершении программы, т.е. во второй раз. Чтобы предотвратить возникновение этой проблемы, необходимо перегрузить оператор присваивания так, чтобы объект, располагаемый слева от оператора присваивания, выделял собственную область памяти.

Реализация этого решения показана в следующей откорректированной программе.

 

 

Эта программа теперь отображает такие результаты (в предположении, что на приглашение "Введите строку: " вы введете "Привет" ).

 

 

Как видите, эта программа теперь работает корректно. Вы должны понимать, почему выводится каждое из сообщений "Освобождение s‑памяти. " . (Подсказка: одно из них вызвано инструкцией delete в теле операторной функции operator=() .)

 

Перегрузка оператора индексации массивов ([])

 

В дополнение к традиционным операторам C++ позволяет перегружать и более "экзотические", например, оператор индексации массивов ([] ). В C++ (с точки зрения механизма перегрузки) оператор "[]" считается бинарным. Его можно перегружать только для класса и только с использованием функции‑члена. Вот как выглядит общий формат операторной функции‑члена operator[]() .

 

 

Оператор "[]" перегружается как бинарный оператор.

Формально параметр индекс необязательно должен иметь тип int , но операторные функции operator[]() обычно используются для обеспечения индексации массивов, поэтому в общем случае в качестве аргумента этой функции передается целочисленное значение.

Предположим, у нас определен объект ob , тогда выражение

 

 

преобразуется в следующий вызов операторной функции operator[]() :

 

 

Другими словами, значение выражения, заданного в операторе индексации, передается операторной функции operator[]() в качестве явно заданного аргумента. При этом указатель this будет указывать на объект ob , т.е. объект, который генерирует вызов этой функции.

В следующей программе в классе atype объявляется массив для хранения трех int‑значений. Его конструктор инициализирует каждый член этого массива. Перегруженная операторная функция operator[]() возвращает значение элемента, заданного его параметром.

 

 

Здесь функция operator[]() возвращает значение i ‑го элемента массива a . Таким образом, выражение ob[2] возвращает число 2 , которое отображается инструкцией cout . Инициализация массива a с помощью конструктора (в этой и следующей программах) выполняется лишь в иллюстративных целях.

Можно разработать операторную функцию operator[]() так, чтобы оператор "[]" можно было использовать как слева, так и справа от оператора присваивания. Для этого достаточно указать, что значение, возвращаемое операторной функцией operator[]() , является ссылкой. Эта возможность демонстрируется в следующей программе.

 

 

При выполнении эта программа генерирует такие результаты.

 

 

Поскольку функция operator[]() теперь возвращает ссылку на элемент массива, индексируемый параметром i , оператор "[]" можно использовать слева от оператора присваивания, что позволит модифицировать любой элемент массива. (Конечно же, его по‑прежнему можно использовать и справа от оператора присваивания.)

Одно из достоинств перегрузки оператора "[]" состоит в том, что с его помощью мы можем обеспечить средство реализации безопасной индексации массивов. Как вы знаете, в C++ возможен выход за границы массива во время выполнения программы без Соответствующего уведомления (т.е. без генерирования сообщения о динамической ошибке). Но если создать класс, который содержит массив, и разрешить доступ к этому массиву только через перегруженный оператор индексации "[]" , то возможен перехват индекса, значение которого вышло за дозволенные пределы. Например, следующая программа (в основу которой положен код предыдущей) оснащена средством контроля попадания в допустимый интервал.

 

 

При выполнении эта программа выводит такие результаты.

 

 

При выполнении инструкции

 

 

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

 

Перегрузка оператора "()"

 

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

 

 

И если в программе создается объект ob этого класса, то инструкция

 

 

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

 

 

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

Рассмотрим пример перегрузки оператора "()" для класса three_d . Здесь создается новый объект класса three_d , координаты которого представляют собой результаты суммирования соответствующих значений координат вызывающего объекта и значений, передаваемых в качестве аргументов.

 

 

Эта программа генерирует такие результаты.

 

 

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

 

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

 

За исключением таких операторов, как new , delete , ‑> , ‑>* и "запятая" , остальные С++‑операторы можно перегружать таким же способом, который был показан в предыдущих примерах. Перегрузка операторов new и delete требует применения специальных методов, полное описание которых приводится в главе 17 (она посвящена обработке исключительных ситуаций). Операторы ‑> , ‑>* и "запятая" – это специальные операторы, подробное рассмотрение которых выходит за рамки этой книги. Читатели, которых интересуют другие примеры перегрузки операторов, могут обратиться к моей книге Полный справочник по C++ .

 


Дата добавления: 2018-09-22; просмотров: 503; Мы поможем в написании вашей работы!

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






Мы поможем в написании ваших работ!