Студопедия

КАТЕГОРИИ:


Архитектура-(3434)Астрономия-(809)Биология-(7483)Биотехнологии-(1457)Военное дело-(14632)Высокие технологии-(1363)География-(913)Геология-(1438)Государство-(451)Демография-(1065)Дом-(47672)Журналистика и СМИ-(912)Изобретательство-(14524)Иностранные языки-(4268)Информатика-(17799)Искусство-(1338)История-(13644)Компьютеры-(11121)Косметика-(55)Кулинария-(373)Культура-(8427)Лингвистика-(374)Литература-(1642)Маркетинг-(23702)Математика-(16968)Машиностроение-(1700)Медицина-(12668)Менеджмент-(24684)Механика-(15423)Науковедение-(506)Образование-(11852)Охрана труда-(3308)Педагогика-(5571)Полиграфия-(1312)Политика-(7869)Право-(5454)Приборостроение-(1369)Программирование-(2801)Производство-(97182)Промышленность-(8706)Психология-(18388)Религия-(3217)Связь-(10668)Сельское хозяйство-(299)Социология-(6455)Спорт-(42831)Строительство-(4793)Торговля-(5050)Транспорт-(2929)Туризм-(1568)Физика-(3942)Философия-(17015)Финансы-(26596)Химия-(22929)Экология-(12095)Экономика-(9961)Электроника-(8441)Электротехника-(4623)Энергетика-(12629)Юриспруденция-(1492)Ядерная техника-(1748)

Тип комплексных чисел

Унарные постфиксные операторы

Унарные префиксные операторы

Для любого префиксного оператора @ выражение @aa интерпретируется либо как

а) нестатическая функция-член без аргументов

aa.operator@()

либо как

б) функция-не-член с одним аргументом

operator@(aa)

Для любого постфиксного оператора @ выражение aa@ интерпретируется либо как

а) нестатическая функция-член с аргументом типа int

aa.operator@(int)

либо как

б) функция-не-член следующего вида:

operator@(aa,int).

Если определены обе функции, то для определения того, какую (возможно, никакую) из них использовать, применяется механизм разрешения перегрузки. Пример:

class X{//члены

X* operator&();//префиксный унарный оператор & (чей-то адрес)

X operator&(X); //бинарный оператор & (И)

X operator++(int); //постфиксный инкремент

X operator&(X,X); //ошибка: 3 операнда

X operator/(); //ошибка: унарный оператор /

};

//функции-не-члены

X operator-(X); //префиксный унарный -

X operator-(X,X); //бинарный -

X operator--(X&,int); //постфиксный декремент

X operator-(); //ошибка: отсутствует операнд

X operator-(X,X,X); //ошибка: три операнда

X operator%(X); //ошибка: унарный оператор %

 

 

 

Рассмотрим использование перегрузки операторов на примере создания класса комплексных чисел.

Конструирование. Мы должны обеспечить создание объекта-комплексного числа следующими способами:

 

complex c1,//1

c2(1),

c3=2,

c4(3,4),

c5=complex(5,6);

 

Для этого мы определяем набор конструкторов:

class complex{

public:

complex():re(0),im(0){}//1.0

complex(double real):re(real),im(0){}

complex(double real,double imag):re(real),im(imag){}

private:

double re,im;

}

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

 

complex(double real=0,double imag=0):re(real),im(imag){}//1

 

Операторы, модифицирующие операнды. Мы хотим, чтобы добавление значения к комплексному числу выглядело в стиле C++:

c1+=c4;//2

c2+=7;

Для этого мы определяем оператор +=. У нас есть выбор: определить его в классе или как функцию-не-член класса. Так как нам нужен доступ к представлению класса (re, im), то объявляем в классе:

complex& operator+=(const complex& a);//2

Обратите внимание на объявление аргумента const complex& a. В этом случае формальный параметр a является ссылкой на фактический параметр. При этом в функцию передается адрес фактического параметра (4 байта). Альтернативой было бы сделать объявление следующим образом:

complex& operator+=(complex a);

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

complex& operator+=(const complex *a);

Но дело в том, что данная оператор-функция вызовется только в случае, когда правый операнд – адрес, т.е.:

c1+=&c4;

Но такой синтаксис – это не то, что мы хотели бы использовать. Поэтому в качестве формального параметра применяем все-таки ссылку.

Для добавления действительного числа объявляем функцию-член класса

complex& operator+=(double a);//2

 

Определяем эти функции-операторы в.cpp – файле.

complex& complex::operator+=(const complex& a)//2

{

re+=a.re;//добавление действительной части аргумента к действительной части данного объекта

im+=a.im;//добавление мнимой части

return *this;//возврат значения

}

В данной функции выполняется два действия: 1) выполняется операция добавления и 2) выполняется возврат значения. Возврат значения нужен, чтобы результат оператора += можно было использовать в качестве операнда. Например:

