- •( Setq a ( 0.25 "абв" 46 ) )
- •( Setq a '( 0.25 "абв" 46 ) )
- •( Load "имя_файла" )
- •( Command t1 t2 .. Tn )
- •Текущая ширина линии равна 0.00
- •( Cdr ( list 10 20 ) ) возвращает ( 20 )
- •( Setq a ( append a ( list 20 ) ) )
- •( Foreach name list exp )
- •( Mapcar 'f l1 l2 ... Ln )
- •__ Два объекта называются конструктивно подобными, если их соответствующие проекции представляются одними и теми же графами.
- •Библиографический список
- •1 Например. Доц. Троицкий д.И. - потенциальный источник ошибок на лекциях и неприятностей на экзаменах.
Министерство общего и профессионального образования РФ
®
Тульский государственный университет
Технологический факультет
Кафедра "Автоматизированные станочные системы"
Троицкий Д.И.
КУРС ЛЕКЦИЙ ДЛЯ СТУДЕНТОВ НАПРАВЛЕНИЯ 522900
ТУЛА 1997-99
УСЛОВНЫЕ ОБОЗНАЧЕНИЯ:
__ - определение понятия;
__ - важная информация, ее надо запомнить;
__ - потенциальный источник ошибок и неприятностей1;
__ - пример.
Автор курса лекций - кандидат технических наук, доцент кафедры "Автоматизированные станочные системы" Тульского государственного университета. E-mail troitsky@uic.tula.ru, WWW www.geocities.com/eureka/3120/koi_vic.html
ЛЕКЦИЯ №1
Классификация языков программирования. Язык Лисп
Разработка программного обеспечения для компьютеров в настоящее время производится почти исключительно при помощи языков программирования высокого уровня. Данные языки представляют собой систему мнемонических обозначений с жестко заданными синтаксисом и семантикой, которые понятны человеку и преобразуются в последовательность машинных команд при помощи специальной программы-транслятора. Существует множество классификаций языков программирования. Рассмотрим классификацию по стилю программирования.
__ Стиль - совокупность правил, лежащих в основе синтаксиса и семантики языка программирования. Различают следующие стили:
-
неструктурный;
-
структурный;
-
логический;
-
объектно-ориентированный;
-
функциональный.
Рассмотрим перечисленные стили подробнее.
__ Неструктурное программирование допускает использование в явном виде команды безусловного перехода (в большинстве языков GOTO). Типичные представители неструктурных языков - ранние версии Бейсика и Фортрана. Данный стиль вызван особенностями выполнения машиной программы в кодах и унаследован от программ на языке ассемблера, поскольку там команда перехода является обязательной. Однако в языках высокого уровня наличие команды перехода влечет за собой массу серьезных недостатков: программа превращается в “спагетти” с бесконечными переходами вверх-вниз, ее очень трудно сопровождать и модифицировать. Фактически неструктурный стиль программирования не позволяет разрабатывать большие проекты. Ранее широко практиковавшееся первоначальное обучение программированию на базе неструктурного языка (обычно Бейсика) приводило к огромным трудностям при переходе на более современные стили. Как отмечал известный голландский ученый Э. Дейкстра, “программисты, изначально ориентированные на Бейсик, умственно оболванены без надежды на исцеление”.
__ Структурный стиль был разработан в середине 60х - начале 70х гг. В его основе лежат две идеи:
-
задача разбивается на большое число мелких подзадач, каждая из которых решается своей процедурой или функцией (декомпозиция задачи). При этом проектирование программы идет по принципу сверху вниз: сначала определяются необходимые для решения программы модули, их входы и выходы, а затем уже эти модули разрабатываются. Такой подход вместе с локальными именами переменных позволяет разрабатывать проект силами большого числа программистов.
-
как доказал Э. Дейкстра, любой алгоритм можно реализовать, используя лишь три управляющие конструкции: последовательное выполнение, ветвление и цикл (рисунок 1.1; обратите внимание на обозначения в соответствии с действующим стандартом). Данное обстоятельство позволяет при наличии соответствующих операторов исключить из языка команду перехода GOTO.
а) Последовательное б) Ветвление в) Цикл
выполнение
Рисунок 1.1 - Основные управляющие конструкции.
Принципы структурного программирования были реализованы в языке Алгол, но наибольшую популярность завоевал язык Паскаль, созданный в 1970 швейцарским ученым Н. Виртом. Паскаль получил широчайшее распространение и может считаться образцовым языком программирования, наиболее популярным и сейчас (например, в версии Delphi фирмы Imprise).
__ Логическое программирование представляет собой попытку возложить на программиста только постановку задачи, а поиски путей ее решения предоставить транслятору. Логические языки (Пролог, Симула) имеют специальные конструкции для описания объектов и связей между ними. Например, если дано:
__ БРАТЬЯ ИМЕЮТ ОДНОГО ОТЦА
ДЖОН - ОТЕЦ ДЖЕКА
МАЙК - БРАТ ДЖЕКА
то система логического программирования должна сделать вывод:
ДЖОН - ОТЕЦ МАЙКА.
Хотя работы по логическому программированию ведутся с 50-х гг., в настоящее время данное направление несколько потеряло свою актуальность в связи с отсутствием реальных результатов, поскольку большинство реализованных на принципах логического программирования систем оказались практически непригодными.
__ Объектно-ориентированное (ОО) программирование, разработанное в середине 70х гг. Керниганом и Риччи и реализованное в ОО-версиях языков Си и Паскаль, представляет собой отображение объектов реального мира, их свойств (атрибутов) и связей между ними при помощи специальных структур данных. Структурное программирование подразумевает наличие ряда встроенных структур данных: целых, вещественных и строковых переменных, массивов, записей - при помощи которых и производится отображение свойств объектов реального мира. При объектно-ориентированном подходе для объекта создается своя структура данных (класс), содержащая как свойства объекта (поля), так и процедуры для управления объектом (методы).
__ Например, рассмотрим простейший объект - точку на экране. Она имеет как минимум три поля (координаты и цвет) и методы вроде "ChangeColor" (поменять цвет), "MoveXY" (переместиться в точку х, у) и т.д. Объектно-ориентированный подход является в настоящее время доминирующим и позволяет сократить время разработки и увеличить надежность больших проектов. Однако программы в данном стиле отличаются громоздким синтаксисом; в целом идеология объектно-ориентированного подхода весьма неочевидна часто воспринимается с трудом (особенно это характерно для языка Си, который и в своем первоначальном виде отличается крайне неудобочитаемым синтаксисом) и переход к его использованию труден или невозможен для большого числа программистов.
__ В основе функционального стиля лежит понятие функции как "черного ящика", имеющего вектор параметров (аргументов) на входе и результат r (скаляр) на выходе:
f()=r (1.1)
Допустим случай , т.е. вектор параметров отсутствует. В функциональных языках программирования отсутствуют операторы: все действия, в том числе и управляющие конструкции, выполняются при помощи вызовов функций. Поскольку каждая функция возвращает значение, ее можно подставить в качестве аргумента другой функции, что позволяет записывать сложные выражения в функциональной форме. Одним из первых функциональных языков стал язык Лисп, созданный в конце 50-х гг. как язык искусственного интеллекта.
К языкам искусственного интеллекта (сокращенно обозначается AI - artificial intelligence) относят такие языки, которые способны в зависимости от набора исходных данных модифицировать алгоритм работы, т.е. "на ходу" менять программу (поговорка гласит, что на языках искусственного интеллекта программируют те, кому не хватает интеллекта естественного).
Рисунок 1.1 - Создатель языка Лисп Джон Маккарти.
Рассмотрим язык Лисп, созданный американским ученым Джоном Маккарти (рисунок 1.1) в 1957 в Массачусетском технологическом институте (там он и работает по сей день) и нашедший широкое применение.
Название языка официально означает LISt Processing, а неофициально - Lots of Idiotic Silly Parentheses. Лисп отличается высокой компактностью - ядро интерпретатора занимает 4..10Кб, функциональным стилем программирования и использованием обратной польской нотации.
Основное понятие языка Лисп - список.
__ Список - перечень атомов или списков, отделенных друг от друга пробелами и заключенных в скобки.
__ Как видно из определения, списки могут быть вложенными. А что же такое атом в Лиспе? Это простой (в отличие от списка) тип данных: число, символьная строка, функция.
__ Внимание: в Лиспе нет различия между текстом программы и обрабатываемыми ею данными!
В других языках (например, в Паскале) программа (PROCEDURE, FUNCTION) и данные (VAR, CONST) жестко разделены. В Лиспе же и данные, и текст программы являются списками. Разумеется, список, являющийся программой, включает в себя атомы-функции, которые и вызываются при выполнении программы. То, что с программой можно работать, как с данными, и определяет возможность динамической модификации текста программы, что является свойством языков искусственного интеллекта. Как же отличить список-программу от списка-данных?
__ В Лиспе по умолчанию любой список является программой и интерпретатор будет пытаться ее выполнить. Если список - не программа, а данные, надо явно отключить его интерпретацию.
Из того, что текст программы - тоже список, вытекает необходимость использования специальной системы его записи - обратной польской нотации (названа так по причине изобретения ее польским математиком Яном Лукасевичем). Вызов любой функции в данной нотации записывается как список следующего вида:
__ (1.2)
где - аргументы функции.
__ Например, если функция сложения двух чисел имеет имя "+", то операция 2+3 запишется как ( + 2 3 ). В качестве аргументов могут фигурировать другие функции, что позволяет записывать сколь угодно сложные формулы в обратной польской нотации.
Пусть дана функция . В обратной польской записи она приобретет следующий вид:
( * ( / ( + ( * 2 x ) 3 ) ( - y 1 ) ) ( - y x ) ).
П
Теперь рассмотрим конкретную реализацию языка Лисп - встроенный в САПР AutoCAD интерпретатор языка AutoLISP (Автолисп). Выбор столь экзотического языка в качестве встроенного для столь популярной САПР вызван тем, что список - оптимальный способ представления графической информации, а также легкостью реализации и небольшими размерами интерпретатора. Однако следует признать относительно большую трудоемкость разработки программ на Автолиспе. Зато программы на Автолиспе отличаются чрезвычайно высокой надежностью; за многие годы работы ошибок в работе интерпретатора не выявлено.
Начнем с атомов.
__ Атом в Автолиспе является аналогом указателя на динамическую переменную в Паскале и представляет собой ссылку (адрес) ячейки памяти, начиная с которой записана та или иная информация.
Каждый атом имеет имя, дающееся по следующим правилам - допускаются английские буквы, цифры, большинство имеющихся на клавиатуре знаков за исключением ";", "(", ")", ".", "," ," ", строчные и заглавные буквы не различаются, первым символом должна быть буква.
__ Буква T зарезервирована и не должна использоваться в качестве имени атома. Слово NIL зарезервировано и не должно использоваться в качестве имени атома. Использование в качестве имени атома имени встроенной функции приведет к потере работоспособности этой функции до перезагрузки AutoCAD'а.
__ Длина имени формально не ограничена, но для экономии памяти настоятельно рекомендуется не превышать длину в шесть знаков.
Итак, программист в тексте программы дает атому имя, а интерпретатор Автолиспа сам динамически выделяет память под содержимое атома и автоматически определяет тип и размер хранящейся в атоме информации (рисунок 1.2)
Атом Динамическая память
|
|
|
|
|
|
Адрес |
Содержимое |
Имя |
Number |
|
|
|
|
0010:0020 |
0 |
Адрес |
0010:0020 |
|
|
|
|
0010:0021 |
5 |
Тип |
Целое число |
|
|
|
|
0010:0022 |
45 |
Размер |
2 байта |
|
|
|
|
0010:0023 |
18 |
Рисунок 1.2 - Хранение в памяти атома с именем Number, представляющего собой ссылку на целое число (в данном случае 5).
__ Автолисп поддерживает следующие типы данных:
-
целое число со знаком от -32768 до 32767 или от 0 до 65535 (2 байта) без знака;
-
вещественное число, записываемое через десятичную точку: 10.52 или в экспоненциальном формате: 2.52Е-12;
-
строка символов длиной до 127 знаков, заключенная в двойные кавычки. Символ "\" является служебным и, если он нужен в тексте, должен удваиваться: текст "3\2" запишется как "3\\2". "\" используется для обозначения перевода строки: "\n", возврата каретки: "\r" и табуляции: "\t";
-
логический тип, принимающий два возможных значения: истина (обозначатся Т) или ложь.(обозначается NIL);
-
ссылка на встроенную функцию языка;
-
ссылка на созданный программистом список (программу или данные);
-
ссылка на переменную;
-
ссылка на таблицу диспетчера виртуальной памяти.
ЛЕКЦИЯ №2
Присваивание значений в Автолиспе.
Математические функции. Работа с интерпретатором
__ Особенности организации памяти в Автолиспе приводят к тому, что следует ясно различать два понятия: переменная и значение переменной.
__ Переменная - указатель на область динамической памяти, имеющий имя.
__ Значение переменной - данные, записанные в динамической памяти начиная с адреса, записанного в переменной.
__ Изменить можно как переменную, так и ее значение. В первом случае та же переменная будет указывать на другой участок динамической памяти, а во втором - в том же участке динамической памяти будет помещено новое значение.
Для присваивания значений переменным в Автолиспе имеются две функции - SET и SETQ.
__ Функция SETQ (set by quote) меняет значение переменной, а не саму переменную. Аргументами функции является перечень пар "переменная" - "значение". Функция возвращает результат последнего присваивания.
Например, запись ( SETQ a 10 ) помещает число 10 в область памяти, на которую указывает переменная а и одновременно задает тип переменной а - целое число. Можно в одной функции присвоить несколько переменных:
__ ( SETQ a 10 b "213" )
Порядок выполнения нескольких присваиваний в функции SETQ определен слева направо. Если мы напишем:
__ ( SETQ a 10 b ( + a 10 ) )
то значением переменной b станет 20.
__ Изменение значения переменной на NIL освобождает занимаемую ее значением область памяти.
( SETQ a NIL ) сохраняет саму переменную а, но теперь она указывает "в никуда" (для ее значения памяти не выделено).
SETQ - наиболее часто используемая функция присваивания. Она аналогична по своему действия оператору присваивания ":=" в Паскале и в принципе позволяет не задумываться об особенностях организации памяти в Автолиспе.
__ Вторая функция присваивания - SET - работает не со значениями, а с самими переменными:
( SET a b ) заставляет переменную а ссылаться на ту же область памяти, что и переменная b.
__ Как записать в значение переменной атом (число, текстовую строку...) понятно. А как быть, если в переменную нужно записать список? Например, имеется список с данными вида: ( 0.25 "АБВ" 46 ). Проблема заключается в том, что, как мы уже знаем, по умолчанию любой список считается программой и первый же элемент списка, стоящий сразу после открывающей скобки, рассматривается как имя функции. Если мы напишем функцию присваивания вида:
( Setq a ( 0.25 "абв" 46 ) )
то интерпретатор попытается вызвать функцию с именем "0.25" и аргументами "АБВ" и "46", что приведет к возникновению ошибки "Неверное имя функции", поскольку функции с именем "0.25" нет и быть не может. Таким образом, необходимо как-то сказать Автолиспу: не пытайся выполнить этот список, это не программа, а данные. Для этого используется функция подавления вычисления списка QUOTE:
__ Функция QUOTE запрещает вычисление списка, являющегося ее аргументом и возвращает сам этот список.
В рассматриваемом примере нужно написать:
__ ( SETQ a ( QUOTE ( 0.25 "АБВ" 46 ) ) )
и в значением переменной а станет список ( 0.25 "АБВ" 46 ). Поскольку данные в программе встречаются весьма часто, введена сокращенная запись функции QUOTE в виде апострофа:
( Setq a '( 0.25 "абв" 46 ) )
__ Применять функцию QUOTE к атому бессмысленно: ( QUOTE 0.25 ) и 0.25 - одно и то же.
А что будет, если применить функцию QUOTE к переменной? Она вернет саму переменную, а не ее значение:
__ ( SETQ a 5 )
( SETQ b a ) значением переменной b будет 5
( SETQ b 'a ) значением переменной b будет переменная а.
Поэтому функцию SET можно использовать и для изменения значения переменной:
__ ( SET 'a 10 ) делает то же, что и ( SETQ a 10 )
поскольку запись 'a означает значение переменной а.
Итак, значением одной переменной может быть другая переменная! Такой прием используется редко, но знать о его существовании следует.
Предположим теперь, что мы записали в значение переменной как данные список, который на самом деле является программой:
( SETQ a '( + 5 10 ) )
Как теперь заставить Автолисп вычислить этот список? Для этого есть очень полезная функция EVAL (от evaluation):
__ Функция EVAL вычисляет список, являющийся ее аргументом, и возвращает вычисленное значение.
В нашем случае ( EVAL a ) вернет 15. Таким образом, функция EVAL обратна по отношению к функции QUOTE. Ее использование позволяет "на ходу", в ходе выполнения программы, сформировать список-программу (содержащий вызовы функций) и исполнить его. В этом и заключается динамическая модификация текста программы, необходимая для решения задач искусственного интеллекта.
Теперь рассмотрим математические и логические функции Автолиспа, которые для удобства сведены в таблицу 2.1.
Таблица 2.1 - Основные математические функции Лиспа.
Имя функции |
Аргументы |
Возвращаемое значение |
+ |
a1 a2 ... an |
a1+a2+...+an |
- |
a1 a2 ... an |
a1-a2-...-an |
* |
a1 a2 ... an |
a1a2...an |
/ |
a1 a2 ... an |
a1/a2/.../an |
1+ |
a |
a+1 |
1- |
a |
a-1 |
ABS |
a |
|
SQRT |
a |
|
EXP |
a |
|
EXPT |
a b |
|
GCD |
a b |
НОД чисел а, b |
LOG |
a |
Ln(a) |
MIN |
a1 a2 ... an |
Минимальное из чисел a1 a2 ... an |
MAX |
a1 a2 ... an |
Максимальное из чисел a1 a2 ... an |
REM |
a b |
Остаток от деления a/b |
Тригонометрические функции |
||
SIN |
a |
Sin(a), a - в радианах |
COS |
a |
cos(a), a - в радианах __ |
ATAN |
a |
atn(a) в радианах |
В языке также предусмотрена встроенная переменная PI, со значением, равным числу .
__ Пример: Вычислить площадь треугольника S со сторонами a, b, c по формуле Герона:
Запишем программу вычислений:
( SETQ p ( / ( + a b c ) 2 ) )
( SETQ s ( SQRT ( * p ( - p a ) ( - p b )
( - p c ) ) ) )
__ Для того, чтобы эту программу выполнить, ее можно либо построчно набирать в ответ на приглашение Автокада "Команда:", либо записать в текстовый файл с расширением *.LSP и загрузить его. Функции будут выполнены в процессе загрузки файла.
__ Внимание! Автокад не загружает автоматически файл с программой после того, как в него внесены изменения. Это надо делать принудительно при помощи меню или функции LOAD. В противном случае можно долго искать ошибку: программа исправлена, а работает, как и прежде.
__ Чтобы просмотреть значение переменной, надо в командной строке набрать восклицательный знак, а за ним - имя переменной:
Команда: !а
выведет на экран значение переменной а.
При наборе программ на Автолиспе легко ошибиться с парностью скобок, поэтому рекомендуется использовать текстовый редактор, отслеживающий скобки (простейший из них - The Norton Editor).
__ Если неверное по скобкам выражение введено с командной строки Автокада, то приглашение "Команда:" меняется на "1>", где 1 - число незакрытых правых скобок. Следует прервать ввод (нажав + или +) и попробовать еще раз.
ЛЕКЦИЯ №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 еще не выполняется, а только помещается в память - для выполнения она должна быть явно вызвана.
__ Пользовательские функции не могут иметь произвольное число аргументов, как встроенные функции типа "+", "-" и др. Вызов пользовательской функции с неверным числом аргументов приведет к возникновению ошибки "Неверное число аргументов".
__ Поскольку пользовательская функция, как и любая другая, обязательно возвращает значение, возникает вопрос: как определить, что же она вернет? Ведь внутри функции, как правило, большое количество списков-программ. Воспользуемся следующим правилом нахождения возвращаемого пользовательской функцией значения:
-
Найти закрывающую скобку, парную открывающей перед словом DEFUN (т.е. последнюю);
-
Найти предпоследнюю закрывающую скобку;
-
Найти парную ей открывающую скобку;
-
Значение. возвращаемое функцией, стоящей после этой открывающей скобки, и будет возвращаемым значением всей пользовательской функции.
Иначе говоря, возвращаемое значение есть результат вычисления последнего списка, находящего внутри DEFUN.
__ Автолиспу все равно, в какой последовательности в тексте LSP-файла идут описания функций DEFUN - ведь перед выполнением программы все они сначала загружаются в память. Поэтому при написании программы последовательность описания функций не важна (в отличие от Паскаля, где к каждая вызываемая процедура или функция должна быть уже определена).
__ Пример: В файле написано следующее:
( DEFUN f1 ()
......
( f2 )
)
( UN 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, которую можно набрать в командной строке Автокада: