Отправляет email-рассылки с помощью сервиса Sendsay
  Все выпуски  

Программирование для начинающих #18


Служба Рассылок Subscribe.Ru проекта Citycat.Ru

Программирование для начинающих

Выпуск 18

21 MAY 2001

 
 
 
Ведущий рассылки: Вячеслав Мацнев
e-mail: stac@stacmv.net
Saluton!

В этом выпуске читайте:

ОТСЕБЯТИНА

О, как бывает.

Вчера бился над своими MHTML скриптами. При компиляции одного файла выдавалось сообщение, что какая-то переменная не определена, причем, какая именно не сообщалось.

Ну, раз такое дело дело, стал просматривать весь код в поисках возможной ошибки.

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

Короче, я потратил несколько часов, нашел примерно десять ошибок, которые могли бы вызвать сообщение о неопределенной переменной, но последнее так и не перестало появляться.

Потом я ее нашел!

Все заработало как надо. А заодно были исправлен и десяток других ошибок. Во!

К чему я это все рассказываю?

Мы с вами уже можем писать кое-какие программы. Для самых простых из них мы можем добиться отсутствия ошибок. Но более сложные нужно отлаживать, чтобы ошибки исправить, а для начала их, хотя бы, найти (что иногда бывает довольно непросто).

Для тех, кто еще не понял, что у нас сегодня важное занятие, сообщаю:

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

{Если то, что я буду рассказывать окажется для вас новым, конечно. :)}

ТЕОРИЯ .::. Подпрограммы

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

Пристегните ремни!

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

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

И вы будете не правы и правы одновременно. Трудность здесь состоит в определении того, что мы считаем задачей. Есть, разумеется, специальное определение этого слова.

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

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

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

Самые сообразительные уже должно быть догадались, что любую задачу любой сложности можно разложить или разбить на более простые подзадачи.

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

Теоретически разделение служит целям упрощения и может продолжаться до бесконечности.

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

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

Я понимаю, что все это очень интересно и познавательно, но сейчас нам не поможет. Когда-нибудь я обязательно напишу книгу на эту тему, так что следите за рекламой.

"Какого лешего ты давишь нам на мозги?", спросите вы, "давай уже переходи к сути!"

Однако, не торопитесь друзья, сначала я должен рассказать вам историю.

Когда-то давным-давно, когда инженеры IBM еще даже не придумали Фортран, не говоря уже о персональных компьютерах, жил-был один программист. Чтобы никому не было обидно, назовем его Джоном.

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

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

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

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

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

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

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

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

"В смысле?", сказал Джон, недоумевая. И Мэлори рассказала ему, как только что, буквально секунду назад, он придумал, как обхитрить этих собак, администраторов и управляющих. Джон узнал, что у него возникла мысль о том, что он должен заиметь несколько учеников и передать каждому часть мастерства, так чтобы никто из них не мог сам написать всю программу целиком.

Слава женщине, жене древнего программиста!

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

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

Дело пошло много быстрее. Более того, к радости Мэлори и маленькой Сюзи, Джона повысили и сделали начальником отдела.

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

Потом Джон сообразил, что если код для вывода на АЦПУ уже написан для одного проекта, на его не надо писать для другого и поэтому можно сэкономить. Также и с остальными кусками программ. Зачем писать то, что уже написано? Джон собрался увольнять своих учеников.

Однако у них тоже были жены и дети, которые тоже хотели кушать.

Бриджит, жена Стива сказала мужу:" Стив, ты умный человек и честный труженик. Ты пишешь код для печати на стареньком АЦПУ изо дня в день и уже поднаторел в этом искусстве, найди себе учеников, пусть одни инициализируют устройство, другие двигают печатающую головку и т.д."

"О, Бриджит, жена моя", ответил Стив, "ты умная женщина, именно поэтому я на тебе и женился, но ты не знаешь этого скунса, Джона. Он уже записал мой код на перфокарту и больше я ему не нужен, на этом АЦПУ он может печатать и без меня".

