
- •1.1 Введение
- •1.2 Парадигмы программирования
- •1.2.1 Процедурное программирование
- •Void f ()
- •1.2.5 Объектно-ориентированное программирование
- •1.5.1 Механизм вызова
- •Virtual void rotate ( int );
- •1.5.3 Множественное наследование
- •1.6 Пределы совершенства
- •2.2 Имена
- •2.3.2 Неявное преобразование типа
- •2.4 Литералы
- •2.4.4 Строки
- •2.6. Экономия памяти
- •2.6.1 Поля
- •3.1.1 Анализатор
- •3.1.2 Функция ввода
- •Int main(int argc, char* argv[])
- •3.2 Сводка операций
- •3.2.3 Инкремент и декремент
- •3.2.5 Преобразование типа
- •3.2.6 Свободная память
- •Void generate(enode* n)
- •3.3.2 Оператор goto
- •4.1 Введение
- •4.3.1 Единственный заголовочный файл
- •Inline name* insert(const char* s) { return look(s,1); }
- •Int main(int argc, char* argv[]) { /* ... */ }
- •4.3.2 Множественные заголовочные файлы
- •Int main(int argc, char* argv[]) { /* ... */ }
- •4.4 Связывание с программами на других языках
- •4.6.3 Передача параметров
- •Inline. Удалите из файлов .C все описания внешних, а определения
- •5.1 Введение и краткий обзор
- •Int month, day, year;
- •Int month, day, year;
- •5.3.1 Альтернативные реализации
- •5.3.2 Законченный пример класса
- •Void task::shedule(int p) { /* ... */ }
- •5.5.3 Свободная память
- •Int no_of_members;
- •Int no_of_members;
- •5.5.5 Массивы объектов класса
- •6.1 Введение и краткий обзор
- •Void f()
- •Void g()
- •6.2.3 Иерархия классов
- •6.2.4 Поля типа
- •6.4.1 Монитор экрана
- •6.5 Множественное наследование
- •7.1 Введение
- •7.3 Пользовательские операции преобразования типа
- •7.3.2 Операции преобразования
- •7.3.3 Неоднозначности
- •7.5 Большие объекты
- •Inv() обращает саму матрицу m, а не возвращает новую, обратную m,
- •7.13 Предостережения
- •8.1 Введение
- •8.4.5 Введение операций с помощью параметров шаблонного класса
- •8.7.1 Задание реализации с помощью параметров шаблона
- •Void some_function()
- •V value;
- •9.1 Обработка ошибок
- •9.1.2 Другие точки зрения на особые ситуации
- •9.3.2 Производные особые ситуации
- •X(const char* X, const char* y)
- •9.4.2 Предостережения
- •9.4.3 Исчерпание ресурса
- •10.1 Введение
- •10.2 Вывод: То, что для прикладной программы представляется выводом,
- •10.2 Вывод
- •10.2.1 Вывод встроенных типов
- •10.4.1.2 Поля вывода
- •10.4.1.4 Вывод целых
- •10.5.1 Закрытие потоков
- •10.5.2 Строковые потоки
- •Int printf(const char* format, ...)
- •X Целый параметр выдается в шестнадцатеричной записи;
- •11.1 Введение
- •11.2 Цели и средства
- •11.3 Процесс развития
- •11.3.1 Цикл развития
- •11.3.2 Цели проектирования
- •11.3.3 Шаги проектирования
- •11.3.3.1 Шаг 1: определение классов
- •11.3.3.2 Шаг 2: определение набора операций
- •11.3.3.3 Шаг 3: указание зависимостей
- •11.3.3.4 Шаг 4: определение интерфейсов
- •11.3.3.5 Перестройка иерархии классов
- •11.3.3.6 Использование моделей
- •11.3.4 Эксперимент и анализ
- •11.3.5 Тестирование
- •11.3.6 Сопровождение
- •11.3.7 Эффективность
- •11.4 Управление проектом
- •11.4.1 Повторное использование
- •11.4.2 Размер
- •11.4.3 Человеческий фактор
- •11.5 Свод правил
- •11.6 Список литературы с комментариями
- •12.1 Проектирование и язык программирования.
- •12.1.1 Игнорирование классов
- •12.1.2 Игнорирование наследования
- •12.1.3 Игнорирование статического контроля типов
- •12.1.4 Гибридный проект
- •12.2.1 Что представляют классы?
- •12.2.2 Иерархии классов
- •Void f(Vehicle* p)
- •12.2.3 Зависимости в рамках иерархии классов.
- •Void f()
- •Void q(cfield* p)
- •12.2.6 Отношения использования
- •12.2.7 Отношения внутри класса
- •Void f(Rational r, Big_int I)
- •12.3 Компоненты
- •12.4 Интерфейсы и реализации
- •12.5 Свод правил
- •13.1 Введение
- •13.2 Конкретные типы
- •13.5.1 Информация о типе
- •13.6 Обширный интерфейс
- •Void put(const t*);
- •13.7 Каркас области приложения
- •13.8 Интерфейсные классы
- •13.10 Управление памятью
Void f ()
{
stack_id s1;
stack_id s2;
s1 = create_stack ( 200 );
// ошибка: забыли создать s2
push ( s1,'a' );
char c1 = pop ( s1 );
destroy_stack ( s2 ); // неприятная ошибка
// ошибка: забыли уничтожить s1
s1 = s2; // это присваивание является по сути
// присваиванием указателей,
// но здесь s2 используется после уничтожения
}
Иными словами, концепция модульности, поддерживающая парадигму упрятывания данных, не запрещает такой стиль программирования, но и не способствует ему.
В языках Ада, Clu, С++ и подобных им эта трудность преодолевается благодаря тому, что пользователю разрешается определять свои типы, которые трактуются в языке практически так же, как встроенные. Такие типы обычно называют абстрактными типами данных, хотя лучше, пожалуй, их называть просто пользовательскими. Более строгим определением абстрактных типов данных было бы их математическое определение. Если бы удалось его дать, то, что мы называем в программировании типами, было бы конкретным представлением действительно абстрактных сущностей. Как определить "более абстрактные" типы, показано в $$4.6. Парадигму же программирования можно выразить теперь так:
Определите, какие типы вам нужны; предоставьте полный набор операций для каждого типа.
Если нет необходимости в разных объектах одного типа, то стиль программирования, суть которого сводится к упрятыванию данных, и следование которому обеспечивается с помощью концепции модульности, вполне адекватен этой парадигме.
Арифметические типы, подобные типам рациональных и комплексных чисел, являются типичными примерами пользовательских типов:
class complex
{
double re, im;
public:
complex(double r, double i) { re=r; im=i; }
complex(double r) // преобразование float->complex
{ re=r; im=0; }
friend complex operator+(complex, complex);
friend complex operator-(complex, complex); // вычитание
friend complex operator-(complex) // унарный минус
friend complex operator*(complex, complex);
friend complex operator/(complex, complex);
// ...
};
Описание класса (т.е. определяемого пользователем типа) complex задает представление комплексного числа и набор операций с комплексными числами. Представление является частным (private): re и im доступны только для функций, указанных в описании класса complex. Подобные функции могут быть определены так:
complex operator + ( complex a1, complex a2 )
{
return complex ( a1.re + a2.re, a1.im + a2.im );
}
и использоваться следующим образом:
void f ()
{
complex a = 2.3;
complex b = 1 / a;
complex c = a + b * complex ( 1, 2.3 );
// ...
c = - ( a / b ) + 2;
}
Большинство модулей (хотя и не все) лучше определять как пользовательские типы.
1.2.4 Пределы абстракции данных
Абстрактный тип данных определяется как некий "черный ящик". После своего определения он по сути никак не взаимодействует с программой. Его никак нельзя приспособить для новых целей, не меняя определения. В этом смысле это негибкое решение. Пусть, например, нужно определить для графической системы тип shape (фигура). Пока считаем, что в системе могут быть такие фигуры: окружность (circle), треугольник (triangle) и квадрат (square). Пусть уже есть определения точки и цвета:
class point { /* ... */ };
class color { /* ... */ };
Тип shape можно определить следующим образом:
enum kind { circle, triangle, square };
class shape
{
point center;
color col;
kind k;
// представление фигуры
public:
point where () { return center; }
void move ( point to ) { center = to; draw (); }
void draw ();
void rotate ( int );
// еще некоторые операции
};
"Поле типа" k необходимо для того, чтобы такие операции, как draw () и rotate (), могли определять, с какой фигурой они имеют дело (в языках вроде Паскаля можно использовать для этого запись с вариантами, в которой k является полем-дескриминантом). Функцию draw () можно определить так:
void shape :: draw ()
{
switch ( k )
{
case circle:
// рисование окружности
break;
case triangle:
// рисование треугольника
break;
case square:
// рисование квадрата
break;
}
}
Это не функция, а кошмар. В ней нужно учесть все возможные фигуры, какие только есть. Поэтому она дополняется новыми операторами, как только в системе появляется новая фигура. Плохо то, что после определения новой фигуры нужно проверить и, возможно, изменить все старые операции класса. Поэтому, если вам недоступен исходный текст каждой операции класса, ввести новую фигуру в систему просто невозможно. Появление любой новой фигуры приводит к манипуляциям с текстом каждой существенной операции класса. Требуется достаточно высокая квалификация, чтобы справиться с этой задачей, но все равно могут появиться ошибки в уже отлаженных частях программы, работающих со старыми фигурами. Возможность выбора представления для конкретной фигуры сильно сужается, если требовать, чтобы все ее представления укладывались в уже заданный формат, специфицированный общим определением фигуры (т.е. определением типа shape).