авторефераты диссертаций БЕСПЛАТНАЯ БИБЛИОТЕКА РОССИИ

КОНФЕРЕНЦИИ, КНИГИ, ПОСОБИЯ, НАУЧНЫЕ ИЗДАНИЯ

<< ГЛАВНАЯ
АГРОИНЖЕНЕРИЯ
АСТРОНОМИЯ
БЕЗОПАСНОСТЬ
БИОЛОГИЯ
ЗЕМЛЯ
ИНФОРМАТИКА
ИСКУССТВОВЕДЕНИЕ
ИСТОРИЯ
КУЛЬТУРОЛОГИЯ
МАШИНОСТРОЕНИЕ
МЕДИЦИНА
МЕТАЛЛУРГИЯ
МЕХАНИКА
ПЕДАГОГИКА
ПОЛИТИКА
ПРИБОРОСТРОЕНИЕ
ПРОДОВОЛЬСТВИЕ
ПСИХОЛОГИЯ
РАДИОТЕХНИКА
СЕЛЬСКОЕ ХОЗЯЙСТВО
СОЦИОЛОГИЯ
СТРОИТЕЛЬСТВО
ТЕХНИЧЕСКИЕ НАУКИ
ТРАНСПОРТ
ФАРМАЦЕВТИКА
ФИЗИКА
ФИЗИОЛОГИЯ
ФИЛОЛОГИЯ
ФИЛОСОФИЯ
ХИМИЯ
ЭКОНОМИКА
ЭЛЕКТРОТЕХНИКА
ЭНЕРГЕТИКА
ЮРИСПРУДЕНЦИЯ
ЯЗЫКОЗНАНИЕ
РАЗНОЕ
КОНТАКТЫ


Pages:     | 1 |   ...   | 4 | 5 || 7 |

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

-- [ Страница 6 ] --

b) расстояние от выпуклой оболочки до заданной прямой;

c) расстояние от выпуклой оболочки до заданного отрезка;

d) расстояние от выпуклой оболочки до заданного треугольника;

e) расстояние от выпуклой оболочки до заданного стандартного прямо угольника;

f) среднее арифметическое расстояний от заданной точки до вершин вы пуклой оболочки;

g) сумму квадратов расстояний от заданной точки до вершин выпуклой оболочки;

h) сумму квадратов расстояний от начала координат до середин сторон выпуклой оболочки;

210 Глава III. Применение ООП к разработке программных проектов i) количество пар вершин выпуклой оболочки, расстояние между которы ми не превосходит единицу;

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

Задача 2.12. Модифицируйте текст эталонного проекта Выпуклая оболочка так, чтобы индуктивно определить:

a) находится ли единичная окружность с центром в начале координат внутри выпуклой оболочки;

b) количество вершин выпуклой оболочки, расположенных в кольце x2 + y 2 4;

c) количество вершин выпуклой оболочки, расстояние от которых до квад рата с вершинами (0, 0), (1, 0), (0, 1) и (1, 1) не превосходит единицу;

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

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

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

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

h) диаметр выпуклой оболочки;

диаметром d(M ) множества M называ ется точная верхняя граница расстояний между всевозможными точками множества:

d(M ) = sup |x 2 |;

1x x1,x2 M i) длину минимальной диагонали выпуклой оболочки;

j) координаты центра тяжести выпуклой оболочки с вершинами, массы mi которых вводятся вместе с их координатами;

x-координата центра тя жести определяется по формуле x i mi cx = ;

mi y-координата вычисляется аналогично.

Задача 2.13. Модифицируйте текст эталонного проекта Выпуклая оболочка так, чтобы индуктивно определить:

a) мощность множества точек пересечения границы выпуклой оболочки с окружностью x2 + y 2 = 1;

b) мощность множества точек пересечения границы выпуклой оболочки с кругом x2 + y 2 1;

c) мощность множества точек пересечения границы выпуклой оболочки с §2. Проект Выпуклая оболочка эллипсом x2 /a2 + y 2 /b2 = 1;

d) мощность множества точек пересечения границы выпуклой оболочки с гиперболой x2 /a2 y 2 /b2 = 1;

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

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

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

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

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

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

Задача 2.14. Модифицируйте текст эталонного проекта Выпуклая оболочка так, чтобы индуктивно определить:

a) площадь части выпуклой оболочки, расположенной в верхней полу плоскости;

b) периметр части выпуклой оболочки, расположенной в верхней полу плоскости;

c) площадь части выпуклой оболочки, расположенной в первом квадран те;

d) периметр части выпуклой оболочки, расположенной в первом квадран те;

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

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

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

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

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

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

Задача 2.15. Модифицируйте текст эталонного проекта Выпуклая оболочка так, чтобы индуктивно определить:

a) количество вершин выпуклой оболочки, лежащих внутри заданного 212 Глава III. Применение ООП к разработке программных проектов треугольника;

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

c) количество вершин выпуклой оболочки, лежащих внутри заданного эл липса x2 /a2 + y 2 /b2 = 1;

d) количество вершин выпуклой оболочки, лежащих вне заданного эллип са x2 /a2 + y 2 /b2 = 1;

e) количество вершин выпуклой оболочки, лежащих в 1-окрестности за данной прямой;

f) количество вершин выпуклой оболочки, лежащих вне 1-окрестности за данной прямой;

g) количество вершин выпуклой оболочки, лежащих в 1-окрестности за данного отрезка;

h) количество вершин выпуклой оболочки, лежащих вне 1-окрестности заданного отрезка;

i) количество вершин выпуклой оболочки, лежащих в 1-окрестности за данного заполненного треугольника;

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

Задача 2.16. Модифицируйте текст эталонного проекта Выпуклая оболочка так, чтобы индуктивно определить:

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

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

c) количество ребер выпуклой оболочки, целиком лежащих внутри задан ного эллипса x2 /a2 + y 2 /b2 = 1;

d) количество ребер выпуклой оболочки, целиком лежащих вне заданного эллипса x2 /a2 + y 2 /b2 = 1;

e) количество ребер выпуклой оболочки, целиком лежащих в 1 окрестности заданной прямой;

f) количество ребер выпуклой оболочки, целиком лежащих вне 1 окрестности заданной прямой;

g) количество ребер выпуклой оболочки, целиком лежащих в 1 окрестности заданного отрезка;

h) количество ребер выпуклой оболочки, целиком лежащих вне 1 окрестности заданного отрезка;

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

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

Задача 2.17. Модифицируйте текст эталонного проекта Выпуклая оболочка так, чтобы индуктивно определить:

a) угол, под которым выпуклая оболочка видна из начала координат;

b) угол, под которым видно из начала координат самое длинное ребро выпуклой оболочки;

c) количество всех острых внутренних углов выпуклой оболочки;

d) количество внутренних острых углов выпуклой оболочки, больших /4;

e) сумму всех внутренних углов выпуклой оболочки;

f) сумму внутренних углов выпуклой оболочки, величина которых не пре восходит /4;

g) сумму углов, под которыми ребра выпуклой оболочки пересекают за данную прямую;

h) сумму углов, под которыми ребра выпуклой оболочки пересекают за данный отрезок;

i) сумму углов, под которыми ребра выпуклой оболочки пересекают сто роны заданного стандартного прямоугольника;

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

Задача 2.18. Модифицируйте текст эталонного проекта Выпуклая оболочка, превратив его в аплет, так чтобы индуктивно определить и изобразить в окне размером 600x600:

a) пересечение границы выпуклой оболочки с полосой 1 y 1;

b) часть выпуклой оболочки, расположенную в кольце 1 x2 + y 2 4;

c) выпуклую оболочку с выделенными цветом ребрами, параллельными осям координат;

d) выпуклую оболочку и (другим цветом) ее ограничивающий прямо угольник;

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

f) выпуклую оболочку и (другим цветом) ее минимальную диагональ;

g) множество точек пересечения границы выпуклой оболочки с заданной прямой;

h) множество точек пересечения границы выпуклой оболочки с заданным отрезком;

i) множество точек пересечения границы выпуклой оболочки с заданным заполненным прямоугольником;

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

214 Глава III. Применение ООП к разработке программных проектов Задача 2.19. Модифицируйте текст эталонного проекта Выпуклая оболочка, превратив его в аплет, так чтобы индуктивно определить и изобразить в окне размером 600x600:

a) часть выпуклой оболочки, расположенную в верхней полуплоскости;

b) часть выпуклой оболочки, расположенную в первом квадранте;

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

d) часть выпуклой оболочки, расположенную внутри заданного треуголь ника;

e) часть выпуклой оболочки, расположенную внутри заданного круга;

f) часть выпуклой оболочки, расположенную внутри заданного эллипса x2 /a2 + y 2 /b2 = 1;

g) часть выпуклой оболочки, расположенную в 1-окрестности заданной прямой;

h) часть выпуклой оболочки, расположенную в 1-окрестности заданного отрезка;

i) часть выпуклой оболочки, расположенную в 1-окрестности заданного заполненного стандартного прямоугольника;

j) часть выпуклой оболочки, расположенную в 1-окрестности заданного заполненного стандартного треугольника.

7. Текст эталонного проекта.

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

Эталонный проект.

//Класс, описывающий точку (Point) на плоскости (R2).

class R2Point { private double x, y;

public R2Point(double x, double y) { this.x = x;

this.y = y;

} public R2Point() throws Exception { x = Xterm.inputDouble("x - ");

y = Xterm.inputDouble("y - ");

} public static double dist(R2Point a, R2Point b) { return Math.sqrt((a.x-b.x)*(a.x-b.x)+(a.y-b.y)*(a.y-b.y));

} public static double area(R2Point a, R2Point b, R2Point c) { return 0.5*((a.x-c.x)*(b.y-c.y)-(a.y-c.y)*(b.x-c.x));

} §2. Проект Выпуклая оболочка public static boolean equal(R2Point a, R2Point b) { return a.x==b.x && a.y==b.y;

} public static boolean isTriangle(R2Point a, R2Point b, R2Point c) { return area(a, b, c) != 0.0;

} public boolean inside(R2Point a, R2Point b) { return (a.x = x && x = b.x || a.x = x && x = b.x) && (a.y = y && y = b.y || a.y = y && y = b.y);

} public boolean light(R2Point a, R2Point b) { double s = area(a, b, this);

return s 0.0 || ( s == 0.0 && ! inside(a, b));

} } // Непрерывная реализация дека.

class Deq { private final static int DEFSIZE = 16;

private R2Point[] array;

private int size, head, tail;

private int forward(int index) { return ++index array.length ? index : 0;

} private int backward(int index) { return --index = 0 ? index : array.length - 1;

} public Deq(int size) { array = new R2Point[size];

this.size = head = 0;

tail = array.length - 1;

} public Deq() { this(DEFSIZE);

} public int length() { return size;

} public void pushFront(R2Point p) { array[head=backward(head)] = p;

size += 1;

} public void pushBack(R2Point p) { array[tail=forward(tail)] = p;

Глава III. Применение ООП к разработке программных проектов size += 1;

} public R2Point popFront() { R2Point p = front();

head = forward(head);

size -= 1;

return p;

} public R2Point popBack() { R2Point p = back();

tail = backward(tail);

size -= 1;

return p;

} public R2Point front() { return array[head];

} public R2Point back() { return array[tail];

} } // Интерфейс, задающий новый тип - фигуру.

interface Figure { public double perimeter();

public double area();

public Figure add(R2Point p);

} // Класс "нульугольник", реализующий интерфейс фигуры.

class Void implements Figure { public double perimeter() { return 0.0;

} public double area() { return 0.0;

} public Figure add(R2Point p) { return new Point(p);

} } // Класс "одноугольник", реализующий интерфейс фигуры.

class Point implements Figure { private R2Point p;

public Point(R2Point p) { §2. Проект Выпуклая оболочка this.p = p;

} public double perimeter() { return 0.0;

} public double area() { return 0.0;

} public Figure add(R2Point q) { if (!R2Point.equal(p,q)) return new Segment(p, q);

else return this;

} } // Класс "двуугольник", реализующий интерфейс фигуры.

class Segment implements Figure { private R2Point p, q;

public Segment(R2Point p, R2Point q) { this.p = p;

this.q = q;

} public double perimeter() { return 2.0 * R2Point.dist(p, q);

} public double area() { return 0.0;

} public Figure add(R2Point r) { if (R2Point.isTriangle(p, q, r)) return new Polygon(p, q, r);

if (q.inside(p, r)) q = r;

if (p.inside(r, q)) p = r;

return this;

} } // Класс "многоугольник", реализующий интерфейс фигуры.

class Polygon extends Deq implements Figure { private double s, p;

private void grow(R2Point a, R2Point b, R2Point t) { p -= R2Point.dist(a, b);

s += Math.abs(R2Point.area(a, b, t));

} public Polygon(R2Point a, R2Point b, R2Point c) { pushFront(b);

Глава III. Применение ООП к разработке программных проектов if (b.light(a, c)) { pushFront(a);

pushBack(c);

} else { pushFront(c);

pushBack(a);

} p = R2Point.dist(a, b) + R2Point.dist(b, c) + R2Point.dist(c, a);

s = Math.abs(R2Point.area(a, b, c));

} public double perimeter() { return p;

} public double area() { return s;

} public Figure add(R2Point t) { int i;

// Ищем освещенные ребра, просматривая их одно за другим.

for (i=length();

i0 && !t.light(back(),front());

i--) pushBack(popFront());

// УТВЕРЖДЕНИЕ: либо ребро [back(),front()] освещено из t, // либо освещенных ребер нет совсем.

if (i0) { R2Point x;

grow(back(), front(), t);

// Удаляем все освещенные ребра из начала дека.

for (x = popFront();

t.light(x, front());

x = popFront()) grow(x, front(), t );

pushFront(x);

// Удаляем все освещенные ребра из конца дека.

for (x = popBack();

t.light(back(), x);

x = popBack()) grow(back(), x, t);

pushBack(x);

// Завершаем обработку добавляемой точки.

p += R2Point.dist(back(), t) + R2Point.dist(t, front());

pushFront(t);

} return this;

} } // Класс "выпуклая оболочка".

class Convex { private Figure fig;

§3. Проект Компилятор формул public Convex() { fig = new Void();

} public void add(R2Point p) { fig = fig.add(p);

} public double area() { return fig.area();

} public double perimeter() { return fig.perimeter();

} } // Тест для выпуклой оболочки.

class ConvexTest { public static void main(String[] args) throws Exception { Convex convex = new Convex();

while (true) { convex.add(new R2Point());

Xterm.println("S = " + convex.area() + ", P = " + convex.perimeter());

} } } § 3. Проект Компилятор формул В данном параграфе изучаются простейшие вопросы теории компи ляции, рассматриваются языки простейших арифметических формул и стекового калькулятора, а также задача автоматического перевода с пер вого на второй.

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

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