"О, Стив, ты гений, как я рада, что не будешь уволен и твое искусство будет востребовано и оплачено!"

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

Слава женщине, пособнице прогресса!

Через несколько дней старенькое АЦПУ при таинственных обстоятельствах вышло из строя. Было похоже, что кто-то нечаянно уронил его с седьмого этажа. Всеобщей скорби не было предела. Но работать надо и было закуплено новое АЦПУ, совершенно не совместимое со старым, так как покупал его Стив.

Это конец сказки. Но не конец истории. Каждый из вас в состоянии продолжить ее.

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

На самом деле у нее куча моралей, одни для программистов, другие для администраторов, третьи для жен итак далее.

Я выделю лишь две.

Номер один. Если какой-то код используется в программе не один раз, то его можно выделить и написать отдельно.

Номер два. Выделенный и написанный отдельно код можно использовать не только в этой программе, но и в других. Более того, его могут использовать вообще другие люди, также как и вы можете использовать написанный кем-то код в своих программах.

Эти два принципа сейчас являются определяющими в современном программировании. Вы можете видеть его абсолютно везде, на всех уровнях. В качестве примера могу привести всем известные динамически компонуемые библиотеки (DLL), элементы управления Visual Basic (VBX), элементы управления Active X и т.д. и т.п.

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

Кстати, к "Принципу решения сложных задач" есть комментарии, один из которых гласит: "Если задача простая, то разбивать на подзадачи ее не надо".

Кстати, структурное программирование, как раз, основано на этом принципе.

А началось все с ...

... подпрограмм.

Что, если в вашей программе какую-то последовательность команд надо повторять много раз? На ум приходит слово "цикл", не так ли? Но если эту последовательность нам надо повторять в разных местах программы, циклы нам ничем не помогут.

Простой пример. Надо напечатать "++++++++++" (десять плюсиков). Это делается с помощью цикла:

FOR I =1 TO 10: PRINT "+";: NEXT I

Но если в программе нужно печатать эти плюсики двадцать раз в разное время? Напишем этот цикл 20 раз? Можно.

Но есть другой вариант - написать цикл один раз и потом этот код 20 раз вызывать.

Мы имеем три выгоды от этого:

1) Не надо печатать 20 циклов (напомню, что это простой пример, а еще бывают сложные);
2) Получаем более короткую программу, как на Бейсике, так и в машинных кодах, ведь один цикл занимает меньше памяти, чем 20.
3) Получаем легко модернизируемую программу. Если в один прекрасный день мы решим, что звездочки смотрятся лучше плюсиков, то нам нужно будет сделать одно изменение, а не 20.

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

Первой подобной конструкцией в Бейсике была конструкция GOSUB...RETURN (Уйти_в_подпрограмму...Вернуться). Сейчас эта конструкция очень устарела и почти не употребляется. Но все же приведу один пример, чтобы вы знали как она работает:

10 PRINT "WELCOME..."
20 GOSUB 200
30 PRINT
40 INPUT ">",COMLINE$
50 GOSUB 200
60 PRINT
 ...
200 FOR I =1 TO 10
210 PRINT "+";
220 NEXT I
230 RETURN

В строках 20 и 50 стоят команды вызова подпрограммы. При этом считается, что подпрограмма начинается со строки 200.

Подпрограмма выполняется, пока не встретится оператор RETURN. При этом управление передается на строку, следующую за вызвавшей подпрограмму. Т.е. на строку 30 или 60 в данном примере.

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

Что если мы добавим строку 190:

190 PRINT "-";

А один из операторов GOSUB 200 исправим на GOSUB 190. Что мы получим, одну подпрограмму с двумя точками входа или две пересекающиеся подпрограммы? Не понятно.

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

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

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

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

Ведь каждая Бейсик-команда при компиляции заменяется подпрограммой, написанной ранее разработчиками компилятора. Эти подпрограммы активно используют функции ДОС, которые являются тоже подпрограммами, но написанными уже разработчиками ДОС. Такая же ситуация и с Windows. Вы, наверное, уже слышали про Windows API (Application Programing Interface). Этот интерфейс представляет собой набор из нескольких тысяч (кажется более шести) подпрограмм имеющихся в Windows и которыми могут пользоваться все, кому не лень, программисты.

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

