Анимация
JavaScript
|
Главная Библионтека Аналогия проблеме "сдвиг как вывод" может быть найдена в проектировании компьютерных систем. Большинство проектировщиков аппаратуры были бы счастливы использовать + вместо OR, а * вместо AND, потому что такая запись используется во многих системах проектирования электронных компонентов. Несмотря на это, перегрузка операции operator+() в качестве OR явно не нужна в Си++. К тому же, лексема << означает "сдвиг" в Си и Си++; она не означает "вывод". Как завершающий пример этой проблемы - я иногда видел реализации класса "множество", определяющие и & со значениями "объединение" и "пересечение". Это может иметь смысл для математика, знакомого с таким стилем записи, но при этом не является выражением ни Си, ни Си++, поэтому будет незнакомо для вашего среднего программиста на Си++ (и вследствие этого с трудом сопровождаться). Амперсанд является сокращением для AND; вы не должны назначать ему произвольное значение. Нет абсолютно ничего плохого в a.Union(b) или a.intersect(b). (Вы не можете использовать a.union(b) со строчной буквой u, потому что union является ключевым словом). 146. Используйте перегрузку операций только для определения операций, имеющих аналог в Си (без сюрпризов) Перегрузка операций была реализована в языке, прежде всего, для того, чтобы вы могли интегрировать разработанный вами арифметический тип в существующую арифметическую систему языка Си. Этот механизм никогда не предназначался в качестве средства расширения этой системы. Следовательно, перегрузку операций лучше применять, используя классы для реализации лишь арифметических типов. Тем не менее, также разумно использовать перегруженные операции и там, где аналогии с Си незаметны. Например, большинство классов будет перегружать присваивание. Перегрузка operator==() и operator!=() также разумна в большинстве классов. Менее ясным (и более противоречивым) примером является класс "итератор". Итератор является средством просмотра каждого члена структуры данных, и он используется почти точно так же, как если бы он был указателем на массив. Например, вы можете в Си итерировать массив, просматривая каждый элемент, следующим образом: string array[ size ]; string *p = array; for( int i = size; -- i >=0;)
visit( *p++ ); функции visit() передается строка. Аналог в Си++ может выглядеть вот так (keys является деревом, чьи узлы имеют строковые ключи; здесь могут быть любые другие структуры данных): tree<string> keys; двоичное дерево с узлами, имеющими строковые ключи iterator p = keys; ... for( int i = keys.sizeO; --i >=0;) visit( *p++ ); функции visit() передается строка. Другими словами, вы обращаетесь с деревом как с массивом, и можете итерировать его при помощи итератора, действующего как указатель на элемент. И так как iterator(p) ведет себя точно как указатель в Си, то правило "без сюрпризов" не нарушается. 147. Перегрузив одну операцию, вы должны перегрузить все сходные с ней операции Это правило является продолжением предыдущего. После того, как вы сказали, что "итератор работает во всем подобно указателю", он на самом деле должен так работать. Пример в предыдущем правиле использовал лишь перегруженные * и ++, но моя настоящая реализация итератора делает аналогию полной, поддерживая все операции с указателями. Таблица 4 показывает различные возможности (t является деревом, а ti - итератором для дерева). Обе операции *++p и *p++ должны работать и т. д. В предыдущем примере я бы должен был также перегрузить в классе tree операции operator [] и (унарная) operator*() для того, чтобы аналогия дерева с массивом выдерживалась везде. Вы уловили эту мысль? Таблица 4. Перегрузка операторов в итераторе
Одна из проблем здесь связана с операциями operator==() и operator ! = (), которые при первом взгляде кажутся имеющими смысл в ситуациях, где другие операции сравнения бессмысленны. Например, вы можете использовать == для проверки двух окружностей на равенство, но означает ли равенство "одинаковые координаты и одинаковый радиус", или просто "одинаковый радиус"? Перегрузка других операций сравнения типа < или <= еще более сомнительна, потому что их значение не совсем очевидно. Лучше полностью избегать перегрузки операций, если есть какая-либо неясность в их значении. 148. Перегруженные операции должны работать точно так же, как они работают в Си Главной новой проблемой здесь являются адресные типы lvalue и rvalue. Выражения типа lvalue легко описываются в терминах Си++: они являются просто ссылками. Компилятор Си, вычисляя выражение, выполняет операции по одной за раз в порядке, определяемом правилами сочетательности и старшинства операций. Каждый этап в вычислениях использует временную переменную, полученную при предыдущей операции. Некоторые операции генерируют "rvalue" - действительные объекты, на самом деле содержащие значение. Другие операции создают "lvalue" - ссылки на объекты. (Кстати, "l" и "r" используются потому, что в выражении l=r слева от = генерируется тип lvalue. Справа образуется тип rvalue). Вы можете сократить эффект неожиданности для своего читателя, заставив свои перегруженные операции-функции работать тождественно их эквивалентам на Си в пределах того, что они могут. Далее описано, как работают операции Си и как имитировать их поведение: • Операции присваивания (=, +=, -= и т.д.) и операции автоинкремента и автодекремента (++, --) требуют операндов типа lvalue для адресата - части, которая изменяется. Представьте ++ как эквивалент для + = 1, чтобы понять, почему эта операция в той же категории, что и присваивание. В перегруженных операциях функций-членов указатель this на самом деле является lvalue, поэтому здесь не о чем беспокоиться. На глобальном уровне левый операнд перегруженной бинарной операции 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 [ 67 ] 68 69 70 71 72 73 74 75 76 |