Перегрузка двухместных операций в виде в Point2D




Израилев Владимир Яковлевич

Части курса:

  1. Эпизодическое ООП
  2. Тотальное ООП

void main()

{

App theApp();

App.run;

}

 

1983 – год создания языка. Первоначально назывался C with classes. Позже был переименован в C++

Далее Microsoft в 2001 году выпустила свой язык программирование, называемый C#

 

Обзор новых возможностей С++

  1. Комментарии (/* ---- */) в С++ появился // и считается законченным окончанием строки.
  2. Константы. Можно задавать беззнаковые константы. Также ужесточилось действие ключевого слова const. Теперь целые константы, описанные словом const могут использоваться для описания размеров массива. Константы в С++ подчиняются тем же правилам видимости, что и переменные.

12u[u] – беззнаковая константа

12 – знаковая неименованная константа

//константы double

const double PI=3.14;

const double E=2.8;

//указатель на константу

const double *ptoconst=Π

//константный указатель

double *const constpconst=&E;

//константы на указатель на константу вщгиду

const double *const constpconst=Π

 

*ptoconst=2.5; //ошибка. Значение по указателю менять нельзя

ptoconst=Π // правильно

constp=&E; //ошибка

*constp+=3.0;

 

 

Встраиваемые функции

В языке С, программисты часто использовали макроопределения

#define SQR(x) x*x

 

i=SQR(z); //i=x*x;

SQR(x++); //нет проверки типов, будет ошибка

 

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

inline double SQR(double x){return x*x}

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

 

 

Объявление структур, объединений, перечислений

В С++ введен упрощенный синтаксис для определения переменных, типа структур, объединений и перечислений, в котором можно опускать ключевые слова struct, union, enum.

//C

enum day{sun,mon,tue,wen};

struct path{char str[30];

enum day Week;}

struct path Link;

//C++

enum day{sun,mon,tue,wen};

struct path{char str[30];

day Week;}

path List;

 

Допускается использовать неименованные перечисления и объявления, что тоже делает синтаксис более простым и логичным.

struct Goods{

char *name;

union{double d, long l, int i)};

};

 

Переменные в С++ разрешается объявлять в любом месте программы. Важно лишь то, чтобы переменная была объявлена до ее использования. Допускается даже объявлять переменные цикла for в заголовке цикла.

for(int i;i<n;i++)...

В старых компиляторах область действия распространяется до конца блока, где находится оператор for. Во всех современных компиляторах распространяется только на тело цикла.

 

 

Ссылки

В С++ появилась принципиально новая конструкция, которая называется ссылка. РБНФ:

тип “&” имя1”=”имя2”;”

Такое объявление фактически назначает переменной с именем 2 второе имя с названием имя1. Ссылка в объявлении всегда должна быть проинициализирована, затем ее уже нельзя изменить.

int a,b;

int &alt = a; //alt есть ссылка на переменную целого типа a

alt=b; //все равно как если бы a=b

alt++; //все равно как если бы a++

Пусть имеется следующая конструкция

int *point=&a; //следующие условные выражения будут всегда истинны

*point == alt;

point == &alt;

Ссылку можно рассматривать как постоянный указатель, который постоянно разадресован и можно не выполнять для него операцию разадресации. Если одновременно объявлять несколько переменных типа ссылка, то перед каждым из них должен стоять &.

Для чего нужны:

1. Для передачи аргументов в функцию

void f(int &a) {...}

...

int b,c;

f(b);

Ссылка не создает копии объекта, а является другим его именем. Таким образом, в обычном случае условное выражение &alt =&a. Из этого правила имеется два исключения:

  1. Это происходит при объявлении ссылки на константу. При этом случае генерируется временная переменная.

char &fcn=’A’; //создается временная переменная и ссылка будет на эту временную переменную. Это необходимо из соображений безопасности, чтобы не было объединения области памяти одинаковых констант.

  1. Временный объект создается также при инициализации ссылки переменной другого типа.

unsigned int u=20;

int &refi = u; //создаст временную переменную, выполнит преобразование unsigned в int и уже назначит новое имя не для оригинальной переменной, а для временной

В случае создания временного объекта, компилятор как правило выдает соответствующее предупреждение.

 

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

  1. При передачи в функцию аргумента, который должен быть изменен
  2. В случае больших структур

 

void Swap(int *a, int *b)

{

int temp=*a;

*a=*b;

*b=temp;

}

 

int x=5, y=10;

Swap(&x,&y);

 