Возможно вы слышали такой термин как "нисходящее программирование". Вот что скрывается за этими простыми, но мудрыми словами:

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

Рассмотрим это на примере задачи basic\1\5 (решение квадратного уравнения). Но для начала я расскажу, что из себя представляют современные подпрограммы на Бейсике и как с ними работать.

Существует два типа подпрограмм: процедуры и функции.

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

Т.е. вы, как пользователь среды разработки можете придумывать свои функции, что есть очень здорово. Так, ведь?

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

Подпрограмма может иметь параметры или аргументы, что одно и то же. Параметры передаются в подпрограмму, и она их как-нибудь использует (или не использует, что тоже бывает, смотри функцию POS() Quick Basic'а).

Вызов подпрограммы-процедуры (SUBROUTINE) осуществляется просто указанием ее имени и параметров или с помощью оператора CALL (вызвать).

Положим мы имеем подпрограмму с именем INIT, не имеющую параметров. Вызвать мы ее можем так:

INIT

или

CALL INIT

Если же процедура SUM имеет параметры a,b,c, то вызываться она может так:

SUM a,b,c

или

CALL SUM(a,b,c)

Обратите на обязательность скобок во втором случае и на их обязательно отсутствие в первом..

Чтобы использовать подпрограмму, ее надо написать и объявить.

Подпрограмма начинается со строки типа:

SUB имя [(параметр AS тип [,параметр AS тип,...])] - для процедуры

и

FUNCTION имя [(параметр AS тип [,параметр AS тип,...])] - для функции

А заканчивается все это дело строкой:

END {SUB|FUNCTION}

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

FUNCTION sqrt(x AS INTEGER)
  sqrt=x*x
END FUNCTION

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

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

Вот, посмотрите на программу, которая возводит в квадрат число:

'Эта программа возводит число в квадрат
'Объявление функции
DECLARE FUNCTION sqrt(x AS DOUBLE)
'Обхявление переменных
DIM number AS DOUBLE
DIM result AS DOUBLE

'Ввод исходных данных PRINT "Kvadrat 1.0":PRINT INPUT "Введите число:",number 'Возведение в квадрат LET result=sqrt(number) 'Печать результата PRINT number;"^2=";result END

'Подпрограмма для возведения числа в квадрат FUNCTION sqrt(x AS DOUBLE) sqrt=x*x END FUNCTION

Что можно сказать об этом примере?

Ну, он очень примитивен. В третьей строке стоит оператор DECLARE FUNCTION, с помощью которого объявляется функция. Quick Basic обычно сам пишет эти операторы, как только вы сохраняете файл, имеющий подпрограммы.

Теперь поговорим о параметрах. Есть два простых понятия: формальный параметр и фактический параметр.

В приведенном выше примере x это формальный параметр. Он указан в объявлении функции и используется в теле функции. Фактическим параметром в данном примере является number.

Таким образом, фактический параметр это то, что передается в подпрограмму.

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

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

Это сложный вопрос,который мы сейчас немного обсудим, но в общем - да.

Есть несколько умных выражений на эту тему, которые вы, возможно, уже слышали от своих друзей программистов. Теперь вы тоже сможете их употреблять и пугать окружающих. Вот примеры таких выражений:

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

Если пользователь в неудачный момент обнаружит ошибку в вашей программе, можете ввернуть подобную фразу, только не пугайте его сильно, ок?

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

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

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

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

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

Раз есть уровень модуля, значит есть и еще какой-нибудь. Да, это уровень подпрограммы.

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

При этом имена переменных уровня подпрограммы могут совпадать с именами переменных уровня модуля или уровня другой подпрограммы, но это будут совершенно разные переменные, и это важно понять и запомнить!

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

Пример:

DIM a AS INTEGER

LET a = 10 CALL change_a PRINT a END

SUB change_a DIM a AS INTEGER LET a = 5 PRINT a END SUB

При выполнении программа напечатает:
5
10

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

Когда подпрограмма заканчивает выполнение ее пространство имен уничтожается (т.е. время жизни переменных уровня подпрограммы ограничено временем выполнения этой подпрограммы).

Есть правда такие хитрые переменные, которые по выходе из подпрограммы не уничтожаются и даже не теряют свое значение, но это уже из другой сказки. А то что же, мне все интересное за один раз рассказывать ?:-)

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

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

