Системы программирования



Скачать 467,17 Kb.
страница1/3
Дата17.06.2015
Размер467,17 Kb.
ТипДокументы
  1   2   3

Системы программирования.
Курс записан со слов Тамары Васильевны РУДЕНКО студентами 2 потока 2 курса 2005 года запуска. За ошибки дорогая редакция ответственности не несёт.
План курса:

1. Системы программирования.

2. Элементы теории трансляции.

3. Язык C++.


Рекомендуемая литература:

C++:


  • Б. Страуструп. ‘Язык C++. Специальное издание’. Бином, 2001.

  • Г. Шилдт. ‘Самоучитель C++’.

  • Ира Пол. ‘ООП на C++’.

  • Стандарт C++. ISO/IEC 14882. (1998)

  • Б. Страуструп. ‘Дизайн и эволюция языка C++’.

  • Г. Буч. ‘Об’ектно-ориентированный анализ и проектирование с примерами приложений на C++’. http://vmk.ugatu.ac.ru/book/buch/index.htm

  • Г. Буч, Д. Рэмбо, А. Джакобсон. ‘Язык UML. Руководство пользователя’. http://povt.zaural.ru/edocs/uml/content.htm

СП:

  • А.Ю. Молчанов. ‘Системное программное обеспечение’, 2003.

  • А.В. Гордеев, А.Ю. Молчанов. ‘Системное программное обеспечение’, 2001.

  • А. Ахо, Р. Сети, Дж. Ульман. ‘Компиляторы. Принципы, технологии, инструменты’.

  • А. Ахо, Дж. Ульман. “Green Dragon”. ‘Теория компиляции’.

  • Волкова И.А., Руденко Т.В. ‘Формальные грамматики и языки. Элементы теории трансляции’. 1999.

Материалы в поддержку лекций и практикума доступны на http://cmcmsu.no-ip.info/

Системы программирования. Вводные понятия.

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

Программа – текст на языке программирования, который его автор способен запустить на выполнение на ЭВМ в рамках некоторой конкретной среды, дать входные данные, получить результаты и понять их. Например, мы пишем программы в процессе обучения на ВМК.

Программный продукт – программа, которую пользователь может инсталлировать, задать ей входные данные, сам анализировать результаты, а так же сопровождать её, то есть программный продукт может жить без присутствия автора (отличительное свойство – отчуждаемость). Для этого программа должна быть хорошо документирована, тщательно протестирована, должна быть возможность настройки. Расходы при разработке программного продукта возрастают примерно в три раза относительно разработки программы.

Интегрированный программный продукт – программный продукт, который может быть интегрирован с другими программными продуктами в рамках некоторой программной системы. Интегрированные программные продукты одной системы, как правило, имеют похожий пользовательский интерфейс, средства обмена данными друг с другом. Возникает проблема совместного доступа к ресурсам (процессор, память).

Например, Microsoft Office. Приложения имеют похожие кнопочки и менюшки, могут обмениваться данными с помощью технологии OLE (Object Linking Environment).

При разработке интегрированного программного продукта расходы возрастают ещё в три раза.
Технологический цикл программного продукта.

Технологический цикл (жизненный цикл) – последовательность действий, выполняющихся при разработке программного продукта.

По статистике Standish Group, разработка программ не всегда заканчивается успешно. Было исследовано 300 компаний и 24 тысячи проектов. Только 26% из них были завершены в срок, бюджет не был превышен, вся функциональность была реализована. 46% были завершены не вовремя, бюджет был превышен, функциональность реализована не полностью. 28% проектов вообще не были завершены. Поэтому выбор технологии программирования – очень важная и нетривиальная задача. Он зависит от различных факторов: от специфики проекта, от компании-разработчика и т.д.



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

Этапы технологического цикла:



  1. анализ требований (требования заказчика уточняются, документируются, хотя бы частично формализуются. Для этого применяется простое текстовое описание, таблица решений, которая содержит условия на входные данные и эффекты на них, а также специальные языки спецификации, например Use case.)

  2. проектирование (как правило, используется стратегия divide & conquer, проектирование в большом и в малом. Сначала выявляется структура, потом уточняются элементы структуры. Применяется алгоритмическая или об’ектная декомпозиция. При первой процесс обработки данных разбивается на модули, в рамках каждого из которых выбираются некоторые алгоритмы. При второй предметная область разбивается на активные части – об’екты, которые могут взаимодействовать с другими об’ектами. Применяются диаграммы взаимодействия об’ектов, а также UML – Universal Markup Language.)

  3. кодирование (по описанной в спецификации функциональности и интерфейсам кодится модуль/класс)

  4. тестирование и отладка (тестирование – запуск программы на некоторых входных данных с целью обнаружить дефект – несоответствие ожидаемого результата и выданного программой. Отладка – обнаружение причины дефекта и её исправление. Различают тестирование методом белого ящика и методом чёрного ящика (когда известен исходный код, или, соответственно, нет). По цели различают функциональное, нагрузочное, стрессовое, регрессионное, адаптивное тестирование.)

  5. внедрение (перенос продукта на целевую систему с инструментальной)

  6. сопровождение (исправление ошибок, перевод в новую среду, добавление функциональности).

