Logo GenDocs.ru

Поиск по сайту:  

Загрузка...

Лекции по технологии программирования - файл lekts_(tekh).doc


Лекции по технологии программирования
скачать (14.1 kb.)

Доступные файлы (1):

lekts_(tekh).doc67kb.15.09.2003 14:21скачать

содержание
Загрузка...

lekts_(tekh).doc

Реклама MarketGid:
Загрузка...
Лекции по технологии программирования.


1.1. Введение. 1

1.2. Парадигма программирования. 1

1.2.1. Процедурное программирование. 2

1.2.2. Модульное программирование 3

1.2.3. Абстракция данных. 3

1.2.4. Проблемы абстракции данных. 5

1.2.5. Объектно-ориентированное программирование. 6

1.1. Введение.


Цель разработки язык программирования С++.

  • улучшение С

  • поддержка механизма абстракции данных

  • поддержка объектно-ориентированного программирования
^

1.2. Парадигма программирования.


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

Язык поддерживает стиль программирования, если он предоставляет средства и делает удобным (т.е. довольно легким, безопасным и эффективным) использование этого стиля. Язык на самом деле не поддерживает парадигму программирования, если требуются исключительные усилия или выдающиеся способности для написания соответствующий программ; такой язык лишь дает возможность следовать парадигме. Например можно писать структурированные программы на Фортране и объектно-ориентированные программы на С, но это неизменно будет трудно (во втором случае особенно), поскольку Фортран и С не поддерживают эти парадигмы.

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

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

  1. Все средства должны быть искусно и элегантно встроены в язык.

  2. Должна присутствовать возможность использования этих средств в комбинации друг с другом.

  3. Число паразитных и предназначенных для специальных целей средств должно быть минимальным.

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

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

Язык С++ был разработан с тем, чтобы в дополнение к традиционной технологии программирования на С поддерживать абстракцию данных и объектно-ориентированное программирование при определяющей роли этих пяти ограничений. Однако это вовсе не означает, пользователям навязывается определенный стиль программирования.
^

1.2.1. Процедурное программирование.


Со времени возникновения программирования господствующей была следующая парадигма.

«Определите, какие процедуры вам нужны; используйте лучшие алгоритмы, которые только сможете найти».

В языках программирования эта парадигма поддерживается функциями, обеспечивающими передачу параметров и возврат значения. Этот стиль мышления отражен в литературе в форме дискуссий о том, каким способом передаются параметры, как различать различные виды параметров, различные виды функций (процедуры, подпрограммы, макроопределения) и т. п. Фортран – первый процедурный язык; Алгол60, Алгол68, Паскаль и С – более поздние изобретения, следующие той же традиции.

Типичный пример «хорошего стиля программирования» - функция для вычисления квадратного корня; если задан параметр-аргумент функции, то она выдаёт результат. Это осуществляется посредством очевидных математических вычислений.
double sqrt ( double arg)

{

// код для вычисления квадратного корня

}
void some_function ()

{

double root2 = sqrt(2);

// …

}
Функции вносят порядок в лабиринте алгоритмов; в этом и заключается их роль в деле организации программ.
^

1.2.2. Модульное программирование


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

«Определите какие модули вам нужны; расчлените программу так, чтобы данные были скрыты в модулях».

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

Модула-2 непосредственно поддерживает концепцию модульного программирования, тогда как С дает лишь возможность её использования.

Поскольку данные – это только один из элементов, которые мы стремимся «упрятать», то идея ограничения доступа к данным очевидным образом обобщается до концепции «скрытия информации», т.е. имена переменных, констант, функций и типов также можно сделать локализованным в модуле. Хотя разработка С++ не была специально ориентированна на поддержку модульного программирования, концепция класса в С++ в действительности поддерживает и модульность. Кроме этого, С++ конечно поддерживает модульность в смысле С, реализованную на базе механизма раздельной компиляции.
^

1.2.3. Абстракция данных.


Программирование с использованием с использованием модулей в конечном счете приводит к идее сосредоточить контроль над всеми данными одного типа в одном управляющем модуле. Если есть необходимость в двух стеках, то можно определить модуль управления стеком, интерфейс которого выглядит примерно так:
class stack_id {/* … */} // stack_id – это тип

// детали реализации на этом этапе не известны

stack_id create_stack (int size); // создает стек и возвращает его ид.

void push( stack_id, char);

char pop(stack_id);

destroy_stack(stack_id) // уничтожает стек
Это действительно значительное усовершенствование традиционного, не использующего классов, подхода, но, вместе с тем, «типы», построенные подобным способом, очевидным образом отличаются от основных, встроенных в язык, типов.

Во-первых, каждый модуль управления данными определенного типа должен иметь отдельный механизм для создания «переменных» этого типа.

Во-вторых, имена «переменных» такого типа неизвестно компилятору и среде программирования, и, наконец, такие «переменные» не подчиняются обычным правилам видимости и не передаются функциям подобно обычным параметрам.

