Категории
Самые читаемые
Лучшие книги » Компьютеры и Интернет » Программирование » Язык программирования C++. Пятое издание - Стенли Липпман

Язык программирования C++. Пятое издание - Стенли Липпман

Читать онлайн Язык программирования C++. Пятое издание - Стенли Липпман

Шрифт:

-
+

Интервал:

-
+

Закладка:

Сделать
1 ... 90 91 92 93 94 95 96 97 98 ... 297
Перейти на страницу:

Упражнение 7.23. Напишите собственную версию класса Screen.

Упражнение 7.24. Добавьте в свой класс Screen три конструктора: стандартный; получающий высоту, ширину и заполняющий содержимое соответствующим количеством пробелов; получающий высоту, ширину и заполняющий символ для содержимого экрана.

Упражнение 7.25. Может ли класс Screen безопасно полагаться на заданные по умолчанию версии операторов копирования и присвоения? Если да, то почему? Если нет, то почему?

Упражнение 7.26. Определите функцию Sales data::avg_price как встраиваемую.

7.3.2. Функции, возвращающие указатель *this

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

class Screen {

public:

 Screen &set(char);

 Screen &set(pos, pos, char);

 // другие члены, как прежде

};

inline Screen &Screen::set(char c) {

 contents[cursor] = с; // установите новое значение в текущей позиции

                       // курсора

 return *this;         // возвратить этот объект как l-значение

}

inline Screen &Screen::set(pos r, pos col, char ch) {

 contents[r * width + col] = ch; // установить позицию по данному

                                 // значению

 return *this; // возвратить этот объект как l-значение

}

Как и функция move(), функция-член set() возвращает ссылку на объект, из которого они вызваны (см. раздел 7.1.2). Возвращающие ссылку функции являются l-значениями (см. раздел 6.3.2), а это означает, что они возвращают сам объект, а не его копию. Это позволяет связать несколько их вызовов в одно выражение:

// переместить курсор в указанную позицию и присвоить

// символу значение

myScreen.move(4,0).set('#');

Эти операции выполнятся для того же объекта. В этом выражении сначала перемещается курсор (move()) в окно (myScreen), а затем устанавливается (set()) заданный символ. Таким образом, этот оператор эквивалентен следующему:

myScreen.move(4,0);

myScreen.set('#');

Если бы функции move() и set() возвращали тип Screen, а не Screen&, этот оператор выполнялся бы совсем по-другому. В данном случае он был бы эквивалентен следующему:

// если move возвращает Screen, а не Screen&

Screen temp = myScreen.move(4,0); // возвращаемое значение было

                                  // бы скопировано

temp.set('#'); // содержимое myScreen осталось бы неизменно

Если бы функция move() имела возвращаемое значение не ссылочного типа, то оно было бы копией *this (см. раздел 6.3.2). Вызов функции set() изменил бы лишь временную копию, а не сам объект myScreen.

Возвращение *this из константной функции-члена

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

Логически отображение объекта класса Screen (окна) не изменяет его, поэтому функцию display() следует сделать константным членом. Но если функция display() будет константной, то this будет указателем на константу, а значение *this — константным объектом. Следовательно, типом возвращаемого значения функции display() будет const Screen&. Однако, если функция display() возвратит ссылку на константу, мы не сможем вставить вызов функции display() в последовательность действий:

Screen myScreen;

// если display возвращает константную ссылку,

// вызов в последовательности будет ошибкой

myScreen.display(cout).set('*');

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

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

Перегрузка на основании константности

Функции-члены вполне можно перегружать исходя из того, являются ли они константными или нет, причем по тем же причинам, по которым функцию можно перегружать исходя из того, является ли ее параметр указателем на константу (см. раздел 6.4). Неконстантная версия неприменима для константных объектов; она применима только для константных объектов. Для неконстантного объекта можно вызвать любую версию, но неконстантная версия будет лучшим соответствием.

В этом примере определим закрытую функцию-член do_display() для фактического вывода окна. Каждая из функций display() вызовет эту функцию, а затем возвратит объект, для которого она выполняется:

class Screen {

public:

 // display перегружена на основании того, является ли

 // объект константой или нет

 Screen &display(std::ostream &os)

 { do_display(os); return *this; }

 const Screen &display(std::ostream &os) const

 { do_display(os); return *this; }

private:

 // функция отображения окна

 void do_display(std::ostream &os) const {os << contents;}

 // другие члены как прежде

};

Как и в любом другом случае, при вызове одной функции-члена другой неявно передается указатель this. Таким образом, когда функция display() вызывает функцию-член do_display(), ей неявно передается собственный указатель this. Когда неконстантная версия функции display() вызывает функцию do_display(), ее указатель this неявно преобразуется из указателя на неконстанту в указатель на константу (см. раздел 4.11.2).

Когда функция do_display() завершает работу, функция display() возвращает объект, с которым они работают, обращаясь к значению указателя this. В неконстантной версии указатель this указывает на неконстантный объект, так что эта версия функции display() возвращает обычную, неконстантную ссылку; константная версия возвращает ссылку на константу.

Когда происходит вызов функции display() для объекта, вызываемую версию определяет его константность:

Screen myScreen(5, 3);

const Screen blank(5, 3);

myScreen.set('#').display(cout); // вызов неконстантной версии

blank.display(cout);             // вызов константной версии

Совет. Используйте закрытые вспомогательные функции

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

Зачем же она нужна? Причин здесь несколько.

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

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

• Во время разработки в тело функции display(), вероятно, придется добавить отладочный код, который в финальной версии будет удален. Это будет проще сделать в случае, когда весь отладочный код находится в одной функции do_display().

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

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

Упражнения раздела 7.3.2

Упражнение 7.27. Добавьте функции move(), set() и display() в свою версию класса Screen. Проверьте свой класс, выполнив следующий код:

Screen myScreen(5, 5, 'X');

myScreen.move(4,0).set('#').display(cout);

cout << "n";

myScreen.display(cout);

cout << "n";

Упражнение 7.28. Что если бы в предыдущем упражнении типом возвращаемого значения функций move(), set() и display() был Screen, а не Screen&?

Упражнение 7.29. Пересмотрите свой класс Screen так, чтобы функции move(), set() и display() возвращали тип Screen, а затем проверьте свое предположение из предыдущего упражнения.

1 ... 90 91 92 93 94 95 96 97 98 ... 297
Перейти на страницу:
На этой странице вы можете бесплатно скачать Язык программирования C++. Пятое издание - Стенли Липпман торрент бесплатно.
Комментарии