Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
шпоры печатать.docx
Скачиваний:
0
Добавлен:
01.05.2025
Размер:
379.36 Кб
Скачать

16. Полиморфизм и виртуальные функции. Применение динамического полиморфизма. Виртуальные деструкторы. Абстрактные классы и чисто виртуальные функции.

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

C++ упрощает создание полиморфных объектов.

Для создания полиморфных объектов ваши программы должны использовать виртуальные (virtual) функции.

Виртуальная (virtual) функция — это функция базового класса, перед именем которой стоит ключевое слово virtual.

Любой производный от базового класс может использовать или перегружать виртуальные функции.

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

Полиморфизмом в программировании называется переопределение наследником функций-членов базового класса, например

class Figure {

...

void Draw() const;

...

};

class Square : public Figure {

...

void Draw() const;

...

};

class Circle : public Figure {

...

void Draw() const;

...

};

В этом примере, какая из функций будет вызвана — Circle::Draw(), Square::Draw() или Figure::Draw(), определяется во время компиляции. К примеру, если написать

Figure* x = new Circle(0,0,5);

x->Draw();

то будет вызвана Figure::Draw(), поскольку x — объект класса Figure.

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

class Figure {

...

virtual void Draw() const;

...

};

class Square : public Figure {

...

virtual void Draw() const;

...

};

class Circle : public Figure {

...

virtual void Draw() const;

...

};

Figure* figures[10];

figures[0] = new Square(1, 2, 10);

figures[1] = new Circle(3, 5, 8);

...

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

figures[i]->Draw();

В этом случае для каждого элемента будет вызвана Square::Draw() или Circle::Draw() в зависимости от вида фигуры.

Виртуальный деструктор.

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

class ANY_CLASS

{

public:

ANY_CLASS(); //конструктор по умолчанию

vrtual ~ANY_CLASS(); //а это - виртуальнный деструктор

};

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

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

Виртуальная функция-элемент некоторого класса может быть объявлена чистой. Это выглядит так:

virtual тип имя функции(список параметров} = 0;

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

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

Абстрактные классы

не могут, как уже сказано, иметь представителей;

не могут использоваться в качестве типа параметров или возвращаемых значений;

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

Тем не менее, можно объявлять указатели или ссылки на абстрактный класс.

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

17. Ввод/вывод в C ++. Предопределенные потоки. Простота ввода/вывода в С ++. Потоки cin, cout и сегг. Операции выделения ( >> ) и вставки ( << ).

В пакет компилятора Micrisoft Visual C++ включена стандартная библиотека, содержащая функции, часто используемые в среде C++. В C++ по-прежнему можно пользоваться библиотекой стандартного ввода/вывода С, описанной в заголовочном файле stdio.h. В C++ имеется другой заголовочный файл iostream.h, который реализует набор собственных функций ввода/вывода.

В языке C++ потоковый ввод/вывод описывается как набор классов в iostream.h. Эти классы используют перегруженные операции "занести в" и "получить из" << и >>.

В С отсутствуют встроенные операции ввода или вывода; функции, подобные printf(), являются частью стандартной библиотеки, но не самого языка. В C++ также нет встроенных средств ввода/вывода; это обеспечивает большую гибкость при разработке эффективного пользовательского интерфейса со структурами данных проектируемого приложения.

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

Аналогами потоков stdin, stdout и stderr, имеющих прототипы в файле stdio.h, в C++ являются cin, cout и сеrr, описанные в iostream.h. Эти три потока открываются автоматически при запуске программы и становятся интерфейсом между программой и пользователем. Поток cin связан с клавиатурой терминала. Потоки cout и сегг связаны с видеодисплеем.

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

эквивалентный оператор на C++: cout << "Integer value: " << lvalue << ", Float value: " << fvalue;

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

Когда вам необходимо ввести информацию, вы выделяете ее (>>) из входного потока cin и заносите в некоторую переменную — например, ivalue. Для вывода информации вы берете копию информации из переменной ivalue и вставляете ее (<<) в выходной поток cout.

Последней, но немаловажной, особенностью операций вставки и выделения является их дополнительное преимущество — малый размер результирующего кода. Универсальные функции ввода/вывода printf() и scanf() помимо своего кода включают в исполняемый модуль программы много зачастую ненужной информации. В C++, напротив, компилятор включает только те процедуры, которые действительно нужны.

Операция вставки << не генерирует автоматически символ перевода строки. При необходимости это можно делать непосредственно, включая в программу символ \n или endl.

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

18. От файла STREAM.H к файлу IOSTREAM.H. Операции и методы классов. Вывод символов в C++. Преобразование системы счисления в C++. Форматирование строк в C++. Форматирование чисел в C++. Файловый ввод и вывод в C++.