Типы, создаваемые как модули, отличаются от основных типов в наиболее существенных аспектах и имеют более низкий уровень поддержки по сравнению с ними. Проблема заключается в том, что пользователь программирует в терминах небольших «дескрипторов объекта» (подобных stack_id в вышеприведенном примере), а не в терминах самих объектов. Это значит, что компилятор не сможет обнаружить «мелкие очевидные ошибки».

Например:

Void f()

{

stack_id s1;

stack_id s2;

s1 = create_stack(200);

// ошибка забыли создать S2
push(s1,’a’);

char c1 = pop(s1); // некрасивая запись
destroy_stack(s2);

// ошибка забыли уничтожить s2

s1 = s2; // фактически присваивается указатель, кроме того,

// s2, используется после уничтожения

}
Другими словами, концепция модуля, которая поддерживает сокрытие данных, не поддерживает механизмы абстракции данных в полном объёме.

Такие языки как Ада, Clu, C++, разрешают эту проблему, позволяя пользователю определять типы, возможности работы с которыми почти те же, что и у основных типов. Такие типы часто называют абстрактными типами данных, хотя термин «типы, определяемые пользователем» или «пользовательские типы» является более точным: обоснованное определение абстрактного типа данных должно содержать «абстрактные» математические спецификации. То, что в С++ называется типами, на самом деле представляет собой конкретное воплощение подобных более абстрактных «сущностей». Парадигма программирования снова трансформируется.

«Определите, какие типы вам понадобятся; заготовьте полный набор операций для каждого типа».

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

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

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

Большинство, но, к сожалению, не все модули представляются как типы, определяемые пользователем.
^

1.2.4. Проблемы абстракции данных.



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

class point{ /* … */} // класс точка

class color{/* … */} // класс цвет
Тогда shape можно описать следующим образом:

enum kind {circle, triangle, square}

// {окружность, треугольник, квадрат}
class shape

{

point center;

color col;

// …

public:

point where() {return center;}

void move(point to) {center = to; draw();}

virtual void draw();

virtual void rotate(int);

// другие операции

}
«Поле типа» - переменная К – необходима для того, чтобы в операциях, таких, как drow () и rotate (), было известно, с какой разновидностью фигуры они работают (в языках, подобных Паскалю, для этих целей можно применить запись из вариантов с использованием tag k). Функцию drow можно описать как:


void shape::draw()

{

switch (k)

{

case circle:

// изобразить окружность

break;

case triangle:

// изобразить треугольник

break;

case square:

// изобразить квадрат

break;

}

}

Это определенно недоразумение. Получается, что функции, подобные drow должны быть «информированы» о всех фигурах, которые только существуют. Код такой функции увеличивается в размерах по мере добавления в систему новых фигур. Если вы включаете в систему еще одну фигуру, то нужно проверить и, скорее всего, модифицировать каждую операцию, работающую с фигурами. Невозможно добавить новую фигуру в систему без доступа к исходному коду каждой операции. По сколько включению новых фигур подразумевает воздействие на код каждой существенной операции над фигурами, это потребует большой сноровки и будет служить потенциальным источником появления ошибок в коде, который относится к другим, более ранним, фигура. Выбор способа представления данных для конкретных фигур сильно ограничивается тем требованиям, что по крайне мере часть их данных должна помещена в ограниченный каркас, представленный описанием общего типа shape.
^

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


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

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

Class shape

{

point center;

color col;

// …

public:

point where() {return center;}

void move(point to) {center = to; draw();}

virtual void draw();

virtual void rotate(int);

// …

}

Функции, у которых известен интерфейс вызова, но реализация не может быть заданна в общем случае, а может быть определенна только для конкретных фигур, называются «виртуальными» (термин из Симулы и С++,т означающий, что функция «может быть переопределена позднее в производном классе»). После того, как определен класс shape, можжно написать общие функции для работы с фигурами:

void rotate_all(shape v[], int size, int angle)

// повернуть все элементы массива «v» имеющего «size» компонент,

// на угол «angle»

{

int i = 0;

while (i<size)

{

v[I].rotate(angle);

i = i + 1;

}

}

Чтобы определить конкретную фигуру, нужно указать, что она является фигурой, и дополнить описание конкретными свойствами (включая виртуальные функции):
class circle : public shape

{

int radius;

public:

void draw() {/* … */};

void rotate(int) {}

// именно так, функция с пустым телом

}

В С++ класс circle называется производным от класса shape, а класс shape – базовым для circle. В другой терминологии circle shape называются подклассом и суперклассом соответственно.

Парадигма программирования приобретает окончательный вид.

«Определите, какие классы вам нужны; заготовте полный набор операций для каждого класса; выразите общие свойства явным способом, используйте наследование»

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

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

а) разрабатывать как строительные блоки для других типов, и

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







Скачать файл (14.1 kb.)

Поиск по сайту:  

© gendocs.ru
При копировании укажите ссылку.
обратиться к администрации