Смекни!
smekni.com

Правила правой руки 17 Замечания для программистов на c 17 Глава 1 (стр. 35 из 43)

не членом. Это часто имеет место для функций, кторые реализуют

операции, не требующие при применении к фуныаментальным типам

lvalue в качестве операндов (+, -, || и т.д.).

- стр 195 -

Если никакие преобразования типа не определены, то оказывается,

что нет никаких существенных оснований в пользу члена, если есть

друг, который получает ссылочный параметр, и наоборот. В некоторых

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

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

обращения матрицы m запись m.inv(). Конечно, если inv()

действительно обращает матрицу m, а не просто возвращает новую

матрицу, обратную m, ей следут быть другом.

При прочих равных условиях выбирайте, чтобы функция была членом:

никто не знает, вдруг когда-нибудь кто-то определит операцию

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

изменения изменить статус объекта. Синтаксис вызова функции члена

ясно указывает пользователю, что объект можно изменить; ссылочный

параметр является далеко не столь очевидным. Кроме того, выражения

в члене могут быть заметно короче выражений в друге. В функции

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

использовать неявный this. Если только не применяется перегрузка,

имена членов обычно короче имен друзей.

6.11 Предостережение

Как и большая часть возможностей в языках программирования,

перегрузка операций может применяться как правильно, так и

неправильно. В частности, можно так воспользоваться возможность

определять новые значения старых операций, что они станут почти

совсем непостижимы. Представьте, например, с какими сложностями

столкнется человек, читающий программу, в которой операция + была

переопределена для обозначения вычитания.

Данный аппарат должен уберечь программиста/читателя от худших

крайностей применения перегрузки, потому что программист

предохранен от изменения значения операций для основных типов

данных вроде int, а также потому, что синтаксис выражений и

приоритеты операций сохраняются.

Может быть. разумно применять перегрузку операций главным образом

так, чтобы подражать общепринятому применению операций. В тех

случаях, когда нет общепринятой операции или имеющееся в C++

множество операций не подходит для имитации общепринятого

применения, можно использовать запись вызова функции.

6.12 Упражнения

1. (*2) Определите итератор для класса string. Определите

операцию конкатенации + и операцию "добавить в конец" +=.

Какие еще операции над string вы хотели бы осуществлять?

2. (*1.5) Задайте с помощью перегрузки () операцию выделения

подстроки для класса строк.

3. (*3) Постройте класс string так, чтобы операция выделения

подстроки могла использоваться в левой части присваивания.

Напишите сначала версию, в которой строка может присваиваться

подстроке той же длины, а потом версию, где эти длины могут

быть разными.

4. (*2) Постройте класс string так, чтобы для присваивания,

передачи параметров и т.п. он имел семантику по значению, то

- стр 196 -

есть в тех случаях, когда копируется строковое представление,

а не просто управляющая структура данных класса sring.

5. (*3) Модифицируйте класс string из предыдущего примера таким

образом, чтобы строка копировалась только когда это

необходимо. То есть, храните совместно используемое

представление двух строк, пока одна из этих строк не будет

изменена. Не пытайтесь одновременно с этим иметь операцию

выделения подстроки, которая может использоваться в левой

части.

6. (*4) Разработайте класс string с семантикой по значению,

копированием с задержкой и операцией подстроки, которая может

стоять в левой части.

7. (*2) Какие преобразования используются в каждом выражении

следующей программы:

struct X {

int i;

X(int);

operator+(int);

};

struct Y {

int i;

Y(X);

operator+(X);

operator int();

};

X operator* (X,Y);

int f(X);

X x = 1;

Y y = x;

int i = 2;

main()

{

i + 10;

y + 10;

y + 10 * y;

x + y + i;

x * x + i;

f(7);

f(y);

y + y;

106 + y;

}

Определите X и Y так, чтобы они оба были целыми типами.

Измените программу так, чтобы она работала и печатала значения

всех допустимых выражений.

8. (*2) Определите класс INT, который ведет себя в точности как

int. Подсказка: определите INT::operator int().

9. (*1) Определите класс RINT, который ведет себя в точности как

int за исключением того, что единственные возможные операции -

- стр 197 -

это + (унарный и бинарный), - (унарный и бинарный), *, /, %.

Подсказка: не определяйте $ (R?)INT::operator int().