Этапы 1 – 4 относятся к непосредственной разработке продукта. Используют различные модели чередования этапов:

    • каскадная (устар.)



    • каскадно-возвратная (появляется возможность возврата в любой момент)



    • спиральная (итерационная) – сейчас наиболее распространена

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



Идеальная система программирования

Идеальная система программирования должна включать в себя некоторые составные части, поддерживающая технологический цикл.



1, 2. Анализ требований и проектирование:

    • средство для создания текстовых материалов и диаграмм

    • контролёр непротиворечивости (например, в таблице решений – неперекрываемость входных данных)

    • браузер, который показывает взаимодействие компонентов системы

    • средства вывода этой информации

    • репозиторий проекта – организованное хранилище всех этих материалов, для него должны быть обеспечены:

      • согласованность изменений (непротиворечивость дополнений по сравнению со старой архитектурой)

      • непротиворечивость

      • санкционированный доступ (разрешение прав группам – проектировщикам, которые могут менять структуру проекта, кодеры могут менять интерфейс об’ектов, которые они описывают, но только в разрешённых пределах)

      • контроль версий (конфигураций) (версии, зависимости, восстановление)

3. Кодирование:

  • частичная автоматизация генерации кода (например, генерация кода пользовательского интерфейса)

  • богатые библиотеки алгоритмов/классов (и программа-библиотекарь)

  • синтаксически-ориентированный редактор (хинты, хоткеи, выделение служебных слов, баланс скобок, дописывание идентификаторов)

  • транслятор (компилятор или интерпретатор) – это нечто, что может помочь человеку, написавшему программу на языке высокого уровня, обеспечить её выполнение в рамках некоторой среды

  • редактор связей (линкер) – программа, получающая на вход отдельные файлы с неразрешёнными внешними ссылками и увязывающая отдельные связи

  • макрогенератор

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

4. Тестирование и отладка:

  • отладчик (желательно общение программы с программистом не в терминах адресов, а в терминах программы – переменных, функций, об’ектов, т.е. использование не только адресно-кодового, но и символьного отладчик)

  • генератор тестов (по частично формализованной спецификации)

  • автоматизация прогона (полезно при регрессивном тестировании). По словам ТВ, в Microsoft проги тестируют автоматически по ночам. Я знал, что их пишут не для людей 

  • частичная автоматизация обработки результатов прогона

  • анализ тестового покрытия – проверка на соответствие выборки заявленному требованию (например, при тестировании методом белого ящика с проверкой всех операторов программы аналайзер проверит, прошлись ли мы по всем ветвям алгоритма)

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

  • средства заблаговременного планирования работ (составление списка задач, графика работ, распределение ответственности)

  • управление рисками – оценка степени рисков и решение проблем (менеджеры говорят, что проблемы возникают, как правило, не технические, а организационные: нестабильные требования заказчика, низкая вовлечённость высшего руководства, низкое привлечение пользователей)

  • координация и отслеживание плана работ

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

Близки к нашей идеальной системе программирования так называемые CASE-средства (Computer Aided Software Engineering). Там подразумевается:



  • наличие репозитория проекта

  • поддержка визуальных методов проектирования (мощные графические средства, язык моделирования и т.д.)

  • средства ведения документации

  • интеграция всех этих средств.

Реальные CASE-средства:

  • Rational Suite – очень дорогая система. Компания Rational предлагает не только инструментальные средства, но и методику использования – Rational Unified Process

  • Rational Rose (анализ и проектирование)

  • Rational Team Test (тестирование)

  • Rational Robot – запоминание переходов в пользовательском интерфейсе

  • ERwin от фирмы Computer Outsource Internal

  • Paradigm Plus от Platinum

  • Oracle Design от угадайте кого