void Swap(int &a, int &b)

{

int temp=a;

a=b; //разадресация не нужна

b=temp;

}

 

int x=5, y=10;

Swap(x,y);

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

 

Логические ошибки при использовании ссылок:

double Sum(double A[], int &n)

{

double smp;

while() st=A[]+n;

... //недописано

 

После вызова функции Sum, значение переменной функции m будет равно 0. Чтобы избежать ошибок подобного рода, параметры ссылки, которые не должны меняться, надо всегда объявлять ключевым словом const. Тогда компилятор будет выдавать сообщение при изменении этого параметра. Если все же требуется вернуть значение через список параметров для обычных функции, лучше использовать не ссылки, а традиционные указатели.

 

Применение ссылки в качестве возвращаемого значения. В С++ могут возвращать ссылку на переменную. В этом случае вызов функции будет стоять в левой части операции присваивания. Если возвращаемое значение является ссылка, то в return нельзя возвращать локальную переменную.

char & strCh(char *str, char ch)

{

while(*str && *ch!=ch) str++;

return *str;

}

 

 

Отличия С++ от С

Преобразования типов

  1. Неявное преобразование
  2. Явное преобразование int(i)/int(j)

 

В языке С использовалась операция приведения типов следующего вида:

“(“имя типа”)”выражение

Наряду со старой формой операции приведения типов в С++ введен новый формат:

имя типа “(”выражение”)”

a = (double)b;

a = double (b); //функциональная форма

 

Новый формат не может быть использован, если имя состоит из нескольких слов.

 


Новые операции

Язык С++ имеет все операции языка С, но есть и дополнительные.

  1. Операция расширения области видимости – язык С++ имеет мощную структуру, т.е. одно и то же имя может обозначать различные объекты, в зависимости от вложенности блоков. При этом правила следующие: объявление идентификатора во внутреннем блоке перекрывает соответствующее объявление в охватывающем блоке, а также и глобальное объявление, если оно есть.

“::” – данная операция позволяет обратиться к глобальным переменным, если последняя перекрывается локальной переменной.

#include <stdio.h>

int i=1;

void main(void)

{

int i=10;

{

int i=100;

printf(“%d %d”,i,::i);

}

printf(“%d %d”,i,::i);

}

Эта операция нарушает значения ввода, поэтому использовать ее надо крайне осторожно.

  1. Операции расширения динамической памяти – в языке С работать с динамической памятью можно было лишь через соответствующие функции распределения памяти, которые громоздкие и требовали ручной работы. Всвязи с этим, а также из-за специфики создания новых типов данных, в С++ введены новые операции: new и new[] для выделения памяти и delete и delete[] для удаления. С помощью new можно выделить для одиночного элемента, а с помощью new[] для массива. В случае успеха результатом операции будет адрес выделенного блока памяти. В случае неудачи: в старых версиях компилятора будет выделено значение 0, для современных компиляторов будет инициирована исключительная ситуация и программа аварийно остановится.

double *pd = new double;

double *a = new double[20];

double &dr = new double; //ошибка, т.к. new возвращает double

double &dr = *new double;

 

delete pd;

delete [] a;

delete &dr;

Следует отметить, что для освобождения массива данных надо всегда пользоваться операцией delete[]. Для встроенных типов данных разницы нет, но для данных, определяемых программистом, могут быть неприятности, связанные с отсутствием вызова специальных функций, называемых деструктор. В реальных системах операция new может выделить не более 64 кб памяти. Для функции освобождения нельзя пытаться освободить уже освобожденную область памяти.

 

В С++ существует еще две операции для доступа к элементам структуры или классов. Их действие рассмотрим чуть позднее.

 

Объявление функций

В языке С объявление функций до использования было желательным, но не обязательным. В С++ все функции перед использованием должны быть объявлены с полным списком формальных параметров и с указанием типа возвращаемого значения. Компилятор языка С++ очень строго следит за соответствием типов, выполняет необходимые преобразования типов фактических параметров к типам формальным, если возможно, а если не возможно, то выдает сообщение об ошибке.

Объявление функции в виде f() в С++ эквивалентно f(void). В языке С f() говорило о том, что функция может иметь любое количество параметров.

 

Аргументы по умолчанию

При объявлении функции в С++ одному или нескольким параметрам может быть назначено значение по умолчанию.

void Func(double a, char c=’*’, int i=2);

Func(10.6);

Func(10.6, ‘-’);

Func(10.6, ‘+’,3);

Func 10.6,, 3) //ошибка