Дополнительная информация о различных подходах к реализации компиляторов может быть найдена в книге [9].

220 Глава III. Применение ООП к разработке программных проектов 1. Стековый калькулятор. Стековый калькулятор, как это следует из его названия, представляет из себя некоторый объект, использующий хорошо известный нам контейнер стек. С формальной точки зрения стековый калькулятор это класс, реализующий следующий интерфейс.

Интерфейс стекового калькулятора.

interface StackCalc { // Добавить число в стек.

void push(int val);

// Сложить.

int add() throws Exception;

// Вычесть.

int sub() throws Exception;

// Умножить.

int mul() throws Exception;

// Разделить.

int div() throws Exception;

// Показать вершину стека.

int top() throws Exception;

} Методы push и top хорошо известны и комментариев не требуют, а семантика остальных четырех такова: из стека извлекаются (с помощью метода pop) два верхних числа, над ними выполняется указанная в на звании метода арифметическая операция, а результат кладется обратно в стек (с помощью метода push). При этом в качестве первого аргумен та арифметической операции берется тот из двух извлеченных элемен тов, который был положен в стек раньше другого. Если в момент вызова одного из этих четырех методов глубина стека меньше двух, возникает исключительная ситуация. При реализации данного интерфейса на базе ограниченного вектора выполнение метода push также может привести к исключительной ситуации, связанной с переполнением стека.

Стековый калькулятор можно использовать для вычисления значений различных арифметических выражений типа 5(7 + 8) + 25. Нужно только написать предварительно программу для вычисления значения.

С целью сокращения длины подобной программы будем записывать последовательность вызываемых методов в строку, разделяя их просто пробелом. Названия методов арифметических операций заменим на со ответствующие им знаки действий (+, -, * и /), вместо вызова метода push с аргументом val будем записывать только его аргумент, а завер шающий любую программу вызов метода top вообще включать в такую сокращенную запись программы не будем.

§3. Проект Компилятор формул 5 7 8 15 75 25 5 7 5 Рис. 17. Выполнение программы 5 7 8 + * 25 + С использованием этих сокращений программа для вычисления выра жения 5(7 + 8) + 25 примет вид 5 7 8 + * 25 +. Рисунок 17 показывает последовательность состояний стека стекового калькулятора при выпол нении этой программы.

В рассмотренном примере программа для стекового калькулятора бы ла написана по исходной формуле 5(7 + 8) + 25 нами. Основной задачей этого параграфа является реализация компилятора программы, ко торая сможет осуществлять преобразование формулы в программу для калькулятора автоматически.

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

Напомним некоторые важнейшие определения, связанные с языками и грамматиками, которые были рассмотрены в §3 первой главы.

Пусть некоторый алфавит, N метаалфавит, т.е. какой-то дру гой алфавит, не пересекающийся с ( N = ). Элементы метаалфа вита N называются метасимволами. Грамматикой G называется набор (, N, P, S), где множество символов, N множество метасимволов, множество правил вывода вида:, где N какой-то ме P тасимвол, ( N ) произвольная цепочка над объединением двух алфавитов, и для каждого N встречается хотя бы одно правило с в левой части (до стрелочки), а S N так называемый стартовый метасимвол.

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

222 Глава III. Применение ООП к разработке программных проектов Заметим, что если в цепочке встречается метасимвол, то ее можно преобразовать дальше, применив одно из правил грамматики с этим ме тасимволом в левой части. Если же метасимволов в цепочке не осталось, то процесс ее преобразования закончен и больше с цепочкой ничего сде лать нельзя. По этой причине обычные символы (из алфавита ) часто называют терминалами, а метасимволы (из N ) нетерминалами.

Языком L(G), порожденным грамматикой G, называется множество всех терминальных выводимых цепочек.

Для задания грамматики часто используют очень наглядную форму представления, называемую нормальной формой Бэкуса-Наура (НФБН).

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

Возьмем в качестве алфавита множество, состоящее из четырех зна ков арифметических операций (+,, и /) и 26-и идентификаторов от a до z, которыми будут обозначаться произвольные целые числа:

= {+,,, /, a, b,..., z}.

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

Для описания языка правильных программ для стекового калькуля тора можно воспользоваться следующей грамматикой GS.

e ee | ee+ | ee | ee/ | a | b |... | z Именно язык L(GS ) мы и будем рассматривать, как язык правильных программ для стекового калькулятора.

2. Грамматики языка правильных арифметических формул.

Разобравшись с языком программ стекового калькулятора, на который мы будем осуществлять перевод, попробуем теперь формально описать язык правильных арифметических формул, с которого этот перевод будет производиться. Для простоты ограничимся случаем, когда в формулах §3. Проект Компилятор формул x +y z x+y *z * Рис. 18. Деревья вывода формулы x + y z фигурируют только односимвольные переменные, запрещено использова ние унарных операций, а символ, обозначающий умножение, не может быть опущен.

В качестве алфавита возьмем то же самое множество, что и рань ше, состоящее из двух круглых скобок (открывающей и закрывающей), четырех знаков арифметических операций (+,, и /) и 26-и иденти фикаторов от a до z, которыми будут обозначаться произвольные целые числа:

= {(, ), +,,, /, a, b,..., z}.

В качестве метаалфавита рассмотрим множество из трех нетермина лов:

N = {,, }, где символ будет обозначать формулу, имя переменной, а ариф метическую операцию.

Множество правил P грамматики G1 = (, N, P, S) зададим так:

() | | a | b |... | z +|||/ Стартовым метасимволом этой грамматики является нетерминал, а примером вывода в ней может служить следующий вывод формулы x + y:

x x + x + y.

Для формулы x + y z существует два существенно различных множе ства эквивалентных между собой цепочек вывода, каждому из которых соответствует свое дерево вывода. Эти деревья изображены на рисунке и отличаются друг от друга порядком появления в формуле операций + 224 Глава III. Применение ООП к разработке программных проектов и. Хотя в результате различных выводов получается одна и та же фор мула, вычисления по ним дадут различные результаты. В одном случае выражение x + y z трактуется как (x + y) z, а в другом как x + (y z).

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

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

Множество нетерминалов для этой грамматики будет состоять из че тырех метасимволов F, T, M и V, обозначающих соответственно фор мулу, терм, множитель и имя переменной. Множество правил P грам матики G2 зададим так:

F T | T +F | T F T M | M T | M/T M (F ) | V V a | b |... | z По ряду причин чуть позже нам понадобятся другие грамматики рас сматриваемого языка.

Грамматика G0 отличается от только что рассмотренной G2 порядком следования нетерминалов в правой части первых двух правил:

F T | F +T | F T T M | T M | T /M M (F ) | V V a | b |... | z Важные свойства именно этой грамматики определяют ее обозначе ние. Именно грамматика G0 языка правильных арифметических формул будет использоваться в целом ряде последующих учебных курсов.

Еще один вариант грамматики (назовем ее G3 ) таков:

F T {+T } | T {T } T M {M } | M {/M } M (F ) | V V a | b |... | z Фигурные скобки в этой записи означают повторение фрагмента, в них стоящего, нуль или более раз. Таким образом, первое правило этой грамматики означает, что метасимвол F может быть преобразован в T, T + T, T T, T + T + T, T + T T, T T + T, T T T и т.д.