Система программирования C в UNIX:

  • компиляторы

  • редакторы

  • отладчики

  • координатор make – программа, которая по описанию зависимостей между частями проекта и действий по их сборке (нужен более широкий термин!) обеспечивает перекомпиляцию только тех частей проекта, которые это требуют (которые были изменены); непосредственно компиляцией и сопутствующими действиями make не занимается, используя указанные ему средства

  • Source Code Control System (SCCS) – управление доступом и сохранение версий

  • lint анализирует код лучше, чем компилятор, но не создаёт об’ектного модуля

  • manual (man man)

  • форматёры nroof (визуализирует специально размеченный текст) и tbl (перевод в таблицу)

  • lex – генератор лексических анализаторов и yacc – генератор синтаксический анализаторов

ЧАСТЬ I

Системы программирования, поддерживающие об’ектно-ориентированную разработку. Принципы об’ектно-ориентированного программирования.

Проектирование (декомпозиция) – разбиение системы на подсистемы.

Нас интересует об’ектная декомпозиция, то есть статическая структура системы – об’екты и связи между ними, и динамическое поведение системы – обмен сообщениями между об’ектами. В C++ обмен сообщениями представлен набором методов об’екта, которые можно вызывать из других об’ектов.



Основные принципы об’ектной модели:

  • абстракция и инкапсуляция

  • наследование

  • полиморфизм

Абстракция – приём борьбы со сложностью. В предметной области необходимо выявить об’екты, выделить нужные нам свойства, то есть создать модель предметной области.

Инкапсуляция – средство, позволяющее спрятать реализацию и показывать пользователю только интерфейс абстракции (возможности взаимодействия с другими абстракциями).

Наследование – возможность выявить иерархию об’ектов, в рамках которой мы можем строить абстракции от общего к частному и использовать для частностей общие методы.

Полиморфизм различают статический, динамический и типовой (параметрический).

Статический полиморфизм – возможность иметь несколько функций с одинаковыми именами, но разными профилями (выбор осуществляется по списку параметров во время компиляции) – в C++ это overloading (перегрузка).

Динамический полиморфизм – перегрузка функций в рамках выстроенной иерархии и механизм виртуальных функций. Выбор осуществляется не на этапе компиляции, а во время выполнения.

Типовой (параметрический) полиморфизм (шаблоны функций/классов) – возможность настройки абстракции на конкретный тип данных. Обработка состоит из двух этапов: настройка на типовой параметр и вызов этой функции, уже настроенной на тип. Например, при описании класса, реализующего стек, мы не хотим привязываться к конкретному типу элементов, чтобы не реализовывать два разных стека для, скажем, целых и вещественных чисел.

Следует различать понятия об’ектного языка и об’ектно-ориентированного языка.



Об’ектный язык – язык, который поддерживает все принципы ООП за исключением наследования. Например, такими были языки Simula ’67, ADA’85.

Об’ектно-ориентированный язык – язык, поддерживающий все принципы ООП. Например, C++, Java, Eiffel, Object Pascal и другие.

Об’ектно-ориентированное проектирование.

Мы должны научиться выделять об’екты и конкретизировать их состояние и поведение. При анализе существительные переходят в об’екты. Например, при описании банкомата об’ектами будут сам банкомат, счёт, банк и т.д. При этом следует следить за отсутствием избыточности (грубо говоря, в списке об’ектов не должно быть синонимов). Свойства об’ектов не должны быть об’ектами, например, деньги – не самостоятельный об’ект, а свойство счёта. Операции с об’ектом также не являются об’ектами. Возникает проблема, на каком этапе надо задумываться об об’ектах, которых нет в предметной области, но которые нужны для реализации? Существуют разные мнения на эту тему: либо на этапе проектирования, либо отложить их до кодирования. Например, таким об’ектом является очередь клиентов при доступе к счетам.



Пример. Моделирование предметной области для банковской сети.

Выделяем следующие абстракции – об’екты предметной области:



  • баксомёт

  • клиент

  • карта

  • счёт

Диаграмма нарисована в стиле UML-карточки. Вверху пишется имя об’екта, во второй части его свойства – атрибуты, в третьей – методы – характеристики его поведения.

Далее нужно проанализировать инкапсуляцию. Обычно прячут всё, что возможно, так как другие об’екты не должны знать, как функционирует об’ект, а только его интерфейс. В большинстве случаев, свойства прячут, а методы открывают. В диаграмме ‘-‘ означает права доступа private, ’+’ – public, а ‘#’ – protected.

Если об’екты получаются из существительных, то их методы возникают из глаголов. Также, у любого об’екта есть конструктор или конструкторы и деструктор. Для скрытых свойств иногда нужны специальные методы – setters и getters, которые читают и записывают скрытые поля.

Некоторые особенности языка C++.

В C++, в отличие от C, в функцию можно передавать параметры по ссылке, примерно как в Pascal. Например, в C мы бы написали



void swap (int *x, int *y){

int t; t = *x; *x = *y; *y = t;

}

А в теле программы вызывали бы int a, b; ... swap (&a,&b);



А на плюсах можно написать и так:

void swap (int &x, int &y){

int t; t = x; x = y; y = t;

}

И тогда вызывать swap (a,b);



Для работы с динамической памятью в C использовались функции malloc, calloc, realloc и free. В C++ есть операции new и delete, которые призваны заменить их, хотя по-старому писать, конечно, тоже можно. Используют их примерно так:

int *p;

p = new int; // вместо p = (int*) malloc (sizeof(int))

*p = 5;

// работа с p



delete p;

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



int *p = new int[10]; //вместо p = (int*)malloc(10*sizeof(int))

// работа с p



delete[] p;

Классы, специальные функции-члены класса.

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



Конструктор класса – специальная функция-член класса, с помощью которой создаётся об’ект, имя которой совпадает с именем класса, и не возвращающая никакого значения. Любой класс содержит конструкторы, по дефолту есть два конструктора – умолчания и копирования.

Конструктор умолчания – конструктор вида X (); Впрочем, параметры могут присутствовать, но для них всех должны быть заданы значения по умолчанию, поэтому точное определение такое: конструктор, который может быть вызван без параметров. Если в классе явно определён хотя бы один конструктор, конструктор умолчания автоматически не создаётся!

Конструктор копирования – конструктор вида X (const X&); который служит для инициализации об’екта другим об’ектом. Дополнительно могут быть параметры кроме первого, но для них должны быть заданы умолчания. При неиспользовании const в определении типа аргумента могут возникнуть проблемы при инициализации об’екта определяемого класса константным об’ектом.

Обычные конструкторы – конструкторы, у которых есть параметры, не все из них имеют значения по умолчанию, и первый из них не const X&. Такой конструктор вызывается при создании об’екта (что-нить типа X a (3,5);) или при создании об’екта в динамической памяти (X *p; p = new X (3,5); - тут происходит сначала заказ памяти по new, а потом вызывается конструктор об’екта p*), а также в некоторых других случаях, которые будут рассмотрены позднее.

Деструктор класса – специальная функция-член класса, с помощью которой удаляется об’ект, имя которой отличается от имени класса на тильду вначале, не имеющая параметров и не возвращающая никакого значения. Любой класс содержит один деструктор. Если он не описан явно, то действует дефолтный деструктор:

~X () {}


Пример. Описание класса комплексных чисел.

Вывод на консоль обычно осуществляется в C++ через поток cout. Для его использования надо подключить заголовочный файл iostream.



class Complex{

double re, im;

public:

void print() const{ // печать числа в алгебраической форме

cout<

}

}



В секцию private попадают имена, которые не видны извне, соответственно, в секцию public – интерфейс класса. По умолчанию, сначала идут скрытые члены класса. Модификатор устанавливает права доступа вниз полям и методам до следующего модификатора или конца класса. В структурах, наоборот – по умолчанию идут общедоступные члены. В остальном, конструкции class и struct совпадают.

Создание об’екта:

Complex a;

Компилятор отведёт автоматическую память под об’ект, вызывается конструктор умолчания.

В об’явлении функции print() содержится слово const. Это значит, что функция не должна менять об’ект, который её вызвал. Только такие функции могут использоваться объектами класса, об’явленными константными.

void change(const double a) { re += a;}

Эту функцию мы не можем об’явить константной, так как она меняет поле класса.

Функции можно определять также и вне класса. Например, в классе мы напишем:

void print() const;

А вне его:



