Docsity
Docsity

Подготовься к экзаменам
Подготовься к экзаменам

Учись благодаря многочисленным ресурсам, которые есть на Docsity


Получи баллы для скачивания
Получи баллы для скачивания

Заработай баллы, помогая другим студентам, или приобретай их по тарифом Премиум


Руководства и советы
Руководства и советы

Объективное программирование реферат по информатике , Сочинения из Информатика

Объективное программирование реферат по информатике

Вид: Сочинения

2016/2017

Загружен 11.04.2017

refbank13248
refbank13248 🇷🇺

3

(1)

19 документы

1 / 34

Toggle sidebar

Сопутствующие документы


Частичный предварительный просмотр текста

Скачай Объективное программирование реферат по информатике и еще Сочинения в формате PDF Информатика только на Docsity! ВВЕДЕНИЕ В ОБЪЕКТНОЕ ПРОГРАММИРОВАНИЕ Лекция 1. Объектное программирование как технология программирования ------------------------------------------------------------- Традиционная технология программирования 70-х годов - структурное программирование: - модульное программирование; - нисходящее программирование; - структурное проектирование процедур и данных (программирование без goto). Язык Паскаль - соответствует указанным принципам и был разработан под влиянием идей структурного программирования. Альтернативный подход - восходящее программирование - предполагает в простейшем случае создание слоя структур данных и процедур, обеспечивающих полный набор действий над объектами, которые представлены в данной задаче. Пример традиционного подхода библиотека стандартных функций. Следующий шаг - введение в программу объектов. Под объектом понимается структура данных, которая содержит полную информацию о состоянии соответствующего физического объекта, который отображается программой. В Си этому может соответствовать структура struct), в Паскале - запись (record). Множество объектов одного типа составляют понятие класса. Объектно-ориентированный подход к разработке программ предполагает, что в программе устанавливается взаимно- однозначное соответствие между физическими объектами, отображаемыми программой, и програмнными объектами, являющимися, по существу, структурированными переменными (в дальнейшем под термином "объект" будем понимать программный объект). Традиционный подход: ---------- переменная тип данных Объектно-ориентиро- физический программный класс ванный подход: объект объект объектов При создании объектов программист определяет множество функций, при помощи которых (а точнее, исключительно через которые)над объектом выполняется некоторое допустимое множество операций. Такие функции должны иметь обязательный параметр - ссылку на текущий объект, для которого они вызываются. Сами функции являются неотъемлимой частью понятия класса объектов, так как они определяют возможные действия над объектами одного и того же типа (то есть класса). Объектно-ориентированные программы можно разрабатывать и с помощью традиционных языков программирования. Рассмотрим пример определения объектов типа "дата" на классическом Си. //------ структура dat - аналог класса объектов "дата" --------typedef struct dat { unsigned day; unsigned month; unsigned year; } DAT; //----- набор функций для класса объектов "дата" --------------static int mm[] = {31,28,31,30,31,30,31,31,30,31,30,31}; //----- Проверка на корректность -----------------------------int TestData(p) DAT *p; { if (p->month ==2 && p->day==29 && p->year %4 ==0) return(1); if (p->month ==0 || p->month >12 || p->day ==0 || p->day >mm[p->month]) return(0); return(1); } //------ Следующая дата ----------------------------------------void NextData(p) DAT *p; { p->day++; if (p->day <= mm[p->month]) return; if (p->month ==2 && p->day==29 && p->year %4 ==0) return; p->day=1; p->month++; if (p->month !=13) return; p->month=1; p->year++; } //------- Следующая дата через n дней --------------------------void PlusData (p,n) DAT *p; int n; { while (n-- !=0) NextData(p); } //------- Основная программа --------------------------------- void main() { DAT a; do { scanf("%d%d%d", &a.day, &a.month, &a.year); } while(TestData(&a) ==0); PlusData(&a, 17); } //-------------------------------------------------------- Фактически определение класса объектов как типа данных и известного набора функций для выполнения операций над переменными этого типа эквивалентно понятию базового типа данных (БТД) языка программирования. Единственное отличие класса от БТД заключается в том, что первый определяется программистом, а второй встроен в определение языка программирования. Язык программирования Си++ представляет собой расширение языка Си для программирования объектов и их классов. При этом использование классов эквивалентно вплоть до синтаксиса использованию базовых типов данных: Понятия классического Си Понятия Си++ ------------------------- ----------- БТД: Класс: элемент данных языка, для определяемая пользователем которого известно множество структура, элементы которой значений, форма представления, являются ранее определен набор операций. ными типами данных и классами, и множества функций,оперирующих с ним. --------------------------------------------------------- Переменная: Объект: область памяти, содержащая переменная, содержащая структуру данных определенного структуру данных, определенную типа. как класс. --------------------------------------------------------- Операция: Переопределение операторов: операция над переменной интер- функция, определенная для объек претируется по отношению к тому тов указанного класса может быть БТД, к которому относится пере- вызвана в виде одной из стандарт менная (так операция '+' ных операций языка Си, которая по-разному интерпретируется для переопределяется, если операндом переменных типа int и double). ее является объект класса, а не переменная БТД. Лекция 2. Дополнительные возможности языка Си++ ----------------------------------------------- Ниже рассмотрим средства, расширяющие классический Си. Хотя они и не относятся непосредственно к классам, с их помощью можно реализовать рассмотренные выше принципы объектно- ориентированного программирования. 2.1. Присваивание структур ------------------------- Операция присваивания может быть применена к структурам одного типа. В этом случае предполагается их побайтное копирование одной в другую. Она (а не ссылка на нее) может быть также фактическим параметром и результатом функции. Если имеется ссылка на структуру с именем p, то результатом операции *p является структура в целом. Таким образом, структура приближается к базовым типам данных в том смысле, что над ней возможны вышеуказанные операции. Для обозначения структуры можно также использовать имя структуры без ключевого слова struct. struct dat { int day,month,year; } dat NextDat(dat x) // Формальный параметр - структура { ... return(x); } // Возвратить структуру как результат dat Nextdat1(dat *p) { ... return(*p); } // Возврат структуры косвенно по ссылке dat a,b,c,*q; // Ключевое слово struct не используется void main() { q = &b; a = b; // Прямое присваивание структур a = *q; // Косвенное присваивание по ссылке c = NextDat(b); // Присваивание структуры как результата c = NextDat1(&b); // функции, фактический параметр в } // NextDat - копия структуры 2.2. Обращения по адресу (неявная ссылка) ---------------------------------------- При работе со структурами большого размера - при передаче их }; //----------- набор функций для класса объектов "дата" --------static int mm[] = {31,28,31,30,31,30,31,31,30,31,30,31}; //----------- Проверка на корректность -----------------------int dat::TestData () { if (month ==2 && day==29 && year %4 ==0) return(1); if (month ==0 || month >12 || day ==0 || day >mm[month]) return(0); return(1); } //----------- Следующая дата ----------------------------------void dat::NextData() { day++; if (day <= mm[month]) return; if (month ==2 && day==29 && year %4 ==0) return; day=1; month++; if (month !=13) return; month=1; year++; } //--------- Основная программа --------------------------------void main() { dat a; do { scanf("%d%d%d", &a.day, &a.month, &a.year); } while(a.TestData() ==0); a.PlusData(17); } //------------------------------------------------------- Как видно из примера, в качестве элементов структуры могут выступать функции. Такие элементы-функции имеют следующие особенности: - тело функции может быть определено в самой структуре (функция PlusData). В этом случае функция имеет стандартный вид; - в определении структуры дается только прототип функции (заголовок с перечислением типов формальных параметров). Определение самой функции дается отдельно, при этом полное имя функции имеет вид <имя структуры>::<имя функции> - в теле фукции неявно определен один формальный параметр с именем this - ссылка на структуру, для которой вызывается функция (В нашем примере это будет struct dat *this ). Поля этой структуры доступны через явное использование этой ссылки this->month = 5; this->day++; или неявно month = 5; day++; - для переменной, имеющей тип некоторой структуры, вызов функцииэлемента этой структуры имеет вид <имя переменной>.<имя функции> ( <список параметров> ) 2.4. Переопределение функций --------------------------- В Си++ возможно определение нескольких функций с одинаковым именем, но с разными типами формальных параметров. При этом компилятор выбирает соответствующую функцию по типу фактических параметров. Переопределяемую функцию необходимо объявить с ключевым словом overload: overload SetDat; void SetDat(int dd,int mm,int yy,dat *p) { // Дата вводится в виде трех целых p->day=dd; p->month=mm; p->year=yy; } void SetDat(char *s,dat *p) // Дата вводится в виде строки { sscanf(s,"%d%d%d", &p->day, &p->month, &p->year); } void main() { dat a,b; SetDat(12, 12, 1990, &a); // Вызов первой функции SetDat("12,12,1990", &b); // Вызов второй функции } Функции-элементы также могут быть переопределены, при этом явного объявления не требуется. struct dat { int day,month,year; void SetDat(int,int,int); void Setdat(char *); } void dat::SetDat(int dd,int mm,int yy) { day=dd; month=mm; year=yy; } void dat::SetDat(char *s) { sscanf(s,"%d%d%d",&day,&month,&year); } void main() { dat a,b; a.SetDat(12,12,1990); b.SetDat("12,12,1990"); } 2.5. Операторы управления динамической памятью --------------------------------------------- В библиотеке Си имеются две функции управления динамической памятью - malloc() и free(), которые выделяют и освобождают область памяти заданного размера (в байтах). В этой области программа может разместить переменную (или массив), которая называется динамической. При выделении памяти под динамическую переменную необходимо при помощи операции sizeof определять количество байтов, необходимое для размещения переменной указанного типа. В Си++ введены два оператора, аналогичные функциям malloc и free new и delete. Они отличаются от соответствующих функций тем, что допускают использования в качестве аргументов непосредственно спецификацию типа создаваемой динамической переменной и ссылки на динамическую переменную: Си++ "Классический" Си ------------------------- ---------------------------------char *s,x[80]; char *s,x[80]; dat *p,*q; struct dat *p,*q; void main() void main() { { p = new dat; p = malloc(sizeof (struct dat)); q = new dat[15]; q = malloc(15*sizeof (struct dat)); gets(x); gets(x); s = new char[strlen(x)+1]; s = malloc(strlen(x)+1); ... ... delete p; free(p); delete q; free(q); delete s; free(s); } Операторы имеют вид: <результат: ссылка на <абстрактный динамическую переменную> new описатель типа> delete <ссылка на динамическую переменную> 2.6. Параметры функций по умолчанию ---------------------------------- При определении формальных параметров функции может быть указано его значение, принимаемое при вызове по умолчанию при отсутствии этого параметра в списке фактических: //----- Функция устанавливает по умолчанию текущее значение года, //----- месяца и дня #include <dos.h> void dat::SetDat(int d=0, int m=0, int y=0) { struct date x; getdate(&x); // Стандартная функция получения // текущей даты // Проверка на значение по умолчанию year = (y == 0) ? x.da_year : y; month= (m == 0) ? x.da_month: m; day = (d == 0) ? x.da_day : d; } 2.7 Контроль преобразования типов ссылок --------------------------------------- В "классическом" Си при выполнении присваивания, передаче фактических параметров происходит автоматическое преобразование ссылок к базовым типам данных (int,unsigned) и наоборот, а также преобразование одного типа ссылки к другому. В Си++ такие "вольности" исключены, программист должен сам выполнить явное преобразование. Например, при использовании функции распределения динамической памяти, имеющей прототип в "alloc.h" extern void* malloc(int n); dat *p; p = (dat *) malloc (10*sizeof(dat)); ¦ L--- преобразование void* в dat* Естественно, что это преобразование типов фиктивное в том смысле, что не меняет значения ссылки и не приводит к генерации кода. Оно только меняет "точку зрения" транслятора на данную ссылку. 2.8 Вставляемые (inline) функции ------------------------------- Если функция (обычная или элемент-функция структуры или класса) объявлены inline-функциями, то при вызове таких функций транслятор выполняет подстановку по тексту программы тела функции с соответствующей заменой формальных параметров на фактические. Элемент-функция также считается inline по умолчанию, если ее тело определено непосредственно в определении структуры (или класса),например: struct dat { int d,m,y; void Setdat(char *p) // Функция inline по умолчанию { ... // Тело функции } 2.9 Ссылки на элементы структуры ------------------------------- Если структура имеет несколько элементов одного типа,то для нее может быть создана "внутренняя" ссылка, которая принимает значение внутреннего адреса (смещения) элемента относительно выбранной структуры. Формирование и использование такой ссылки ясно из примера: struct dat { int day,month,year; void Getdat(); void Putdat(); void Nextdat(); } int dat::*p; // Ссылка на элемент типа int // в структуре dat p = & dat::month; // Значение p - смещение (адрес) // элемента month в структуре типа // dat dat x,*px = &x; // x.*p = 5; // Обращение по внутренней ссылке px->*p = 5; // <dat> . *<ссылка на элемент> // <*dat>-> *<ссылка на элемент> Эквивалентно x.month = 5; px->month =5; Аналогичная внутренняя ссылка может быть создана для элементов-функций, принадлежащих одной структуре, при этом функции должны быть идентичными по результатам и параметрам: void (dat::*fun)(); // Ссылка на элемент-функцию // структуры dat fun = & dat::Putdat(); // Значение fun - ссылка на // элемент-функцию Putdat в dat (x.*fun)(); // Вызов элемента-функции по (px->*fun)(); // ссылке fun для структуры x // и для структуры по ссылке px Эквивалентно x.Putdat(); px->Putdat(); 2.10 Неизменяемые переменные (константы) --------------------------------------- В Си++ введен дополнительный контроль за изменением значений переменных. Ключевое слово const, используемой при определении и инициализации переменной, запрещает ее изменение, что контролируется транслятором при ее дальнейшем { int day,month,year; public: dat(int,int,int); // Конструктор с параметрами // (возможно умолчание) dat(char *); // Конструктор с одним параметром dat(); // Конструктор без параметров ~dat(); // Деструктор }; //------- Конструктор с параметром - текстовая строка ---------dat::dat (char *s) { int i; char ss[80]; strcpy(ss,s); for (i=0; ss[i] !=0; i++) if (ss[i]=='-') ss[i]=','; // Замена '-' на ',' sscanf(ss,"%d%d%d",&day,&month,&year); } // Конструктор с тремя параметрами (по умолчанию 0 - текущая дата) dat::dat(int d=0, int m=0, int y=0) { struct date x; getdate(&x); // Стандартная функция получения // текущей даты // Проверка на значение по умолчанию year = (y == 0) ? x.da_year : y; month= (m == 0) ? x.da_month: m; day = (d == 0) ? x.da_day : d; } //------ Конструктор без параметров --------------------------dat::dat() { struct date x; getdate(&x); // Стандартная функция получения // текущей даты year = x.da_year ; month= x.da_month; day = x.da_day ; } //------ Деструктор ------------------------------------------dat::~dat() { printf("Дата ==> %2d-%2d-%4d\n",day,month,year); } //------------------------------------------------------dat a("12-12-1990"); // Внешняя переменная - конструктор // вызывается перед main() dat b[10]; // Массив объектов - конструктор без // параметров вызывается перед main() void xxx(dat &p) { dat c(12,12); // Вызывается Конструктор dat(int,int,int) // для автоматического объекта dat d = p; // Конструктор для автоматического объекта не ... // вызывается, т.к. объект инициализируется ... // копированием } // При выходе из функции вызываются деструкторы // для объектов c и d void main() { int i,n; scanf("%d",&n); dat *p = new dat[n]; // Создание массива динамических объектов // конструктор без параметров явно вызывается for (i=0; i<10; i++) // n раз xxx(b[i]); // При вызове неявно передается ссылка на b[i] for (i=0; i< n; i++) // xxx(p[i]) // При вызове неявно передается ссылка на p[i] delete[n] p; // Уничтожение массива динамических объектов // деструктор явно вызывается n раз } // Деструктры для a и b[10] вызываются после // выхода из main() } 3.3 Ограничение доступа к объектам класса. Дружественность. ---------------------------------------------------------- Выше было дано формальное определение класса. Содержательная его сторона состоит в том, что класс выступает в Си++ как определенный программистом тип данных. Такой тип данных включает в себя: - описание структуры объектов класса. Объект класса представляет собой совокупность элементов (структуру), каждый из которых является переменной некоторого типа или объектом ранее определенного класса; - описание множества допустимых операций над объектом класса, которые представлены элементами-функциями; - описание процедур создания и уничтожения объекта класса (конструктор и деструктор). При этом внутренняя структура объектов (описанная в "приватной" части класса) доступна только элементам-функциям. Наоборот, "публичная" часть определения класса - это описание той части объекта, к которой возможен доступ из любого места программы, если доступен сам объект. Данное ограничение позволяет исключить некорректное использование объектов класса, как случайное, так и умышленное. Возможные ошибки, связанные с неправильным "поведением" объектов класса, локализуются таким образом в определении самого класса, а не в использовании его объектов. Иногда требуются исключения из этого правила, когда к "приватной" части объекта класса требуется доступ из некоторой отдельной функции, либо из всех элементов-функций другого класса, либо из переопределяемой в другом классе операции. Тогда в определении класса, к объектам которого разрешается такой доступ, должно быть объявление функции или другого класса "дружественным". Это согласуется с тем принципом, что сам класс определяет права доступа к своим объектам "со стороны". Объявление дружественной функции предствляет собой прототип функции, объявление переопределяемой операции или имя класса, которым разрешается доступ, с ключевым словом friend впереди. Общая схема объявления такова: class A { int x; // Приватная часть класса ... friend class B; // Функции класса B дружественны классу A // (имеют доступ к приватной части A) friend void C::fun(A&); // Элемент-функция fun класса C имеет // доступ к приватной части A friend void xxx(A&,int); // Функция xxx дружественна классу A friend void C::operator+(А&); // Переопределяемая в классе C операция // <объект C>+<объект A> дружественна // классу A class B { public: int fun1(A&);// Необходим доступ к приватной части A void fun2(A&);// ----------------------------------- } class C { public: void fun(A&);// ------------------------------------ void operator +(A&);//------------------------------- .... } К средствам контроля доступа относятся также объявления элементов-функций постоянными (const). В этом случае элементфункция не имеет права изменять значение текущего объекта, с которым она вызывается. Заголовок функции при этом имеет вид void dat::put() const { } 3.4 Статические элементы класса ------------------------------ Иногда требуется определить данные, которые относятся ко всем объектам класса. Это требуется, если объекты класса разделяют некоторый общий ресурс, связаны в общий список и т.д.. С этой целью в определении класса могут быть введены статические элементы - переменные. Такой элемент сам в объекты класса не входит, зато при обращении к нему формируется обращение к внешней переменной с именем <имя класса>::<имя элемента> соответствующего типа. Доступность ее определяется стандартным образом в зависимости от размещения в приватной или общей части класса. Сама переменная должна быть явно определена в программе и инициализирована. Пример: объекты класса связаны в односвязный список --------------------------------------------------class list { static list *fst; // Ссылка на первый элемент static list *lst; // Ссылка на последний элемент list *next; // Ссылка на следующий элемент .... ..... public: void insfst(); // Вставить в начало списка void inslst(); // Вставить в конец списка void show(); // Просмотр всех объектов void extract(); // Исключть из списка list(); // Конструктор ~list(); // Деструктор } list list::fst=NULL; // Определение статических элементов list list::lst=NULL; //-------------------------------------------------------void insfst() { next = NULL; if (fst==NULL) fst=lst=this; else { next=fst; fst=this; } } //-------------------------------------------------------void inslst() { next = NULL; if (fst==NULL) fst=lst=this; else { lst->next=this; lst=this; } } //-------------------------------------------------------void list::extract() { list *p,*pred; // Поиск текущего и предыдущего for (pred=NULL,p=fst; p !=NULL; // в списке pred=p,p=p->next) if (p=this) break; // Если найден - выход if (p !=NULL) { // Найден - исключение из списка if (pred==NULL) fst = next; else pred->next=next; } } //-------------------------------------------------------void list::show() { list *p; for (p=fst; p !=NULL; p=p->next) { ...вывод информации об объекте... } } //------ При создании объекта он помещается в список -----------list::list() { insfst(); } //------ При уничтожении объекта он исключается из списка ------ list::~list() { extract(); } Примером использования внутреннего списка объектов является система всплывающих окон. При выполнении операций над одним из окон часто требуется произвести некоторые действия с другими окнами, то есть в любой момент программе должен быть известен список созданных объектов - окон. Последовательность объектов в списке может отражать последовательность отображения окон на экране. Тогда при выполнении операции "всплытия" окна необходимо изменить посложение соответствующего объекта в списке. Естественно, что конструктор и деструктор объекта включают его в список и исключают. Статическими могут быть объявлены также и элементы-функции. Их "статичность" определяется тем, что вызов их не связан с конкреетным объектом и может быть выполнен по полному имени. Соответственно в них не используются неявная ссылка this. Они вводятся, как правило, для выполнения действий, относящихсмя ко всем объектам класса. Для предыдущего примера class list { ... static void show(); // Стaтическая функция просмотра } // всего списка объектов //-------------------------------------------------------static void list::show() { list *p; for (p=fst; p !=NULL; p=p->next) { ...вывод информации об объекте... } } //-------------------------------------------------------void main() { ... list::show(); // Вызов функции по полному имени } Лекция 4. Переопределение операторов. ------------------------------------ Напомним, что под классом понимается определяемый программистом тип данных, используемый наравне со стандартными базовыми типами. С точки зрения "равноправия" вновь вводимого типа данных желательно иметь возможность расширения (переопределения) операций языка, в которых один или несколько - автоматический объект x в теле функции создается в стеке перед вызовом функции. Если он инициализируется копией другого объекта (например, текущего), то конструктор для него не вызывается: dat x = *this; В противном случае вызывается конструктор с соответствующим набором параметров: dat x; dat x("13-JUL-1990"); - в любом случае перед выходом из функции вызывается деструктор для автоматического объекта; - если функция имеет формальный параметр - объект класса, то для него вызывается деструктор после выхода из функции (если это переопределяемый оператор, то после вычисления выражения); - если функция возврашает объект по значению (а не по явной или неявной ссылке), то функция получает дополнительный неявный параметр - ссылку на результат (см. "Неявные ссылки"), а по оператору return производится копирование возвращаемого объекта в объект-результат по заданной ссылке. Однако при вызове такой функции транслятор может по-разному формировать эту ссылку: - если производится прямое присваивание результата функции, то передается ссылка на объект в левой части операции присваивания; - в остальных случаях на каждый вызов функции (по синтаксису программы) вызывающей функцией формируется дополнительный автоматический объект. Конструктор для него не вызывается, так как он формируется копированием результата функции, а деструктор вызывается при выходе из вызывающей функции, как для обычного автоматического объекта. Наиболее эффективным способом работы с объектом является передача его на вход (формальный параметр) и выдача на выход в качестве ссылки (преимущественно неявной). Однако в этом способе все изменения, производимые при выполнении операции, происходят в исходном объекте. Это не согласуется с интерпретацией большинства бинарных операций, где результат является отдельным элементом данных (объектов), а операнды не меняются. В качестве примера рассмотрим вариант операции сложения "дата + целое", в котором исходная дата увеличивается на заданное значение и одновременно является результатом операции. class dat { int day,month,year; public: void next(); // Элемент-функция вычисления // следующего для dat& operator+(int); // Операция "дата + целое" // с неявным операндом через this //------ Операция "дата + целое" ------------------------------//1. Дружественная элемент-функция с полным списком аргументов //2. Альтернативный вариант предыдущей функции //3. Первый операнд класса dat - неявная ссылка на фактический // параметр, значение меняется при выполнении операции //4. Тело функции непосредственно в определении класса. //-------------------------------------------------------friend dat& operator+(dat& p,int n) { while (n-- !=0) p.next();// Вызов функции next для объекта p // по неявной ссылке на него. return(p); // Возврат неявной ссылки неа p } // Операция "целое + дата" ------------------------------------//1. Дружественная элемент-функция с полным списком аргументов //2. Второй операнд класса dat - неявная ссылка на фактический // параметр, значение меняется при выполнении операции. //3. Тело функции непосредственно в определении класса. //------------------------------------------------------- friend dat& operator+(int n, dat& p) { while (n-- !=0) p.next(); // Вызов функции next для объекта p // по неявной ссылке на него. return(p); // Возврат неявной ссылки на p } //------------------------------------------------------- dat(); // Конструкторы dat(int,int,int); // (см. предыдущие примеры) dat(char *); // ~dat(); // Деструктор }; // (см. предыдущие примеры) //------ Операция "дата + целое" -------------------------------//1. Элемент- функция с неявным первым аргументом по ссылке this //2. Меняется значение текущего объекта //3. Результат - неявная ссылка на текущий объект //--------------------------------------------------------dat& dat::operator+(int n) { while (n-- !=0) next(); // Вызов функции next с текущим объектом this return(*this); // Возврат неявной ссылки на объект (this) } //--------------------------------------------------------- void main() { int i; dat a; dat b(17,12,1990); dat c(12,7); dat d(3); dat e; dat *p = new dat[10]; e = a++; d=b+15; for (i=0; i<10; i++) p[i] + i; delete[10] p; } Лекция 5. Особенности переопределения различных операций ------------------------------------------------------- Одной из важных возможностей использования переменных базовых типов в операциях является их преобразование к другим типам, которое может производиться неявно (в бинарных арифметических операциях и при присваивании), либо с использованием операции явного преобразования типа. Если преследовать целью достижение при добавлении новых классов всей полноты свойств базовых типов данных, то аналогичные преобразования необходимо ввести и для этих классов. Далее рассмотрим два возможных способа такого преобразования - стандартный, преобразование объекта класса к переменной базового типа данных, и нестандартный - преобразование переменной базового типа данных или объекта класса к объекту другого класса. 5.1 Преобразование к базовому типу данных ---------------------------------------- В качестве примера рассмотрим неявное преобразование объекта класса dat к базовым типам данных int и long. Сущность его заключается в вычислении полного количества дней в дате, заданной входным объектом (long) и количества дней в текущем году в этой же дате (int). Для задания этих операции необходимо переопределить в классе dat одноименные операции int и long. Переопределяемые операции задаются соответствующими элементами-функциями без параметров, ссылка на текущий объект входного класса передается через неявный параметр this. Тип результата совпадает с базовым типом, к которому осуществляется приведение и поэтому не указывается. static int days[] = { 0,31,28,31,30,31,30,31,31,30,31,30,31 }; class dat { int day,month,year; public: operator int(); // Преобразование dat в int operator long(); // Преобразование dat в long long operator -(dat &p); // Операция dat-dat вычисляет // разность дат в днях dat(); // Конструкторы dat(int,int,int); // dat(char *); // ~dat(); // Деструктор }; //------ Преобразование dat в int -----------------------------// Используется ссылка на текущий объект this //------------------------------------------------------- dat::operator int() { int r; // Текущий результат int i; // Счетчик месяцев for (r=0, i=1; i<month; i++) // Число дней в прошедших r += days[month]; // месяцах if ((month>2) && (year%4==0)) r++; // Високосный год r += day; // Дней в текущем месяце return(r); } //------ Преобразование dat в long ---------------------------// Используется ссылка на текущий объект this //------------------------------------------------------ dat::opertor long() { long r; // Текущий результат r = 365 * (year-1) // Дней в предыдущих полных годах r += year / 4; // Високосные года r += (int)(*this); // Дней в текущем году - предыдущая // операция (явное преобразование return(r); // dat в int } //-------- Операция вычисления разницы двух дат ---------------// Первый операнд по ссылке на текущий объект this // Второй операнд по неявной ссылке p //-------------------------------------------------------long dat::operator-(dat& p) { return((long)(*this) - (long)p); // Преобразовать оба объекта // к типу long и вычисл. разность } void main() { dat a("12-05-1990"); // Дата, заданная текстовой строкой dat b; // Текущая дата int c; long d; // Явное преобразование к long printf("С 12-05-1990 прошло %4ld дней\n",(long)b-(long)a); // Явное преобразование к int printf("В этом году прошло %3d дней\n",(int)b); // Неявное преобразование при присваивании c = b; d = b - a; // Операция dat-dat printf("С 12-05-1990 прошло %4ld дней\n",d); printf("В этом году прошло %3d дней\n",c); } 5.2 Преобразование переменной к объекту класса --------------------------------------------- Данный способ не является стандартным и требует проверки работоспособности в используемом компиляторе. Он основан на том факте, что при компиляции явного или неявного преобразования объекта класса к базовому типу данных "xxx" вызывается переопределяемая операция "operator xxx()". Соответственно, при явном или неявном преобразовании к классу "zzz" должна вызываться переопределяемая операция "operator zzz". Логично, что такая операция должна быть определена в классе "zzz". Но тогда имя соответствующей элемента-функции будет "zzz::zzz", что соответствует конструктору. Таким образом, если необходимо определить явное или неявное преобразование от базового типа или класса "xxx" к классу "zzz", то в классе "zzz" необходимо определить конструктор class zzz { int par_zzz; ----------------- входной тип (класс) zzz(xxx p); или zzz(xxx& p); L-------------------- выходной тип (класс) }; void zzz::zzz(xxx &p) { par_zzz = ... p.par_xxx ...; элемент объекта----- L-------элемент объекта выходного класса входного класса } class xxx { friend class zzz; int par_xxx; }; со следующими свойствами: - объект класса "zzz", который является выходным при преобразовании типов доступен как в любом конструкторе через ссылку на текущий объект this; - элементам выходного объекта (например, par_zzz) должны быть присвоены значения с явным или неявным использованием ссылки this this->par_zzz = ... (*this).par_zzz = ... par_zzz = ... } Лекция 6. Производные классы --------------------------- 6.1 Вложенные классы ------------------- Понятие производного класса вводит в систему классов принцип иерархии. Действительно, если определен класс объектов с достаточно общими свойствами то объект данного класса желательно включать в качестве одного из элементов в объекты других классов. Существует два способа такого включения, каждый из них имеет собственные цели и особенности. Первый случай представляет собой обычный способ построения инрархической структуры данных, когда объект старого класса является одним из элементов данных "приватной" части нового класса. Он имеет собственное имя (именован), по которому к нему можно обращаться как к объекту. В элементах-функциях нового класса можно использовать элементы-функции и операции для объекта старого класса. Рассмотрим в качестве примера класс man - информация о человеке, включающая в себя даты рождения и поступления на работу. class man { char name[20]; // Другие элементы класса char *address; dat dat1; // Дата рождения dat dat2; // Дата поступления на работу public: void newadr(); // Элемент-функция man(char*); // Конструктор } //----- Функция "Изменить адрес проживания" ----------------void man::newadr() { int n; char s[80]; // Строка нового адреса if (address != NULL) delete address; // Освободить память printf("Введите новый адрес:"); gets(s); address = new char[strlen(s)+1];// Занять новую память strcpy(address,s); // Заполнить поле адреса } Из данного примера видно, что именованные объекты старого класса можно использовать в элементах-функциях нового класса как обычные элементы, вызывать определенные для них элементы- функции старого класса и выполнять переопределенные для них операции. Заметим, что при этом элементы-функции нового класса не имеют доступа к приватной части объектов базового класса, то есть "содержимое" вложенных объектов для них закрыто. Но здесь возникает вопрос, как инициализируются и уничтожаются объекты старого класса при создании или уничтожении объекта нового класса, то есть как взаимодействуют их конструкторы и деструкторы. В случае, если конструктор объекта нового класса задан обычным образом, то перед вызовом этого конструктора будут вызваны конструкторы без параметров для входящих в него объектов старого класса. И наоборот, после вызова деструктора для объекта нового класса будут вызваны деструкторы вложенных объектов старого класса. Однако при конструировании вложенных объектов им желательно передавать параметры. Поэтому при описании конструктора объекта нового класса можно в заголовке в явном виде указать тот вид конструктора объекта старого класса, который требуется. Кроме того, его параметры могут зависеть от параметров вызова конструктора нового класса: class man { char name[20]; // Другие элементы класса dat dat1; // Дата рождения dat dat2; // Дата поступления на работу public: man(char *,char *,char *); // Конструкторы man(char *); } //----- Конструктор класса man с неявным вызовом конструкторов // для dat1 и dat2 без параметров //----------------------------------------------------- man::man(char *p) { } //----- Конструктор класса man с явным вызовом конструкторов // для dat1 и dat2 с параметрами //--------------------------------------------------------- man::man(char *p,char *p1, char *p2) : dat1(p1), dat2(p2) { ¦ ¦ ¦ // --- Тело конструктора --- ¦ ¦ ¦ } ¦ ¦ ¦ Вызов конструктора для ------------------ ¦ ¦ вложенного объекта dat1 ¦ ¦ В качестве параметра передается ------------- ¦ строка - второй параметр вызова ¦ конструктора для класса man Вызов конструктора для вложенного объекта dat2 void main ------ Строка конструктора man { ¦ man JOHN("John","8-9-1958","15-1-1987"); } ¦ L------ Строка передается Строка передается конструктору объекта конструктору объекта dat2 в объекте man dat1 в объекте man 6.2 Производные классы --------------------- Другой случай вложенности классов основывается на понимании класса как совокупности данных и операций над ними. При этом принцип вложенности рассматривается как создание нового "производного" класса, который включает в себя все или большую часть свойств старого "базового" класса, или "наследует" их: структура объекта старого класса включается в новый объект, а все элементы- функции старого класса применимы к объекту нового класса, точнее к его старой составляющей. Старый класс при этом называется базовым классом (БК), новый - производным классом (ПК). Синтаксис определения производного класса имеет вид: class <производный> : <базовый 1>,<базовый 2>,...<базовый n> { определение приватной и публичной части производного класса } Перечислим основные свойства базового и производного классов: - объект базового класса определяется в производном классе как неименованный. Это значит, что он не может быть использован в явном виде как обычный именованный объект; - элементы данных базового класса включаются в объект производного класса (как правило, компилятор размещает их в начале объекта производного класса). Oднако приватная часть базового класса закрыта для прямого использования в производном классе; - элементы-функции базового класса "наследуются" в производном классе, то есть вызов функции, определенной в базовом классе, для объекта производного класса возможен и понимается как вызов ее для входящего в него объекта базового класса; - в производном классе можно переопределить наследуемую функцию, которая будет вызываться вместо наследуемой. При этом для выполнения соответствующих действий над объектом базового класса она может включать явный вызов переопределяемой функции. Пример схемы определения производного класса class a { public: void f() {} void g() {} } class b : a ------------------------ базовый класс { public: void f() -------------------- "f" переопределяется { ... a::f(); -------------- явный вызов "f" для БК } -------------- "g" наследуется из БК void h() {} -------------- собственная функция в ПК } void main() { a A1; b B1; B1.f(); --------------- вызов переопределенной b::f() B1.g(); --------------- вызов наследуемой a::f() } Понятие "наследования" предполагает что при вызове в производном классе функций, наследуемых из базового, транслятор производит преобразование ссылки this на объект производного класса в ссылку на входящий в него объект базового класса, учитывая размещение последнего в объекте производного класса. Взаимоотношение конструкторов и деструкторов базового и производного классов аналогичны выше описанным: - если конструктор производного класса определен обычным образом, то сначала вызывается конструктор базового класса без параметров, а затем конструктор производного класса. Деструкторы вызываются в обратном порядке - сначала для производного, затем для базового; - в заголовке конструктора производного класса может быть явно указан вызов конструктора базового класса с параметрами. Он может быть без имени, а может быть с именем базового класса. Если производный класс включает в себя объекты нескольких базовых классов, то в вызовы конструкторов базовых классов должны быть перечислены через запятую и должны иметь имена базовых классов. 6.3 Права доступа в производных классах -------------------------------------- Производный класс включает в себя как приватную, так и публичную часть базового класса. При этом важно, В какую часть производного класса, приватную или публичную, попадут соответствующие части базового класса. От этого зависит доступность элементов базового класса, как из элементов-функций производного класса, так и извне - через объекты производного класса. Здесь возможны следующие варианты: - приватная часть базового класса A всегда включается в приватную часть производного класса B, но при этом непосредственно недоступна из элементовфункций класса B. Это соответствует тому факту, что в классе B разрешается работать с базовым объектом класса A только разрешенными в классе A средствами, то есть через элементы-функции класса A. Исключение составляет объявление всего класса B дружественным в классе A; - по умолчанию, то есть при использовании заголовка вида class B : A { } публичная часть класса A попадает в приватную часть класса B. Это значит, что элементы-функции класса A доступны из элементов- функций класса B, но не могут быть вызваны извне, то есть при обращении к объектам класса B. То есть для внешнего пользователя класса B интерфейс класса A закрывается; - в противном случае, при объявлении class B : public A { } публичная часть класса A попадает в публичную часть класса B, и внешний пользователь при работе с объектами класса B может применить интерфейсы как производного, так и базового классов; - и наконец, в определении публичной части класса B можно явно указать элементы-функции (а также данные) публичной части базового класса A, которые попадают в публичную часть класса B, то есть выполнить предыдущее действие селективно по отношению к отдельным элементам (при этом указывается только имя элемента): class B : A { ... public: ... public A::fun; ... } Перечисленные варианты изображены на схеме: class A class B -----------¬ ----------------¬ ¦ privat ======================> privat A ¦ +----------+ ¦ (недоступен B)¦ ¦ public ¦ class B:A +---------------+ ¦ ======================> privat B ¦ ¦ ¦ ¦ (доступен B) ¦ ¦ ¦ class B : public A ¦===============¦ ¦ ======================> public B ¦ ¦ ¦ class B : A { ... ¦ ¦ ¦ ¦ public A::newadr; ¦ <---- Доступ к объектам ¦ ----------------------> производного класса L----------- L--------------- Из рассмотренных вариантов видно, что приватная часть базового класса недоступна в любом производном классе, что естественно следует из свойств закрытости определения класса. Однако по аналогии с дружественностью базовый класс может разрешить доступ p[1]->f(); // во всех случаях, хотя f() p[2]->f(); // переопределены Однако по логике поставленной задачи требуется, чтобы вызываемая функция соответствовала тому объекту, который реально находится под ссылкой. Наиболее просто это сделать так: - хранить в объекте базового класса идентификатор "окружающего" его производного класса; - в списке или таблице хранить ссылки на объект базового класса; - при вызове функции по ссылке на объект базового класса идентифицировать тип производного класса и явно вызывать для него переопределенную функцию; - идентификатор класса устанавливать при создании объекта , то есть в его конструкторе. class a { public: int id; // Идентификатор класса void f(); void newf(); // Новая функция f() с идентификацией ПК } a::a() // Конструкторы объектов { ... id = 0; } b::b() { ... id = 1; } c::c() { ... id = 2 } void a::newf() { switch (id) { case 0: a::f(); break; case 1: b::f(); break; case 2: c::f(); break; } } p[0]->newf(); // Вызов b::f() для B1 p[1]->newf(); // Вызов c::f() для C1 p[2]->newf(); // Вызов a::f() для А1 Отсюда следует определение виртуальной функции. Виртуальная функция (ВФ) - это функция, определяемая в базовом и наследуемая или переопределяемая в производных классах. При вызове ее по ссылке на объект базового класса происходит вызов той функции, которая соответствует классу объекта, включающему в себя данный объект базового класса. Таким образом, если при преобразовании типа "ссылка на ПК" к типу "ссылка на БК" происходит потеря информации об объекте производного класса, то при вызове виртуальной функции происходит обратный процесс неявного восстановления типа объекта. Реализация механизма виртуальных функций заключается в создании компилятором таблицы адресов виртуальных функций (ссылок). Такая таблица создается для базового класса и для каждого включения базового класса в производный. В объекте базового класса создается дополнительный элемент - ссылка на таблицу адресов его виртуальных функций. Эта ссылка устанавливается конструктуром при создании объекта производного класса. При вызове виртуальной функции по ссылке на объект базового класса из объекта берется ссылка на таблицу функций и из нее берется адрес функции по фиксированному смещению. Ниже иллюстрируется реализация этого механизма (подчеркнуты элементы, создаваемые неявно компилятром). class A { ------> void (**ftable)(); // Ссылка на таблицу адресов // виртуальных функций public: virtual void x(); virtual void y(); virtual void z(); A(); ~A(); }; // Таблица адресов функций класса А ------> void (*TableA[])() = { A::x, A::y, A::z }; A::A() { ------> ftable = TableA; // Установка таблицы для класса А } class B : public A { public: void x(); void z(); B(); ~B(); }; // Таблица адресов функций класса A // в классе B --> void (*TableB[])() = { B::x, A::y, B::z }; ¦ L переопределяется в B B::B() L------ наследуется из A { --> ftable = TableB; // Установка таблицы для класса B } void main() { A* p; // Ссылка p базового класса A B nnn; // ссылается на объект производp = &nnn; // ного класса B реализация p->z(); ------------------> (*(p->ftable[2]))(); } p nnn TableB B::z() -----¬ -------->--B-----¬ ----->---------¬ --->----------¬ ¦ ------ ftable¦--A---¬¦ ¦ 0+--------+ ¦ ¦ ¦ L----- ¦¦ ------ 1+--------+ ¦ ¦ ¦ ¦+-----+¦ 2¦ --------- L--------- ¦¦ ¦¦ L-------- 7.2 Абстрактные классы --------------------- Если базовый класс используется только для порождения производных классов, то виртуальные функции в базовом классе могут быть "пустыми", поскольку никогда не будут вызваны для объекта базового класса. Такой базовый класс называется абстрактным. Виртуальные функции в определении класса обозначаются следующим образом: class base { public: virtual print() =0; virtual get() =0; } Естественно, что определять тела этих функций не требуется. 7.3 Множественное наследование и виртуальные функции --------------------------------------------------- Множественным наследованием называется процесс создания производного класса из двух и более базовых. В этом случае производный класс наследует данные и функции всех своих базовых классов. Существенным для реализации множественного наследования является то, что адреса объектов второго и т.д. базовых классов не совпадают с адресом объекта производного и первого базового классов, то есть имеют фиксированные смещения относительно начала объекта: class d : public a,public b, public c { }; d D1; pd = &D1; // #define db sizeof(a) pa = pd; // #define dc sizeof(a)+sizeof(b) pb = pd; // pb = (char*)pd + db pc = pd; // pc = (char*)pd + dc D1 pd -------------------->-d---------¬ pa --------------------->-a-------¬¦T T ¦¦ ¦¦¦ ¦ db = sizeof(a) ¦L---------¦¦ + pb --------------------->-b-------¬¦¦ dc = sizeof(a) + sizeof(b) ¦L---------¦¦ pc --------------------->-c-------¬¦+ ¦L---------¦ ¦ ¦ L---------- Преобразование ссылки на объект производного класса к ссылке на объект базового класса требует добавления к указателю текущего объекта this соответствующего смещения (db,dc), обратное преобразование - вычитание этого же смещения. Такое действие выполняется компилятором, когда в объекте производного класса наследуется функция из второго и т.д. базового класса, например при определении в классе "b" функции "f()" и ее наследовании в классе "d" вызов D1.f() будет реализован следующим образом: this = &D1; // Адрес объекта производного класса this = (char*)this + db // Адрес объекта класса b в нем b::f(this); // Вызов функции в классе b со своим // объектом Рассмотрим особенности механизма виртуальных функций при множественном наследовании. Во-первых, на каждый базовый класс в производном классе создается своя таблица виртуальных функций (в нашем случае - для "a" в "d", для "b" в "d" и для "c" в "d"). Во-вторых, если функция базового класса переопределена в производном, то при вызове виртуальной функции требуется преобразовать ссылку на объект базового класса в ссылку на объект производного, то есть для второго и т.д. базовых классов вычесть из this соответствующее смещение. Для этого транслятор включает соответствующий код, корректирующий значение this в виде "заплаты", передающей управление командой перехода к переопределяемой функции. class a { public: virtual void f(); virtual void g(); }; class b { public: virtual void h(); virtual void t(); }; class c : public a, public b { // f(),t() наследуются public: void g(); // g() переопределяется void h(); // h() переопределяется } a A1; b B1; c C1; pa = &A1; pb = &B1; pa->f(); // Вызов a::f() pb->h(); // Вызов b::h() pa = &C1; pb = &C1; pa->f(); // Вызов a::f() pa->g(); // Вызов c::g() pb->h(); // Вызов c::h() pb->t(); // Вызов b::t() Таблицы виртуальных функций для данного примера имеют вид: A1 -a----¬ Таблица ВФ для "a" ¦ ------------>--------¬ +-----+ ¦a::f() ¦ L------ +-------+ ¦a::g() ¦ L------- B1 -b----¬ Таблица ВФ для "b" ¦ ------------>--------¬ +-----+ ¦b::h() ¦ L------ +-------+ ¦b::t() ¦ L------- C1 T --c-----¬ Таблица ВФ для "a" в "c" ¦ ¦--a---¬¦ --------¬ db ¦ ¦¦ ----------->¦a::f() ¦ ¦ ¦L------¦ +-------+ + ¦--b---¬¦ ¦c::g() ¦ ¦¦ -------¬ L------- ¦L------¦ ¦ Таблица ВФ для "b" в "c" ¦ ¦ ¦ ¦ ¦ L--->--------¬ "Заплата" для c::h() L-------- ¦ xxx()----->--xxx()----------------¬ +-------+ ¦ this=(char*)this - db¦ ¦b::t() ¦ ¦ goto c::h ¦ L-------- L---------------------- virtual int CMP(base*)=0; // Сравнение значений объектов virtual char *NAME()=0; // Возвращает имя класса virtual base *COPY()=0; // Возвращает копию объекта virtual operator long()=0; // Преобразование к типу long virtual operator char*()=0; // Преобразование к типу char* virtual base& operator+(char*)=0; // Операция "+ строка" virtual ~base(); // Виртуальный деструктор для // разрушения объекта ПК по }; // ссылке на БК Сама двумерная таблица объектов организована традиционным для структур переменной размерности способом: - элемент БД создается в динамической памяти при добавлении строки к БД; - строка БД представлена массивом ссылок на объекты класса base. Сам массив также создается в динамической памяти при добавлении новой строки в БД; - ссылки на строки собраны в массив, который создается конструктором базы данных и заполняется при вызове функции добавления строки (таблица строк БД); - объект класса БД (table) содержит ссылку TBL на таблицу строк. Особо следует остановиться на способе назначения столбцам типов содержащихся в них элементов БД (или классов объектов). Это делается при помощи строки заголовка БД - head. Этот массив содержит ссылки на объекты, классы которых идентифицируют типы элементов в соответствующих столбцах. При создании новой строки БД виртуальной функцией COPY создаются копии объектов из строки заголовка БД, для которых затем вызывается виртуальная функция ввода значений GET. Строка заголовка создается конструктором объекта класса БД. Имеется меню типов элементов, которое представляет собой массив ссылок (TYPE) на объекты классов string,integer,dat и т.д.. Экранное меню строится при помощи вызова виртуальной функции вывода имени класса TYPE[i]->NAME(). После выбора строки меню ссылка на соответствующий выбранный объект переносится в строку заголовка БД. class table { int nc; // Количество столбцов int nr; // Количество строк char **names; // Имена стробцов base **head; // Строка объектов заголовка БД // для объявления типов объектов base ***TBL; // Таблица строк БД public: void append(); // Добавление строки в БД void sort(int); // Сортировка по значениям столбца long averrage(int); // Подсчет среднего арифметического // для столбца base& operator()(int,int); // Выбор объекта из БД table(); // Конструктор - создание БД ~table(); // Деструктор - удаление БД } объект БД TBL Массив строк БД --¬ ---------¬0 ¦------->+--------+.. Элемент БД L-- +--------+i Строка БД string base*** ¦ ----------->---------¬0 integer +--------+ +--------+.. real +--------+ +--------+j --dat-------¬ base** ¦ -------------->-base-----¬¦ +--------+ ¦L----------¦ base* ¦ ¦ L----------- base head Строка заголовка БД --¬ S0 ¦-------------->---------¬0 -string---¬ L-- ¦ ------------------>-base---¬¦ base** +--------+ ---------->L--------¦ ¦ --------------¬ L--------- +--------+ ¦ ¦ D0 ¦ --------- ¦ -dat------¬ +--------+ L--->-base---¬¦ base* ¦L--------¦ L---------//------------------------------------------------------// Меню классов объектов (типов столбцов) string S0; dat D0; time T0; integer I0; base *TYPE[] = { (base*) &S0; (base*) &D0; (base*) &T0; (base*) &I0; }; //-----------------------------------------------------// Создание структуры БД #define MAXCOL 30 #define MAXREC 1000 table::table() { int i,j,n; char ss[80]; names = new char*[MAXCOL]; // Таблица адресов имен столбцов head = new base*[MAXCOL]; // Таблица ссылок на объекты for (nc=0; nc<MAXCOL; nc++) // заголовка БД { // Ввод имени столбца gets(ss); if (strlen(ss)=0) break;// Пустая строка - выход name[nc] = new char[strlen(ss)+1]; //------ построение меню типов элементов БД ... for (j=0; j<3; j++) { gotoxy(10,5+j); cputs( TYPE[j]->NAME() ); } //------ выбор типа столбца - n head[nc] = TYPE[n]; // Ссылка на объект с классом, // соответствующим классу // объектов столбца TBL = new base**[MAXREC]; nr = 0; // Таблица ссылок на строки БД } } //------------------------------------------------------// Деструктор БД tabe::~table() { int i,j; for (i=0; i<nr; i++) { for (j=0; j<nc; j++) delete TBL[i][j]; // Разрушение объекта i-ой строки // j-го столбца по ссылке на // объект БК // (виртуальный деструктор) delete TBL[i]; } delete TBL; for (j=0; j<nc; j++) delete names[j]; } //------------------------------------------------------// Добавление строки к БД void table::append() { int i; TBL[nr] = new base*[nc]; // Создание таблицы ссылок для строки БД for (i=0; i<nc; i++) // Создание копий объектов ПК из строки { // заголовка БД TBL[nr][i] = head[i]->COPY(); printf("Столбец %s типа %s :",names[i],head[i]->NAME()); // Вывод подсказки имени и типа столбца while(TBL[nr][i]->GET() ==0);// Ввод значения нового объекта } nr++; } //-------------------------------------------------------// Нахождение среднего арифметического по заданному столбцу long table::averrage(int n) { long r; int i; if (n<0 || n>=nc) return(0); for (r=0, i=0; i<nr; i++) // Виртуальная операция преобра r += (long)(*TBL[i][n]);// зования класса base к long return(r / nr); } //-------------------------------------------------------// Сортировка по заданному столбцу методом "пузырька" void table::sort(int n) { int i,k; base *p; do { for (i=0; i< nr-1; i++) // Виртуальная функция сравнения // объектов в соседних строках // n-го столбца if (TBL[i][n]->CMP(TBL[i+1][n]) <0) { p = TBL[i][n]; TBL[i][n] = TBL[i+1][n]; TBL[i+1][n] = p; k++; } } while (k); // Пока есть перестановки } //----------------------------------------------------------// Выбор элемента (i,j) в БД - возвращает неявную ссылку на // БК объекта, по которой возможен вызов любой виртуальной // функции static base empty; // Пустой объект для ошибочного выбора base& table::opertor()(int i,int j) { if (i<0 || i >= nr) return(empty); if (j<0 || j >= nc) return(empty); return (*TBL[i][j]); // Возвратить неявную ссылку на объект } //--------------------------------------------------------// Пример работы с классом РБД void main() { int i,j; table R; // Создание БД for (i=0; i<10; i++) R.append(); // Ввод 10-ти строк R.sort(1); // Сортировка по первому столбцу scanf("%d %d", &i, &j); // Вывод значения элемента R(i,j).PUT(); R(1,2) + "1234"; // Операция "объект + строка" // для элемента 1,2 } Лекция 9. Шаблоны. ----------------- Довольно часто классы создаются для объединения множества элементов данных, которые внутри объекта могут быть связаны массивом ссылок, списком, деревом и т.д.. При этом объект класса содержит ссылки Таким образом, требуется определить некоторое множество идентичных классов с параметризованным типом внутренних элементов. То есть должна быть задана заготовка класса (шаблон), в котором в виде параметра задан тип (класс) входящих в него внутренних элементов данных. Тогда при создании объекта необходимо дополнительно указывать и конкретный тип внутренних элементов в качестве параметра. Создание объекта сопровождается также и созданием соответствующего конкретного класса для заданного конктретного типа. Принятый в Си++ способ определения множества классов с параметризованным внутренним типом данных (иначе, макроопределение) называется шаблоном (template). Синтаксис шаблона рассмотрим на примере шаблона класса векторов, содержащих динамический массив ссылок на переменные заданного типа. --- параметр шаблона - класс "T", внутренний ¦ тип данных ¦ --- имя группы шаблонных классов template <class T> class vector { int tsize; // Общее количество элеметов int csize; // Текущее количество элементов T **obj; // Массив ссылок на параметризован // ные объекты типа "T" public: T *operator[](int); // оператор [int] возвращает ссылку // на параметризованный объект // класса "T" void insert(T*); // функция включения объекта типа "T" int extract(T*); // }; Данный шаблон может использоваться для порождения объектов-векторов, каждый из которых хранит объекты определенного типа. Имя класса при этом составляется из имени шаблона "vector" и имени типа данных (класса), который подставляется вместо параметра "Т": vector<int> a; vector<double> b; extern class time; vector<time>c; Заметим, что транслятором при определении каждого вектора с новым типом объектов генерируется описание нового класса по заданному шаблону (естественно, неявно в процессе трансляции): class vector<int>
Docsity logo