Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Штерн В. - Основы C++. Методы программной инженерии - 2003

.pdf
Скачиваний:
278
Добавлен:
13.08.2013
Размер:
28.32 Mб
Скачать

740

 

Часть IV • Расширенное использование С^-^

Аггау& Array::operator = (const Аггау& а)

// ничего, если допускает самоприсваивание

{

i f (this

== &а) return *this;

 

delete

[

] ptr;

// возвращение существующей памяти

 

set(a.ptr,a.size);

// выделение/установка новой памяти

 

return

*this; }

// поддержка цепочечного присваивания

int Array::getSize() const

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

{

return

size; }

 

int

Array::getlnt (int i)

const

{

i f

 

(i < 0

I I i

>= size)

 

 

 

 

return

ptr[size-l];

 

 

return

p t r [ i ] ;

}

 

void Array::setlnt(int i,int x)

{

i f

(i < 0

I I

i

>= size)

 

 

 

 

return;

 

 

 

 

ptr[i]

= x;

}

 

 

int

mainO

 

 

 

 

{

int arr[] - { 1.3.5,7,11,13,17,19 }

 

 

Array a(arr,

8);

 

 

int

size = a.getSizeO;

 

 

for

(int

i=0;

i < size;

i++)

 

{

cout

«

"

"

« a.getlnt(i); }

 

cout «

endl « endl;

j++)

 

for (int j=0; j < size;

 

{ int X = a.getint (j);

 

 

 

a.setlnt(j, 2*x); }

 

 

for (int к = 0; к < size; k++)

 

{ cout «

" " « a.getlnt(k); }

 

cout «

endl;

 

 

return 0;

}

//объект не изменяется

//индекс выходит за границы

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

//допустимый индекс: возвращение значения

//модификация объекта Array

//проверка допустимости индекса

//выход, если вне границ

//действительный индекс: присвоить значение

//данные для обработки

//создать объект

//получить размер массива

//просмотр каждого компонента

//вывод на печать следующего компонента

//повторный просмотр массива

//получение следующего компонента

//корректировка значения

//вывод на печать скорректированного массива

Окончательно готовый класс Array также должен поддерживать добавление новых компонентов в конец и всередину массива, удаление компонентов, сравне­ ние компонентов,тестирование на наличие допустимых данных и т. п.

Как упоминалось выше, синтаксис использования метода getlntO удобен. Синтаксис применения метода setlntO более громоздкий.

for

(int

j=0; j

< size;

j++)

/ /

повторный

просмотр массива

{

int

X = a . g e t l n t ( j ) ;

/ /

получение

следующего компонента

 

a . s e t l n t ( j ,

2*x);

}

/ /

корректировка значения

Итак, мы просмотрели все компоненты массива. Значение каждого компонента увеличено вдвое. Синтаксис изменения отличается от синтаксиса доступа к ком­ понентам. Между тем встроенные массивы C + + используют тот же самый син­ таксис доступа к компонентам массива (например, х = a[j]) и для присваивания значений компонентам массива (например, а[ j ] = 2*х).

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

 

for

(int j=0; j < size;

j++)

// повторный просмотр массива

//

{

int X = a.getlnt(j);

}

// получение следующего компонента

 

a.setlnt(j. 2*x);

// корректировка значения

 

 

a.setlnt(j) = 2 *x; }

// корректировка значения

Глава 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 ] ;

}

/ / действительный индекс: присвоить значение

742

Часть IV • Расширенное использование C-^-i-

Несмотря на комментарии, показывающие, что объект изменяется, setlntO в листинге 16.7 не меняет свой объект цели. Вспомните историю о кирпиче, рас­ сказанную в главе 8. Не забывайте о модификаторах const.

Теперь, когда обе функции getlntO и setlntO выглядят одинаково, можно исключить одну из них. В листинге 16.8 показан вариант программы из листин­ га 16.7, в котором используется только одна функция getlnt(). Вывод для этого примера такой же, как и для листинга 16.7.

Листинг 16.8. Использование одной функции-члена для получения и установки данных Array

#include <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& getlnt(int i) const;

} ;

 

 