Параметры, которым дается значение по умолчанию, должны быть последовательными, а при вызове не должно быть пропуска параметров. Значения по умолчанию должны быть указаны лишь один раз либо в прототипе функций, либо при ее определении. К моменту вызова функции с неполным списком аргументов значения по умолчанию должно быть определено и видимо в области видимости функции.

 

Перегрузка функций

В языке С++ допускается определение нескольких разных функций с одинаковым именем при условии, что их сигнатуры различаются.

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

double Sun(double A[], int n);

double Sun(double *A[], int n, int m);

double Sun(double A[], int a, int m);

 

Использование функций

extend “C” double cos(double);

extend “C” double sin(double);

 

 

#include <stdio.h>

 

void main()

{

printf(“Hello World!”);

}

 

int printf(count char*,...);

 

В С++ была разработана своя библиотека ввода-вывода, которая называется iostream. Все необходимое расположено в заголовочном файле iostream.h. Стандартный ввод и вывод связаны при помощи двух потоков: cin и cout. Для осуществления ввода-вывода перегружаются две операции: >> - для ввода информации и << - для вывода. Данные операции в заголовочном файле определены для всех встроенных типов данных и их использование жестко контролируется компилятором.

#include <iostream.h>

main()

{

double a,b,c;

 

cout <<”Введите А и В”;

cin >>a>>b;

cout <<”Значение суммы”<<a<<”+”<<b<<”=”<<a+b;

}

В этом примере одни и те же операции используются для ввода и вывода величин типа double и символьных строк. Компилятор сам определяет,какую функцию вызвать для реализации ввода и вывода, используя информацию о типах в объявляемых данных.

Цепочечная запись возможна, так как результат отдельной операции является ссылкой на объект cout или ссылкой на объект cin.

 

 

C++ как С c классами

char name[30];

int age;

double salary;

 

struct Form

{

char name[30];

int age;

double salary;

};

 

Form Ivanov, ivt[22];

ivt[1].age;

 

int GetAge(Form *this)

{

 

}

 

double AvSalary(Form *this)

{

 

}

 

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

 


class Point2D