void Complex::print() const{ cout<Четвероточие – оператор разрешения контекста. Об’явление функций должно в обоих случаях совпадать, то есть тип параметров, тип возвращаемого значения и спецификаторы функции должны быть идентичны.

Зачастую определение функции выносится из класса для удобства. Но есть различия и для компилятора. Если функция описана в классе, то её вызов будет компилироваться не как вызов функции, а просто на место вызова функции подставится её код, настроенный на фактические параметры. В этом случае мы проигрываем по памяти, но выигрываем по скорости. Разумно использовать эту возможность для маленьких функций. Результат будет аналогичным, если дописать к определению функции слово inline, а её тело вынести за класс. Чего-то вроде outline, к сожалению, не существует

Описанный класс содержит требуемые данные, но косяк в том, что мы не можем задать или изменить значения полей re и im. Выход – описать в public-части конструкторы или сеттеры/геттеры. Опишем конструктор:

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

Теперь мы можем написать:

Complex a (3.5,2.7);

Можно описать ещё один конструктор для задания чисел вещественной оси:

Complex (double r) {re = r; im = 0;}

Он будет вызван, если вызвать конструктор с одним параметром.

Создание хотя бы одного конструктора отменяет действие дефолтного. То есть, xs не можем сейчас написать Complex a ();

Но проще вместо всего этого гемора из трёх конструкторов написать

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

При вызове конструктора с одним параметром он считается значением первого аргумента, второй определяется по умолчанию.



Конструктор копирования. Оператор присваивания.

1) Complex d = a;

2) Complex e = Complex (1,2);

В описанных примерах работает конструктор копирования. По умолчанию он будет описан так:

Complex (const Complex &a) {re = a.re; im = a.im;}

В первом случае произойдёт копирование полей об’екта a в поля d. Во втором случае сначала создаётся безымянный временный об’ект, проинициализированный требуемыми значениями, а потом его поля копируются в поля e.

В C для структур допустим оператор присваивания: b = c;

Присваивание определено и для классов по умолчанию как простое копирование полей:

Complex& operator= (const Complex &a)

{re = a.re; im = a.im; return *this;}

Разумеется, оператор присваивания может быть перегружен.

Функция возвращает ссылку на об’ект, чтобы пользователь мог писать цепочки присваиваний типа, как принято в C/C++:

x = y = z;

Возникает логичный вопрос, почему мы возвращаем параметр по ссылке? Рассмотрим, что будет происходить, если об’явить оператор как

Complex operator= (const Complex &a);

Тип возвращаемого результата Complex. Значит, вернуть мы должны об’ект, которому мы что-то присвоили. this определённая в любой нестатической функции класса переменная, которая всегда имеет значение ссылки на об’ект, от которого вызвана функция. Поэтому мы возвращаем *this. Допустим, что пользователь написал цепочку x = y = z; Тогда первое присваивание (правое) вернёт копию y. Потом работает конструктор копирования, получаем временный об’ект, который потом присваивается иксу (левое присваивание). Если же написать амперсанд, то мы вернём ссылку на игрек, который присвоится иксу без всяких временных об’ектов. Зачем платить больше?

Однако на этом отличия не заканчиваются. Подумаем, что произойдёт, если написать:

(x = y).change(2);

Компилятор это одобрит. Но в последнем случае функция вызовется от временного об’екта, который потом умрёт, и эффекта никакого мы не увидим. В первом случае мы возвращаем ссылку на об’ект x, который и ‘изменится’.

Рассмотрим применение описанных функций.



int main (){

Complex z (1.2,3.5); // y = 1.2 + i*3.5

Complex x (1.5); // x = 1.5

Complex y; // y = 0

const Complex t (5); // t – константа 5

Complex z1 = z; // конструктор копирования

Complex z2 = Complex (1,2); // создаётся временный об’ект

//Complex (1,2), потом вызывается конструктор копирования

// компиляторы часто оптимизируют и не создают вр. об’ект

// (стандартом это допускается; в то же время доступность

// конструктора копирования все равно проверяется)

z2 = z; // оператор присваивания

z = t; // для этого пишем const в параметре констр. копир-я

z2.operator= (t); // то же самое

Complex *p;

p = new Complex (1.5); // отводится память в куче

// и вызывается конструктор

/* работа с p */



delete p; // вызывается деструктор, память возвр. в кучу

Complex p[10]; // по адресу p выделяется память для 10

//об’ектов типа Complex. Работает конструктор умолчания

// если он не определён, ошибка!

//инициализировать массив конструкторами с параметрами,

// к сожалению, нельзя 



return 0;

}

Особенности работы с неплоскими классами.



Пример. Описание класса MFC-строк.

