Главная | Обратная связь | Поможем написать вашу работу!
МегаЛекции

Классы. Конструкторы, деструкторы. Разделение доступа к членам класса.




Класс - это определяемый пользователем тип.

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

class Screen { /*... */ };

class Screen { /*... */ } myScreen, yourScreen;

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

Каждое определение вводит новый тип данных. Даже если два класса имеют одинаковые списки членов, они все равно считаются разными типами:

class First {

int memi;

double memd;};

class Second {

int memi;

double memd;};

class First obj1;

Second obj2 = obj1; // ошибка: obj1 и obj2 имеют разные типы

Тело класса определяет отдельную область видимости. Объявление членов внутри тела помещает их имена в область видимости класса. Наличие в двух разных классах членов с одинаковыми именами – не ошибка, эти имена относятся к разным объектам.

После того как тип класса определен, на него можно ссылаться двумя способами:

  • написать ключевое слово class, а после него – имя класса. В предыдущем примере объект obj1 класса First объявлен именно таким образом;
  • указать только имя класса. Так объявлен объект obj2 класса Second из приведенного примера.

Оба способа сослаться на тип класса эквивалентны.

Ссылки на Себя

В функции члене на поля объекта, для которого она была вызвана, можно ссылаться непосредственно. Указатель на объект, для которого вызвана функция член, является скрытым параметром функции. На этот неявный параметр можно ссылаться явно как на this. В каждой функции класса x указатель this неявно описан как x* this; и инициализирован так, что он указывает на объект, для которого была вызвана функция член. this не может быть описан явно, так как это ключевое слово. Класс x можно эквивалентным образом описать так:

class x {

int m;

public:

int readm() { return this->m; }};

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

Конструктор

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

Часто бывает хорошо обеспечить несколько способов инициализации объекта класса.

Это можно сделать, задав несколько конструкторов. Например:

class date {

int month, day, year;

public:

//...

date(int, int, int); // день месяц год

date(char*); // дата в строковом представлении

date(int); // день, месяц и год сегодняшние

date(); // конструктор по умолчанию: сегодня

};

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

Например:

date d = today; // инициализация посредством присваивания

Копирующий конструктор

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

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

Часто почленная инициализация не обеспечивает корректного поведения класса. Поэтому мы явно определяем копирующий конструктор. Копирующий конструктор принимает в качестве формального параметра ссылку на объект класса (традиционно объявляемую со спецификатором const).

Очистка (деструктор)

Определяемый пользователем тип имеет, конструктор, который обеспечивает надлежащую инициализацию. Для многих типов также требуется обратное действие, деструктор, чтобы обеспечить соответствующую очистку объектов этого типа. Имя деструктора для класса X есть ~X() («дополнение конструктора»). В частности, многие типы используют некоторый объем динамической памяти, который выделяется конструктором и освобождается деструктором.

Сокрытие информации – это формальный механизм, предотвращающий прямой доступ к внутреннему представлению типа класса из функций программы. Ограничение доступа к членам задается с помощью секций тела класса, помеченных ключевыми словами public, private и protected – спецификаторами доступа. Члены, объявленные в секции public, называются открытыми, а объявленные в секциях private и protected соответственно закрытыми или защищенными.

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

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

Друзья классов