В библиотеке iostream предопределен набор операций для выполнения считывания и записи встроенных типов данных. Также библиотека обеспечивает пользовательские расширения для обработки классов. Базовые операции ввода обеспечивает класс istream, а операции вывода — класс ostream. Двунаправленный ввод/вывод поддерживается классом iostream, который является производным от istream и ostream.

Для пользователя предопределены четыре потоковых объекта:

cin

Объект класса istream, связанный со стандартным вводом

cout

Объект класса ostream, связанный со стандартным выводом

сеrr

Небуферизированный выходной объект класса ostream, связанный со стандартным устройством ошибок

clog

Буферизированный выходной объект класса ostream, связанный со стандартным устройством ошибок

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

fstream

Производный от iostream, связывает файл с приложением для считывания и записи

ifstream

Производный от istream, связывает файл с приложением только для считывания

ofstream

Производный от ostream, связывает файл с приложением только для записи

Для того, чтобы принимать аргументы любых встроенных типов данных, включая char *, операции выделения >> и вставки << были модифицированы. Их также можно расширить, и они будут принимать в качестве параметров классы. В новой версии каждый объект класса iostream имеет переменные, управляющие операциями форматирования. Флагами статуса формата можно манипулировать при помощи функций setf() и unsetf().

Флаг

Значение

ios:: showbase

Отображает числовые константы в формате, который может читать компилятор C++

ios:: showpoint

Отображает числа float с точкой и нулями в конце

ios:: dec(oct, hex)

Форматирует числа в десятичной (8ричн., 16ричн.) системе счисления (система счисления по умолчанию)

ios:: fixed

Показывает числа float в фиксированном формате

ios:: showpos

Отображает знак "плюс" (+) перед положительными числами

ios:: skipws

Пропускает пробельные символы на входе

ios:: left

Выравнивает слева все значения (дополняя справа указанным заполняющим символом)

ios: right

Выравнивает справа все значения (дополняя слева указанным заполняющим символом, опция по умолчанию)

ios:: internal

Добавляет заполняющие символы после любого знака или указателя системы счисления до начала числа

ios:: unitbuf

Заставляет ostream::osfx очищать поток после каждой операции вставки (по умолчанию сеrr буферизируется)

ios::stdio

Заставляет ostream::osfx очищать stdout и stderr после каждой вставки

cout.setf (ios:: uppercase); // включить заглавные

cout << hex << ivalue;

cout.unsetf(ios:: uppercase); // выключить заглавные

В нынешней библиотеке печатается сама символьная переменная. Если же нужно получить ASCII-код, то используется такой оператор: cout << (int)c;

Есть два способа для вывода значения в разных системах счисления: cout << hex << ivalue; и cout.setf(ios::hex, ios::basefield);

cout << ivalue;

Переход к некоторому другому основанию выполняется при помощи функции unsetf(): cout.unsetf(ios::hex, ios::basefield);

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

pszpadstring38[38 + INULL_TERMINATOR];

ostrstream(pszpadstring38,sizeof(pszpadstring38)) << " " << psz1;

Метод класса ostrstream() является частью файла strstream.h и имеет три параметра: указатель на массив символов, размер массива и выводимая информация. Для выравнивания вправо этот оператор в начало строки psz1 добавляет пробелы.

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

cout.width(20);cout.setf(ios:: left);cout.fill ('0');cout << а;

Если в приложении нужно создать файл для ввода и вывода, то в него необходимо включить заголовочный файл fstream.h (файл fstream.h включает в себя iostream.h). Классы ifstream и ofstream являются производными от istream и ostream и наследуют операции выделения и вставки, соответственно. В примере показано, как объявить файл для считывания и для записи при помощи ifstream и ofstream, соответственно:

#include <iostream>

using namespace std;

#include <fstream.h>

void main(void)

