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

ГЛАВА 3

Создание собственных функций

Область видимости переменных

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

 

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

Стандартный (но не единственный!) способ создания пользовательской функции заключается в использовании функции DEFUN (DEFine FUNction). В общем виде она записывается следующим образом:

 

(DEFUN name ( a1 a2 ... an / v1 v2 ... vm )

( выражение1 )

( выражение2 )

....

( выражение N )

)

 

где name - имя функции;

ai - i-й аргумент функции;

vi - i-я локальная переменная.

 

Функция DEFUN создает в памяти пользовательскую функцию с именем name и списком аргументов a1 a2 ... an. При этом сама функция name еще не выполняется, а только помещается в память - для выполнения она должна быть явно вызвана.

Пользовательские функции не могут иметь произвольное число аргументов, как встроенные функции типа "+", "-" и др. Вызов пользовательской функции с неверным числом аргументов приведет к возникновению ошибки "Неверное число аргументов".

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

  1. Найти закрывающую скобку, парную открывающей перед словом DEFUN (т.е. последнюю);
  2. Найти предпоследнюю закрывающую скобку;
  3. Найти парную ей открывающую скобку;

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

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

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

Пример: В файле написано следующее:

( DEFUN f1 ()

......

( f2 )

)

 

( DEFUN f2 ()

......

)

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

А теперь попробуем наконец-то создать собственную полезную функцию.

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

 

(DEFUN tan ( a )

( / ( SIN a ) ( COS a ) )

)

 

Выполнение данного списка приведет к появлению в памяти переменной tan типа "определенная пользователем функция", значением которой является список-программа вычисления тангенса.

Текст пользовательской функции можно вывести на экран при помощи "!", поскольку он также является значением переменной.

Закономерный вопрос: а куда делись косая черта и загадочные "локальные переменные" v1, v2..vm?

Все переменные в Автолиспе делятся на два вида: глобальные и локальные.

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

Например, функция ( SETQ a 5 ) создает глобальную переменную a.

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

Например, опишем функцию с именем s следующего вида:

 

(DEFUN s ( a / p )

( SETQ p 5 )

)

( SETQ p "ABC" )

 

Здесь p, которой присваивается значение 5 - локальная переменная. Она не будет доступна из других функций. Поэтому функция ( SETQ p "ABC" ) создает глобальную переменную с именем p, никаким образом не связанную с локальной переменной р.

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

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

Если глобальные переменные все же используются, как только необходимость в них отпадает, следует освобождать занимаемую ими память, присваивая им значение NIL (вопрос нехватки памяти в Автолиспе в его реализации для Автокада 10 стоит очень остро).

Итак, пользовательская функция может не иметь как локальных переменных, так и аргументов. Поэтому возможны записи:

 