Функция не член, получившая право доступа к закрытой части класса, называется другом класса (friend). Функция становится другом класса после описания как friend. Функция друг не имеет никаких особенностей, помимо права доступа к закрытой части класса. В частности, friend функция не имеет указателя this (если только она не является полноправным членом функцией

5. Перегрузка операций. Переопределение ввода-вывода в С++.

Перегрузка Операций

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

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

class complex {

double re, im;

public:

complex(double r, double i) { re=r; im=i; }

friend complex operator+(complex, complex);

friend complex operator*(complex, complex);};

complex operator+(complex a, complex b)

{ complex c;

c.re=a.re+b.re;

c.im=a.im+b.im;

return c;}

complex operator*(complex a, complex b)

{ complex c;

c.re=a.re*b.re-a.im*b.im;

c.im=a.re*b.im+a.im*b.re;

return c;}

определяет простую реализацию понятия комплексного числа, в которой число представляется парой чисел с плавающей точкой двойной точности, работа с которыми осуществляется посредством операций + и * (и только). Программист задает смысл операций + и * с помощью определения функций с именами operator+ и operator*. Если, например, даны b и c типа complex, то b+c означает (по определению) operator+(b,c). Теперь есть возможность приблизить общепринятую интерпретацию комплексных выражений. Например:

complex a = complex(1, 3.1);

complex b = complex(1.2, 2);

complex c = b;

a = b+c;

b = b+c*a;

c = a*b+complex(1,2);

Выполняются обычные правила приоритетов, поэтому второй оператор означает b=b+(c*a), а не b=(b+c)*a.

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

+ - * / % ^ & | ~!

= < > += -= *= /= %= ^= &=

|= << >> >>= <<= ==!= <= >= &&

|| ++ -- [] () new delete

Изменить приоритеты перечисленных операций невозможно, как невозможно изменить и синтаксис выражений. Нельзя, например, определить унарную операцию % или бинарную!. Невозможно определить новые лексические символы операций. Имя функции операции есть ключевое слово operator (то есть, операция), за которым следует сама операция, например, operator<<. Функция операция описывается и может вызываться так же, как любая другая функция. Использование операции — это лишь сокращенная запись явного вызова функции операции. Например:

complex c = a + b; // сокращенная запись

complex d = operator+(a,b); // явный вызов

При наличии предыдущего описания complex оба инициализатора являются синонимами.

Бинарная операция может быть определена или как функция член, получающая один параметр, или как функция друг, получающая два параметра. Таким образом, для любой бинарной операции @ aa@bb может интерпретироваться или как aa.operator@(bb), или как operator@(aa,bb). Если определены обе, то aa@bb является ошибкой. Унарная операция, префиксная или постфиксная, может быть определена или как функция член, не получающая параметров, или как функция друг, получающая один параметр. Таким образом, для любой унарной операции @ aa@ или @aa может интерпретироваться или как aa.operator@(), или как operator@(aa). Если определено и то, и другое, то и aa@ и @aa являются ошибками. Рассмотрим следующие примеры:

class X {

// друзья

friend X operator-(X); // унарный минус

friend X operator-(X,X); // бинарный минус

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

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

// члены (с неявным первым параметром: this)

X* operator&(); // унарное & (взятие адреса)

X operator&(X); // бинарное & (операция И)

X operator&(X,X); // ошибка: тернарное};

Когда операции ++ и -- перегружены, префиксное использование и постфиксное различить невозможно.

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

Функция член X::operator T(), где T — имя типа, определяет преобразование из X в T.

Например, можно определить тип tiny (крошечный), который может иметь значение только в диапазоне 0...63, но все равно может свободно сочетаться в целыми в арифметических операциях:

class tiny {

char v;

int assign(int i)

{ return v = (i&~63)? (printf("ошибка диапазона"), 0): i; }

public:

tiny(int i) { assign(i); }

tiny(tiny &i) { v = i.v; }

int operator=(tiny &i) { return v = i.v; }

int operator=(int i) { return assign(i); }

operator int() { return v; }};

Диапазон значения проверяется всегда, когда tiny инициализируется int, и всегда, когда ему присваивается int. Одно tiny может присваиваться другому без проверки диапазона. Чтобы разрешить выполнять над переменными tiny обычные целые операции, определяется tiny::operator int(), неявное преобразование из int в tiny. Всегда, когда в том месте, где требуется int, появляется tiny, используется соответствующий ему int. Например:

tiny c1 = 2;

tiny c2 = 62;

tiny c3 = c2 - c1; // c3 = 60

tiny c4 = c3; // нет проверки диапазона (необязательна)

int i = c1 + c2; // i = 64

c1 = c2 + 2 * c1; // ошибка диапазона: c1 = 0 (а не 66)

c2 = c1 -i; // ошибка диапазона: c2 = 0

c3 = c2; // нет проверки диапазона (необязательна)

Переопределение ввода - вывода на языке С++

Стандартный ввод/вывод

С++ предоставляет четыре предварительно определенных потоковых объекта:

· cin стандартный ввод;

· cout стандартный вывод;

· cerr стандартный вывод ошибок;

· clog полностью буферизованная версия cerr;

Есть возможность перенаправить эти стандартные потоки из и на другие устройства и файлы.

Оператор поразрядного сдвига влево <<, применительно к операциям с потоками, называется оператором вставки или оператором «поместить в», а оператор поразрядного сдвига вправо >> — называется оператором извлечения или оператором «прочитать из».

Класс istream включает перекрываемые определения для оператора >>, используемого со стандартными типами [int, long, double, float, char*(строка)]. Таким образом, предложение cin >> x; вызывает соответствующую функцию оператора >> для istream cin, определенного в iostream.h и использует ее для направления этого входного потока в позицию памяти, представляемую переменной х. Аналогично, класс ostream имеет перекрываемые определения для оператора <<, который разрешает с помощью предложения cout << x; посылать значение x в ostream cout для вывода.

Эти функции оператора возвращают ссылку на соответствующий классовый тип потока (например, ostream&) наряду с перемещением данных. Это позволяет расположить в цепочку несколько таких операторов для ввода и вывода последовательности символов:

int i=0, x=243; double d=0;

cout << "Значение x равно " << x << '\n';

cin >> i >> d; //ввод с клавиатуры int, пробел, затем double

Вторая строка будет выводить на дисплей: «Значение x равно 243», а затем будет идти новая строка. Следующее предложение будет игнорировать пробел, читать и преобразовывать клавишные символы в целое число и помещать его в i, игнорировать следующий пробел, читать и преобразовывать следующие клавишные символы в double и помещать их в d.

Поделиться:





Воспользуйтесь поиском по сайту:



©2015 - 2024 megalektsii.ru Все авторские права принадлежат авторам лекционных материалов. Обратная связь с нами...