{ char c; ifstream ifsin("D:\\Programming\\Programming 02\\text.in", ios::in);

if(!ifsin) cerr << "\nUnable to open 'text.in' for input.";

ofstream ofsout("D:\\Programming\\Programming 02\\text.in",ios::out);

if(!ofsout) cerr << "\nUnable to open 'text.out' for output.";

while(ofsout && ifsin.get(c)) ofsout.put(c);

ifsin.close(); ofsout.close();cout << "\n\n";

ios:: in Открыть для считывания

ios:: out Открыть для записи

ios:: ate Искать конец после создания файла

ios:: trunc Если файл существует, то он очищается

ios:: noreplace Открыть только несуществующий файл

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

19. Условные признаки файлов в C++. Опрос и установка состояния потока. Ошибки потоков. Переадресация ввода-вывода. Резидентные в памяти потоки.

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

Методы функций

Действие

eof()

Возвращает ненулевое значение при достижении конца файла

fail()

Возвращает ненулевое значение, если операция завершена

bad()

Возвращает ненулевое значение, если возникла ошибка

good()

Возвращает ненулевое значение, если отсутствуют установленные разряды

rdstate()

Возвращает текущее состояние потока

clar()

Устанавливает состояние потока (int=0)

Эти методы классов можно использовать при реализации различных алгоритмов для определения конкретных состояний ввода/вывода и для большей читаемости программы:

Класс ios поддерживает информацию о состоянии потока после каждой операции ввода-вывода. Текущее состояние потока хранится в объекте типа iostate, который объявлен следующим образом:

typedef int iostate; Установить нужное состояние можно с помощью функции setsfate(): void setstate(iostate state);

Каждый поток имеет связанное с ним состояние ошибки, т.е. набор битов ошибок, объявленный как перечислимое значение io_state в классе ios.

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

Ошибки ввода/вывода потоком устанавливает соответствующий биты.

Бит состояния

Его смысл

goodbit

Если этот бит не установлен, то все в порядке

eofbit

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

failbit

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

badbit

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

hardfail

Устанавливается, если для данного потока встретилось невосстановимое состояние ошибки.

После того, как поток получил состояние ошибки, все попытки вставки или извлечения из данного потока будут игнорироваться до тех пор, пока не будет исправлено условие, вызвавшее состояние ошибки, а бит(ы) ошибки очищен(ы) (при помощи, например, функции компонента ios::clear(i).

Резидентные потоки применяются для выполнения форматных операций ввода/вывода для данных, находящихся в памяти. В файле strstream.h определены три класса резидентных потоков: istrstream, ostrstream и strstream. Создавая резидентный поток, нужно указать буфер и его размер. Если буфер ввода

заканчивается символом '\0', то размер буфера указывать не обязательно. При записи в буфер вывода в конце нужно добавить символ '\0' для правильной работы метода ostrstream::str.

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

ostream os1; // резидентный поток с динамическим буфером

char Buf[100];

ostream os2(Buf, siseof(Buf)); // резидентный поток со статическим буфером

20. Полнофункционольный ввод/вывод в С++. Перечисляемые типы enum. Ссылочные переменные. Значения аргументов по умолчанию. Функция memset().

Перечисляемые типы enum.

В языке С++ определяемые пользователем перечисляемые типы отличаются от своих аналогов, имеющихся в С. В частности, типы С enum совместимы с типом int. Это означает их взаимозаменяемость, и при этом компилятор не выдаст никаких предупреждений. В С++, однако, эти два типа несовместимы.

Второе различие между перечисляемыми типами С и С++ заключается в том, что при описании перечисляемых переменных в С++ используется более компактная синтаксическая конструкция. В следующем примере показаны отличие типа enum в этих двух языках:

typedef enum boolean {FALSE, TRUE};

void main(void)

{ //enum boolean bflag=0; в С это допустимо, в С++ нет

boolean bcontinue, bflag=FALSE;

bcontinue=(boolean)1;

bflag=bcontinue;}

в начале этого примера определяется перечисляемый тип boolean, который является стандартным в некоторых других языках высокого уровня. В соответствие с очередностью описаний — FALSE, затем TRUE — компилятор присваивает значение 0 константе FALSE и значение 1 — константе TRUE. Это согласуется с логическим смыслом констант в программе.

Закомментированный оператор в функции main() представляет собой допустимый оператор С. Напомним, что при описании перечисляемых переменных в С, например bflag, необходимо использовать ключевое слово enum вместе с полем тега перечисляемого типа — в данном случае это boolean. Поскольку типы С enum совместимы с типами int, также допускается инициализация переменной целым значением. Компилятор С++ не пропустит этот оператор. Эквивалентный ему допустимый оператор С++ показан во второй строке функции main()

Два других оператора в программе показывают, как использовать перечисляемые типы. Обратите внимание на то, что в С++ для преобразования 1 в совместимый тип boolean необходимо явное приведение типа (boolean).

Ссылочные переменные.

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

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

void vByValueCall (stStudent);

vByValueCall(astLargeClass[0]);

void vByVariableCall (stStudent *pstAStudent);

vByVariableCall (&astLargeClass[0]);

void vByReferenceCall (stStudent &rstAStudent);

vByReferenceCall(astLargeClass[0]);

Значения аргументов по умолчанию.

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

void fdefault_argument(char ccode = 'Q', int ivalue = 0, float fvalue = 0);

void main(void)

{ fdefault_argument('A', 2, (float)12.34);

fdefault_argument();}

void fdefault_argument(char ccode, int ivalue, float fvalue)

{ if(ccode == 'Q')

cout << "\n\nUsing default values only."; // используются значения только

cout << "\nivalue= " << ivalue; // по умолчанию

cout << "\nfvalue= " << fvalue << endl;}

В данной программе все три формальных параметра имеют значения по умолчанию. Функция fdefault_argument() проверяет значение переменной ccode и включает или выключает соответствующее сообщение. Результат работы программы достаточно прост:

ivalue = 2 fvalue = 12.34 Using default values only. ivalue = 0 fvalue = 0

Функция memset().

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