{

private:

double a,b;

public:

void read();

double mod() count

{

return sqrt(x*x+y*y);

}

void print() const;

Имя типа Point2D будет представлять новый тип данных. Это имя может быть использовано для объявления переменных этого нового типа. Эти переменные называются объектами.

void main()

{

Point2D p1,p2;

 

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

  1. Член данные – данные, характеризующие описываемую абстракцию
  2. Член функции – действия, функции или операции, которые могут быть выполнены над переменными этого типа.

В нашем примере для описания класса используются слова public и private. Кроме этих слов еще может быть слово protected. Эти слова управляют доступом к членам класса. Если член класса объявлен после public. то они считаются общими, т.е. открытыми для доступа из любой точки программы. Ключевые слова private и protected говорят о том, что следующие за ними члены доступны только для членов функций этого класса. Подобные ограничение доступа носит название сокрытие информации. Ключевое слово protected ограничивает использование член данных потомками базового класса при наследовании. В нашем случае эти слова эквивалентны. Хорошим стилем программирования считается объявление функций в части public, а данных в private,

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

 

#ifndef _POINT2D_H

#define _POINT2D_H //если имя нигде не встречается, то имя логического выражения =0. Если есть, то =1

 

class Point2D

{

private:

double x,y; //+ еще Point2D *const this;

 

public:

double mod()const

{

return (sqrt(x*x+y*y));

}

void read(); //Point2D *this

void print()const;

};

#endif

 

Метод mod определяется прямо при описании класса. Подобные методы по умолчанию считаются встроенными, т.е. inline. Два других типа требуют определения. Обычно для таких методов создаются файлы с расширением cpp.

 

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

 

//Point2D.cpp

#include <iostream.h>

#include <math.h>

#include “point2d.h”

 

/*имя_класса::имя_член_функции*/

void Point2D::read()

{

cout<<”Точка:”;

cin>>x>>y;

}

 

void Point2D::print()const

{

cout<<”Точка: ”<<x<<” “<<y<<”\n”;

};

 

Если при определении член функции используется ключевое слово inline, то такая функция будет явно встраиваемой. При определении член-функции класса доступ к другим членам класса без использования операции уточнения (“->” и “.”). Это вызвано тем, что всем член-функциям передается неявный элемент – указатель на объект класса, для которого данная функция вызывается (this). Поэтому все неуточненные член-функции неявно уточняются этим указателем. (my_class *const this).

Для функции со спецификатором const, this имеет следующий вид: const my_class *const this.

 

//main.cpp

#include <iostream.h>

#include “point2d.h”

Point2D p0; //определен статически. Статические данные заполняются нулями.

 

 

void main()

{

Point2D p1,

*p2=new Point2D,

&p3=*new Point2D; //будет находиться мусор

 

p1.read();

p2->read();

p3.read();

cout<<p1.mod()<<” “<<p2->mod()<<” “<<p3.mod()<<”\n”;

 

delete p2;

delete &p3;

};

 

 

//язык программирования С

class Point2D

{

double x,y;

 

double mod(const point2D *this)

{

return (sqrt(this->x*this->x+this->y*this->y));

}

 

void print()

{

...

}

 

Очевидным недостатком является то, что нельзя проинициализировать переменную данного класса при объявлении. В С++ есть специальное средство, позволяющее обеспечить возможность начальной инициализации при объявлении объекта. Этим средством являются специальные член-функии, имя которых совпадает с именем класса. Называются они конструкторы. Эти функции вызываются автоматически при объявлении переменной соответствующего класса.

Point2D(double, double)

 

//point2d.cpp

Point2D::Point2D(double x, double y)

{

x=_x;

y=_y;

}

 

//main.cpp

Poind2D p10=Point2d(1,2);

p20=Poind2D(2,2);

 

Poind2D p10(1,2), p20(2,2); //большинство программистов используют сокращенную запись. Происходит 2 действия: выделение физической памяти и вызов специальной функции для помещения в память начального значения.

Т.к. член-функции представляются собой функцию, но с ограниченной областью видимости (внутри класса0 на них распространяются все правила, определенные для функции, в частности они могут быть перегружаемыми. Т.е. в определении класса может быть объявлено несколько функции с одним и тем же именем, но с разной сигнатурой. Все сказанное относится и к конструкторам.

//point2d.h

Poind2D(double a=0)

{

x=a;

y=a;

}

 

Poind2D p1, p2(2); //вызовется конструктор без параметров

Point2D p3(5,6) //вызовется с 2 параметрами

p4=4.0; //с одним параметром

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

 

class Arr

{

int n;

double *pw;

 

n p
public:

Ar(int n);

{

n=_n;

pv=new double[n];

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

{

pv[i]=0.0;

}

}

~Ar()

{

delete []pv;

}

}

 

//main.cpp

main()

{

Ar A(n);

}

 

Конструкторы могут быть перегружаемыми, а значит содержать любое количество элементов. Деструктор для каждого класса может быть только один и не должен содержать аргументов.

 

 

Point2D p1(1,2), p2(2,3), p3;

cout<<p1.mod()<<”\n”;

p1+p2; //ошибка на этапе компиляции

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

возвр.значение имя_класса::operator#(список_аргументов)

{

//выполняемые_действия...

}

Вместо значка # ставится знак перегружаемой операции.

Количество операндов у перегружаемой функции определяется тем, какая операция перегружается: одноместная или двухместная, а также наличием неявного аргумента у методов класса.

 

 

Перегрузка двухместных операций в виде в Point2D

Point2D operator-(Point2D p2);

//Point2D.cpp

Point2D Point2D::operator-(Point2D p2)

{

Point2D temp;

temp.x=x-p2.x;

temp.y=this->y-p2.y;

return temp;

}

Когда перегружается бинарная операция, у функции будет только один параметр. Этот параметр получит объект, расположенный справа от знака операции. Объект слева вызывает функцию операции и передается неявно посредством использования указателя this. Важно понимать, что для написания функции операции имеется множество вариантов. Рассмотрим вторую реализацию

 

Point2D operator-(Point2D p2) const;

 

//Point2D.cpp

Point2D Point2D::operator-(Point2D p2) const

{

return Point2D(x-p2.x, y-p2.y);

}

Этот метод не изменяет член-данные. Мы не стали создавать явные временные объекты, а воспользовались так называемым анонимным экземпляром. Он существует лишь во время вычисления выражения, в котором он встречается. При перегрузки унарной операции с использованием функции-члена, у функции не будет параметров, поскольку имеется только один вариант, он и вызывает функцию-операцию.

 

//Point2D.h

Point2D operator-() const;

 

//Point2D.cpp

Point2D Point2D::operator-() const

{

return Point2D(-x, -y);

}

 

//

void main()

{

const Point2D p1(1,2);

Point2D p2(2,3), p3;

(p1-p2).print(); //неявный вызов операции минус

p1.operator-(p2).print(); //явный вызов

cout<<p1*p2<<”\n”;

(p1-20).print(); //будет использоваться операция преобразования типов

}

Надо обратить внимания, что имеется оператор с двумя переменными, выражение p1-20 будет корректно выполняться. В этом случае конструктор автоматически организует вызов конструктора с одним параметром и конструируется для него временный объект. И лишь после этого вызовет функцию-операцию. 2.0-p1 конструироваться не будет, потому что перегруженная операция является методом класса своего первого оператора, а для своего типа данных никакие перегрузки операций недопустимы.

Операции с С++ можно перегружать. Не перегружаются следующие операции:

  1. .
  2. .*
  3. ::
  4. ?:
  5. sizeof

Можно перегружать + * / % ^ & |~!= <> += -= /= == && || ++ ->* -> [] () new delete

Большинство операций не может быть равно перегрузке. То есть те операции,которые выполняются раньше при перегрузке будут вести себя раньше. Изменить количество оперантов, которые берет на себя операция, невозможно. Перегружаемые унарные операции остаются унарными, а бинарные – бинарными. Нельзя создавать новые операции, можно перегружать только существующие. Нельзя изменить с помощью перегрузки операций смысла работы с переменами встроенного типа.

 

 

Дружественные функции

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

 

Point2D p1, p2(1,2), p3;

(p1+p2).print();

(p1+2.0).print();

(2.0+p2).print(); //ошибка компиляции

В некотором случае хотелось бы более свободно использовать перегруженные операции дял объекта Point2D. Это в принципе возможно,… При этом из глобальных функций будет невозможен доступ к закрытой части класса, а следовательно работа с самым классом. В С++ есть возможность разрешить доступ к закрытым частям класса глобальным функциям или член-функциям другого класса. Для разрешения доступа из этих функций ко всем данным класса надо перечислить эти функции в описании класса ключевым словом friend, создав таким образом дружественные классу функции.

//point2d.cpp

Point2D operator +(Point2D p1, Point2D p2)

{

return Point2D(p1.x+p2.x, p1.y+p2.y);}

//point2d.h

friend Point2D operator+(Point2D, Point2D); //дружественная к оператору +

  1. Спецификатор доступа private, protected, public не имеет отношения к дружественности, так что это описание омжет помещаться в любом месте описания класса.
  2. Рекомендуется помещать определения дружественности первыми в классе непосредственно после его заголовка и не предварять его каким-либо спецификатором доступа
  3. Так как функции, объявленные как friend не являются член-функциями этого класса им не передается неявный указатель на объект класса. Значит этот аргумент должен быть явно указан в списке аргументов функции.
  4. При определении дружественной функции для доступа к полям всем аргументов, надо использовать составные имена.

 

ostream <<Point2D

ostreamf – возвращаемое значение

 

friend ostream& operator <<(ostream &, Point2D);

friend istream& operator >>(istream&, Point2D&);

 

//point2d.cpp

ostreamf operator<<(ostream& out, Poind2D p)

{

return out<<”Точка: ”

<<p.x<<” “

<<p.y;

}

 

istream operator >>(istream &in, Point2D&)

{

cout<<”Точка ”;

return in >>p.x>>p.y;

}

 

Point2D p2, const Point2D p1(1,2);

cin>>p2;

cout<<p1+p2<<”\n”

<<p1*p2<<”\n”;

cout<<p1+2<<2+p1<<”\n”<<p.y;

 

Аналогичным образом можно объявить дружественные для класса обычным глобальным функциям, член-функциям другого класса и даже сделать все член-функции одного класса дружественными другому классу.

class B;

class C;

class A

{

int m1;

char m2;

friend void ff(A); //глобальная функция ff дружественная классу А

public

char fm(c);

char fm2(b,c);

};

 

class B

{

double mb2;

friend char A::fm2(B,C); //функция класса А, дружественная классу В

...

}

 

class C

{

char mc1;

friend class A; //все член-функции класса А дружественные классу С

...

}

 

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

 

 



Поделиться:




Поиск по сайту

©2015-2024 poisk-ru.ru
Все права принадлежать их авторам. Данный сайт не претендует на авторства, а предоставляет бесплатное использование.
Дата создания страницы: 2016-08-20 Нарушение авторских прав и Нарушение персональных данных


Поиск по сайту: