Чтение RSS
Рефераты:
 
Рефераты бесплатно
 

 

 

 

 

 

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

ВВЕДЕНИЕ В ОБЪЕКТНОЕ ПРОГРАММИРОВАНИЕ

Лекция 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 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. Обращения по адресу (неявная ссылка)

----------------------------------------

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

Си++ Эквивалент в "классическом" Си
------------------------ -----------------------------//-------------
-- Инициализация константой -----------------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 day++ x->day++; ----¬ стек +---+ return(x); г===== b ¦ a = *Inc(Inc(&b)); ¦ ¦ L---- +-¦-+ p = Inc(&b); ¦ ¦ ----¬ ¦return(x) a = *p; ¦ L-- a ¦

L---
Пример 3. Параметры - неявные ссылки
---------------------------------------------------------dat& Inc(dat& x) x.day++ неявная ссылка dat* px
{ x.day++ x.day++; ----¬ стек +---+ return(x); г===== b ¦ a = Inc(Inc(b)); ¦ ¦ L---- +-¦-+ p = &Inc(b); ¦ ¦ ----¬ ¦return(px) a = *p; ¦ 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 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);
}

Операторы имеют вид:

delete

2.6. Параметры функций по умолчанию

----------------------------------

При определении формальных параметров функции может быть указано его значение, принимаемое при вызове по умолчанию при отсутствии этого параметра в списке фактических:

//----- Функция устанавливает по умолчанию текущее значение года,
//----- месяца и дня
#include

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; // . *

// -> *
Эквивалентно 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; // Уничтожение динамического объекта
} %2d-%2d-%4dn",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; inext=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
#include
#include

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 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к известно, определение объекта класса в виде

=

приводит к тому, что объект инициализируется путем побайтного копирования содержимого другого объекта без вызова конструктура. 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 Производные классы

---------------------

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

Старый класс при этом называется базовым классом

 
     
Бесплатные рефераты
 
Банк рефератов
 
Бесплатные рефераты скачать
| Интенсификация изучения иностранного языка с использованием компьютерных технологий | Лыжный спорт | САИД Ахмад | экономическая дипломатия | Влияние экономической войны на глобальную экономику | экономическая война | экономическая война и дипломатия | Экономический шпионаж | АК Моор рефераты | АК Моор реферат | ноосфера ба забони точики | чесменское сражение | Закон всемирного тяготения | рефераты темы | иохан себастиян бах маълумот | Тарых | шерхо дар борат биология | скачать еротик китоб | Семетей | Караш | Influence of English in mass culture дипломная | Количественные отношения в английском языках | 6466 | чистонхои химия | Гунны | Чистон | Кус | кмс купить диплом о language:RU | купить диплом ргсу цена language:RU | куплю копии дипломов для сро language:RU
 
Рефераты Онлайн
 
Скачать реферат
 
 
 
 
  Все права защищены. Бесплатные рефераты и сочинения. Коллекция бесплатных рефератов! Коллекция рефератов!