class String { // С-шная строка, с которой хранится её длина.

int size; // длина строки без ‘\0’

char *p; // указатель на строку

public:

String (const char *str){ // конструктор

p = new char [(size = strlen(str)) + 1];

strcpy (p, str);

} // теперь можно создавать строки: String a (“Hello!”);

~String () { delete[] p;}

Пусть теперь с классом работает некоторая функция:

void f(){

String s1 (“Hello!”);

String s2 = s1;

String s3 = String (“2007”);

s3 = s1;

}

Тут происходит поверхностное копирование конструкторами по умолчанию. Это значит, что копируются указатели, но не содержание строк. В этой ситуации s1.p и s2.p ссылаются на одну и ту же область памяти, а при присваивании в последней строчке мы оставляем в памяти мусор. Но самое интересное начнётся при выходе из функции, когда начнёт работать описанный нами деструктор. Он захочет три раза удалить память, занятую строчкой “Hello!”. Но двум смертям не бывать, получим runtime error.



Напишем специальные функции, поддерживающие глубинное копирование.

String (const String &a){

p = new char [(size = a.size) + 1];

strcpy (p, a.p);}

String& operator= (const String &a){

if (this == &a) return *this; // если s1 == s2

delete[] p; // освобождаем память для старой строки

p = new char [(size = a.size) + 1];

strcpy (p, a.p);

return *this;}



Абстрактные типы данных. Перегрузка операций.

Абстрактный тип данных (АТД) — это такой тип, который предоставляет для работы с элементами этого типа определённый набор функций, а также возможность создавать элементы этого типа при помощи специальных функций. Вся внутренняя структура такого типа спрятана от разработчика программного обеспечения — в этом и заключается суть абстракции. Абстрактный тип данных определяет набор независимых от конкретной реализации типа функций для оперирования его значениями. Например, можно определить абстрактные типы для стека, очереди, списка.

Перегрузка операций – описание своих операций (+, -, new и т.д.) вместо уже определённых.

Нельзя перегружать операции: ., ?:,::, .* , # , ## , sizeof, typeid.

Если мы перегружаем какой-нибудь значок, надо стараться, чтобы он определял похожую на обычный вкладываемый в него смысл сущность. Например, разумно использовать * для перемножения матриц или многочленов или для кон’юнкции.

К сожалению, мы не можем изменить арность операций, то есть бинарная операция всегда будет бинарной, а унарная – унарной. Также операции будут подчиняться законам старшинства, определённым раньше. Часто программисты сталкиваются с проблемой при перегрузке оператора ^ в качестве возведения в степень. От побитового сложения по модулю 2 нам достался приоритет, меньший, чем у сложения или умножения. Очевидно, это противоречит вкладываемому в него смыслу, и приходится использовать скобки.



Перегрузка бинарных операций.

Существует три возможности перегрузки операций:

1) написать функцию-член класса с одним параметром;

2) с помощью глобальной функции с двумя параметрами (в этом случае нам нужен доступ к скрытым полям класса с помощью сеттеров/геттеров, но так получается медленнее и вообще не используется);

3) функция-друг класса с двумя параметрами (если класс об’являет функцию своим другом, она может использовать все поля класса).

Пример. Продолжение описания класса комплексных чисел.

const Complex operator+ (const Complex &a) const{

Complex t (re + a.re, im + a.im);

return t;}

При использовании z = x + y, x – вызывающий об’ект, а y – параметр. Можно опустить const у параметра, но тогда нельзя будет складывать об’екты нашего класса с константами, то есть z = y + 5.

Почему мы возвращаем значение не по ссылке? Если бы мы действительно сделали так, мы вернули бы адрес локальной переменной, которая лежит в стеке, и её память освобождается после выхода из функции. Конечно, можно рассчитывать на то, что компилятор не заругается (зависит от реализации), и значение t не успеет затереться, просто переместится указатель стека. Но тогда, если мы напишем цепочку сложений, то при следующем вызове функции об’ект t скорее всего будет создан на том же месте, и тогда точно программа полетит. Не возвращайте локальные переменные по ссылке!

lvalue – переменная, которой возможно присваивание (она обладает адресом).

rvalue – кусок памяти, которому нельзя присваивать, например, константа или временный результат вычисления.

Самый первый const превращает полученный результат из lvalue в rvalue. Иначе можно будет вызвать, например (a+b).change(1), а это не по-C++ному.



int main (){

double t = 7.5;

Complex x(1,2), y(5), w;



const Complex z(1,3);

w = x + y; // равносильно w = x.operator+(y);

// или w.operator= (x.operator+(y));

w = x + z; // можно из-за const у параметра

w = z + x; // можно из-за const в конце

w = x + t; // преобразование типа double к const Complex

// w = x.operator+ (Complex(t)); т.к. есть Complex(double)

w = t + x; // ашыпка



return 0;}

Теперь рассмотрим перегрузку с помощью дружественной функции. Для этого в классе нужно написать:



friend const Complex operator+ (const Complex &a, const Complex &b);