Докажите самостоятельно, что все приведенные выше грамматики за дают один и тот же язык правильных арифметических формул.

§3. Проект Компилятор формул 3. Рекурсивный компилятор формул. Как уже было отмечено в пре дыдущей секции, грамматика G1 не подходит для использования ее в ка честве базовой при реализации компилятора формул из-за отсутствия в ней учета приоритета операций. Поэтому поставим перед собой задачу ре ализовать компилятор, т.е. программу, осуществляющую автоматический перевод с сохранением семантики, с языка L(G2 ) на язык L(GS ) программ стекового калькулятора.

Задача 3.1. Напишите программу (компилятор формул), которая для любой правильной арифметической формулы (элемента языка L(G 2 )), данной ей на вход, выдает семантически эквивалентную ей программу стекового калькулятора (элемент языка L(GS )).

Сначала даже не ясно, как подступиться к такой задаче. Однако ее разрешимость сомнения вызывать не должна существуют же компиля торы с огромного множества языков программирования, включая язык Java!

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

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

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

Будем трактовать поставленную задачу следующим образом: реали зовать класс RecursCompf с методом compile, получающим в качестве аргумента исходную формулу (цепочку языка L(G2 )) в виде массива символов, который компилирует эту формулу и печатает получивший ся результат (цепочку языка L(GS ). Метод main, предназначенный для тестирования получившейся программы реализуем в отдельном классе RecursCompfTest. Для ввода/вывода информации будем использовать ме тоды класса Xterm, а весь исходный текст программы разместим в одном файле, который будет иметь имя RecursCompfTest.java.

После завершения работы над программой она должна вести себя при мерно так:

226 Глава III. Применение ООП к разработке программных проектов [roganov@msiu compf]$ java RecursCompfTest Введите формулу - a a Введите формулу - a-b ab Введите формулу - (a-b)*(a+b) ab-ab+* Приступим собственно к решению. Создадим отдельный метод для обработки каждого из четырех метасимволов грамматики G 2. Для ком пиляции формулы (метасимвола F ) будем использовать метод compileF, терма compileT, множителя compileM, а имени переменной метод compileV. Так как любая цепочка входного языка представляет из себя формулу, что соответствует метасимволу F грамматики G2, то ее компи ляция, выполняемая методом compile, должна сводится к вызову метода compileF.

В соответствии с первым правилом грамматики G2 формула всегда начинается с терма. Таким образом, первое действие, которое должен вы полнить метод compileF, это вызвать метод compileT. Опять таки в соответствии с грамматикой далее возможны три различных варианта:

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

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

Обратите внимание, что при такой реализации метод compileF не пы тается откомпилировать всю полученную методом compile формулу це ликом им обрабатывается только некоторая ее часть, соответствующая одному метасимволу F в процессе ее вывода.

Для того чтобы можно было реализовать описанную идею, необходи мо конкретизировать форму представления исходной формулы. Так как метод compile получает ее в виде массива символов str, вполне есте ственно сделать этот массив private-компонентой класса RecursCompf, доступной всем его методам и с помощью еще одной private-компоненты index отслеживать ту часть формулы, которая уже обработана. Обработ ка завершается, когда достигается конец формулы, длина которой равна str.length.

Этого вполне достаточно для реализации метода compileF:

private void compileF() { §3. Проект Компилятор формул compileT();

if (index = str.length) return;

if (str[index] == ’+’){ index++;

compileF();

Xterm.print("+ ");

return;

} if (str[index] == ’-’){ index++;

compileF();

Xterm.print("- ");

} } Обработка терма совершенно аналогична обработке формулы это следует из грамматик G2 и GS, поэтому метод compileT пишется мгно венно:

private void compileT() { compileM();

if (index = str.length) return;

if (str[index] == ’*’){ index++;

compileT();

Xterm.print("* ");

return;

} if (str[index] == ’/’){ index++;

compileT();

Xterm.print("/ ");

} } Множитель M в грамматике G2 является либо заключенной в скобки формулой, либо именем переменной. В первом случае необходимо пропу стить открывающую скобку, затем вызвать метод compileF и пропустить закрывающую скобку, а во втором достаточно просто вызвать метод об работки имени переменной:

private void compileM() { if (str[index] == ’(’) { index++;

compileF();

index++;

} else compileV();

228 Глава III. Применение ООП к разработке программных проектов } Последний из оставшихся методов является самым простым обра ботка имени переменной сводится к печати этого имени (и перемещению указателя index):

private void compileV() { Xterm.print("" + str[index++] + " ");

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

Потенциально опасными в данном случае являются методы compileF и compileT, которые являются рекурсивными. Перед каждым таким рекур сивным вызовом, однако, обработанная часть исходной формулы увели чивается (возрастает значение переменной index), что, в силу конечности длины исходной формулы, и гарантирует завершение работы программы в целом.

Сделаем одно небольшое чисто технологическое замечание. Хотя по строенная нами реализация и является достаточно простой, програм ма в целом использует три различных файла (RecursCompfTest.java, RecursCompf.java и Xterm.java), размещенных в двух директориях (ка талогах). Хорошим средством для автоматизации работы над сложными программными проектами является утилита make, которая позволяет зна чительно облегчить труд программиста в процессе написания и отладки большой программы (на любом языке).

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

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

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

§3. Проект Компилятор формул Вот полный текст управляющего файла, который используется в рас сматриваемом проекте:

Makefile.

# -*- mode: makefile -*.PHONY : run clean # Запустить тест рекурсивного компилятора формул.

run: RecursCompfTest.class java RecursCompfTest # Откомпилировать текст рекурсивного компилятора формул.

RecursCompfTest.class: RecursCompfTest.java RecursCompf.java \ Xterm.java javac RecursCompfTest.java # Удалить лишние файлы.

clean:

rm -f *.class *.expand Кроме описания трех целей (run, RecursCompfTest.class и clean) в нем содержится информация для редактора emacs о специальном режиме работы с этим файлом и указание для утилиты make, сообщающее, что цели run и clean относятся к разряду особых они не является име нами файлов, создаваемых при их построении. Символ \ в конце строки означает, что следующая строка файла должна рассматриваться, как про должение предыдущей.

Команда make, которая в данном случае эквивалентна make run, сна чала выяснит, нет ли уже построенной цели RecursCompfTest.class. Если она существует и времена модификации всех файлов, от которых зави сит эта цель (RecursCompfTest.java, RecursCompf.java и Xterm.java) не превосходят времени создания цели RecursCompfTest.class, то будет просто выполнена команда запуска java RecursCompfTest. Если же хотя бы один из файлов с исходными текстами был модифицирован, то про изойдет его перекомпиляция.

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

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

Глава III. Применение ООП к разработке программных проектов F T F T F M M T V M V V a bc Рис. 19. Дерево вывода формулы a b c И еще одно замечание. При компиляции формулы a b c получа ется результат a b c - -, что явно не верно!. Правильным результатом является a b - c -. Значит написанная нами программа ошибочна?

На самом деле программа написана абсолютно правильно, а причина неверной компиляции заключается в грамматике G2. Дело в том, что эта грамматика, верно отражая приоритеты арифметических операций, неяв но считает их все правоассоциативными, в то время как они являются на самом деле левоассоциативными. Это хорошо видно из рисунка 19, где изображено дерево вывода формулы a b c в грамматике G2.

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

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

§3. Проект Компилятор формул Такая реализация компилятора будет уже корректно обрабатывать все правильные арифметические формулы. Ее основной недостаток невоз можность простой модификации. Внесение даже небольших изменений во входной язык требует внесения глобальных изменений и переписыва ния значительной части программы. Примером подобного изменения мо жет быть повышение приоритета вычитания так, что формула a b c должна будет трактоваться, как a (b c).

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

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

Для доказательства этого факта применим отрицание критерия ин дуктивности. Результатом компиляции цепочек w1 = a b и w2 = (a b) является одна и та же цепочка a b -. Если дописать к обеим этим цепоч кам двухэлементную цепочку x = c (любая одноэлементная приводит к неправильной формуле), то результатом компиляции первой из них будет программа a b c * -, a второй a b - c *, т.е. (w1 x) = (w2 x).

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

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

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

Пусть сформулированное выше утверждение справедливо для любой формулы, число операций в которой не превосходит n. Рассмотрим про извольную формулу с n + 1 операцией. Выделим ту из этих операций, которая должна выполняться последней в соответствии с действующими приоритетами и ассоциативностью. Данная операция разделяет два фраг мента исходной формулы, в каждом из которых содержится не более, чем 232 Глава III. Применение ООП к разработке программных проектов по n операций. По предположению индукции каждый из них может быть откомпилирован с соблюдением двух сформулированных выше условий.

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

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

Требуемый нам класс Compf, содержащий метод compile, можно сде лать выведенным из класса Stack, являющегося непрерывной реализаци ей стека символов на базе вектора. Как это было объяснено выше, метод compile должен обеспечивать последовательную обработку всех символов исходной формулы, что будет осуществляться с помощью private-метода processSymbol.

В момент появления в формуле правой скобки необходимо выполнить все отложенные, но еще не выполненные операции, содержащиеся в стеке.

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

public void compile(char[] str) { processSymbol(’(’);

for(int i = 0;

i str.length;

i++) processSymbol(str[i]);

processSymbol(’)’);

Xterm.print("\n");

} Все возможные входные символы делятся (с помощью метода symType) на четыре категории: две скобки (SYM_LEFT и SYM_RIGHT), знаки операций (SYM_OPER) и все остальные (SYM_OTHER). К последним относятся, прежде всего, имена переменных.

Реализация метода processSymbol полностью соответствует проведен ному выше обсуждению компиляции с помощью стека:

§3. Проект Компилятор формул private void processSymbol(char c) { switch (symType(c)) { case SYM_LEFT:

push(c);

break;

case SYM_RIGHT:

processSuspendedSymbols(c);

pop();

break;

case SYM_OPER:

processSuspendedSymbols(c);

push(c);

break;

case SYM_OTHER:

nextOther(c);

break;

} } Открывающая скобка всегда заносится в стек отложен ных операций;

закрывающая скобка приводит к вызову метода processSuspendedSymbols, обрабатывающего отложенные символы, и удалению из стека парной ей открывающей скобки;

символ операции вызывает обработку отложенных символов и сам затем помещается в стек;

а имя переменной просто печатается с помощью методов nextOther и nextOper.

Метод processSuspendedSymbols обеспечивает обработку всех тех от ложенных операций, которые выполнимы в данный момент, что опреде ляется приоритетом операций (метод priority) и правилами предше ствования (метод precedes):

private void processSuspendedSymbols(char c) { while (precedes(top(), c)) nextOper(pop());

} private int priority(char c) { return c == ’+’ || c == ’-’ ? 1 : 2;

} private boolean precedes(char a, char b) { if(symType(a) == SYM_LEFT) return false;

if(symType(b) == SYM_RIGHT) return true;

return priority(a) = priority(b);

} protected void nextOper(char c) { Xterm.print("" + c + " ");

} Текст проекта целиком приведен в последней секции параграфа. Обра тите внимание, насколько эта реализация компилятора сложнее рекурсив ной. Однако она позволяет легко модифицировать ее при изменении вход ного языка. Например, изменение ассоциативности всех арифметических 234 Глава III. Применение ООП к разработке программных проектов операций требует только удаления одного символа: в методе precedes нужно = заменить на. Многие значительно более сложные задачи на модификацию также сводятся к минимальным изменениям в тексте про граммы и не требует изменения структуры всей реализации в целом.

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

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

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

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

В случае программ на языках C и C++ компилятор позволяет полу чить файл, который содержит машинные команды и, следовательно, мо жет быть выполнен непосредственно. Для языка Java компилятор строит так называемый байт-код, для исполнения которого необходима специ альная программа (запускаемая с помощью команды java).

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

§3. Проект Компилятор формул Таблица 1. Запись чисел в римской системе счисления 1 2 3 4 I II III IV V 6 7 8 9 VI VII VIII IX X 11 13 18 19 XI XIII XVIII XIX XXII 34 39 40 60 XXXIV XXXIX XL LX XCIX 200 438 649 999 CC CDXXXVIII DCXLIX CMXCIX MCCVII 2045 3555 3678 3900 MMXLV MMMDLV MMMDCLXXVIII MMMCM MMMCMXCIX В рассматриваемом нами случае языка правильных арифметических формул для реализации интерпретатора достаточно реализовать стеко вый калькулятор. Эту задачу мы сейчас и рассмотрим, предварительно обсудив запись чисел в различных системах счисления.

Выше уже отмечалось, что наряду с десятичной системой счисления в программировании часто используют двоичную, восьмеричную и шест надцатеричную. В целом ряде языков программирования принято согла шение, согласно которому числа, запись которых начинается с нуля, счи таются восьмеричными, а те, запись которых начинается с 0x или 0X, шестнадцатеричными. Таким образом, запись 0458 является некоррект ной (так как восьмеричная система счисления не содержит цифры 8), а в записи чисел, начинающихся с 0x или 0X, могут использоваться буквы от A до F, обозначающие шестнадцатеричные цифры со значениями от до 15.

Хорошо известным примером непозиционной системы счисления яв ляются римская. В этой системе цифры I, V, X, L, C, D и M всегда обозначают 1, 5, 10, 50, 100, 500 и 1000 соответственно, вне зависимости от позиции цифры в записи числа. При записи чисел в римской системе счисления значением числа является алгебраическая сумма цифр, в него входящих. При этом цифры в записи числа следуют, как правило, в по рядке убывания их значений, и не разрешается записывать рядом более трех одинаковых цифр. В том случае, когда за цифрой с бльшим зна о чением следует цифра с меньшим, ее вклад в значение числа в целом 236 Глава III. Применение ООП к разработке программных проектов является отрицательным. Таблица 1 содержит типичные примеры, ил люстрирующие общие правила записи чисел в римской система счисления (не превосходящих 3999).

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

Задача 3.2. Напишите программу (интерпретатор формул), кото рая для любой правильной арифметической формулы (элемента языка L(G0 )), содержащей цифры от 0 до 9, вычисляет и печатает значение этой формулы.

Для того чтобы реализовать стековый калькулятор, конечно, необхо дим стек целых чисел. Его непрерывная реализация будет осуществляться классом StackInt. Класс Calc, который будет осуществлять интерпрета цию арифметических формул, во многом похож на класс Compf. Одним из отличий является то, что в формуле присутствуют числа, а не иден тификаторы переменных. Вторым и более существенным выполнение операций вместо их печати в виде программы для стекового компилято ра.

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

Из вышесказанного следует, что класс Calc целесообразно сделать до черним по отношению к ранее нами созданному классы Compf, включив в него private-компоненту s (стек целых чисел) и переопределив методы symOther, nextOther и nextOper:


protected int symOther(char c) { if (c ’0’ || c ’9’) { Xterm.println("Недопустимый символ: " + c);

System.exit(0);

} return SYM_OTHER;

} protected void nextOper(char c) { int second = s.pop();

int first = s.pop();

switch (c) { case ’+’:

s.push(first + second);

break;

case ’-’:

§3. Проект Компилятор формул s.push(first - second);

break;

case ’*’:

s.push(first * second);

break;

case ’/’:

s.push(first / second);

break;

} } protected void nextOther(char c) { s.push(char2int(c));

} Метод char2int позволяет преобразовать символ в соответствующее ему целое число:

private static int char2int(char c) { return (int)c - (int)’0’;

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

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

6. Задачи для самостоятельного решения.

Задача 3.3. Докажите, что языки, порожденные двумя указанными грамматиками, совпадают:

a) G0 и G1 ;

b) G0 и G2 ;

c) G0 и G3.

Задача 3.4. Задайте с помощью грамматики следующие языки:

a) язык всех целых десятичных чисел;

