Конспект лекций по предмету "ИТ"


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

ВВЕДЕНИЕ В ОБЪЕКТНОЕ ПРОГРАММИРОВАНИЕ Лекция 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. Дополнительные возможности языка Си Ниже рассмотрим средства, расширяющие классический Си. Хотя они и не относятся непосредственно к классам, с их помощью можно реализовать рассмотренные выше принципы объектно-ориентированного программирования. 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. Обращения по адресу неявная ссылка При работе со структурами большого размера - при передаче их в качестве параметров и результатов функций - копирование их является неэффективной операцией. Гораздо эффективнее передавать ссылку на эту структуру. Для того, чтобы постоянно не указывать операции взятия адреса и косвенного обращения по ссылке в Си введен тип - неявная ссылка при определении переменной неявно вводится ссылка, указывающая на эту переменную. Использование этой переменной в большинстве операций предполагает косвенное обращение по соответствующей ссылке. При инициализации такой переменной значением другой переменной создается ссылка на эту другую переменную.При использовании в любом выражении переменной - неявной ссылки реально производится косвенное обращение по созданной ссылке. Си Эквивалент в классическом Си Инициализация константой int a 5 int a, pa a pa 5 Инициализация переменной int x int x, pa int a x pa x a 5 pa 5 Неявная ссылка на структуру struct dat struct dat int day,month,year int day,month, year dat x dat x dat b x dat pb x dat c 12,12,1990 dat cc 12,12,1990 dat pc cc b.year 1990 pb- year 1990 c.day b.day 3 pc- day pb- day 3 c b Копирование pc- day pb- day структуры pc- month pb- month pc- year pb- year Наиболее часто неявные ссылки используются при передаче параметров и результатов функций. В этом случае транслятор сам выбирает, что необходимо использовать в качестве фактического параметра - переменную или ссылку на нее, и что используется в качестве результата - ссылка или переменная, косвенно адресуемая по ссылке.Цель подобных ухищрений будет видна позднее - при переопределении операторов, а пока можно заметить, что вызов функций, с параметрами - обычными значениями и неявными ссылками - синтаксически идентичен. То же самое касается результатов.В качестве иллюстрации рассмотрим три примера функций, имеющих в качестве формального параметра и результата структуру, которая передается соответственно - значением - явной ссылкой - неявной ссылкой. Пример 1. Параметры - значения dat Inc dat x - копирование - ссылка x.day return x стек x.day b x L return x void main стек - временная dat a,b, p a x переменная a Inc Inc b L L p Inc b x.day a p Пример 2. Параметры - явные ссылки dat Inc dat x x- day x- day x- day стек return x г b x L return x г void main a стек - L x dat a,b, p a Inc Inc b L p Inc b return x a p L L L a L Пример 3. Параметры - неявные ссылки dat Inc dat x x.day неявная ссылка dat px x.day x.day стек return x г b px L return px г void main a стек - L px dat a,b, p a Inc Inc b L p Inc b return px a p L L L a L Сравнение этих примеров показывает следующее - при работе с формальным параметром - неявной ссылкой используется имя формального параметра в качестве идентификатора переменной, которая заменяется транслятором на косвенное обращение по неявной ссылке - при возвращении результата используется имя переменной,которая заменяется транслятором неявной ссылкой на нее - примеры 2 и 3 идентичны по реализации, но отличаются по синтаксису вызова функции - примеры 1 и 3 отличаются по реализации, но идентичны по синтаксису вызова функции - из предыдущего следует, что при вызове функции список фактический параметров недостаточен для определения транслятором способа их передачи значением или ссылкой , поэтому в Си для каждой внешней функции необходимо задать прототип. Так как размер структуры, передаваемой в качестве результата функции, может быть сколь угодно большим, то для его хранения необходимо создать временную переменную. Транслятор Borland C в этом случае поступает следующим образом - при входе в функцию в стеке предполагается существование неявного параметра - длинной ссылки на структуру, в которой размещается результат функции - при выполнении операции return x , где x - локальная переменная или формальный параметр, выполняется побайтовое копирование переменной x по адресу, заданному неявным параметром - если результат функции непосредственно присваивается другой переменной-структуре, то при вызове такой функции в стек помещается неявный параметр - ссылка на переменную в левой части операции присваивания - во всех остальных случаях в вызывающей функции создается по одной неявной автоматической переменной на каждый вызов функции, возвращающей структуру в качестве результата, а при вызове передается соответствующая ссылка на эту переменную-структуру. Такие переменные имеют все свойства автоматических, они существуют все время работы вызывающей функции, возможно даже получить ссылку на такую переменную. Программа на Си Реализация неявный параметр dat Inc dat x void Inc dat r,dat x x.day x.day return x r x Копирование результата void main void main dat a,b p dat a, b, p dat t,u Временнye переменнye a Inc b Inc a,b Ссылка на левую часть p Inc b Inc t,b Присаивание временной p t переменной и получение a p a p ссылки на нее a Inc Inc b Inc u,b Промежуточный результат Inc a,u во временной переменной 2.3. Функции - элементы структуры Повторим рассмотренный выше пример в другой форме структура dat - аналог класса объектов дата struct dat unsigned day unsigned month unsigned year int TestData void NextData void PlusData int n while n ! 0 dat NextData this набор функций для класса объектов дата 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, используемой при определении и инициализации переменной, запрещает ее изменение, что контролируется транслятором при ее дальнейшем использовании. Такая же возможность существует и для формальных параметров функции, например const int n 5 n Запрещено int xxx const int m m Запрещено Применительно к ссылке const может использоваться в двух вариантах, применительно к самой ссылке адресу и применительно к указуемому значению - при использовании conts применительно к указуемому значению разрешается модифицировать саму ссылку при помощи присваивания и операций адресной арифметики, а изменения операнда косвенно по ссылке запрещены. Такая ссылка называется ссылкой на постоянный объект const char p p 1234567890 Разрешено присваивание ссылке p 3 Разрешена модификация ссылки p 3 3 Запрещено присваивание по ссылке p Запрещен инкремент по ссылке - при использовании const применительно к ссылке запрещается менять значение ссылки после инициализации, в том числе средствами адресной арифметики. Такая ссылка называется постоянной ссылкой на объект char const p 1234567890 char c p 3 Разрешено присваивание по ссылке p Запрещено изменение значения c p 3 самой ссылки Полная фиксация ссылки и адресуемого ею объекта возможна в виде const char const p 1234567890 2.11 Общие замечания о дополнениях в Си Основные отличия Си от классического Си - структура struct приближена по свойствам к базовым типам данных char,int - введено понятие элемента-функции. Элементы-функции играют роль своеобразного интерфейса для использования определенной программистом структуры - расширены возможности транслятора по контролю и преобразованию параметров при вызове функции неявная ссылка, переопределение, параметры по умолчанию . Именно поэтому вызову любой внешней функции должно предшествовать объявление ее прототипа заголовка функции со списком типов параметров . Все эти новые свойства необходимы при определении понятий класса и объекта. Лекция 3. Классы. Объекты. Конструкторы и деструкторы 3.1.Понятие класса и объекта в Си В самом простом виде класс определяется в Си как структура, работа с элементами которой возможна только через элементы-функции. В отличие от структуры класс имеет приватную личную часть, элементы которой не могут быть доступны иначе как через другие элементыфункции, и публичную общую часть, элементы которой могут быть использованы непосредственно. Объектом называется определяемая в программе переменная, тип которой определен как класс структура Определение структуры Определение класса struct dat class dat Приватная часть int day,month,year int day,month,year public Публичная часть void SetDat int,int,int void SetDat int,int,int void SetDat char void SetDat char аа void main void main Опред-ние переменных a,b Опред-ние объектов a,b класса dat dat a,b dat a,b a.day 5 Непосредственное использование a.month 12 приватной части объекта запрещено bAA.SetDat 12,12,1990 b.Setdat 12,12,1990 Приватная часть класса не обязательно должна следовать в начале определения класса. Для ее обозначения в произвольном месте определения класса можно использовать служебное слово private. Tаким образом в первом приближении класс отличается от структуры четко определенным интерфейсом доступа к его элементам. Объекты класса обладают всеми свойствами переменных, в том числе такими, как область действия и класс памяти время жизни . Последнее свойство наиболее интересно, так как процессы создания и уничтожения объектов класса могут сопровождаться вызовом функций конструктор и деструктор . Напомним, что по классам памяти и времени жизни в Си различаются переменные - статические внешние , создаваемые в статической памяти программы и существующие в течение всего времени работы программы - автоматические, создаваемые в стеке в момент вызова функции и уничтожаемые при ее завершении - динамические, создаваемые и уничтожаемые в свободной памяти задачи в моменты вызова функций malloc и free или выполнения операторов new и delete. Соответственно в программе возможно определение статических, автоматических и динамических объектов одного класса class dat dat a,b Статические объекты dat p Ссылка на объект void main dat c,d Автоматические объекты p new dat Динамический объект delete p Уничтожение динамического объекта Уничтожение автоматических объектов 3.2. Создание и уничтожение объектов. Конструкторы и деструкторы Процесс создания и уничтожения объектов класса ассоциируется при объектном программировании с созданием и уничтожением соответствующих им физических объектов. Поэтому с этими действиями необходимо связывать определяемые программистом функции для установки начальных значений, резервирования памяти и т.д Неявно вызываемые функции при создании и уничтожении объектов класса называются конструкторами и деструкторами. Они определяются как элементы-функции класса и имена их совпадают с именем класса. Конструкторов для данного класса может быть сколь угодно много, если они отличаются формальными параметрами, деструктор же всегда один и имеет имя имя класса . С процессом создания объектов связано понятие их инициализации. Инициализировать объекты обычным способом нельзя. Их инициализация осуществляется либо явным присваиванием копированием другого объекта, либо неявным вызовом конструктора.Если конструктор имеет формальные параметры, то в определении переменной после ее имени должны присутствовать в скобках значения фактических параметров. Момент вызова конструктора и деструктора определяется временем создания и уничтожения объектов - для статических объектов - конструктор вызывается перед входом в main , деструктор - после выхода из main . Конструкторы вызываются в порядке опредлеления объектов, деструкторы - в обратном порядке - для автоматических объектов - конструктор вызывается при входе в функцию блок , деструктор - при выходе из него - для динамических объектов - конструктор вызывается при выполнении оператора new, деструктор - при выполнении оператора delete. В Си возможно определение массива объектов класса. При этом конструктор и деструктор вызываются для каждого элемента массива и не должны иметь параметров. При выполнении оператора delete для ссылки на массив объектов необходимо также указывать его размерность.Конструктор для массива объектов должен быть без параметров. include stdio.h include dos.h static int days 0,31,28,31,30,31,30,31,31,30,31,30,31 class dat 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. Переопределение операторов. Напомним, что под классом понимается определяемый программистом тип данных, используемый наравне со стандартными базовыми типами. С точки зрения равноправия вновь вводимого типа данных желательно иметь возможность расширения переопределения операций языка, в которых один или несколько операндов могут быть объектами этого класса Это достигается введением элемента-функции специального вида, обращение к которой компилятор формирует при трансляции такой операции. Естественно, что такая функция должна иметь результат значение или неявная ссылка , если предполагается использование этой операции внутри другого выражения. Переопределение операций осуществляется в рамках стандартного синтаксиса языка Си, то есть обозначение операций и количество операндов остается прежним. Необходимо отметить также и тот факт, что для каждой комбинации типов операндов переопределяемой операции необходимо ввести отдельную функцию, то есть транслятор не может производить перестановку операндов местами, даже если базовая операция допускает это. Например, при переопределении операции сложения объекта класса dat с целым необходимо две функции dat int и int dat. Для переопределения операции используется особая форма элемента-функции с заголовком такого вида operator операция список параметров-операндов При этом имя функции состоит из ключевого слова operator и символа данной операции в синтаксисе языка Си. Список формальных параметров функции является списком операндов количество, типы, способы передачи операции. Результат функции тип, способ передачи является результатом переопределяемой операции. Способ передачи и тип указывают на возможности использования результата в других выражениях.Имеется два способа описания функции, соответствующей переопределяемой операции - если функция задается как обычная элемент-функция класса, то первым аргументом соответствующей операции является объект, ссылка на который передается неявным параметром this - если первым аргументом переопределяемой операции не является объект некоторого класса, либо функция получает на вход не ссылку на объект, а сам объект, тогда соответствующая элементфункция должна быть определена как дружественная с полным списком аргументов. Естественно, что полное имя дружественной функцииоператора не содержит при этом имени класса. В качестве примера рассмотрим доопределение стандартных операций над датами. include stdio.h include conio.h include dos.h static int days 0,31,28,31,30,31,30,31,31,30,31,30,31 class dat int day,month,year public void next Элемент-функция вычисления следующего для dat operator Операция dat operator int Операция дата целое с неявным операндом через this friend dat operator dat,int Операции с явной передачей friend dat operator int, dat всех параметров по значению dat Конструкторы dat int,int,int см. предыдущие примеры dat char dat Деструктор см. предыдущие примеры Функция вычисления следующего дня Используется ссылка на текущий объект this, который изменяетсмя в процессе операции void dat next day if day days month if month 2 day 29 year 4 0 return day 1 month if month 13 month 1 year Операция инкремента даты 1. Форма элемента-фукнции с неявным операндом по ссылке this 2. Возвращает копию входного объекта операнда до увеличения 3. Соответствует операции dat увеличение после использования 4. Замечание для унарных операций типа или использование их до или после операнда не имеет значения вызывается одна и та же функция . dat dat operator Создается временный объект dat x this В него копируется значение текущего объекта dat next Увеличивается значение текущего объекта return x Возвращается временный объект Операция дата целое 1. Элемент-функция с неявным первым аргументом по ссылке this 2. Входной объект не меняется, результат возвращается копией внутреннего автоматического объекта x dat dat operator int n dat x x this Копирование текущего объекта в x while n ! 0 x.next Вызов функции next для объекта x return x Возврат копии объекта x Операция дата целое 1. Дружественная элемент-функция с полным списком аргументов 2. Альтернативный вариант предыдущей функции 3. Первый операнд класса dat - передается по значению, поэтому может модифицироваться без изменения исходного объекта dat operator dat p,int n while n ! 0 p.next p Вызов функции next для объекта p return p Возврат копии объекта x Операция целое дата 1. Дружественная элемент-функция с полным списком аргументов 2. Второй операнд класса dat - передается по значению, поэтому может модифицироваться без изменения исходного объекта dat operator int n, dat p while n ! 0 p.next Вызов функции next для объекта p return p Возврат копии объекта p 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 clrscr e a d b 15 for i 0 i 10 i p i p i i delete 10 p Многие из переопределяемых операций представляют собой аналог некоторого конвейера, по которому продвигается объект в процессе обработки. Такие операции в качестве одного из операндов и в качестве результата имеют объект одного класса. Вышеприведенные операции с датами как раз являются таковыми. Но преобразование объекта может производиться в таких операциях двумя разными способами. При этом возникают некоторые особенности в вызове конструкторов и деструкторов, на которых необходимо остановиться. В рассмотренном выше примере одним из параметров элементафункции - объект класса dat передается по значению, аналогично передается и результат операции. Эти два объекта определены неявно и создаются путем копирования из других объектов. Поэтому конструкторы для них не вызываются. Однако при уничтожении этих объектов производится вызов деструкторов.Таким образом, при проектировании класса и передаче его объектов по значению необходимо учитывать эти лишние объекты и деструкторы. Перечислим возможные случаи возникновения и уничтожения объектов в функции - объект -формальный параметр - создается в стеке, его значение представляет собой копию соответствующего фактического параметра, соответственно, конструктор для него не вызывается - автоматический объект 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 - объект или переменная того класса или базового типа, которые являются входными в преобразовании типов, доступны через соответствующий формальный параметр, который может быть как значением копией объекта или переменной , так и неявной ссылкой. Значение переменной или элементов входного объекта могут использоваться как аргументы при преобразовании типов - для доступа из функции класса zzz к приватной части объекта класса xxx класс zzz должен быть объявлен дружественным в определении класса xxx . В качестве примера рассмотрим обратное преобразование базового типа long к типу dat - количество дней от начала летоисчисления преобразуется к дате. Здесь же рассмотрим другой класс - man, в котором одним из элементов приватной части является дата. static int days 0,31,28,31,30,31,30,31,31,30,31,30,31 class dat int day,month,year public dat long Преобразование long в dat dat man Преобразование man в dat dat Конструкторы dat int,int,int dat char dat Деструктор class man friend class dat Класс dat дружественен классу man int d,m,y Элемент дата в объекте класса man public man dat Преобразование dat в man man Конструктор man Деструктор Преобразование man в dat Используется ссылка на текущий объект this для выходного класса, формальный параметр - неявная ссылка - для входного класса void dat dat man p day p.d month p.m year p.y Преобразование long в dat Используется ссылка на текущий объект this для выходного класса формальный параметр типа long передается по значению void dat dat long p year p 365.25 Число лет с учетом високосных p p - year-1 365L - year 4 Остаток дней в текущем году year Начальный год - 0001 for month 1 p 0 month Вычитание дней по месяцам p - days month if month 2 year 4 0 p month Восстановление последнего p days month месяца if month 2 year 4 0 p day p 1 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.3 Переопределение операций new и delete Операции создания и уничтожения объектов в динамической памяти могут быть переопределены следующим образом void operator new size t size void operator delete void где void - ссылка на область памяти, выделяемую под объект, size - размер объекта в байтах. Переопределение этих операций позволяет написать собственное распределение памяти для объектов класса. 5.4 Переопределение операций Переопределение class one public typeout operator type1,type2 Вызов type1 a Вызов оператора совпадает с type2 b синтаксисом вызова функции one obj с именем данного объекта obj a,b эквивалентно obj.operator a,b Переопределение - class two public type Y class one two operator- Операция должна возвращать объект или two operator- или ссылку на объект класса two, в котором определен элемент Y Вызов one obj obj- Y эквивалентно obj.operator Y Переопределение используется для моделирования виртуальных массивов элементов определенного типа. class text page char page Массив ссылок на строки public int operator char Ассоциативный поиск индекса по строке char operator int Выделение строки по индексу 5.5 Переопределение операции копирования объектов Kaк известно, определение объекта класса в виде имя класса объект 2 объект 1 приводит к тому, что объект инициализируется путем побайтного копирования содержимого другого объекта без вызова конструктура. K таким же объектам относятся объекты - формальные параметры функций, которые инициализируются копиями фактических параметров. Eсли функция возвращает объект, то оператор return также выполняет копирование объекта - операнда в объект назначения. Taкое копирование не корректно в том случае, если объекты содержат ссылки на другие объекты или переменные в динамической памяти. В этом случае можно воспъльзоваться специальным конструктором копирования, параметром котрого является неявная ссылка на объект - источник, а this указывает на объект приемник. Будучи определенным, он вызывается во всех вышеперечисленных случаях копирования объектов один в другой. Пример корректного конструктора копирования для класса строк имеет вид class string char s Ссылка на строку int sz Длина строки public string string Конструктор копирования создает копию строки в динамической памяти для объекта - приемника string string string right s new char right- sz strcpy s,right- s Лекция 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 Из рассмотренных вариантов видно, что приватная часть базового класса недоступна в любом производном классе, что естественно следует из свойств закрытости определения класса. Однако по аналогии с дружественностью базовый класс может разрешить доступ к своим элементам личной части в производных классах. Это делается при помощи объявления защищенных protected элементов. Элемент с меткой protected в базовом классе входит в приватную часть базового класса. Кроме того, он доступен и в приватной части производного класса. Если же базовый класс включается в производный как public, то защищенный элемент становится защищенным и в производном классе, то есть может использоваться в последующих производных классах. Сказанное поясним примером и схемой class A int a1 Обычный приватный элемент protected int a2 Защищенный приватный элемент public class B A a1,a2 в приватной части B void x void B x a1 5 Ошибка a1 недоступен в B a2 3 a2 доступен в приватной части B class B public A a2 доступен и защищен в приватной части B, неявно имеет место protected int a2 class A class B privat privat A недоступен B protected class B A privat B доступен B class B public A public L protected B public 6.4 Ссылки на объекты базового и производного классов Из классического Си известно, что путем присваивания ссылкам различного типа одного и того же значения адреса можно работать с общей памятью как с различными структурами данных. При этом преобразование типа и присваивание не меняют значения ссылки, то есть адреса памяти. Применительно к базовому и производному классу можно сказать, что, преобразуя ссылку на объект производного класса к ссылке на объект базового класса, мы получаем доступ к вложенному объекту базового класса. Но при таком трактовании преобразования типа ссылки транслятору необходимо учитывать размещение объекта базового класса в производном, что он и делает. В результате при таком преобразовании присваивании значение ссылки адрес памяти может оказаться не равным исходному. Ввиду того, что такой переход от объекта производного класса к базовому часто встречается и корректируется транслятором, это преобразование типа ссылки в Си может бытьл выполнено неявно остальные преобразования типов ссылок должны быть явнями Побочный эффект такого преобразования состоит в том, что транслятор забывает об объекте производного класса и вместо переопределенных в нем функций вызывает функции базового класса. class A public void f1 class B A public void f1 Переопределена в классe B void f2 A pa B pb B x pa x Неявное преобразование ссылки на объект класса B в ссылку на объект класса A pa- f1 Вызов функции из вложенного объекта базового класса A f1 , хотя она переопределена Обратное преобразование от ссылки на базовый класс к ссылке на производный может быть сделано только явно. При этом корректность такого преобразования зависит от программы pb B pa Обратное преобразование - явное pb - f2 Корректно, если под pa был объект класса B 6.5 Принцип объектно-ориентированного программирования Понятие производного класса является основой объектноориенированного подхода к программированию, которое можно определить как программирование от класса к классу . Традиционное программирование от функции к функции предполагает, что вновь разрабатываемые структуры данных включают в себя определенные ранее, а новые функции включают вызовы ранее определенных. При разработке объектно-ориентированной программы программист создает производные классы, которые автоматически наследуют все свойства базовых, а затем переопределяет некоторые их функции и добавляет новые. В принципе ничто не препятствует на любом уровне разработки перейти к традиционному программированию и создавать линейную программу, используя объекты уже существующих классов. Следование же технологии объектно-ориентированного программирования до конца предполагает, что прикладная программа представляет собой класс самого верхнего уровня, в ее выполнение - создание объекта этого класса или выполнение для него некоторой функции типа run . Лекция 7. Виртуальные функции. 7.1 Понятие виртуальной функции Достаточно часто программисту требуется создавать структуры данных, включающих в себя переменное число объектов различных типов. Для представления их в программах используются списки или массивы ссылок на эти объекты. Объекты разных классов имеют соответственно различные типы ссылок, а для хранения в массиве или списке требуется один тип ссылок. Для преодоления этого противоречия все эти классы объектов требуется сделать производными от одного и того же базового класса, а при записи в массив преобразовывать ссылку на объект производного класса в ссылку на объект базового. p A1 -b -a b f L L C1 -c L -a c f L L A1 L -a a f L class a void f class b public a void f class c public a void f a A1 b B1 c C1 a p 3 Массив ссылок на объекты БК p 0 B1 Ссылки на объекты БК в p 1 C1 объектах ПК p 2 A1 Однако при таком преобразовании типа ссылка на объект ПК к типу ссылка на объект БК происходит потеря информации о том, какой объект производного класса окружает доступный через ссылку объект базового класса. Поэтому вместо переопределенных функций в производных классах будут вызываться функции в базовом, то есть p 0 - f Вызов a f 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 Отсюда следует определение виртуальной функции. Виртуальная функция ВФ - это функция, определяемая в базовом и наследуемая или переопределяемая в производных классах. При вызове ее по ссылке на объект базового класса происходит вызов той функции, которая соответствует классу объекта, включающему в себя данный объект базового класса. Таким образом, если при преобразовании типа ссылка на ПК к типу ссылка на БК происходит потеря информации об объекте производного класса, то при вызове виртуальной функции происходит обратный процесс неявного восстановления типа объекта. Реализация механизма виртуальных функций заключается в создании компилятором таблицы адресов виртуальных функций ссылок . Такая таблица создается для базового класса и для каждого включения базового класса в производный. В объ.


Не сдавайте скачаную работу преподавателю!
Данный конспект лекций Вы можете использовать для создания шпаргалок и подготовки к экзаменам.

Поделись с друзьями, за репост + 100 мильонов к студенческой карме :

Пишем конспект самостоятельно:
! Как написать конспект Как правильно подойти к написанию чтобы быстро и информативно все зафиксировать.