с1=(с2+=с3)+7;

Результатом оператора += должно быть значение его левого операнда (в примере – c2) после того, как к нему добавлен правый операнд (в примере c3). Когда вызывается функция complex::operator+=, левый операнд для нее доступен как объект, для которого она вызвана. Таким образом, эта функция должна вернуть значение самого объекта. Это можно сделать, разименовав указатель this.

Почему же мы возвращаем ссылку, а не объект? Потому, что для типа int следующее выражение должно увеличить значение переменной a, а не копии переменной a:

(a+=b)++;// после того, как к a добавили b, увеличить a на 1.

Это же должно быть справедливо и для проектируемого нами типа complex, а для этого нужно возвращать ссылку на левый операнд.

 

Реализация второго оператора += несколько проще, так как добавление идет только к действительной части комплексного числа:

complex complex::operator+=(double a){

re+=a;

return *this;

}

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

c1=3+c5;//3

c1=c5+3;

c1=c4+c5;

Для этого определим оператор +. Опять у нас есть выбор: определить его как член класса или как не-член-класса. В пользу последнего решения есть два довода. Во-первых, мы не можем определить оператор-член-класса так, чтобы он был вызван для случая 3+c5, т.к. левый операнд не является объектом. Во-вторых, для выполнения сложения не требуется иметь доступ к представлению класса complex. Итак, определяем функции-не-члены класса:

complex operator+(const complex& a, const complex& b);//3.1

complex operator+(const complex& a, double b);//3.2

complex operator+(double a, const complex& b);

 

Эти три формы оператора учитывают все интересующие нас комбинации типов операндов. Определения этих функций используют оператор += для добавления к локальной переменной:

 

complex operator+(const complex& a,const complex& b){

complex r=a;

return r+=b;

}

 

complex operator+(const complex& a,double b){

complex r=a;

return r+=b;

}

 

complex operator+(double a,const complex& b){

complex r=b;

return r+=a;

}

И опять, как и в случае с +=, сложение complex с double проще, чем сложение двух complex.

 

Сравнение. Мы должны иметь возможность сравнивать комплексные числа друг с другом и с double в виде, принятом для встроенных типов, например:

c2==c4;//4

c2==8;

3==c2;

Для этого определим оператор ==. Как и в случае с оператором +, мы не можем определить оператор == как функцию-член, т.к. она не может быть вызвана для случая 3==с2. Следовательно, это должна быть функция-не-член. Сравнение двух комплексных чисел:

 

bool operator==(const complex& a,const complex& b);//4.2

 

Модификаторы const используются для указания того, что функция не изменяет аргументы. Этой функции требуется доступ к представлению класса (re, im), однако представление класса имеет модификатор private: и к нему нельзя получить доступ из этой функции напрямую. Проблема решается введением функций доступа для извлечения действительной и мнимой частей:

 

public:

double real()const{return re;}

double imag()const{return im;}

 

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

bool operator==(const complex& a,const complex& b){

return a.real()==b.real()&&a.imag()==b.imag();

}

Для выполнения сравнения complex с double и double с complex мы могли бы определить еще два оператора ==:

bool operator==(const complex& a,const double& b){

return a.real()==b && a.imag()==0;

}

bool operator==(const double& a,const complex& b){

return a==b.real() && 0==b.imag();

}

Следует заметить, что в этих функциях выполняются те же действия, что и в первом варианте оператора==, но вместо действительной части используется сам аргумент типа double, а вместо мнимой части – 0. То есть сравнение производится с комплексным числом, у которого вместо действительной части – аргумент типа double, а мнимая часть равна 0. Если бы мы могли из действительного числа получить комплексное указанным способом, мы могли бы работу двух последних операторов == возложить на первый оператор ==. Оказывается, такой механизм преобразования у нас уже имеется – это конструктор complex, в случае, когда он вызывается с одним аргументом типа double. Поэтому определять две последние функции operator== мы не будем. А для того, чтобы их работа была выполнена первым оператором ==, нам не нужно ничего определять дополнительно. Компилятор сам в нужных местах вызовет конструктор, чтобы из double получить complex. Например:

 

c2==c4;//operator==(c2,c4)

c2==8;//operator==(c2,complex(8))

3==c2;//operator==(complex(3),c2)

 

Ввод-вывод. И, наконец, мы хотим выводить комплексные числа принятым в C++ способом:

 

std::cout<<c1<<c2<<c3;

,

Для этого определим оператор вывода для комплексного числа:

 

std::ostream& operator<<(std::ostream& os, const complex& a)

{

return os<<'('<<a.real()<<','<<a.imag()<<')';

}

Теперь запись std::cout<<c1<<c2<<c3; эквивалентна следующему:

operator<<(operator<<(operator<<(std::cout,c1),c2),c3);

Класс Matrix

Перегрузку операторов вызова функции и индексирования рассмотрим на примере класса Matrix. Этот класс предоставляет динамически размещаемые двумерные массивы.