b) язык всех целых неотрицательных восьмеричных чисел;

c) язык всех целых неотрицательных шестнадцатеричных чисел;

d) язык всех римских чисел от 1 до 3999;

e) язык всех допустимых идентификаторов в Java;

f) язык всех констант типа double в Java;

g) язык над алфавитом = {0, 1}, состоящий из всех цепочек, в которых четное количество нулей и нечетное количество единиц;

h) язык над алфавитом = {0, 1}, состоящий из всех цепочек, в которых количество нулей и единиц совпадают;

i) язык над алфавитом = {0, 1}, состоящий из всех цепочек, инвариант ных относительно операции инвертирования;

238 Глава III. Применение ООП к разработке программных проектов j) язык над алфавитом = {0, 1}, состоящий из всех цепочек, начинаю щихся с нуля и заканчивающихся нулем, в которых и количество нулей и количество единиц нечетны.

Задача 3.5. Модифицируйте текст эталонного проекта Рекурсивный компилятор формул так, чтобы:

a) все арифметические операции трактовались, как левоассоциативные;

b) приоритеты операций сложения и вычитания были выше, чем у опера ций умножения и деления;

c) в качестве имен переменных допускались произвольные идентифика торы языка Java;

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

e) компилировались формулы, содержащие операцию % (остаток от деле ния) с приоритетом, равным приоритету операций умножения и деления;

