Создание собственных функций
Область видимости переменных
Организация диалога с пользователем
Мы уже говорили о том, что в языке Лисп возможно создание функций пользователя. По сути дела, программирование на Лиспе сводится к написанию набора пользовательских функций, выполняющих определенные действия. Головная функция, должна явно вызываться тем или иным способом, которые будут рассмотрены ниже.
Стандартный (но не единственный!) способ создания пользовательской функции заключается в использовании функции 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 еще не выполняется, а только помещается в память - для выполнения она должна быть явно вызвана.
Пользовательские функции не могут иметь произвольное число аргументов, как встроенные функции типа "+", "-" и др. Вызов пользовательской функции с неверным числом аргументов приведет к возникновению ошибки "Неверное число аргументов".
Поскольку пользовательская функция, как и любая другая, обязательно возвращает значение, возникает вопрос: как определить, что же она вернет? Ведь внутри функции, как правило, большое количество списков-программ. Воспользуемся следующим правилом нахождения возвращаемого пользовательской функцией значения:
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 при ошибке загрузки возвращает "имя_файла", при нормальной загрузке - ссылку на последнюю определенную в загруженном файле пользовательскую функцию (имя этой функции выводится на экран).
Задание имени файла сопряжено с тремя "подводными камнями":
Имя файла либо является переменной типа "текст", либо выражением, вычисление которого дает результат текстового типа. Если имя задается явно, как константа, оно заключается в кавычки.
Расширение имени файла и точка перед ним не указываются. Автолисп сам добавляет расширение .LSP. Поэтому программу можно записывать в файл только с таким расширением, иначе ее будет невозможно загрузить.
Если указывается полный путь доступа к файлу, то символ "\" нужно заменить на "/" и вместо "E:\ACAD\STUDENT\1.LSP" написать "E:/ACAD/STUDENT/1". Это связано с тем, что Автолисп воспринимает символ "\" как управляющий код.А как же теперь вызвать нашу функцию на исполнение? Принципиально возможны три способа:
Первый способ очевиден: после загрузки файла, содержащего, к примеру, функцию вычисления тангенса tan мы просто наберем в командной строке ( tan 0.25 ), чтобы узнать тангенс угла в 0.25 радиан. Однако скоро набирать скобки надоест.
Если имя пользовательской функции начинается с "C:", то после ее загрузки (и до окончания сеанса работы или до явного удаления функции из памяти) в Автокаде появляется новая команда с именем, совпадающем с именем функции. При этом пользовательская функция не может иметь аргументов.
Как же тогда передавать нашей функции информацию? Интерактивно: запрашивать ее у пользователя. Для этого в Автолиспе есть набор функций, обеспечивающих ввод-вывод информации.
Начнем с вывода. Экран Автокада может работать в двух режимах: текстовом и графическом (в версии 10 они переключаются клавишей F10 , в более поздних - F2 ). Эти режимы переключаются функциями Автолиспа
и
Сначала поговорим о выводе на текстовый экран. Для этого предназначены следующие функции (таблица 3.1).

|
Наименование |
Аргументы |
Описание |
|
PRINC |
Любое выражение |
Вывод выражения на экран без учета управляющих кодов |
|
PRIN1 |
Любое выражение |
Вывод выражения на экран с учетом управляющих кодов |
|
|
Любое выражение |
Вывод выражения на экран с учетом управляющих кодов, с новой строки и с пробелом в конце |
|
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:
действие которой распространяется
только на одну следующую за ней 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 )
)