(DEFUN s

( a b / cd )

(DEFUN s

( ab )

(DEFUN s

( / c d )

(DEFUN s ()

 

В любом случае пара скобок после имени функции сохраняется.

Попробуем теперь написать более сложную функцию.

Пример: написать функцию вычисления площади треугольника со сторонами a, b, c.

Очевидно, снова нужно воспользоваться формулой Герона. Назовем нашу функцию trisqu (triangle square), помня о правиле шести символов. Ее аргументами, очевидно, будут стороны треугольника. Введем также локальную переменную p для записи полупериметра:

 

( DEFUN trisqu ( a b c / p )

( SETQ p ( / ( + a b c ) 2 )

( SQRT ( * p ( - p a ) ( - p b ) ( - p c ) ) )

)

 

Обратите внимание, что нет необходимости записывать площадь в переменную - мы сразу сделали ее возвращаемым значением.

При вызове функции trisqu ей обязательно должны быть переданы три аргумента, значения которых запишутся в локальные переменные a, b, c, т.е. мы должны написать:

 

( trisque 3 4 5 ) или ( trisque a 4.5 ( / 2 b ) ) или...

 

Рассмотрим теперь способы занесения пользовательских функций в память и их вызова. Как мы уже знаем, наиболее удобный путь - записать пользовательские функции в текстовый файл с расширением .LSP и загрузить его функцией LOAD, которую можно набрать в командной строке Автокада:

 

( LOAD "имя_файла" )

 

Функция LOAD при ошибке загрузки возвращает "имя_файла", при нормальной загрузке - ссылку на последнюю определенную в загруженном файле пользовательскую функцию (имя этой функции выводится на экран).

Задание имени файла сопряжено с тремя "подводными камнями":

  1. Имя файла либо является переменной типа "текст", либо выражением, вычисление которого дает результат текстового типа. Если имя задается явно, как константа, оно заключается в кавычки.
  2. Расширение имени файла и точка перед ним не указываются. Автолисп сам добавляет расширение .LSP. Поэтому программу можно записывать в файл только с таким расширением, иначе ее будет невозможно загрузить.
  3. Если указывается полный путь доступа к файлу, то символ "\" нужно заменить на "/" и вместо "E:\ACAD\STUDENT\1.LSP" написать "E:/ACAD/STUDENT/1". Это связано с тем, что Автолисп воспринимает символ "\" как управляющий код.

А как же теперь вызвать нашу функцию на исполнение? Принципиально возможны три способа:

  1. Вызов функции с командной строки.
  2. Вызов функции из меню Автокада (вызов функции из меню требует знания правил его модификации и не может быть рекомендован неопытным пользователям).
  3. Введение новой команды в Автокад.

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

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

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

Начнем с вывода. Экран Автокада может работать в двух режимах: текстовом и графическом (в версии 10 они переключаются клавишей F10 , в более поздних - F2 ). Эти режимы переключаются функциями Автолиспа

 

( TEXTSCR )

и

( GRAPHSCR )

 

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

  Таблица 3.1 - Функции вывода информации на текстовый экран.

Наименование

Аргументы

Описание

PRINC

Любое выражение

Вывод выражения на экран без учета управляющих кодов

PRIN1

Любое выражение

Вывод выражения на экран с учетом управляющих кодов

PRINT

Любое выражение

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

PROMPT

Текст

Вывод текста на экран с учетом управляющих кодов

TERPRI

Нет

Вывод пустой строки

 

Автолисп "понимает" следующие управляющие коды в выводимых на экран текстовых строках (таблица 3.2):

  Таблица 3.2 - Управляющие коды.

Код

Значение

\e

Символ с кодом 27 (ESC)

\n

Переход на новую строку

\r

Переход в начало той же строки

\t

Переход на следующую позиции табуляции (8 пробелов)

\nnn

Ввод символа с восьмеричным кодом nnn

 

Если нужно просто вывести на печать символ "\", его нужно удвоить, т.е. написать "\\".

Пример: для вывода классической фразы "Hello, world!" с новой строки в Автолиспе следует написать:

 

( PROMPT "\nHello, world!" )

 

Функция PRIN1 не возвращает значения и удобна для "тихого" завершения работы головной программы.

Для вывода на экран цветного текста, управления положением курсора и т.д. можно использовать драйвер экрана и клавиатуры ANSI.SYS, который должен быть установлен на компьютере и которым можно управлять при помощи ESC-последовательностей (для этого и нужен управляющий код "\e").

Ввод данных осуществляется семейством GET-функций (таблица 3.3).

Таблица 3.3 - Ввод данных с клавиатуры.

Наименование

Аргументы

Описание

GETINT

Текст подсказки

Ввод целого числа

GETREAL

Текст подсказки

Ввод вещественного числа

GETSTRING

Флаг пробела (T или NIL)

Текст подсказки

Ввод текста. Если флаг пробела=T, в тексте могут быть пробелы, иначе (по умолчанию) пробел воспринимается как окончание ввода

 

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

 

( SETQ ( GETINT "\nВведите X:" ) )

( SETQ m ( GETSTRING T "\nВведите фамилию и имя:" ) )

 

Ввод данных всегда чреват возникновением ошибок. Как известно, в большинстве случаев на подсказку "Введите целое число от 1 до 3" пользователь пишет "Вася" и злорадно ожидает реакции компьютера. Для автоматического исключения наиболее очевидных ошибочных ситуаций предназначена функция INITGET:

 

( INITGET сумма_кодов )

 

действие которой распространяется только на одну следующую за ней GET-функцию.

Возможны следующие значения кодов (таблица 3.4):

 

Таблица 3.4 - Основные коды функции INITGET.

Код

Значение

1

Запретить пустой ввод

2

Запретить ввод нуля

4

Запретить ввод отрицательных чисел

 

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

 

( INITGET 7 )

( SETQ p ( GETINT "\nВведите номер позиции детали:" ) )

 

Пример: Написать интерактивную программу вычисления площади треугольника.

( DEFUN C:trisque ( / a b c p s )

( TEXTSCR )

( INITGET 7 )

( SETQ a ( GETREAL "\nДлина стороны A:" ) )

( INITGET 7 )

( SETQ b ( GETREAL "\nДлина стороны B:" ) )

( INITGET 7 )

( SETQ c ( GETREAL "\nДлина стороны C:" ) )

( SETQ p ( / ( + a b c ) 2 ) )

( PROMPT "\nПлощадь треугольника равна " )

( PRIN1 ( SQRT ( * p ( - p a ) ( - p b ) ( - p c ) ) ) )

( PRIN1 )

)

 


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

 

Содержание

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

Тесты

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