void Array::set(const int* a, int n)

// оценить размер массива

{ size = n;

ptr = new int[size];

// запросить память динамически

 

 

// распределяемой области

if (ptr ==0) {cout < "Out of memory\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::getlnt(int i) const

// объект Array не изменяется

{ if (i < 0 11 i >= size)

// проверка допустимости индекса

return ptr[size-l];

// выход, если вне границ

return ptr[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![](i); }

/ /

вывод на печать

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;

/ /

количество строк и столбцов

746

Часть IV • Расширенное использование С-*-*-

 

int* make(int size)

 

size];

// закрытая функция-распределитель

 

{

int* р = new int [size *

// общее количество компонентов

 

 

if (р == 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;

/ / доступ или модификация компонентов

 

"MatrixO

{ delete [ ]

cells; }

//деструктор

Оператор присваивания освобождает существующую память динамически распределяемой области, выделяет новую память в динамически распределяемой области и копирует данные из параметра объекта Matrix в цель присваивания.

Matrix&

Matrix::operator

= (const

Matrix& m)

/ /

присваивание

{ i f

(this

== &m)

return

*this;

/ /

ничего,

если

обеспечивается

delete

[

] cells;

 

/ /

самоприсваивание

 

/ /

возврат существующей памяти

cells

= make(m.size);

 

/ /

выделение/установка памяти

size = m.size;

 

 

 

/ /

установить размер матрицы

for

(int

i=0;

i<size*size; i++)

/ /

копировать данные

 

c e l l s [ i ]

=

m . cells[i];

/ /

для поддержки цепочечного присваивания

return

*this;

}

 

 

Функция get о

объединяет обязанности функций get Int () и setlntO из пре­

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

int&

Matrix: :get (int

г, int

с) const

 

{ i f

(r<0 II c<0 II r>=size

||

c>=size) / /

проверка правильности значения

 

return cells[size*size - l];

/ / возвращение последнего элемента матрицы

return cells[r*size

+ с];

}

/ /

возвращение запрошенного элемента

Возвращение последнего элемента матрицы, если координаты строки или столбца выходят за границы матрицы, не самое лучшее решение. Другая возмож­ ность заключается в завершении выполнения или в генерации исключительной ситуации. Кроме того, можно возвратить некоторое значение, не используемое каким-либо иным образом в приложении. Например, максимальное целое зна­ чение max_int. Однако значение-константу невозможно вернуть по ссылке (если только не модифицировать его).

int&

Matrix: :get (int

г, int

с) const

{ i f

(r<0 II c<0 II r>=size

|| c>=size)

 

return MAX_INT;

 

 

return cells[r*size

+ c];

}

/ /

не очень хороший вариант

/ /

проверка правильности значения

/ /

не допускается возврат по ссылке

/ / возвращение запрошенного элемента

Глава 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; }

 

 

// конец матрицы

748

Часть IV • Расширенное использование С-^+

int mainO

 

 

{

cout « endl « endl;

// объект Matrix

 

int i, j, n = 5; Matrix m1(n);

 

for (i=0; i < n; i++)

// инициализация ячеек

 

for (j=0; j < n; j++)

 

m1.get(i,j) = (i+1) * (j+i);

// m1[i][j] = (i+i)*(j+i);

 

printMatrix(ml);

// вывод на печать состояния матрицы

 

for (i=0; i < n; i++)

// занесение нулей по главной диагонали

 

m1.get(i,i) = 0;

//m1[i][i] - О

 

printMatrix(ml);

// вывод на печать нового состояния

 

cout «"m[10][10] = " «m1.get(10,10) «endl;

// вне диапазона

 

return 0;

 

 

 

}

 

 

1

2

3

4

5

2

4

6

8

10

3

6

9

12

15

4

6

12

16

20

5

10

15

20

25

0

2

3

4

5

2

0

6

8

10

3

6

0

12

15

4

8

12

0

20

5

10

15

20

0

тС10][10] = 0

Рис. 16.8.

Вывод программы из листинга 16.10

Клиентская функция main() создает объект — квадратную матрииу и инициализирует каждый компонент значением, равным произведе­ нию его номера строки и номера столбца. В этом цикле функция main() использует возвращенное значение функции get() как 1-значе- ние. Затем main() вызывает printMatrix(), которая использует воз­ вращенное значение функции get() как г-значение. Следом функция mainO устанавливает компоненты диагонали главной матрицы в нуль (используя get() как 1-значение) и выводит на печать матрииу. Нако­ нец, main О пытается осуществить доступ к ячейке вне матрицы, и функция get О возвращает значение последнего элемента матрицы, которое было установлено в нуль. Результаты выполнения представ­ лены на рис. 16.8.

Преобразование функции get() в перегруженную операцию вызова функции очень простое. Имя get заменяется зарезервированным сло­ вом operator и добавляются символы операции (две пустые скобки).

int&

Matrix: :operator()

(int

г, int

с)

const

 

{

i f (r<0 I I c<0 11 r>=size

11 c>=size)

/ / проверка правильности значения

 

return cells[size*size - l];

/ /

возвращение последнего элемента матрицы

 

return cells[r*size

+ с];

}

/ /

возвращение запрошенного элемента

Эту функцию можно вызвать, используя синтаксис вызова функции как синоним для get().

void

printMatrix(const

Matrix& m)

/ /

клиентская функция

{ int size = m.getSizeO;

 

 

 

for (int i=0; i

< size; i++)

/ /

просмотр

каждой строки

{

for

(int

j=0;

j < size; j++)

/ /

и каждого

столбца

 

 

cout

«setw(4)

« m . o p e r a t o r ( ) ( i , j ) ;

/ /

элемент m [ i ] [ j ]

 

cout

«

endl;

}

 

/ /

конец текущей строки

cout «

endl; }

 

 

/ /

конец матрицы

Все перегруженные операции выглядят странно, пока к ним не привыкнешь. Преобразование синтаксиса вызова функции в синтаксис выражения также нео­ бычно. Формальное применение правил C++ даст в результате что-то подобное fn()i,j. Вместо этого C++ разрешает записать это как m(i,j). Некоторым программистам C++ кажется, что подобный синтаксис не осуществляет доступ к компонентам матрицы. Однако многим программистам, работающим в научных областях, это напоминает то, что позволяет делать FORTRAN.

В листинге 16.11 показана полная версия программы из листинга 16.10, где вызовы функции get О заменяются на вызовы перегруженной операции вызова функции — operator()(). (Имя operator()() формируется так же, как и имя

#inclucle <iostream> #include <iomanip> using namespace std;

Глава 16 • Расширенное использование перегрузки опероций

| 749 щ

любой другой функции,) Вывод программы соответствует выводу программы

влистинге 16.10 (см. рис. 16.8).

Ли с т и нг 16.11. Использование класса Matrix с перегруженной операцией вызова функции

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& operator () (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;

for (int i=0; i<size*size; i++) cells[i] = m.cells[i];

return *this; }

int& Matrix::operator () (int r, int c) const

{ if (r<0 il c<0 II r>=size || c>=size) return cells[size*size-l];

return cells[r*size + c]; }

void printMatrix(const Matrix& m) { int size = m.getSizeO;

for (int i=0; i < size; i++)

{ for(int j=0; j < size; j++) cout « setw(4) « fn(i, j);

cout « endl; } cout « endl; }

int mainO

{ cout « endl « endl;

int i, j, n = 5; Matrix m1(n); for (i=0; i < n; i++)

for (j=0; j < n; j++) m1(i,j) = (i+1) * (j+1);

//присваивание

//ничего, если самоприсваивание

//возвращение существующей памяти

//выделение/установка новой памяти

//установка размера матрицы

//копирование данных

//для поддержки цепочечного присваивания

//проверка правильности значения

//возвращение последнего компонента матрицы

//возвращение запрошенной ячейки

//клиентская функция

//просмотр каждой строки

//и каждого столбца

//вывод на печать элемента

//конец текущей строки

//конец матрицы

//объект Matrix

//инициализация элементов

//m1[i][j] = (i+l)*(j41);

Соседние файлы в предмете Программирование на C++