в язык стекового компилятора при этом также добавляется операция %;

f) компилировались формулы, содержащие пробелы и комментарии двух видов: /* */ и //;

g) компилировались формулы, запись которых состоит из нескольких строк;

h) компилировались формулы, содержащие унарные арифметические опе рации + и -;

в язык стекового компилятора при этом также добавляются операции + и -;

i) компилировались формулы, содержащие бинарные битовые операции | и &;

в язык стекового компилятора при этом также добавляются операции | и &;

j) компилировались формулы, содержащие унарную битовую операцию ^;

в язык стекового компилятора при этом также добавляется операция ^.

Задача 3.6. Модифицируйте текст эталонного проекта Рекурсивный компилятор формул так, чтобы:

a) формулы, содержащие только десятичные числа (до 3999), компили ровались в программы для стекового калькулятора, содержащие восьме ричные числа;

b) формулы, содержащие только десятичные числа (до 3999), компили ровались в программы для стекового калькулятора, содержащие шестна дцатеричные числа;

c) формулы, содержащие только десятичные числа (до 3999), компили ровались в программы для стекового калькулятора, содержащие римские числа;

§3. Проект Компилятор формул d) формулы, содержащие только восьмеричные числа (до 3999), компи лировались в программы для стекового калькулятора, содержащие деся тичные числа;

e) формулы, содержащие только восьмеричные числа (до 3999), компили ровались в программы для стекового калькулятора, содержащие римские числа;

f) формулы, содержащие только шестнадцатеричные числа (до 3999), ком пилировались в программы для стекового калькулятора, содержащие де сятичные числа;

g) формулы, содержащие только шестнадцатеричные числа (до 3999), компилировались в программы для стекового калькулятора, содержащие римские числа;

h) формулы, содержащие только римские числа (до 3999), компилирова лись в программы для стекового калькулятора, содержащие десятичные числа;

i) формулы, содержащие только римские числа (до 3999), компилирова лись в программы для стекового калькулятора, содержащие восьмерич ные числа;

j) формулы, содержащие только римские числа (до 3999), компилирова лись в программы для стекового калькулятора, содержащие шестнадца теричные числа.

Задача 3.7. Напишите программу (компилятор формул), которая для любой правильной арифметической формулы (элемента языка L(G 3 )), данной ей на вход, выдает семантически эквивалентную ей программу стекового калькулятора (элемент языка L(GS )).

Задача 3.8. Модифицируйте текст эталонного проекта Стековый компилятор формул так, чтобы:

a) деление трактовалось, как левоассоциативная операция;

b) приоритеты операций сложения и вычитания были выше, чем у опера ций умножения и деления;

c) в качестве имен переменных допускались произвольные идентифика торы языка Java;

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

e) компилировались формулы, содержащие операцию % (остаток от деле ния) с приоритетом, равным приоритету операций умножения и деления;

в язык стекового компилятора при этом также добавляется операция %;

f) компилировались формулы, содержащие пробелы и комментарии двух видов: /* */ и //;

g) компилировались формулы, запись которых состоит из нескольких 240 Глава III. Применение ООП к разработке программных проектов строк;

h) компилировались формулы, содержащие унарные арифметические опе рации + и -;

в язык стекового компилятора при этом также добавляются операции + и -;

i) компилировались формулы, содержащие бинарные битовые операции | и &;

в язык стекового компилятора при этом также добавляются операции | и &;

j) компилировались формулы, содержащие унарную битовую операцию ^;

в язык стекового компилятора при этом также добавляется операция ^.

Задача 3.9. Модифицируйте текст эталонного проекта Стековый компилятор формул так, чтобы:

a) формулы, содержащие только десятичные числа (до 3999), компили ровались в программы для стекового калькулятора, содержащие восьме ричные числа;

b) формулы, содержащие только десятичные числа (до 3999), компили ровались в программы для стекового калькулятора, содержащие шестна дцатеричные числа;

c) формулы, содержащие только десятичные числа (до 3999), компили ровались в программы для стекового калькулятора, содержащие римские числа;

d) формулы, содержащие только восьмеричные числа (до 3999), компи лировались в программы для стекового калькулятора, содержащие деся тичные числа;

e) формулы, содержащие только восьмеричные числа (до 3999), компили ровались в программы для стекового калькулятора, содержащие римские числа;

f) формулы, содержащие только шестнадцатеричные числа (до 3999), ком пилировались в программы для стекового калькулятора, содержащие де сятичные числа;

g) формулы, содержащие только шестнадцатеричные числа (до 3999), компилировались в программы для стекового калькулятора, содержащие римские числа;

h) формулы, содержащие только римские числа (до 3999), компилирова лись в программы для стекового калькулятора, содержащие десятичные числа;

i) формулы, содержащие только римские числа (до 3999), компилирова лись в программы для стекового калькулятора, содержащие восьмерич ные числа;

§3. Проект Компилятор формул j) формулы, содержащие только римские числа (до 3999), компилирова лись в программы для стекового калькулятора, содержащие шестнадца теричные числа.