10. (*3) Определите класс LINT, ведущий себя как RINT за

исключением того, что имеет точность не менее 64 бит.

11. (*4) Определите класс, который реализует арифметику с

произвольной точностью. Подсказка: вам надо управлять памятью

аналогично тому, как это делалось для класса string.

12. (*2) Напишите программу, доведенную до нечитаемого состояния

с помощью макросов и перегрузки операций. Вот идея: определите

для INT + чтобы он означал - и наоборот, а потом с помощью

макроопределения определите int как INT. Переопределение часто

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

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

устроить полную неразбериху.

13. (*3) Поменяйтесь со своим другом программами, которые у вас

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

понять, что делает программа вашего друга. После выполнения

этого упражнения вы будете знать, чего следует избегать.

14. (*2) Перепишите примеры с comlpex (#6.3.1), tiny (#6.3.2) и

string (#6.9) не используя friend функций. Используйте только

функции члены. Протестируйте каждую из новых версий. Сравните

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

посмотрите Упражнение 5.3.

15. (*2) Определите тип vec4 как вектор их четырех float.

Определите operator[] для vec4. Определите операции +, -, *,

/, =, +=, -=, *=, /= для сочетаний векторов и чисел с

плавающей точкой.

16. (*3) Определите класс mat4 как вектор из четырех vec4.

Определите для mat4 operator[], возвращающий vec4. Определите

для этого типа обычные операции над матрицами. Определите

функцию, которая производит исключение Гаусса для mat4.

17. (*2) Определите класс vector, аналогичный vec4, но с длиной,

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

vector::vector(int).

18. (*3) Определите класс matrix, аналогичный mat4, но с

размерностью, задаемой параметрами конструктора

matrix::matrix(int,int).

* Глава 7 *

Производные Классы

Не надо размножать объекты без необходимости

- У. Оккам

В этой главе описывается понятие производного класса в C++.

Производные классы дают простой, гибкий и эффективный аппарат

задания для класса альтернативного интерфейса и определения класса

посредством добавления возможностей к уже имеющемуся классу без

перепрограммирования или перекомпиляции. С помощью производных

классов можно также обеспечить общий интерфейс для нескольких

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

с объектами этих классов одинаковым образом. При этом обычно в

каждый объект помещается информация о типе, чтобы эти объекты могли

обрабатываться соответствуюшим образом в ситуациях, когда их тип

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

обработки таких динамических зависимостей типов имеется понятие

виртуальной функции. По своей сути производные классы существуют

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

7.1 Введение

Представим себе процесс написания некоторого средства общего

назначения (например, тип связанный список, таблица имен или

планировщик для системы моделирования), кооторое предназначается

для использоания многими разными людьми в различных

обстоятельствах. Очевидно, что в кандидатах на роль таких средств

недостатка нет, и выгоды от их стандартизации огромны. Кажется,

любой опытный программист написал (и отладил) дюжину вариантов

типов множества, таблицы имен, сортирующей функции и т.п., но

оказывается, что таблиц имен каждый программист и каждая программа

используют свою версию этих понятий, из-за чего программы слишком

трудно читать, тяжело отлаживать и сложно модифицировать. Более

того, в большой программе вполне может быть несколько копий

идентичных (почти) частей кода для работы с такими фундаментальными

понятиями.

Причина этого хаоса частично состоит в том, что представить такие

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

зрения, а частично в том, что средства, обладающие достаточной

общностью, налагают дополнительные расходы по памяти и/или по

времени, что делает их неудобными для самых простых и наиболее

напряженно используемых средств (связанные списки, вектора и т.п.),

где они были бы наиболее полезны. Понятие производного класса в

C++, описываемое в #7.2, не обеспечивают общего решения всех этих

проблем, но оно дает способ справляться с довольно небольшим числом

важных случаев. Будет, например, показано, как определить

эффективный класс общего связанного списка таким образом, чтобы все

его версии использовали код совместно.

Написание общецелевых средств - задача непростая, и часто

основной акцент в их разработке другой, чем при разработке программ

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

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

- стр 199 -

средствам, которые описываются в этой главе, можно относиться так,

что они становятся все более полезны с ростом объема и сложности

создаваемых программ.

7.2 Производные Классы

Чтобы разделить задачи понимания аппарата языка и методов его