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

ГЛАВА 5

Работа со списками.

Управляющие конструкции Автолиспа - ветвление

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

До некоторой степени список аналогичен массиву в языках типа Паскаля. Там доступ к конкретному элементу списка решается просто - указывается его номер: a[5]. В Лиспе все несколько сложнее.

Функция ( CAR l ) возвращает первый элемент списка l.

Например, ( CAR ( LIST 10 20 ) ) вернет 10.

Если список l является описанием координат точки, то ( CAR l ) возвращает координату X.

Название функции идет от первой, 1958 года, реализации языка Лисп на древнем компьютере, в работе которого немаловажную роль играл адресный регистр памяти (Contents 0f Address Register), сокращенно CAR. Дж. Маккарти назвал в честь этого регистра одну из функций языка Лисп.

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

( CDR ( LIST 10 20 ) ) возвращает ( 20 )

Названа функция также в честь одного из регистров древнего компьютера - Contents of Decrement Register.

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

( SETQ p ( LIST 10 20 ) ) ; координаты точки p

( SETQ y ( CAR ( CDR p ) ) )

Но такая запись выглядит весьма громоздко. Поэтому в Лиспе предусмотрена возможность использования вложенных функций CAR и CDR, которые будут называться соответственно CADR, CDAR, CAAR, CDDR и так далее (до четырех уровней вложенности). При этом (CADR l) эквивалента ( CAR ( CDR l ) ).

Последний элемент списка как атом возвращает функция ( LAST l ). В принципе ее можно использовать для получения координаты Y, но где гарантия, что в один прекрасный день пользователь не включит режим использования трехмерных точек? Тогда LAST станет возвращать уже координату Z и ваша программа тут же потеряет работоспособность.

И, наконец, самая общая функция выделения элементов из списка: ( NTH n l ), которая возвращает n-й элемент списка l. Название функции происходит от английского окончания порядковых числительных -th.

Нумерация элементов списка в функции NTH начинается с нуля!

Хуже того, в Лиспе нет единообразия в этом вопросе: часть функций все же уверена, что нумерация начинается с единицы. Такая путаница - один из наиболее крупных "проколов" Лиспа.

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

Таблица 5.1 - Получение координат точек.

Точка p

Координата Х

Координата Y

( CAR p )

( CADR p )

( NTH 0 p )

( NTH 1 p )

 

Разбор точек на координаты используется, если необходимо рассчитать положение точки через приращения. Например, при отрисовке фаски нужно узнать координаты точки P2, зная точку P1:

 

Рисунок 5.1 - Отрисовка фаски.

Хотя угол здесь задан в явном виде, использование функции POLAR будет некорректным: мы не знаем расстояние P1P2.

Если его высчитывать как гипотенузу прямоугольного треугольника, точность вычисления квадратного корня окажется ограниченной и мы не попадем в точку P2. Если бы мы рисовали фаску вручную, то мы бы ввели приращения по осям в виде @3,3 (т.е. переместиться от точки P1 вправо на 3мм и верх на 3 мм). Сделаем то же самое в Автолиспе:

( SETQ p2 ( LIST ( + ( CAR p1 ) 3 )

( + ( CADR p1 ) 3 ) ) )

Такая запись является стандартным способом расчета координат точек в приращениях.

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

Обратите внимание: для добавления в список атома его сначала нужно превратить список из одного элемента!

Пример: в переменной а хранится список вида ( 19 49 ). К нему нужно добавить число 20. Делается это так:

( SETQ a ( APPEND a ( LIST 20 ) ) )

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

И ветвление, и цикл в обязательном порядке содержат проверку условия. В качестве условий в Лиспе используются логические функции, возвращающие T (true - истина) или NIL (ложь): "<", ">", "<=", ">=", "=", "/=". Обратите внимание: операция "не равно" записывается не так, как в большинстве других языков программирования!

Функции сравнения могут применяться к целым и вещественным числам, а также текстовым строкам, но не к спискам.

Примеры:

( = 2 2 ) возвращает T

( = 2 5 ) возвращает NIL

( = "ABC" "AB" ) возвращает NIL

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

( SETQ a1 ( SIN 0.0 ) )

( SETQ a2 ( SIN ( * 2.0 PI ) ) )

Тогда ( = a1 a2 ) возвращает NIL, т.е. с точки зрения Лиспа , поскольку не равен точно нулю.

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

( EQUAL e1 e2 точность )