Задача 3.10. Модифицируйте текст эталонного проекта Стековый компилятор формул так, чтобы:

a) формулы, содержащие унарные операции sin и cos, правильно компи лировались на язык стекового калькулятора, расширенный операциями S и C, которые вычисляют синус и косинус элемента, расположенного на вершине стека, размещая результат там же;

b) формулы, содержащие операцию возведения в степень ^, которая пра воассоциативна и имеет максимальный приоритет, правильно компилиро вались на язык стекового калькулятора, расширенный операцией ^;

c) формулы, содержащие операцию возведения в степень **, которая пра воассоциативна и имеет максимальный приоритет, правильно компилиро вались на язык стекового калькулятора, расширенный операцией ^;

d) для коммутативных операций аргументы в программе для стекового компилятора появлялись в алфавитном порядке;

e) формулы, содержащие квадратные скобки [], обозначающие удвоение выражения, в них стоящего, компилировались на язык стекового кальку лятора, расширенный операцией D (duplicate), которая извлекает верхний элемент из стека и записывает его обратно в стек дважды;


f) формулы, содержащие фигурные скобки {}, обозначающие возведение в квадрат выражения, в них стоящего, компилировались на язык стекового калькулятора, расширенный операцией D (duplicate), которая извлекает верхний элемент из стека и записывает его обратно в стек дважды;

g) перед обработкой каждого символа формулы печаталась ее откомпи лированную часть, содержимое стека отложенных операций и необрабо танную еще часть формулы;

h) формулы, содержащие переменную a, значение которой следует счи тать равным нулю, компилировались в оптимизированные формулы, не содержащие лишних сложений и вычитаний;

i) формулы, содержащие переменную b, значение которой следует счи тать равным единице, компилировались в оптимизированные формулы, не содержащие лишних умножений и делений;

j) при компиляции неправильных формул выдавалась диагностика об ошибке и корректная часть исходной формулы.

Задача 3.11. Модифицируйте текст эталонного проекта Стековый компилятор формул так, чтобы:

a) формулы, содержащие переменные a и b, значение которых следует считать равным двойке, компилировались в оптимизированные формулы, 242 Глава III. Применение ООП к разработке программных проектов в которых умножение заменено сложением;

b) результатом его работы была формула на входном языке, из которой удалены все лишние скобки;

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

d) результатом его работы было дерево вывода исходной формулы.

Задача 3.12. Модифицируйте текст эталонного проекта Интерпре татор формул так, чтобы:

a) деление трактовалось, как левоассоциативная операция;

b) приоритеты операций сложения и вычитания были выше, чем у опера ций умножения и деления;

c) в качестве аргументов допускались произвольные целые неотрицатель ные числа;

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

e) вычислялись значения выражений, содержащих операцию % (остаток от деления), с приоритетом, равным операциям умножения и деления;

f) вычислялись значения выражений, содержащих пробелы и коммента рии двух видов: /* */ и //;

g) вычислялись значения выражений, запись которых состоит из несколь ких строк;

h) вычислялись значения выражений, содержащих унарные арифметиче ские операции + и -;

i) вычислялись значения выражений, содержащих бинарные битовые опе рации | и &;

j) вычислялись значения выражений, содержащих унарную битовую опе рацию ^.

Задача 3.13. Модифицируйте текст эталонного проекта Интерпре татор формул так, чтобы:

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

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

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

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

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

§3. Проект Компилятор формул f) вычислялись значения формул, содержащих только шестнадцатерич ные числа (до 3999), а результат печатался в виде десятичного числа;

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

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

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

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

Задача 3.14. Модифицируйте текст эталонного проекта Интерпре татор формул так, чтобы:

a) вычислялись значения формул, содержащих операцию возведения в степень ^, которая правоассоциативна и имеет максимальный приоритет;

b) вычислялись значения формул, содержащих операцию возведения в степень **, которая правоассоциативна и имеет максимальный приори тет;

c) вычислялись значения формул, содержащие квадратные скобки [], обо значающие удвоение выражения, в них стоящего;

d) вычислялись значения формул, содержащие фигурные скобки {}, обо значающие возведение в квадрат выражения, в них стоящего.

Задача 3.15. Модифицируйте текст эталонного проекта Стековый компилятор формул, превратив его в аплет, который:

a) строит график функции y = f (x), где формула f (x) вводится с клави атуры;

b) строит график функции, заданной в полярных координатах соотноше нием r = f (), где зависимость f () вводится с клавиатуры (для обозна чения переменной при этом следует использовать идентификатор t);

c) строит график функции, заданной параметрически соотношениями x = x(t), y = y(t), где зависимости x(t) и y(t) вводятся с клавиатуры.

7. Тексты эталонных проектов. Сначала приведем все исходные тек сты рекурсивного компилятора формул.

Makefile для рекурсивного компилятора формул.

# -*- mode: makefile -*.PHONY : run clean # Запустить тест рекурсивного компилятора формул.

run: RecursCompfTest.class java RecursCompfTest # Откомпилировать текст рекурсивного компилятора формул.

Глава III. Применение ООП к разработке программных проектов RecursCompfTest.class: RecursCompfTest.java RecursCompf.java \ Xterm.java javac RecursCompfTest.java # Удалить лишние файлы.

clean:

rm -f *.class *.expand Рекурсивный компилятор формул.

// Рекурсивный компилятор формул.

