Штерн В. - Основы C++. Методы программной инженерии - 2003
.pdfГлава 16 • Расширенное использование перегрузки операций |
741 |
Втрадиционном программировании это невозможно — возврандаемое значе ние функции не может использоваться в левой части присваивания. С-Ы- обеспе чивает такую возможность, если функция возвращает ссылку на значение, а не само значение. Ссылка должна быть действительной и не должна исчезать при завершении функции.
Вглаве 7 обсуждались возможности, которые открывает для написания крат кой и выразительной клиентской программы возвращение ссылки из функций. Устраним параметр-значение для интерфейса setlntO и изменим возвращаемый тип setlntO из целого значения в целый указатель.
int& |
Array::setlnt(int i ) |
/ / |
изменение объекта Array |
||
{ i f |
( i < О I I i |
>= size) |
/ / |
проверка допустимости |
индекса |
|
return ptr[size - 1]; |
/ / |
возвращение последнего |
компонента |
|
return p t r [ i ] ; |
} |
/ / |
действительный индекс: |
присвоить значение |
|
Эта функция поддерживает клиентский цикл, приведенный выше: он возвращает ссылку на целое значение, а в цикле значение присваивается по адресу, на кото рый указывает ссылка. Критическим компонентом этой схемы является то, что ссылка указывает не на локальное значение, которое исчезало бы, когда функция setlntO завершается. Ссылка указывает на компонент массива, который имелся до вызова setlntO и будет существовать после завершения setlnt().
Сравним getlntO и новую версию setlntO. Видно, что их реализации одина ковые. Требуются ли клиентской программе обе функции? Между ними сущест вуют два различия, касающихся интерфейса функции. Возвращенное значение getlntO является значением, а не ссылкой. Это не серьезная проблема. Изменим возвращаемое значение getlntO на ссылку на целое число.
int& Array::getlnt(int i ) const |
/ / |
объект |
не изменяется |
|
{ i f ( i < О I I i |
>= size) |
/ / |
индекс |
вне границ |
return p t r [ s i z e - l ] ; |
/ / |
возвращение последнего компонента |
||
return p t r [ i ] ; |
} |
/ / действительный индекс: присвоить значение |
||
Сэтой функцией клиентская программа в листинге 16.7 будет работать, как
ипрежде.
for |
(int |
i=0; |
i |
< size; |
i++) |
/ / |
просмотр |
каждого компонента |
|||
{ cout |
« |
" " |
« |
a . g e t l n t ( i ) ; } |
/ / |
OK, |
если |
возвращается |
ссылка |
||
cout |
« |
endl |
« |
endl; |
|
|
|
|
|
|
|
for |
(int |
j=0; |
j |
< size; |
j++) |
/ / |
повторный просмотр всех компонентов |
||||
{ |
int |
|
X = a . getlnt(j); |
|
/ / |
OK, |
если возвращается |
ссылка |
|||
|
a . s e t l n t ( j ) |
= 2 * х; |
} |
/ / |
OK, |
если |
возвращается |
ссылка |
|||
Второе отличие состоит в том, что getlntO не изменяет состояния обрабаты ваемого объекта, а помечается как константа. С другой стороны, setlntO меняет состояние объекта, отправляемого как сообщение, следовательно, оно не указы вается как константа.
Это типичная ошибка многих программистов C+ + , сталкивающихся с исполь зованием модификаторов const. Функция setlntO изменяет состояние динами чески распределяемой области памяти, которая принадлежит объекту цели. Но эта память не является частью объекта — она только принадлежит ему. Элемен ты данных представляет собой часть объекта, а не памяти динамически распреде ляемой области. Функция setlntO не изменяет элементы данных объекта цели. Это одна из тех концепций, которую всегда должен помнить программист C++.
Функция-член setlntO спроектирована неверно. Ее необходимо пометить как const, потому что она не изменяет состояния своего объекта цели.
int& |
Array::setlnt(int i ) const |
/ / |
объект Array не изменяется |
|
{ i f |
( i < О I I i |
>= size) |
/ / |
проверка действительности индекса |
|
return p t r [ s i z e - l ] ; |
/ / |
возвращение последнего компонента |
|
return p t r [ i ] ; |
} |
/ / действительный индекс: присвоить значение |
||
Глава 16 • Расширенное использование перегрузки операций |
743 |
||
int mainO |
|
|
|
{ |
|
// данные для обработки |
|
int агг[] = { 1,3,5,7,11,13,17,19 } |
|
||
Array а(агг, 8); |
// создать объект |
|
|
int size = a.getSizeO; |
// получить размер массива |
|
|
for (int i=0; i < size; i++) |
// просмотр каждого компонента |
|
|
{ cout « " " « |
a.getlnt(i); } |
// вывод на печать следующего компонента |
|
cout « endl « |
endl; |
// повторный просмотр массива |
|
for (int j=0; j < size; j++) |
|
||
{ int X = a.getlnt(j); |
// получить следующий компонент |
|
|
a.getlnt(]) = 2*x; } |
// корректировка значения |
|
|
for (int к = 0; к < size; k++) |
// вывод на'печать скорректированного |
массива |
|
{ cout « " " « |
a.getlnt(k); } |
||
cout « endl; |
|
|
|
return 0 ; |
|
|
|
Следуюндим шагом является замена функции-члена getlntO на перегружен ную операцию индексирования. Изменение самой функции очень простое.Берется функция, вырезается ее имя getint, перемещается в зарезервированное слово operator идобавляется символ для операции (в данном случае []).
// int& Array::getlnt(int |
i) const |
// объект Array не изменяется |
int& Array::operator [](int i) const |
// заголовок операции |
|
{ if (i < 0 I I i >= size) |
|
// проверка допустимости индекса |
return ptr[size-1] ; |
|
// выход, если внеграниц |
return ptr[i]; } |
// действительный индекс: определить ссылку |
|
Подобные изменения должны быть сделаны в клиентской программе — имя
функции-члена теперь будет operator[ ], а не getint.
int size = a.getSizeO; |
/ / |
получить |
размер массива |
||
for (int i=0; i < size; i++) |
/ / |
просмотр |
каждого компонента |
||
{ cout « " " « |
a.operator; } |
/ / |
вывод на печать |
||
cout « |
endl « |
endl; |
/ / |
следующего компонента |
|
|
|
|
|||
for (int j=0; j < size; j++) |
/ / |
повторный просмотр массива |
|||
{ int X = a.operator[](j); |
/ / |
получить следующий компонент |
|||
a.operator[](j) = 2*x; } |
/ / |
корректировка значения |
|||
for (int к = 0; к < size; k++) |
|
|
|
||
{ cout « " " « |
a. operator[] (k); } |
/ / |
вывод на печать |
||
cout « |
endl; |
|
/ / |
скорректированного массива |
|
|
|
|
|
||
Весь путь от первой реализации в листинге 16.7 не был проделан только для того, чтобы остановиться на этом. Синтаксис с вызовом функции следует заменить на синтаксис с выражением. Однако интерпретация operator[], как и любой другой операции, дает в результате громоздкую программу. Как, например, интер претировать operateг+? Цель сообщения используется как первый операнд, затем как символ из оператора, например +, и потом как второй операнд.
a.operator+(b); |
/ / то же, что и а + Ь; |
Если выполнить это же с операцией индексирования, получится что-то нечитаемое,
cout « " " « a.operator[ ](i); |
/ / то же, что и a[]i |
744 |
Часть IV • Расширенное использование С*^^ |
Чтобы функция операции индексирования не противоречила использованию встроенной операции индексирования, C++ отбрасывает специальную часть. Компилятору дается указание разрешить отклонение от обш,его правила. В лис тинге 16.9 приведен этот пример с использованием перегруженной операции ин дексирования.
Листинг 16.9. Использование перегруженной операции индексирования для получения и определения значения данных Array
#inclucle <iostream> using namespace std;
class Array { public:
|
int |
size; |
|
// количество действительных компонентов |
|
|
int |
*ptr; |
|
// указатель намассив целых значений |
|
|
void set(const int* a, int n) ; |
// выделить/инициализировать память |
|||
public: |
|
// динамически распределяемой области |
|||
|
// общий |
конструктор |
|||
|
Array (const int* a, int n); |
||||
|
Array (const Array&s); |
// конструктор копирования |
|||
|
"ArrayO; |
|
// возвращение памяти динамически распределяемой области |
||
|
Array& operator = (const Array& a) |
// копировать массив |
|||
|
int getSizeO const; |
|
//получить/установить значение вположение i |
||
|
int& operator [ ](int i); |
||||
} : |
|
|
|
|
|
void Array::set(const int* a, int n) |
|
||||
{ |
size = n; |
|
// оценить размер массива |
||
|
ptr = new int[size]; |
|
// запросить память из динамически распределяемой области |
||
|
if (ptr ==0) {cout « |
"Out ofmemory\n"; exit(O); } |
|||
|
for (int i=0; i <size; |
i++) |
// скопировать клиентские данные в"кучу" |
||
|
|
ptr[i] =a[i]; } |
|
||
Array::Array(const int* a, int n) |
//общий |
конструктор |
|||
|
{ set(a,n); } |
|
|
|
|
Array::Array (const Array &a) |
// конструктор копирования |
||||
|
{ set(a.ptr,a.size); |
} |
|
|
|
Array::~Array() |
|
// деструктор |
|||
|
{ delete [ ]ptr; } |
|
|
|
|
Array& Array::operator = (const Array& a) |
|
||||
{ |
if (this ==&a) return *this; |
// ничего, если обеспечивается самоприсваивание |
|||
|
delete [ ] ptr; |
|
// возвращение существующей памяти |
||
|
set(a.ptr,a.size); |
|
// выделение/установка новой памяти |
||
|
return *this; } |
|
// для поддержки цепочечного присваивания |
||
int Array::getSize() const |
// получить размер массива |
||||
{ |
return size; } |
|
|
|
|
int& Array::operator [](int i) |
// объект Array не изменяется |
||||
{ |
if (i < 0 11 i >= size) |
// проверка допустимости индекса |
|||
|
|
return ptr[size-l]; |
|
// выход, если вне границ |
|
|
return ptr[i]; } |
|
// действительный индекс: установить значение |
||
int main () |
|
} ; |
|
||
{ |
int arr[] = { 1,3,5,7,11,13.17.19 |
|
|||
|
Array a(arr, 8); |
|
// данные для обработки |
||
|
int size = a.getSizeO; |
|
// создать объект |
||
|
|
|
Глава 16 • Расширенное использование перегрузки операций |
745 J |
||
for (int i=0; i < size; i++) |
// получить размер массива |
|
||||
//{ cout « |
" " « a . operator[](i); } |
// альтернативный синтаксис |
|
|||
{ cout « " " « |
a[i]; } |
// вывод на печать следующего компонента |
||||
cout « |
|
endl « |
endl; |
// повторный просмотр всего массива |
|
|
for (int j=0; j < size; j++) |
|
|||||
// |
{ int X = a[j]; |
// специальное присваивание |
|
|||
{ int X = a.operator[](j); |
// альтернативный синтаксис |
|
||||
|
a[j] = 2*х; |
} |
// специальное присваивание |
|
||
for (int к = 0; к < size; к++) |
|
|
||||
{ |
cout |
« |
" " « |
а [ к ] ; } |
/ / вывод на печать скорректированного |
массива |
cout « |
|
endl; |
|
|
|
|
return |
0; |
|
|
|
||
}
Не совсем понятно, что улучшилось в этом варианте по сравнению с оригина лом из листинга 16.7. Однако синтаксис операции полезен для рассмотрения во просов, связанных с возвратом ссылки из функции, а не для возвращения значения и использования модификаторов const.
Операция вызова функции
Операция вызова функции (две скобки рассматриваются как операция в СН- + ) также может использоваться для осуществления доступа или определения значе ний компонентов объекта контейнерного класса. Операция применяется, когда контейнер рассматривает структуру динамически распределяемой области памяти как двумерный массив.
Причина использования операции вызова функции вместо операции индекси рования заключается в том, что для двумерного массива С-Ы- использует две объединенные операции индексирования, например m[i][ j ]. Применение синтак сиса обычного программирования с одной операцией индексирования, например m[i, j], сделало бы индекс тернарной операцией. (В этом случае ее операндами являются массив m и индексы i и j.) В многомерных массивах может быть больше двух индексов.
Разработчики С и C++ понимали, что все будет в порядке, если разрешить операции плюса изменить свою четность — допуская как унарный плюс, так и бинарный плюс. Но подобное разрешение не было сделано для операции индек сирования. Это бинарная операция, и у нее не может быть более двух операндов.
Вместо операции индексирования можно воспользоваться операцией вызова функции. Ее преимущество в этой ситуации состоит в том, что она может иметь любое количество параметров.
Для примера рассмотрим класс Matrix, который реализует квадратную мат рицу. Клиентская программа обрабатывает компоненты матрицы, определяет два индекса — один для строки и один для столбца матрицы. Объекты матрицы могут создаваться, передаваться как параметры функции и присваиваться друг другу. Реализация будет основываться на динамически выделенном линейном массиве, размер которого зависит от размера квадратной матрицы.
Класс Matrix использует закрытую функцию такеО, подобную функции set() из предыдущего примера, но она не выполняет инициализацию памяти динами чески распределяемой области. Функция такеО вызывается конструктором преобразования, конструктором копирования и перегруженным оператором при сваивания.
class |
Matrix |
{ |
|
int |
*cells; |
/ / |
массив в динамически распределяемой области |
|
|
/ / |
памяти для размещения матрицы |
int |
size; |
/ / |
количество строк и столбцов |
Глава 16 • Расширенное использование перегрузки операций |
747 |
В листинге 16.10 показана программа, которая реализует класс Matrix с толь ко что описанной функцией get(). Клиентская функция printMatrix() просматри вает строки и столбцы матрицы и выводит на печать по очереди каждую строку. Обратите внимание на использование манипулятора setw(). К сожалению, вклю чаемого файла <iostream> недостаточно для программы, использующей манипу ляторы, и в заголовке файла требуется указать <iomanip>.
Листинг 16.10. Использование класса Matrix в качестве контейнера для квадратной матрицы
#inclucje <iostream> #inclucle <iomanip> using namespace std;
class Matrix { |
|
|
|
// массив вдинамически распределяемой |
|
|
int *cells; |
|
|
|
|
|
int size; |
|
|
|
// области памяти для размещения матрицы |
|
|
|
|
// количество строк и столбцов |
|
|
int* make(int size) |
|
|
|
// закрытая функция-распределитель |
|
{ int* p = new int [size * size]; |
|
// общее количество элементов |
||
|
if (p ==NULL) {cout « |
"Matrix too |
big\n"; exit(O); } |
||
|
return p; } |
|
|
|
// возвращение указателя надинамически |
public: |
|
|
|
// распределяемую область памяти |
|
|
|
|
// конструктор преобразования |
||
|
Matrix (int sz) : size(sz) |
|
|
||
|
{ cells =make(size); } |
|
|
// память динамически распределяемой |
|
|
Matrix (const Matrix& m): size(m.size) |
// области не инициализируется |
|||
|
// конструктор копирования: длябезопасности |
||||
|
{ cells =make(size); } |
|
|
||
|
Matrix& operator = (const Matrix& m); |
|
// оператор присваивания |
||
|
int getSizeO const |
|
|
|
// размер стороны |
|
{ return size; } |
|
|
|
// доступ или модификация компонента |
|
int& get (int r, int c) const; |
|
|||
|
~Matrix() {delete [] cells; } |
|
// деструктор |
||
Matrix& Matrix::operator |
(const Matrix& m) |
//присваивание |
|||
{ |
if (this =^&m) return |
this; |
|
// ничего, если обеспечивается самоприсваивание |
|
|
delete [ ] cells; |
|
|
|
// возвращение существующей памяти |
|
cells =make(m.size); |
|
|
// выделение/установка новой памяти |
|
|
size = m.size; |
|
i++) |
// установка размера матрицы |
|
|
for (int i=0; i<size*size; |
// копирование данных |
|||
|
cells[i] =m.cells[i]; |
|
// поддержка цепочечного присваивания |
||
|
return *this; } |
|
|
||
int& Matrix::get (int r, int c) const |
// проверка допустимости |
||||
|
{ if (r<0 II c<0 II r>=size |
|| c>=size) |
|||
|
return cells[size*size-1]; |
// возвращение последнего элемента матрицы |
|||
|
return cells[r*size +c]; } |
// возвращение запрашиваемого элемента |
|||
void printMatrix(const Matrix& m) |
// клиентская функция |
||||
{ |
int size = m.getSizeO; |
i++) |
// просмотр каждой строки |
||
|
for (int i=0; i <size; |
||||
|
{ for(int j=0; j < size; j++) |
// и каждого столбца |
|||
|
cout «setw(4) «m.get(i,j); |
// печать элемента m[i][j] |
|||
|
cout « endl; } |
|
|
// конец текущей строки |
|
|
cout « endl; } |
|
|
// конец матрицы |
|