Зона видимости функции совпадает с областью видимости класса. private: на неё не действует! Описание в классе или вне класса (тот же профиль без friend) приводит к одинаковому результату.



const Complex operator+ (const Complex &a, const Complex &b){

Complex t (a.re + b.re, a.im + b.im);



return t;}

int main(){

double t = 7.5;

Complex x(1,2), y(3), z;

z = x + y; // z = operator+ (x, y);

z = x + t; // z = operator+ (x, Complex(t));

z = t + x; // z = operator+ (t, Complex(x));

return 0;}

Так что, если мы хотим смешивать данные, лучше заводить друзей. ;)

Формально можно перегрузить оператор и в классе, и с помощью друга.

z = x + y; // неоднозначность - ошибка

z = x + t; // неоднозначность - ошибка

z = t + x; // друг работает



Перегрузка унарных операций.

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



const Complex operator- () const{

Complex t (-re,-im); return t;}



int main (){

double t = 7.5;

Complex x(1,2), y;

const Complex z (1);

y = -x; // y = x.operator-();

y = -z; // y = z.operator-();

-x = y; // низя, т.к. –x не l-value

x = -t; // x = Complex (-t); - здесь нет нашего отрицания

return 0;}

Друга пишут аналогично прошлому разу.



Особенности перегрузки операций ++ и --.

Как обычно, есть 3 варианта.

Чтобы отличать описание префиксной формы от описания постфиксной, договорились, что функция для префиксной формы описывается без формальных параметров, а для постфиксной – с одним параметром типа int.

const Complex& operator++ () // ++x;

{ ++re; ++im; return *this;}



const Complex operator++ (int notused) // x++;

{Complex t = *this;

++re; ++im;

return t;}

int main () {

Complex x(1,2); y;

y = ++x; // y = x.operator++();

y = x++; //y = x.operator++(0); -обычно там реально ноль

++ ++x; // нельзя, т.к. ++x не l-value

y = (x + y)++; // нельзя, так мы определили сложение



return 0;}

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



Виды отношений между классами.

Выделяют следующие темы межклассовых отношений:



  • ассоциация

  • наследование

  • агрегация

  • использование

  • инстанцирование (выделяют не всегда)

Ассоциация – тот факт, что пара об’ектов связана между собой. Связь не конкретизируется, обычно такая связь появляется в начале планирования, а потом уточняется и переходит в другие формы.

Далее будем приводить UML-подобные примеры, точнее, мы будем рисовать ER-диаграммы (Entity Relations), которые пришли из области баз данных.





Степень связи (мощность) показывает, сколько об’ектов с каждой стороны могут участвовать в связи. Обозначается спецификацией около карточки.

Возможные виды связей:



  • один к одному

  • один ко многим

  • многие ко многим



Наследование – взаимодействие, выражающее связь ‘частное - общее’ (‘is a‘), например, любое млекопитающее есть животное.

Общее считается базовым классом, а частное – производным. В другой терминологии, принятой, например, в Java, это суперкласс и подкласс. На диаграммах стрелочки рисуются от производного класса к базовому. Производный класс может расширить базовый класс, а может переопределить некоторые его члены.



Агрегация – соотношение ‘часть - целое’ (‘has a’) – ситуация, при которой об’ект одного класса внутри себя содержит об’екты другого класса.

Различают строгую и нестрогую агрегацию. Строгая агрегация (композиция) – агрегация, при которой вложенный об’ект не может исчезнуть, пока существует его хозяин. Тогда ромбик на диаграмме закрашивают.



class Triangle { ... Point v1, v2, v3; ... }

Нестрогая агрегация (часто просто агрегация) – часть может исчезнуть, пока существует целое, например, у треугольника можно отрезать вершину.  Тогда ромбик не закрашивают.



class Shareholder { ... Share *asserts; ... }

Возможно, share == NULL. Вот такой у нас безNULL. 



Использование (зависимость) возникает в трёх случаях:

  • ситуация, при которой имя класса используется в профиле функции-члена другого класса (тип параметров или возвращаемого значения);

  • функция-член одного класса для реализации использует локальную переменную типа – другого класса;

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

При этом используемый класс называется сервером, использующий – клиентом.



Инстанцирование – получение из некоторого абстрактного типа конкретного, из которого потом генерируются об’екты, то есть это связь шаблона класса и того, что генерируется по шаблону.



Единичное наследование в C++

Теперь можно рассказать о модификаторе доступа protected. Если имена попали в эту секцию, это значит, что вне класса они не видимы, так же, как private, однако они доступны в производных классах.