public class RecursCompf { private static final int DEFSIZE = 255;

private char[] str;

private int index;

private void compileF() { compileT();

if (index = str.length) return;

if (str[index] == ’+’){ index++;

compileF();

Xterm.print("+ ");

return;

} if (str[index] == ’-’){ index++;

compileF();

Xterm.print("- ");

} } private void compileT() { compileM();

if (index = str.length) return;

if (str[index] == ’*’){ index++;

compileT();

Xterm.print("* ");

return;

} if (str[index] == ’/’){ index++;

compileT();

Xterm.print("/ ");

} } private void compileM() { if (str[index] == ’(’) { index++;

compileF();

index++;

} else compileV();

} private void compileV() { Xterm.print("" + str[index++] + " ");

} public void RecursCompf() { §3. Проект Компилятор формул str = new char[DEFSIZE];

} public void compile(char[] str) { this.str = str;

index = 0;

compileF();

Xterm.print("\n");

} } Тест для рекурсивного компилятора формул.

// Тест для рекурсивного компилятора формул.

public class RecursCompfTest { public static void main(String[] args) throws Exception { RecursCompf c = new RecursCompf();

while (true) c.compile(Xterm.inputChars("Введите формулу - "));

} } Теперь приведем все исходные тексты, относящиеся к стековому ком пилятору формул и интерпретатору.

Makefile для стекового компилятора и интерпретатора фор мул.

# -*- mode: makefile -*.PHONY : compf calc clean # Запустить тест стекового компилятора формул.

compf: CompfTest.class java CompfTest # Откомпилировать текст стекового компилятора формул.

CompfTest.class: CompfTest.java Compf.java Xterm.java javac CompfTest.java # Запустить тест калькулятора формул.

calc: CalcTest.class java CalcTest # Откомпилировать текст калькулятора формул.

CalcTest.class: CalcTest.java Calc.java Compf.java Xterm.java javac CalcTest.java # Удалить лишние файлы.

clean:

rm -f *.class *.expand Стековый компилятор формул.

// Непрерывная реализация стека символов.

class Stack { private static final int DEFSIZE = 16;

private char[] array;

private int head;

public Stack() { Глава III. Применение ООП к разработке программных проектов array = new char[DEFSIZE];

head = 0;

} public final void push(char c) { array[head++] = c;

} public final char pop() { return array[--head];

} public final char top() { return array[head-1];

} } // Стековый компилятор формул.

public class Compf extends Stack { // Типы символов (скобки, знаки операции, иное).

protected final static int SYM_LEFT = 0, SYM_RIGHT = 1, SYM_OPER = 2, SYM_OTHER = 3;

private int symType(char c) { switch (c) { case ’(’:

return SYM_LEFT;

case ’)’:

return SYM_RIGHT;

case ’+’: case ’-’: case ’*’: case ’/’:

return SYM_OPER;

default:

return symOther(c);

} } private void processSymbol(char c) { switch (symType(c)) { case SYM_LEFT:

push(c);

break;

case SYM_RIGHT:

processSuspendedSymbols(c);

pop();

break;

case SYM_OPER:

processSuspendedSymbols(c);

push(c);

break;

case SYM_OTHER:

nextOther(c);

break;

} } private void processSuspendedSymbols(char c) { while (precedes(top(), c)) nextOper(pop());

} private int priority(char c) { return c == ’+’ || c == ’-’ ? 1 : 2;

} private boolean precedes(char a, char b) { if(symType(a) == SYM_LEFT) return false;

if(symType(b) == SYM_RIGHT) return true;

return priority(a) = priority(b);

} protected int symOther(char c) { §3. Проект Компилятор формул if (c ’a’ || c ’z’) { Xterm.println("Недопустимый символ: " + c);

System.exit(0);

} return SYM_OTHER;

} protected void nextOper(char c) { Xterm.print("" + c + " ");

} protected void nextOther(char c) { nextOper(c);

} public void compile(char[] str) { processSymbol(’(’);

for(int i = 0;

i str.length;

i++) processSymbol(str[i]);

processSymbol(’)’);

Xterm.print("\n");

} } Тест для стекового компилятора формул.

// Тест для компилятора формул.

public class CompfTest { public static void main(String[] args) throws Exception { Compf c = new Compf();

while (true) c.compile(Xterm.inputChars("Введите формулу - "));

} } Интерпретатор формул.

// Непрерывная реализация стека целых чисел.

class StackInt { private static final int DEFSIZE = 16;

private int[] array;

private int head;

public StackInt() { array = new int[DEFSIZE];

head = 0;

} public final void push(int val) { array[head++] = val;

} public final int pop() { return array[--head];

} public final int top() { return array[head-1];

} } // Калькулятор арифметических формул.

public class Calc extends Compf { private StackInt s;

private static int char2int(char c) { Глава III. Применение ООП к разработке программных проектов return (int)c - (int)’0’;

} protected int symOther(char c) { if (c ’0’ || c ’9’) { Xterm.println("Недопустимый символ: " + c);

System.exit(0);

} return SYM_OTHER;

} protected void nextOper(char c) { int second = s.pop();

int first = s.pop();

switch (c) { case ’+’:

s.push(first + second);

break;

case ’-’:

s.push(first - second);

break;

case ’*’:

s.push(first * second);

break;

case ’/’:

s.push(first / second);

break;

} } protected void nextOther(char c) { s.push(char2int(c));

} public Calc() { s = new StackInt();

} public final void compile(char[] str) { super.compile(str);

Xterm.println("" + s.top());

} } Тест для интерпретатора формул.

// Тест для калькулятора формул.

public class CalcTest { public static void main(String[] args) throws Exception { Calc c = new Calc();

while (true) c.compile(Xterm.inputChars("Введите формулу - "));

} } § 4. Проект Изображение полиэдра Этот параграф посвящен рассмотрению еще одного проекта, который позволит познакомиться с простейшими задачами вычислительной гео метрии. Построение изображения полиэдра с удалением невидимых линий пример столь же классической задачи, как и рассмотренная в преды дущем параграфе задача компиляции. В процессе работы над проектом §4. Проект Изображение полиэдра Рис. 20. Пример полиэдра происходит также знакомство с графической библиотекой (пакетом) AWT и системой документирования программ javadoc.

В книгах [9] и [8] содержится большое количество дополнительной ин формации, связанной с рассматриваемой задачей и ее различными обоб щениями. Вопросы работы с пакетом AWT и системой документирования в языке Java более подробно могут быть изучены с помощью книг [11] и [10].

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

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

250 Глава III. Применение ООП к разработке программных проектов При изображении полиэдра реально рисуются не сами многоугольни ки, а линии, их ограничивающие, то есть их ребра. Если, однако, изобра зить все ребра полиэдра, то получится весьма запутанная, непривычная для человека картина. Для получения удобного для человека рисунка нужно при построении изображения учитывать, что некоторые ребра мо гут оказаться полностью или частично невидимыми, так как их могут загородить грани полиэдра. Таким образом, при построении изображе ния надо удалить (не рисовать) части ребер, которые не видны. По этой причине рассматриваемая задача в литературе часто называется задачей удаления невидимых линий.

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

В результате решения этой задачи полиэдр должен изображаться при мерно так, как это показано на рисунке 20.

Изображение полиэдра зависит от того, откуда на него посмотреть.

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

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

Такую уточненную постановку задачи иллюстрирует рисунок 21.

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

§4. Проект Изображение полиэдра  ¦!     "§ )(¦¦%¦¦§ #  ' &  §$  ¤¦¦(& © '!§!§ ¦¤¦¤  § ¦¦¤¦¦ © §§ Рис. 21. Изображение полиэдра Достаточно часто при задании полиэдра всю информацию о нем раз деляют на две части. Первая из них задает так называемый абстрактный полиэдр. При этом определяется только количество граней, ребер и вер шин в нем с указанием того, какие именно ребра принадлежат отдельным граням, а вершины ребрам. Вторая часть информации, называемая метрической, определяет координаты всех вершин полиэдра в простран стве.

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

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

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

8 6 -0.5 -0.5 0. -0.5 0.5 0. 0.5 0.5 0. 252 Глава III. Применение ООП к разработке программных проектов 0.5 -0.5 0. -0.5 -0.5 -0. -0.5 0.5 -0. 0.5 0.5 -0. 0.5 -0.5 -0. 4 1 2 3 4 5 6 2 4 3 2 6 4 3 7 8 4 1 4 8 4 8 7 6 Первая строка говорит о том, что у куба 8 вершин, 6 граней и 24 (!) ребра. Число 24, вдвое превышающее реальное количество ребер у куба, является результатом уже описанного выше эффекта, каждое из ребер куба принадлежит ровно двум граням.

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

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

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

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

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

public class R3Vector { private double x, y, z;

public R3Vector(double x, double y, double z) { this.x = x;

this.y = y;

this.z = z;

} public R3Vector() throws Exception { §4. Проект Изображение полиэдра x = Xterm.inputDouble("X-коорд. вектора проектирования - ");

y = Xterm.inputDouble("Y-коорд. вектора проектирования - ");

z = Xterm.inputDouble("Z-коорд. вектора проектирования - ");



Pages:     | 1 |   ...   | 4 | 5 || 7 |
 





 
© 2013 www.libed.ru - «Бесплатная библиотека научно-практических конференций»

Материалы этого сайта размещены для ознакомления, все права принадлежат их авторам.
Если Вы не согласны с тем, что Ваш материал размещён на этом сайте, пожалуйста, напишите нам, мы в течении 1-2 рабочих дней удалим его.