Для этого нужно использовать ключевое слово SHARED при объявлении переменной на уровне модуля:

DIM SHARED clientname$

Переменная clientname$ будет видима во всех подпрограммах модуля и на уровне модуля, конечно, тоже, т.е. она будет глобальной. При этом в подпрограммах объявлять эту переменную не нужно.

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

Область видимости таких переменных уровень модуля и одна или несколько подпрограмм. В отличие от глобальных разделенные переменные видны не во всех подпрограммах, а только в определенных. Для объявления разделенных переменных тоже используется ключевое слово SHARED, но несколько отличным от предыдущего случая образом:

'Определени переменных на уровне модуля
DIM clientname$
DIM sum AS LONG
 ...

SUB report SHARED clientname$ ... END SUB SUB invice(client$) SHARED sum ... END SUB

Таким образом глобальные и разделенные переменные могут быть быть использованы для обмена данными между подпрограммами и уровнем модуля.

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

Еще одно "ругательное" выражение на заданную тему: "Ты как его посылаешь? Ну так пошли по ссылке!"

Ну, о чем это я? Ах, да. Есть два способа передать параметры в подпрограмму: по ссылке и по значению.

Передача параметров по ссылке

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

Передача параметров по значению

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

Адреса-то, кстати, тоже передаются через стек, но нам пока рано волноваться по этому поводу.

В Бейсике по умолчанию параметры передаются по ссылке.

Сейчас я придумаю пример. Ок:

1)
DIM num AS INTEGER
num=10
PRINT "Num=";num;" Num-2=";nm2(num);" Num=";num
END
FUNCTION nm2(x as INTEGER)
  x=x-2
  nm2=x
END FUNCTION

2) DIM num AS INTEGER num=10 PRINT "Num=";num;" Num-2=";nm2((num));" Num=";num END FUNCTION nm2(x as INTEGER) x=x-2 nm2=x END FUNCTION

Первая программа выдаст:

Num= 10 Num-2= 8 Num= 8

Вторая:

Num= 10 Num-2= 8 Num= 10

Ну-ка, кто первый сообразит, в чем фишка?

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

Кстати, в Quick Basic'е каждая подпрограмма редактируется в своем окне. Чтобы переключиться с одной подпрограммы на другую изредка жмите кнопку F2.

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

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

DECLARE SUB start ()
DECLARE FUNCTION checkesc% ()
DECLARE FUNCTION inputdata% (a%, b%, c%)
DECLARE SUB solve (a%, b%, c%, x1!, x2!)
DEFINT A-C
DEFSNG X
start
DO
  IF checkesc THEN END
  DO UNTIL inputdata(a%, b%, c%)
  LOOP
  PRINT "Решаем уравнение "; a; "*x^2+"; b; "*x+"; c; "=0 :"
  CALL solve(a%, b%, c%, x1, x2)
  PRINT : PRINT
  PRINT "Для того, чтобы решить еще одно уравнение, нажмите ПРОБЕЛ.";
LOOP UNTIL 0

DEFSNG A-C FUNCTION checkesc% PRINT "Нажмите ESC для выхода." DO ch$ = INKEY$ LOOP WHILE ch$ = "" IF ch$ = CHR$(27) THEN checkesc = -1 ELSE checkesc = 0 END IF END FUNCTION