Здесь точность - число (0.1, 0.01..), указывающее, сколько знаков поле запятой принимается во внимание при сравнении выражений e1 и e2. Поэтому функция ( EQUAL ( SIN 0 )( SIN ( * PI 2 ) ) 0.1 ) вернет T.

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

Таблица 5.2 - Логические функции языка Автолисп.

X

Y

X AND Y

X OR Y

X XOR Y

NOT X

T

NIL

NIL

T

T

NIL

T

T

T

T

NIL

NIL

NIL

T

NIL

T

T

T

NIL

NIL

NIL

NIL

NIL

T

 

Запомнить эти функции легко:

AND - и X, и Y истинны, тогда X AND Y будет истинно;

OR - или X, или Y, или X и Y сразу истинны, тогда X OR Y будет истинно;

XOR - или X, или Y истинны по отдельности, но не оба сразу, тогда X XOR Y будет истинно;

NOT - просто "переворачивает", инвертирует значение, превращая T в NIL, а NIL в T.

Пример:

Если в переменных a, b, c хранятся длины сторон треугольника, то весьма желательно, чтобы они все были больше нуля. Тогда получим сложное условие:

( AND ( > a 0 ) ( > b 0 ) ( > c 0 ) )

Теперь мы готовы к рассмотрению функции IF, обеспечивающей ветвление в программе. Ее общий вид:

( IF c

f1

[f2]

)

(сразу будем использовать предложенную Н. Виртом запись с отступами)

Здесь с - условие (простое или сложное):, f1 - функция, выполняемая, если условие истинно (часть "то"), а f2 - функция, выполняемая, когда условие ложно (часть "иначе"), причем квадратные скобки говорят о том, что часть "иначе" может отсутствовать.

Простейший пример:

( IF ( < a 0 )

( PROMPT "\nПеременная a меньше нуля" )

( PROMPT "\n"Переменная a больше или равна нулю" )

)

А как быть, если в случае выполнения (или невыполнения) условия нужно выполнить не одну, а сразу несколько функций? Ведь синтаксис функции IF разрешает записать только одну. Проблема решается так же, как в языке Паскаль: там используются операторные скобки begin..end, а в Лиспе - функция ( PROGN f1 f2 .. fn ). Она всего лишь объединяет функции f1 f2 .. fn в один блок, который можно подставить в функцию IF.

Например, мы решаем квадратное уравнение и в переменную d записали дискриминант. Теперь нужно посчитать действительные корни и вывести их на экран:

( IF ( >= d 0 )

( PROGN

( SETQ x1 ( / ( + ( * b -1 ) ( SQRT d ) ) ( * 2 a ) )

( SETQ x2 ( / ( - ( * b -1 ) ( SQRT d ) ) ( * 2 a ) )

( PROMPT "\nX1=" )

( PRINT x1 )

( PROMPT "\nX2=" )

( PRINT x2 )

); конец PROGN

( PROMPT "\nДействительных корней нет" ); "иначе"

)

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

( COND

( c1 f11 f12 ... f1n1 )

( c2 f21 f22 ... f2n1 )

...

( cm fm1 fm2 ... fmnm )

)

Здесь с1 ..cm - логические условия, fnm - функции, выполняемые при выполнении того или иного условия.

Внимание! Условия проверяются последовательно до первого истинного. Если истинно сразу несколько условий, то выполняются только функции, относящиеся к первому из них, а остальные условия даже не проверяются.

Основное назначение функции COND - обработка ввода пользователя, например, так:

( SETQ a ( GETINT "\n1 - фаска, 2 - галтель, 3 - выточка" ) )

( COND

( ( = a 1 ) .... ); фаска

( ( = a 2 ) .... ); галтель

( ( = a 3 ) .... ); выточка

)

А вот пример неправильного применения функции COND. Пусть мы хотим присвоить переменной flag значение NIL, если хотя бы одна из сторон треугольника a, b, c оказалась отрицательной. Неопытному программисту вместо очевидного

( SETQ flag ( NOT

( OR ( <= a 0 ) ( <= b 0 ) ( <= c 0 ) ) ) )

может прийти в голову следующая пагубная идея:

Программа выглядит работоспособной, но представим себе, что будет происходить при следующих исходных данных: a=10, b=-5, c=2. Функция COND проверит первое условие a>0, убедится в его истинности, установит переменную flag в T и остальные условия вообще проверять не будет! Поэтому тот прискорбный факт, что сторона b меньше нуля, останется незамеченным.


Тесты к главе 5

 

Содержание

Список функций

Тесты

Предметный указатель