straustrup2 (852740), страница 53
Текст из файла (страница 53)
Она сопровождает как эхокаждую введенную строку (ввод происходит с помощью операции << , приведенной ниже):ostream& operator<<(ostream& s, const string& x){return s << x.p->s << " [" << x.p->n << "]\n";}Операция ввода происходит с помощью стандартной функции ввода символьной строки ($$10.3.1):istream& operator>>(istream& s, string& x){char buf[256];s >> buf;// ненадежно: возможно переполнение buf// правильное решение см. в $$10.3.1x = buf;cout << "echo: " << x << '\n';return s;}Операция индексации нужна для доступа к отдельным символам. Индекс контролируется:void error(const char* p){cerr << p << '\n';exit(1);}char& string::operator[](int i){if (i<0 || strlen(p->s)<i) error("недопустимое значение индекса");return p->s[i];}199Бьерн Страуструп.Язык программирования С++В основной программе просто даны несколько примеров применения строковых операций.
Слова извходного потока читаются в строки, а затем строки печатаются. Это продолжается до тех пор, пока небудет обнаружена строка done, или закончатся строки для записи слов, или закончится входной поток.Затем печатаются все строки в обратном порядке и программа завершается.int main(){string x[100];int n;cout << " здесь начало \n";for ( n = 0; cin>>x[n]; n++) {if (n==100) {error("слишком много слов");return 99;}string y;cout << (y = x[n]);if (y == "done") break;}cout << "теперь мы идем по словам в обратном порядке \n";for (int i=n-1; 0<=i; i--) cout << x[i];return 0;}7.12 Друзья и членыВ заключении можно обсудить, когда при обращении в закрытую часть пользовательского типа стоитиспользовать функции-члены, а когда функции-друзья.
Некоторые функции, например конструкторы,деструкторы и виртуальные функции ($$R.12), обязаны быть членами, но для других есть возможностьвыбора. Поскольку, описывая функцию как член, мы не вводим нового глобального имени, приотсутствии других доводов следует использовать функции-члены.Рассмотрим простой класс X:class X {// ...X(int);int m1();int m2() const;friend int f1(X&);friend int f2(const X&);friend int f3(X);};Вначале укажем, что члены X::m1() и X::m2() можно вызывать только для объектов класса X.Преобразование X(int) не будет применяться к объекту, для которого вызваны X::m1() или X::m2():void g(){1.m1();1.m2();}// ошибка: X(1).m1() не используется// ошибка: X(1).m2() не используетсяГлобальная функция f1() имеет то же свойство ($$4.6.3), поскольку ее параметр - ссылка безспецификации const.
С функциями f2() и f3() ситуация иная:void h(){f1(1);f2(1);f3(1);}// ошибка: f1(X(1)) не используется// нормально: f2(X(1));// нормально: f3(X(1));200Бьерн Страуструп.Язык программирования С++Следовательно операция, изменяющая состояние объекта класса, должна быть членом или глобальнойфункцией с параметром-ссылкой без спецификации const. Операции над основными типами, которыетребуют в качестве операндов адреса (=, *, ++ и т.д.), для пользовательских типов естественноопределять как члены.Обратно, если требуется неявное преобразование типа для всех операндов некоторой операции, тореализующая ее функция должна быть не членом, а глобальной функцией и иметь параметр типассылки со спецификацией const или нессылочный параметр. Так обычно обстоит дело с функциями,реализующими операции, которые для основных типов не требуют адресов в качестве операндов (+, -, ||и т.д.).Если операции преобразования типа не определены, то нет неопровержимых доводов в пользуфункции-члена перед функцией-другом с параметром-ссылкой и наоборот.
Бывает, что программиступросто одна форма записи вызова нравится больше, чем другая. Например, многим для обозначенияфункции обращения матрицы m больше нравится запись inv(m), чем m.inv(). Конечно, если функцияinv() обращает саму матрицу m, а не возвращает новую, обратную m, матрицу, то inv() должна бытьчленом.При всех прочих равных условиях лучше все-таки остановиться на функции-члене. Можно привеститакие доводы. Нельзя гарантировать, что когда-нибудь не будет определена операция обращения.Нельзя во всех случаях гарантировать, что будущие изменения не повлекут за собой изменения всостоянии объекта. Запись вызова функции-члена ясно показывает программисту, что объект можетбыть изменен, тогда как запись с параметром-ссылкой далеко не столь очевидна.
Далее, выражениядопустимые в функции-члене могут быть существенно короче эквивалентных выражений в глобальнойфункции. Глобальная функция должна использовать явно заданные параметры, а в функции-членеможно неявно использовать указатель this. Наконец, поскольку имена членов не являются глобальнымиименами, они обычно оказываются короче, чем имен глобальных функций.7.13 ПредостереженияКак и всякое другое языковое средство, перегрузка операций может использоваться разумно инеразумно.
В частности, возможностью придавать новый смысл обычным операциям можновоспользоваться так, что программа будет совершенно непостижимой. Представьте, каково будетчитателю, если в своей программе вы переопределили операцию + так, чтобы она обозначалавычитание. Описанный здесь механизм перегрузки будет защищать программиста и пользователя оттаких безрассудств. Поэтому программист не может изменить ни смысл операций над основнымитипами данных, такими, как int, ни синтаксис выражений и приоритеты операций для них.По всей видимости перегрузку операций имеет смысл использовать для подражания традиционномуиспользованию операций.
Запись с обычным вызовом функции можно использовать в тех случаях,когда традиционной записи с базовой операцией не существует, или, когда набор операций, которыедопускают перегрузку, не достаточен, чтобы записать с его помощью нужные действия.7.14 Упражнения1.(*2) Определите итератор для класса string. Определите операцию конкатенации + и операцию += ,значащую "добавить в конец строки".
Какие еще операции вы хотели бы и смогли определить дляэтого класса?2.(*1.5) Определите для строкового класса операцию выделения подстроки с помощью перегрузки ().3.(*3) Определите класс string таким образом, чтобы операцию выделения подстроки можно былоприменять к левой части присваивания. Вначале напишите вариант, в котором строку можноприсваивать подстроке той же длины, а затем вариант с различными длинами строк.4.(*2) Разработайте класс string таким образом, чтобы объекты его трактовались при передачепараметров и присваивании как значения, т.е.
чтобы в классе string копировались самипредставления строк, а не только управляющие структуры.5.(*3) Измените класс string из предыдущего упражнения так, чтобы строки копировались только принеобходимости. Это значит, что нужно хранить одно общее представления двух одинаковых строк201Бьерн Страуструп.Язык программирования С++до тех пор, пока одна из них не изменится. Не пытайтесь задать операцию выделения подстроки,которую одновременно можно применять и к левой части присваивания.6.(*4) Определите класс string, обладающий перечисленными в предыдущих упражненияхсвойствами: объекты его трактуются как значения, копирование является отложенным (т.е.происходит только при необходимости) и операцию выделения подстроки можно применять к левойчасти присваивания.7.(*2) Какие преобразования типа используются в выражениях следующей программы?struct X {int i;X(int);operator+(int);};struct Y {int i;Y(X);operator+(X);operator int();};extern X operator*(X,Y);extern int f(X);X x = 1;Y y = x;int i = 2;int main(){i + 10;x + y + i;f(y);}y + 10;x * X +i;y + y;y + 10 * y;f(7);106 + y;Определите X и Y как целые типы.
Измените программу так, чтобы ее можно было выполнить и онанапечатала значения всех правильных выражений.8.(*2) Определите класс INT, который будет эквивалентен типу int. Подсказка: определите функциюINT::operator int().9.(*1) Определите класс RINT, который будет эквивалентен типу int, за исключением того, чтодопустимыми будут только операции: + (унарный и бинарный), - (унарный и бинарный), *, / и %.Подсказка: не надо определять RINT::operator int().10. (*3) Определите класс LINT, эквивалентный классу RINT, но в нем для представления целогодолжно использоваться не менее 64 разрядов.11. (*4) Определите класс, реализующий арифметику с произвольной точностью.
Подсказка: Придетсяиспользовать память так, как это делается в классе string.12. (*2) Напишите программу, в которой благодаря макрокомандам и перегрузке будет невозможноразобраться. Совет: определите для типа INT + как -, и наоборот; с помощью макроопределениязадайте int как INT. Кроме того, большую путаницу можно создать, переопределяя широкоизвестные функции, и используя параметры типа ссылки и задавая вводящие в заблуждениекомментарии.13. (*3) Обменяйтесь решениями упражнения [12] с вашим другом.
Попробуйте понять, что делает егопрограмма, не запуская ее. Если вы сделаете это упражнение, вам станет ясно, чего надо избегать.14. (*2) Перепишите примеры с классами complex ($$7.3), tiny ($$7.3.2) и string ($$7.11), не используядружественные функции. Используйте только функции-члены.
Проверьте новые версии этих202Бьерн Страуструп.Язык программирования С++классов. Сравните их с версиями, в которых используются дружественные функции. Обратитесь купражнению 5.3.15. (*2) Определите тип vec4 как вектор из четырех чисел с плавающей точкой. Определите для негофункцию operator[]. Для комбинаций векторов и чисел с плавающей точкой определите операции: +,-, *, /, =, +=, -=, *= и /=.16. (*3) Определите класс mat4 как вектор из четырех элементов типа vec4.
Определите для негофункцию operator[], возвращающую vec4. Определите для этого типа обычные операции сматрицами. Определите в mat4 функцию, производящую преобразование Гаусса с матрицей.17. (*2) Определите класс vector, аналогичный классу vec4, но здесь размер вектора должен задаватьсякак параметр конструктора vector::vector(int).18. (*3) Определите класс matrix, аналогичный классу mat4, но здесь размерности матрицы должнызадаваться как параметры конструктора matrix::matrix(int,int).19. (*3) Завершите определение класса CheckedPtrToT из $$7.10 и проверьте его.