FUNCTION inputdata% (a%, b%, c%) CLS PRINT "a*x^2+b*x+c=0" PRINT PRINT "Введите коэффициенты уравнения:" INPUT "a=", a%: INPUT "b=", b%: INPUT "c=", c% inputdata = -1 END FUNCTION

DEFINT A-C SUB solve (a%, b%, c%, x1, x2) IF a = 0 THEN IF b = 0 AND c = 0 THEN PRINT "X - любое число." ELSEIF b = 0 AND c <> 0 THEN PRINT "Уравнение не имеет корней." ELSE LET x = -c / b PRINT "X="; x END IF ELSE LET d = b ^ 2 - 4 * a * c SELECT CASE d CASE IS < 0 PRINT "Уравнение "; a; "*x^2+"; b; "*x+"; c; PRINT " не имеет действительных корней." CASE 0 LET x = -b / 2 / a PRINT "Уравнение имеет два одинаковых корня:" PRINT "X1,2="; x CASE IS > 0 LET sd = SQR(d) LET x1 = (-b + sd) / 2 / a: LET x2 = (-b - sd) / 2 / a PRINT "Уравнение имеет два различных корня:" PRINT "X1="; x1; CHR$(13); "X2="; x2 END SELECT END IF END SUB

DEFSNG A-C SUB start CLS PRINT "Программа для решения квадратных уравнений (ax^2+bx+c=0)" PRINT "Для продолжения нажмите ПРОБЕЛ."; END SUB

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

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

Если возникнут проблемы или вопросы по сегодняшнему материалу, пишите мне.

БЕЙСИК .::. Введение в отладку программ

Отладка программы это процесс поиска и обнаружения ошибок или изучения работы программы.

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

Однако мы будем использовать отладчик, встроенный в среду разработки Quick Basic, так как он наиболее удобен для отладки именно Бейсик-программ.

Так, дальше мы говорим о Quick Basic 4.0.

Функции отладчика вызываются через меню DEBUG. Рассмотрим основные.

Как вы помните, есть несколько видов ошибок. Часть из них отлавливается компилятором при компиляции (несоответствие типов, числа параметров и все такое прочее), часть отлавливается даже редактором (синтаксические ошибки). Часть возникает в процессе выполнения программы и отлавливается подпрограммой обработки ошибок, которая автоматом добавляется к вашей программе при компиляции. Эта нехитрая подпрограммка выдает сообщение об ошибке и прекращает выполнение программы.

Несмотря на то, что обычно можно легко найти строку программы, вызывающую ошибку периода выполнения (runtime error), найти причину ошибки часто бывает непросто.

Есть есть, так называемые, логические ошибки. Это ошибки в логике программы. Они бывают очень разные, но у них очень похожие симптомы.

Если, вроде бы, правильно написанная программа работает не пойми как и делает не пойми что, то она на 99,9% имеет логическую ошибку. А 0,1% относится на злой умысел автора программы. :)

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

Короче именно для отлавливания логических ошибок и нужен отладчик.

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

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

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

Вспомните пример, который я вам показывал. когда рассказывал про передачу параметров в подпрограмму.

Стоп. Ладно, не вспоминайте. Мне, ведь, ничего не стоит повторить его:

DIM num AS INTEGER
CLS
num=10
PRINT "Num=";num;
PRINT " Num-2=";nm2(num);
PRINT " Num=";num
END
FUNCTION nm2(x as INTEGER)
  x=x-2
  nm2=x
END FUNCTION

Я разбил строчку с PRINT на три строки, чтобы было удобнее наблюдать за переменной num (отладка-то идет построчно).

Так, наберите эту программу в QB (хотя бы через буфер обмена скопируйте, что ли).

Теперь нажмите кнопку F8. Каждый раз, когда вы будете нажимать, будет выполняться очередная строка программы.

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

В любой момент вы можете нажать F4 и увидеть OUTPUT SCREEN - экран вывода, там будет то, что печатает программа.

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

Сейчас мы установим слежку за переменной num. В меню Debug выберите пункт "Add Watch..."(установить слежку :).

Теперь вас спросят об имени подозреваемого, укажите num.