Пример. Сотрудники.

class Person { // человек

protected:

char *name;

int age;

char *address;

public:

Person (char *n, int a, char *addr);

~Person ();

char* getName () const { return name; }

void print () const { /* печать данных */ };

};

Person::Person (char *n, int a, char *addr){



name = new char [strlen(n) + 1];

strcpy (name, n); age = a;

addr = new char [strlen(addr) + 1];

strcpy (address, addr);

}

Person::~Person () {delete[] name; delete[] address;}



Опишем теперь класс-сотрудника. Наследование осуществляется с указанием одного из трёх модификаторов доступа, но, как привило, используется public. Конструкторы, деструкторы и операции присваивания не наследуются, поэтому в следующем коде мы унаследуем 3 поля и 2 метода.

class Employee: public Person { // работник

protected:

char *appointment;

int level;

double earnings;

public:

Employee (char *n, int a, char *addr,



char *app, int l, int ear); // ear – креатив ТВ 

~Employee();



void print () const;

};

Возникает вопрос, как повторно использовать код конструктора? А вот так:



Employee::Employee (char *n, int a, char *addr,

char *app, int l, int ear): Person (n, a, addr){

appointment = new char [strlen(app) + 1];

strcpy (appointment, app);

level = l; earnings = ear;

}

Сначала работает конструктор Person, а потом наш конструктор. Если не написать двоеточие с «вызовом» конструктора класса Person, будет вызываться конструктор умолчания для Person (а в данном случае мы получим ошибку компиляции). Опишем теперь деструктор, иначе будет работать дефолтный, а он нам не подходит.



Employee::~Employee {delete[] appointment;}

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

Оставим getName() такой, какая она была, а print() перегрузим. Можно считать базовый класс об’емлющей областью описания, а производный – внутренней. Таким образом, если описать функцию, которая была об’явлена в базовом классом, то функция из базового класса скрывается.

void Employee::print() const{

Person::print(); // вызываем print() базового

/* печать appointment, level и earnings */

}

int main (){

Person h (“Andrey Ivanov”, 20, “fds7”);

Employee e (“Boris I. Berezin”, 58, “635”,

“top-manager”, 12, 65536.00);

h.print(); // информация об Андрюхе – 3 поля

e.print(); // информация о БИБ – 6 полей

cout<

  1   2   3

Похожие:

Системы программирования iconУчебная программа курса или дисциплины «Основы программирования»
В частности, в курсе рассматриваются основные конструкции языков программирования, анализируются основные типы и структуры данных,...
Системы программирования iconПрограмма по предмету «Языки программирования» для 5, 6 классов
«Языки программирования» позволяет посредством формирования начальных навыков программирования подготовить платформу для изучения...
Системы программирования iconОписание реализованного в интерпретаторе языка программирования
Данный интерпретатор должен быть удобен в использовании как замена стандартному «Калькулятору» Windows, по крайней мере, для человека,...
Системы программирования icon1. Математические модели в экономике. Примеры: модели поведения потребителя и планирования производства в фирме
Общая задача нелинейного программирования. Задача нелинейного программирования и классическая задача условной оптимизации
Системы программирования iconРабочая программа по дисциплине «Технологии интернет-программирования» для студентов направления 552802 «Информационные системы и технологии»
Рабочая программа составлена в соответствии с требованиями Государственного образовательного стандарта
Системы программирования iconРабочая программа Теория и технология программирования
Теория и технология программирования. Рабочая программа. Задания на контрольные работы. Спб: сзгту, 2001, с
Системы программирования iconПрограмма вступительного испытания в магистратуру по направлению «Химическая технология и биотехнология»
Понятие о химической термодинамике. Термодинамическая система. Изолированная, открытая, закрытая системы. Свойства системы. Функции...
Системы программирования iconЛекция 1 (db l01. ppt) Введение в Автоматизированные информационные системы (аис) и Базы данных (БД). Определение бд и банков данных (БнД). Компоненты банка данных. Цели, задачи и структура курса
Примерами таких систем являются автоматизированные системы управления предприятием, банковские системы, системы резервирования и...
Системы программирования iconИнструкция по заполнению
Именно с целью оценки эффективности деятельности руководства структурными подразделениями с ноября 2003 г была развернута системы...
Системы программирования iconПрограмма учебной практики «Основы программирования» Направление подготовки

Разместите кнопку на своём сайте:
docs.likenul.com


База данных защищена авторским правом ©docs.likenul.com 2015
обратиться к администрации
docs.likenul.com