class Matrix{

public:

Matrix(size_t,size_t);

Matrix(const Matrix&);

~Matrix();

private:

size_t d1,d2;

int* m;

};

Определение класса содержит количество элементов d1 по первой и d2 по второй размерности массива. Тип size_t – это тип значения, возвращаемого оператором sizeof. В конструкторе выделяется память для хранения всех элементов данного массива, а базовый адрес выделенной памяти является инициализирующим значением для указателя m.

Matrix::Matrix(size_t dim1,size_t dim2)

:d1(dim1),d2(dim2),m(new int[size()]){}

Для хранения элементов двумерной матрицы могут быть использованы несколько вариантов организации памяти. В данном случае используется одномерный массив. Достоинства такой организации: простота удаления-освобождения памяти. Недостатки: для доступа к элементу используется умножение (см. ниже).

Деструктор выполняет delete[] для освобождения памяти, выделенной оператором new[] в конструкторе.

Matrix::~Matrix(){delete[] m;}

 

В конструкторе используется вспомогательная функция-член size(), возвращающая количество элементов массива. Так как эта функция используется только внутри класса, мы помещаем ее в раздел private:

private:

size_t size() const {return d1*d2;}

Создание матрицы размером 2x3 выглядит следующим образом:

Matrix m(2,3);

 

Мы хотим заполнять матрицу в цикле, например, так:

for(size_t i=0;i<m.dim1();++i)

for(size_t j=0;j<m.dim2();++j)

m(i,j)=rand();

Здесь функции-селекторы dim1() и dim2() возвращают размеры массива по соответствующим размерностям. Эти функции определены в пределах класса:

size_t dim1() const {return d1;}

size_t dim2() const {return d2;}

Обратите внимание на то, в какой форме записано обращение к элементу матрицы – m(i,j). К имени объекта справа приписаны скобки, что означает вызов функции с именем m с двумя параметрами (i,j). Оказывается, вызов функции – это тоже оператор C++. Чтобы запись m(i,j) действительно означала обращение к элементу матрицы, мы должны перегрузить этот оператор. Оператор (), а также операторы =, [] и -> можно перегружать только как нестатические функции-члены класса. Это ограничение гарантирует, что левый операнд указанных операторов будет объектом. Перегружаем оператор ():

int& Matrix::operator()(size_t dd1,size_t dd2){//1

assert(dd1<dim1());

assert(dd2<dim2());

return m[dd1*dim2()+dd2];

}

 

int Matrix::operator()(size_t dd1,size_t dd2)const{//2

assert(dd1<dim1());

assert(dd2<dim2());

return m[dd1*dim2()+dd2];

}

Мы сделали два перегруженных оператора вызова функции, отличающиеся только const­-модификатором и типом возвращаемого значения. Это сделано для того, чтобы в разных случаях вызывалась подходящая версия этого оператора. Так, при заполнении матрицы (см. выше) компилятор выберет первый вариант оператора.

Тела обеих функций operator() одинаковы: выполняется проверка на допустимость индексирующих значений, после чего вычисляется позиция элемента в одномерном массиве m. Затем первая функция возвращает ссылку на данный элемент, а вторая – значение данного элемента. Именно возврат ссылки, а не значения позволяет изменять значение элемента массива, записывая m(i,j)=rand().

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

void print(const Matrix& m){

for(size_t i=0;i<m.dim1();++i){

for(size_t j=0;j<m.dim2();++j){

std::cout<<m(i,j)<<',';

}

std::cout<<std::endl;

}

}

Параметр функции print является ссылкой на константную матрицу. Так как функция print не может изменить матрицу, компилятор выберет версию оператора () с модификатором const. Возврат значения обходится дешевле, чем возврат ссылки, т.к. ссылку требуется разименовывать, а значение – нет, а объем передаваемой памяти одинаков (4 байта).

 

int* Matrix::operator[](size_t dd1)const{

assert(dd1<dim1());

return &m[dd1*dim2()];

}

 

Вызов: m[3][4]

(m.operator[](3))[4]

 

Matrix& Matrix::operator=(const Matrix& t){

if(this!=&t){

delete[] m;

m=new int[(d1=t.dim1())*(d2=t.dim2())];

copy(t);

}

return *this;

}

 

 

<== предыдущая лекция | следующая лекция ==>
Правила опеределения операторных функций | Итераторы
Поделиться с друзьями:


Дата добавления: 2014-01-11; Просмотров: 486; Нарушение авторских прав?; Мы поможем в написании вашей работы!


Нам важно ваше мнение! Был ли полезен опубликованный материал? Да | Нет



studopedia.su - Студопедия (2013 - 2024) год. Все материалы представленные на сайте исключительно с целью ознакомления читателями и не преследуют коммерческих целей или нарушение авторских прав! Последнее добавление




Генерация страницы за: 0.008 сек.