Причем, коллеги, обратите внимание, что делать это вы должны будучи на уровне модуля. Почти уверен, что модуль у вас называется Untitled. Итак сверху, сразу под меню, появится строчка, в которой будет написано: <Untitled> num:. Так, наружку организовали.

Слежка будет вестись не за любой переменной num, а за num уровня модуля.

Ок. Жмите F8, только осторожно. Смотрите, как меняется num.

Так, сначала 0, норма. Затем 10, это тоже нормально, мы же сами это ей присвоили.

Входим в функцию nm2. А это че за ..? "Эй босс, объект зашел в здание.". Видите там написано, что переменная num не видна. Мы сейчас находимся на уроне функции, поэтому переменны уровня модуля не видны.

Если кто не въехал, рекомендую почитать выпуск сначала. Шутка :).

Что ж, ждем выхода из функции. Оп, вышли.

Где там num? 8. Эй, что здесь происходит, было же 10, а стало 8!

ААААААА!

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

Но чего нам делать с num? Мы видим, что она передается как параметр в функцию nm2. Так, идем туда разбираться. (Нажмите кнопку F2 и выберите функцию).

В функции nm2 наша подопечная уже называется x (фальшивым пастортом пользуется что ли?). Вот она-то и меняет свое значение. А если мы вспомним, что параметры передаются по ссылке, то все поймем.

Так, будем бороться. Намечается два варианта решения: 1)передавать параметр по значению; 2) модернизировать функцию nm2.

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

FUNCTION nm2(x as INTEGER)
rem  x=x-2 - уберем эту строку
  nm2=x
END FUNCTION

Чудненько. Чего Вы еще должны знать? Много чего, вообще-то.

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

Поэтому дядьки из Microsoft предлагают вам следующие возможности:

1)При пошаговом выполнении установить следующий оператор, на котором будет остановка (а те, что находятся между текущим и указанным вами следующим, будут выполнены без тормозов. Для этого ткните мышкой в нужную вам строку программы и в меню Debug выберите пункт "Set Next Statement".

2)Установить в нужных местах точки прерывания (breakpoints). Когда курсор будет находиться в нужной строке, нажмите F9 (или выберите соответствующий пункт в меню Debug). Строка будет помечена. И когда вы запустите программу в нормальном режиме, а не пошагово, то перед выполнением помеченной строки произойдет останов выполнения программы, и вы сможете подглядеть за значениями переменных, продолжить выполнение программы строчка за строчкой (F8) или традиционным способом (F5).

Естественно, точек прерывания может быть несколько или даже много.

В Quick Basic'е есть еще немного интересностей из области отладки, я рассказал лишь о самых наиболее часто применяемых и распространенных средствах (они есть почти во всех средах разработки).

На этом я откланиваюсь и прошу разрешения наконец-то заткнуться, а то разговорился я чего-то.

ОКРУЖЕНИЕ

Фанаты Бейсика для ДОС, сидите (пока) тихо, у меня сообщение для тех, кто интересуется VB. VB-шники! Вас ждут.
Здесь: http://www.VBStreets.ru - на сайте для программистов на Visual Basic, VBScript и ASP.

Ну, а теперь .. рассылочка!

Рассылки Subscribe.Ru
Интерактивная Школа Разработчика. Экономика информации.

Для подписки:
напишите письмо на subscribe@subscribe.ru, содержащее одну строку:
SUB ваш_email ваш_пароль comp.prog.ecpp

или сходите по ардесу:
http://subscribe.ru/catalog/comp.prog.ecpp

В конце концов, просто заполните форму :).


Рассылочка сегодня с говорящим названием. Поэтому я закрою ротик и отдохну...
... до следующего выпуска!

Пока.

=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-

С уважением,
Вячеслав Stac Мацнев mailto:stac@stacmv.net
21.05.01.



http://subscribe.ru/
E-mail: ask@subscribe.ru
Отписаться Relayed by Corbina
Рейтингуется SpyLog

В избранное