Глава №9. Итоговый тест

  Юрий  | 

  Обновл. 23 Июн 2019  | 

 5838

 ǀ   13 

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

Теория

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

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

   Перегрузку операторов присваивания (=), индекса ([]), вызова функции (()) или выбора члена (->) выполняйте через методы класса.

   Перегрузку унарных операторов выполняйте через методы класса.

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

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

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

Конструктор копирования — это особый тип конструктора, используемый для инициализации объекта другим объектом того же класса. Конструкторы копирования используются в прямой/uniform инициализации объектов объектами того же типа, копирующей инициализации (Fraction f = Fraction(7,4)) и при передаче или возврате параметров по значению.

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

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

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

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

Тест


Задание №1

Предположим, что Square — это класс, а square — это объект этого класса. Какой способ перегрузки лучше использовать для следующих операторов?

   square + square

   -square

   std::cout << square

   square = 7;

Ответ №1

   Перегрузку бинарного оператора + (плюс) лучше всего выполнять через обычную/дружественную функцию.

   Перегрузку унарного оператора − (минус) лучше всего выполнять через метод класса.

   Перегрузка оператора << должна выполняться через обычную/дружественную функцию.

   Перегрузка оператора = должна выполняться через метод класса.

Задание №2

Напишите класс Average, который будет вычислять среднее значение всех передаваемых ему целых чисел. Используйте два члена: первый должен быть типа int32_t и использоваться для вычисления суммы всех передаваемых чисел. Второй должен быть типа int8_t и использоваться для вычисления количества передаваемых чисел. Чтобы найти среднее значение нужно разделить сумму на количество.

a) Следующий код функции main():

Должен производить следующий результат:

5
7
11
6
7
7

Ответ 2.а)

b) Требуется ли этому классу явный конструктор копирования или оператор присваивания?

Ответ 2.b)

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

Задание №3

Напишите свой собственный класс-массив целых чисел IntArray (не используйте std::array или std::vector). Пользователи должны передавать размер массива при создании объекта этого класса, а сам массив (переменная-член) должен выделяться динамически. Используйте стейтменты assert для проверки передаваемых значений и свой конструктор копирования и перегрузку оператора присваивания, если это необходимо, чтобы следующий код:

Производил следующий результат:

6 7 3 4 5 8
6 7 3 4 5 8

Ответ №3

Задание №4

Значение типа с плавающей запятой — это число с десятичной дробью, где количество цифр после запятой (дробная часть) может меняться. Значение типа с фиксированной запятой — это число с дробью, где дробь (после запятой) фиксированная.

Вам нужно написать класс для реализации значений типа с фиксированной запятой с двумя цифрами после запятой (например, 11.47, 5.00 или 1465.78). Диапазон класса должен быть от -32768.99 до 32767.99, в дробной части могут быть любые две цифры, проблем с точностью не допустить.

a) Какого типа данных переменную-член следует использовать для реализации значений типа с фиксированной запятой с 2-мя цифрами после запятой? (Обязательно прочитайте ответ, прежде чем приступать к выполнению следующего задания)

Ответ 4.а)

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

Лучшее решение: Использовать тип int16_t signed для хранения целой части значения и int8_t signed для хранения дробной части значения.

b) Напишите класс FixedPoint, который реализует рекомендуемое решение из предыдущего задания. Если дробная или целая части значения являются отрицательными, то число должно рассматриваться как отрицательное. Реализуйте перегрузку необходимых операторов и напишите необходимые конструкторы, чтобы следующий код функции main():

Производил следующий результат:

37.58
-3.09
-4.07
-5.07
-0.03
-0.03

Подсказка: Для вывода значения конвертируйте его в double, используя оператор static_cast.

Ответ 4.b)

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

Подсказка №1: Вы можете получить целую часть от числа double путём конвертации double в тип int.

Подсказка №2: Для перемещения одной цифры влево от запятой, используйте умножение на 10. Для перемещения двух цифр, используйте умножение на 100.

Следующий код функции main():

Должен производить следующий результат:

0.03
-0.03
4.01
-4.01

Ответ 4.c)

d) Выполните перегрузку следующих операторов: ==, >>, − (унарный) и + (бинарный).

Следующая программа:

Должна производить следующий результат:

true
true
true
true
true
true
true
true
-0.48
0.48
Enter a number: 5.678
You entered: 5.68

Подсказка: Для выполнения перегрузки оператора >> используйте конструктор с параметром типа double для создания анонимного объекта класса FixedPoint, а затем присвойте этот объект параметру функции перегрузки оператора >>.

Ответ 4.d)

Оценить статью:

Звёзд: 1Звёзд: 2Звёзд: 3Звёзд: 4Звёзд: 5 (26 оценок, среднее: 4,73 из 5)
Загрузка...

Комментариев: 13

  1. Аватар Анастасия:

    По 4 заданию у меня было много вопросов. Сложнее всего далась последняя часть 4го задания, компилятор долго ругался. Посмотрела ответ, но это не сильно помогло, т.к. я изначально делала по-другому. В итоге я на свежую голову пересмотрела и исправила почти весь код и тогда почти сразу заработало. Я не буду задавать вопросы по коду из ответа, но оттуда я почти списала перегрузку ввода и сложения. Вот мой код, сейчас всё работает:

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

  2. Аватар Игорь:

    Не пойму зачем в 3 задании реализовывать конструктор копирования:

    ведь всё работает без него?

    1. Аватар Анастасия:

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

  3. Аватар kmish:

    Не совсем понимаю зачем перегружать —(унарный) в 4м задании, если конструктор

    может принимать отрицательные значения double. Какой смысл тогда в перегрузке -?

    Сделал без нее, результат аналогичный:

    1. Аватар Анастасия:

      Я понимаю, почему надо перегружать унарный минус, но не понимаю, почему у Вас работает без него))
      Вот мои мысли: Чтобы работала эта строчка:

      где а — это объект класса. У нас есть оператор вывода для строк и чисел (стандартный я имею в виду) и перегруженный для объектов класса, но что такое "минус объект"? Этого компилятор без перегрузки оператора "минус" для класса не знает. По идее при Вашем коде он должен как минимум ругнуться на какое-нибудь неявное преобразование в этом месте. Почему он этого не делает — для меня вопрос…

  4. Аватар subj:

    не понял как работает round() в задании 4c, но если его убрать в версии кода из ответов, то выводит 4 вместо 4.01.
    При решении сделал немного по своему конструктор double, который выдавал правильное число:

    1. Аватар Анастасия:

      round просто отбрасывает дробную часть. Я тоже сделала по-другому.

  5. Аватар Андрей:

    Вы можете что-то сказать о графике С++. Какой заголовок подключить, как реализовать работу и тп?

    1. Юрий Юрий:

      Сейчас, к сожалению, уроков на эту тему не предвидится.

  6. Аватар Torgu:

    Правильно ли я понял, что эти условия нужны, чтобы если целая или дробная часть отрицательна (только одна из них, не две!), то при выводе в консоль не происходило вычитание между целой и дробной частью, а только сложение?

    1. Аватар Анастасия:

      Ваш вопрос не понятен. Если ещё актуально, сформулируйте по-другому как-то.

  7. Аватар Shom:

    Сколько же я разных чувств испытал пока решал всё это ))

    1. Юрий Юрий:

      Ох понимаю 🙂

Добавить комментарий

Ваш E-mail не будет опубликован. Обязательные поля помечены *

telegram канал
НОВОСТИ RAVESLI