Рассылка закрыта
Вы можете найти рассылки сходной тематики в Каталоге рассылок.
Программирование для начинающих #27
Программирование для начинающихВыпуск 27
Ведущий рассылки: Вячеслав Мацнев | e-mail: stac@stacmv.net |
|
В этом выпуске читайте: |
В прошлом выпуске было опубликовано несколько ссылок на DOS ресурсы. Я и сам туда сходил, посмотрел "что-чего". Скачал почти все пакеты к FreeDOS, установил их. Обнаружил ряд интересных программ в этих пакетах: текстовые редакторы, ассемблеры, компилятор и интерпретатор Бейсика, другие. К этому вопросу еще вернемся. С сайта FREEDOS удалось выйти еще на ряд интересных сайтов (посмотрите раздел Links! на http://www.freedos.org) с программами для ДОС, с играми. Причем многие программы довольно современные: 1999-2001 годов, это говорит о том, что ДОС жив не только в наших головах, но и в головах множества энтузиастов со всего мира. Среди последнего моего "улова" я хотел бы выделить программы XTC-PLAY и QuickView. Первая - это музыкальный плеер, поддерживающий различные форматы оцифрованной и модульной музыки (MOD, IT, S3M, XM, ..., WAV, MP3,...). Причем, качество звучания субъективно лучше, чем у того же Winamp'a. Может быть, потому что XTC-PLAY напрямую поддерживает мою не-SB звуковую карту. QuickView это программа для просмотра картинок нескольких распространенных форматов, просмотра видео (AVI, MOV) и прослушивания цифровых записей (WAV, MP3). Естественно (точнее, удивительно, но для меня очень приятно) имеется прямая поддержка моего GUS PnP. Кстати насчет видео. Недавно появилась новая версия кодека DivX :-), четвертая. Говорят, что работает быстрее (как кодер, так и декодер). Посмотрел я это дело. Да, немного быстрее. Но я на своем "старом" компе, как смотрел MPEG4-фильмы в маленьком окошке, так и смотрел бы, если бы ... QuickView не поддерживала DivX :-) (3 и 4). Вот под ДОС все это работает, действительно быстро. Признаюсь, я был несказанно рад такому, прямо сказать, продлению жизни моего PC. Конечно, у упомянутых мной программ есть и недостатки, но о них, как раз, говорить не хочется.
Константин Даниленко
BAT-файлыПродолжение. Начало в Выпуске 25.Закон Первый. ВАТ-файл – это программа на алгоритмическом языке DOS; Закон Второй. ВАТ-файл может принести вреда больше, чем пользы; Закон Третий. ВАТ-файл не умеет больше того, что умеет DOS.
Последней темой станет конструкция цикла. Формат ее таков: for %%буква in (парам1 парам2 [парам3]...) do команда
Цикл работает следующим образом. Переменной %%буква присваивается значение
парам1, после чего выполняется команда. Последняя должна зависеть от переменной
%%буква, в противном случае цикл теряет смысл. Затем то же происходит для
парам2 и так далее, пока список в скобках не будет исчерпан.
for %%b in (tmp obj bak) do del *.%%b Цикл переименовывает файлы 01.ТХТ, 02.ТХТ ... 99.ТХТ и THREE.TXT в 001.ТХТ, 002.ТХТ ... 099.ТХТ и 0THREE.TXT соответственно, иными словами, дописывает символ 0 в начало имени: for %%a in (*.txt) do ren %%a 0%%a Цикл копирует пять первых томов, созданных архиватором RAR, из директории C:\TEST в директорию, заданную первым параметром ВАТ-файла: for %%c in (rar 00 01 02 03) do copy c:\test\*.r%%c %1
Может показаться, что количество параметров ВАТ-файла не должно превышать
девяти. В самом деле, ведь они снабжаются именами %1...%9. Однако в
действительности ВАТ-файл не может обрабатывать более девяти параметров
ОДНОВРЕМЕННО. Согласитесь, это не одно и то же.
ВАТ-файл парам1 парам2 ... парам16
Изначально окно занимает крайнее левое положение. Каждая команда shift сдвигает
окно на один параметр вправо. Следовательно, чтобы увидеть
парам16
, надо
выполнить команду shift... сколько? Правильно, 7 раз. При этом фактическому
парам9
будет соответствовать формальный %2, а фактические
парам1
...
парам6
окажутся безвозвратно забыты.
И, напоследок, несколько кратких замечаний, не вошедших в предыдущий текст.
Статья Константина Даниленко "BAT-файлы" выложена на сайте: http://stacmv.boom.ru/lib_articles_batchfiles.html
В прошлом выпуске, Константин Даниленко приводил пример bat-файла для архивирования некоего каталога и уделил особое внимание, так называемой, "защите от дурака". Тем самым, он опередил меня и показал, зачем и как выполнять эту защиту. Мероприятия по организации такой защиты мы будем называть защитным программированием. Этот термин имеет более широкое значение, чем "защита от дурака". "Дурак" это обычно пользователь, но защищать программу приходится не только от пользователя. Большинство современных программ состоят из множества процедур и функций, которые, то и дело, друг друга вызывают. Поэтому приходится защищаться и от вызывающих процедур или функций, передающих не верные параметры. Правильная работа процедуры зависит от многих условий. Мы, как программисты-любители, обычно предполагаем, что все они выполняются и не утруждаем себя проверкой, действительно ли это так. Эти условия могут быть очень разными. Например, если процедура принимает параметр, то условием ее правильной работы может быть принадлежность параметра к определенному множеству значений. Скажем, функция SQR(x), предполагает, что x>=0. CHR$(code) предполагает, что (code>=0 AND code<=255). Если какое-то условие правильной работы функции не выполнено, то и сама функция не может правильно отработать. Она может отработать не правильно, но это нам не нужно, т.к. это может быть опасно (например, для пользовательских данных, для работы компьютера и т.п.). Поэтому если функция или, в общем случае, программа не может правильно отработать в силу каких-то причин, то ее работу надо принудительно остановить и, возможно, попытаться устранить проблему. В простейшем случае нужно выдать сообщение об ошибке и завершить работу программы. При этом другие выполняющиеся программы не страдают. За свою жизнь вы, наверное, сталкивались с тысячами различных сообщений об ошибках: от прикладных программ, от операционной системы, от BIOS. Это говорит о том, что все эти программы имеют некий механизм защиты, т.е. защитное программирование придумал не я, а это совершенно нормальная практика у всех программистов. Элементы защиты имеют все ваши Бейсик-программы, компилятор добавляет соответствующие куски кода при компиляции. За примерами далеко не надо ходить. Возьмите оператор INPUT a%. Если на его запрос вместо числа введете строку, то будет выдано сообщение типа "Redo from start" и запрос будет повторен (конкретные действия зависят от используемого компилятора). В вашей программе нет фразы "Redo from start", откуда же она взялась? Дело в том, что такая реакция на неправильный ввод реализована в операторе INPUT. Другой пример, если вы с помощью команды OPEN попытаетесь открыть не существующий файл, программа выдаст сообщение, что "File not found." и прекратит работу. Вы не программировали такого поведения, это сделал за вас компилятор, а точнее разработчик компилятора. Кстати, одно из преимуществ языков программирования высокого уровня состоит в том, что компилятор за программиста выполняет множество рутинных операций. Ну и последний пример. Если при работе с массивом вы обратитесь к элементу, которого не существует ( DIM a(10): PRINT a(23) ), то опять возникнет ошибка с соответствующим сообщением и традиционным завершением работы программы. Все вышеперечисленные примеры демонстрируют использование принципа защитного программирования на низком уровне, т.е. на уровне функций, предоставленных нам операционной системой, компилятором Бейсика и пр. Но есть еще и высокий уровень. В нашем случае это уровень Бейсик-программы, нашей программы. Ведь, кроме использования системных функций мы пишем и свои и должны сами заботиться о возможном возникновении ошибок. Можно оставить это на совести системы и компилятора, как мы делали раньше. Но у такого решения есть недостаток. Который состоит в том, что... Нам не хочется, чтобы наша программа прерывалась сама, когда ей это вздумается. Да и пользователям этого тоже не хочется. Вспомните, сколько радости вам доставляли сообщения Windows типа "Программа совершила недопустимую операцию и будет закрыта". Столько радости, что иногда хочется клавиатуру сломать об монитор. Это зловещее сообщение, между прочим, не что иное, как демонстрация наличия защиты на уровне операционной системы. Поэтому функции защиты, хотя бы частично, должны быть реализованы нами и на высоком уровне, чтобы не давать системе лишнего повода прекратить работу нашей программы. Прошу вспоминать об этом при выполнении домашних заданий. Начиная с задания basic/3, реализация механизмов защиты является необходимым условием сдачи задания. Также прошу уделить внимание вопросам защиты тем, кто будет принимать участие в наших проектах. Работа над проектами будет вестись несколькими программистами. Значит, функции, написанные вами, будут использовать (вызывать) другие программисты. Как именно они будут это делать заранее сказать сложно, поэтому нужно предусмотреть все возможные, а также невозможные и вовсе невероятные ситуации. Чтобы не получилось потом, что из-за того, что ваша функция не обработала ошибку, рушится вся программа. Кстати, из-за пренебрежения защитным программированием взорвалась не одна ракета, не говорю уже про неисправность на крейсере YorkTown (помните?). Что конкретно нужно предпринимать для защиты мы будем изучать в последующих выпусках, сегодня только начнем. Итак, первое, на что следует обратить внимание, это данные получаемые программой или подпрограммой извне. Сюда относятся данные, вводимые пользователем с клавиатуры, читаемые из файла, параметры, передаваемые в подпрограмму, параметры, передаваемые в программу через командную строку и т.п. Правильность всех этих данных надо подвергать сомнению, а сами данные дополнительной проверке. Но опасность таится не только во внешних данных, полученные (вычисленные) внутри программы данные тоже могут вызвать ошибку. Следует обратить внимание на данные, которые передаются на обработку стандартным Бейсиковским функциям и операторам. Например, при работе с числами и математическими вычислениями следует уделять внимание таким вещам как "деление на ноль", "переполнение", да и просто следить за нахождением чисел в требуемом диапазоне. Что такое "деление на ноль" я уже рассказывал (см. Выпуск 22), а что такое "переполнение" демонстрирует следующий пример. Но сначала, так сказать, о физике явления. Известно, что переменная типа INTEGER может принимать значения от -32768 до 32767, т.к. она занимает в памяти 16 бит, а 2^16=65536 (поделите пополам, получите 32768). Однако, если к 30000 (допустимое значение) прибавить 10000 (допустимое значение) получится 40000 (недопустимое значение). Что должно при этом произойти (под переменную отведено 2 байта, а 40000 в это место не помещается) сказать сложно. Да это и не так важно, потому что компилятор Бейсика такие ситуации отлавливает, выдает сообщение о переполнении и, да-да, принудительно завершает программу. А вот и пример:
REM Префикс % - признак типа INTEGER DIM a%,b%,c% Пробуйте эту программу при разных a% и b% и вы поймете страшную вещь: при определенных значениях программа работает правильно, а при других (но тоже определенных) - не правильно. Вся "страшность" этой ситуации в том, что не определишь заранее, когда программа сработает, а когда нет, поскольку нельзя точно узнать, какие значения будет вводить пользователь. Для этой простой программы, конечно, определишь, но, ведь, программы бывают и посложнее. Ну а здесь что можно сделать? Решение довольно простое: использовать переменную с& вместо c% (тип LONG вместо INTEGER), немного изменить алгоритм, а переменные a% и b% оставить как есть. Переменная типа LONG занимает 4 байта (32 бит) и может вместить любую возможную сумму a%+b%. Общее правило такое: тип переменной результата должен быть длиннее (в байтах) чем типы операндов. Если в программе подразумевается, что сумма должна иметь тип INEGER, то переменную c& все равно нужно объявлять как LONG, затем сравнивать ее значение на предмет попадания в допустимый для INTEGER диапазон и обрабатывать ошибку в случае необходимости, а не доводить дело до прекращения работы программы. Вот пример, как все выше сказанное можно проделать:
DIM a%, b%, c&, sum% DO INPUT a% INPUT b% c& = a% c& = c& + b% IF c& < -32768 OR c& > 32767 THEN PRINT "Переполнение." LOOP WHILE c& < -32768 OR c& > 32767 sum%=c& PRINT sum% Почему вместо c&=a%+b% я написал c&=a% : c&=c&+b% ? Здесь нужно вспомнить (или узнать), как работает оператор присваивания. Сначала вычисляется правая часть выражения, а потом этот результат присваивается переменной в левой части. Причем, до присвоения вычисленный результат нужно где-то хранить. Для этого выделяется некоторая область памяти, размер которой соответствует размеру самого длинного типа в выражении. В исходном примере таким типом является INTEGER, и сумма может в него не поместиться. Переполнение возникнет еще до присвоения c& значения. В строке c&=c&+b% самым длинным типом будет LONG. Другим вариантом решения проблемы будет задание типа LONG для переменных a и b и их анализ еще до сложения. Еще одна проблема, относящаяся к защите заключается в операторе INPUT. Попробуйте запустить второй (исправленный) вариант примера и введите в качестве a% или b% число не допустимое для типа INTEGER, например, 40000. Посмотрели? А теперь посмотрите Выпуск 22, где рассказывалось, как решать такие проблемы. Если в двух словах, то нужно вводить числовые данные, используя строковые переменные в качестве буферов. Теперь еще о вводе. Существуют возможности сделать пользовательский ввод с клавиатуры безопасным. Основаны они все на следующей методике. Для начала отказываемся от использования оператора INPUT для ввода с клавиатуры. Затем пишем собственную функцию, чья работа будет основываться на использовании функции INKEY$. Наша функция может быть любой и она может всячески затруднить ввод пользователем не правильных данных. Знания и соответствующие навыки для того, чтобы реализовать это, вы уже имеете, если выполнили задачу basic/1/6 ("печатная машинка"). Для примера рассмотрим функцию для ввода телефонных номеров в формате ###-##-##, где "решетка" соответствует цифре от 0 до 9. Это кажется сложнее, чем просто оператор INPUT, но никто и не говорил, что будет легко :-)
DECLARE FUNCTION InputPhone$ () PRINT "Введите телефонный номер:"; phone$ = InputPhone$ Обязательно проверьте работу этой программы на своем компьютере, постарайтесь уловить и понять ее идею. Это важно. Написав подобную функцию единожды, вы затем можете использовать ее во всех программах, где она будет уместна. Часто вместо написания специальных функций для ввода, этот ввод упрощают. Цель та же самая - избежать ошибок ввода. Упрощение сводится к тому, что пользователю предлагается нажать меньшее количество кнопок (в идеале одну). Т.е. ему предлагается ряд ответов на вопрос, из которых он должен выбрать один. Во втором ДЗ есть задача, где вы должны написать программу-тест. Некоторые из вас уже выполнили эту задачу, организовав ввод ответов тем или иным способом. Вот пример неудачного решения:
... PRINT "В каком году была война 1812 года?" PRINT "1) 1147" PRINT "2) 1812" PRINT "3) 1941" PRINT "4) 2000" INPUT answer$ IF answer$="1812" THEN ... Формально в этом фрагменте ошибок нет. Но, друзья, не советую вам относиться к своей работе формально, тем более к своему хобби. Автор этого фрагмента ошибся дважды: 1) с точки зрения защиты: пользователь может ввести все, что ему захочется; 2) с точки зрения удобства интерфейса: когда тебе предлагают несколько вариантов ответов, таки хочется выбрать один из них, т.е. ввести 1,2 и т.п., а не весь ответ целиком. В последнем случае, кстати, можно легко ошибиться и недополучить заслуженные баллы на тестировании. Этот фрагмент стоило писать примерно так:
... PRINT "В каком году была война 1812 года?" PRINT "1. 1147" PRINT "2. 1812" PRINT "3. 1941" PRINT "4. 2000" DO ch$=INKEY$ LOOP UNTIL ch$>="1" AND ch$<="4" select case ch$ case "1" ... Вот так. Теперь я бы хотел, чтобы вы вспомнили программу TVStart, которую мы написали в 24-м выпуске. Рассмотрим ее с позиции защитного программирования. Сначала посмотрим, что в ней реализовано с этой позиции верно, а затем взглянем на недостатки. Итак, найдите у себя выпуск 24 и просмотрите быстренько исходный текст программы TVStart. Найдите место, где мы анализируем командную строку, а точнее параметры, с которыми была запущена наша программа.
... 'Берем первый символ командной строки, являющийся цифрой day$ = LTRIM$(STR$(VAL(LEFT$(LTRIM$(COMMAND$), 1)))) ... Вы знаете, что для того чтобы взять первый символ стоки достаточно использовать LEFT$(COMMAND$,1). Мы, однако, в программе нагородили невесть чего. Посмотрим как происходит присвоение значения переменной day$, распишем присвоение на нескольких стоках:
day$=LTRIM$(COMMAND$) 'Параметры командной строки могут иметь ведущие 'пробелы, которые мы убираем с помощью LTRIM$(). day$=LEFT$(day$,1) 'Берем 1-й символ, он может быть каким угодно, 'только не пробелом, пробелы мы только что 'убрали. dayval=VAL(day$) 'Переводим символ в число. Если символ не был 'цифрой, то dayval=0. day$=STR$(dayval) 'Переводим число в строку (строка нам нужна в 'итоге). day$=LTRIM$(day$) 'Избавляемся от ведущего пробела, который всегда 'появляется при переведе положительного числа в 'строку с помощью STR$. Опытные программисты обычно записывают подобные конструкции в одну строку, как сделали мы в программе. Однако есть ситуации, когда важно расписать подобные моменты подлиннее. Это стоит делать, если "расписываемый" фрагмент кода важен для понимания сути работы программы человеком(!). Наш фрагмент выполняет вспомогательные функции, поэтому записан покороче. Теперь смотрите, как реализован основной цикл программы:
i = 1 'Читать файл будем начиная с первой строки htmeof = ReadLine(HTM, i, s$) 'Читаем 1-ую строку файла 'Если файл пустой, то htmeof 'будет равно -1, и программа 'закончит свою работу. DO UNTIL htmeof 'Пока не достигнут конец файла... FOR p = 1 TO 2 'Для 2-х строк поиска pattern()... 'В прочитанной из файла строке ищем строку поиска, вне зависимости 'от регистра. spos = INSTR(LCASE$(s$), LCASE$(pattern$(p))) IF spos > 0 THEN 'Если совпадение найдено... 'Заменяем цифру после "#d" на номер дня day$ CALL ReplaceChar(s$, spos + LEN(pattern$(p)), day$) 'Пишем модифицированную строку в файл. CALL WriteLine(HTM, i, s$, -1) END IF NEXT p i = i + 1 htmeof = ReadLine(HTM, i, s$) 'Читаем следующую строку из файла. LOOP END Вместо того, чтобы сделать цикл DO с постусловием и начать читать файл уже в цикле, мы вынесли первую команду чтения файла за пределы цикла. Как сказано в комментарии к соответствующей строке, если исходный файл окажется пустым, то программа завершит свою работу. Штатно! Действительно, если мы открыли файл, то нет никаких гарантий, что он содержит какую-то информацию, а не является пустым. Обратите теперь внимание, что чтение строки из файла выполняет не процедура, а функция. И более того, обратите внимание, что функция эта возвращает не прочитанную строку, а признак ошибки (достигнут конец файла или нет?). В идеале, с точки зрения защитного программирования, нам следует всюду отказаться от процедур и использовать только функции, возвращающие признак ошибки. Этот признак мы должны проверять сразу после вызова функции, чтобы определить, возникла ли ошибка при ее выполнении и предпринять меры, если так. В реальности мы используем такие функции только в ситуациях, когда возникновение ошибок наиболее вероятно, в противном случае сильно повышается трудоемкость написания программы. В нашем случае вероятность возникновения ошибки равна 100%, так как любой файл имеет конец. Заметьте, что конец файла это в общем-то нормально, но для нашей функции ReadLine() это ошибка, т.к. в этом случае она не может прочитать строку, т.е. выполнить свою задачу. Кстати, в языках типа Си совсем нет процедур, только функции... . Теперь смотрите сюда:
spos = INSTR(LCASE$(s$), LCASE$(pattern$(p))) Мы ищем позицию в строке s$, с которой начинается подстрока pattern$(p). При этом мы защищаемся от того, что строка s$ и подстрока будут в разных регистрах (прописные - строчные) и переводим их в нижний регистр. Подобный прием следует также применять при вводе символов с клавиатуры, если регистр не имеет принципиального значения. Например, если вы задаете пользователю вопрос, требующий ответа Y/N, то нужно рассматривать не только ответы "Y" и "N", но и "y", "n". Помните, пользователь обычно не следит постоянно за состоянием CAPS LOCK. Следующий момент. ReplaceChar и WriteLine реализованы как процедуры, а не функции. Казалось бы, это противоречит тому, о чем мы говорили чуть выше о ReadLine(). На самом деле нет. ReadLine() и WriteLine используют разные подходы к отлову ошибочных ситуаций. ReadLine() возвращает признак и ошибки, и обработка этой ошибки возлагается на вызывающую программу. WriteLine обрабатывает все ошибки сама, тем самым для вызывающей программы ее работа является безошибочной. Очень важным моментом при проектировании программы является определение где, когда и как должны обрабатываться те или иные ошибки. Более правильно обнаруживать ошибки на низком уровне, а обрабатывать на высоком. Так работает ReadLine. Однако и второй подход имеет право на жизнь. Рассмотрим его подробнее на примере процедуры ReplaceChar. Процедура вставляет заданный символ в заданную позицию заданной строки. Т.е. имеют место три параметра, которые мы должны рассматривать с позиции защитного программирования. Мы могли бы реализовать ReplaceChar в виде функции, которая бы возвращала признак ошибки, если бы параметры имели не подходящие значения. В этом случае обрабатывать ошибку пришлось бы вызывающей программе. Эта обработка могла бы свестись к корректировке параметров и повторному вызову ReplaceChar. Но так как в нашем случае параметры вводятся не пользователем, то их корректировка затруднена (хотя и возможна). Поэтому скорее всего мы бы завершали работу программы, если бы с ReplaceChar возникли проблемы. Однако мы пошли другим путем - организовали обработку ошибок внутри процедуры.
SUB ReplaceChar (s$, chpos, ch$) '# Замещает символ строки s$ в позиции chpos '# на символ ch$. '# Если chpos больше длины строки, то строка '# удлиняется путем добавления в ее конец '# пробелов. Сначала мы проверили значение chpos, и если оно больше длины строки, то строка должна быть удлинена. Теперь s$ и chpos соответствуют друг другу. Затем мы подвергаем сомнению тот факт, что символ ch$ действительно является символом, а не строкой из нескольких символов. Кстати, допустим программисту, использующему нашу функцию нужно вставить в первую позицию слова "дом" первую букву из слова "ком". Он может сделать это так:
CALL ReplaceChar("дом",1,LEFT$("ком")) Но если он видел исходный текст процедуры он может написать так:
CALL ReplaceChar("дом",1,"ком") Второй вариант работает быстрее, т.к. LEFT("ком",1) будет вызвана один раз, а не два, как в первом варианте. Но! Возникает другая сложность. Тот факт, что процедура ReplaceChar выполняет LEFT$(ch$,1) не описан в документации (к документации в данном случае относятся комментарии, начинающиеся с "'#"). Таким образом во втором варианте гипотетический программист использует недокументированную возможность ReplaceChar. Исходный текст процедуры ReplaceChar может находиться не в том же файле, что и вызывающая ее программа и может обслуживаться совсем другим программистом. В один прекрасный момент программист, обслуживающий текст ReplaceChar, может решить ускорить работу своей процедуры и переложить ответственность за значение ch$ на вызывающую программу. А он имеет право сделать такое изменение, ведь оно не противоречит документации. Программы, которые используют недокументированную особенность ReplaceChar станут работать неправильно.
Возможно вы не совсем поняли, как это может произойти. Поясняю. мы с Вами работаем над проектом. Процедуру ReplaceChar пишет alien, а другую процедуру, вызывающую ReplaceChar, пишет кто-нибудь еще. Ну, значит, написали они свои процедуры. Потом мы решаем выпустить вторую версию, которая будет работать быстрее и иметь меньший размер. Все оптимизируют свои процедуры, alien убирает недокументированную особенность. Программа, состоящая из обновленных процедур компилируется и выкладывается на сайт. Заметьте, что компилятор не заметит ошибки, т.к. эта ошибка логическая и не отлавливается никакими компиляторами. Я немного отвлекся от защитного программирования, но ничего, зато вы, я надеюсь, поняли важную роль документации и опасность использования недокументированных особенностей. Но вернемся к защитному программированию. В программе TVStartесть еще несколько интересных моментов (найдите их сами :). Давайте лучше посмотрим на ее недостатки. По большей части они (по крайней мере те, что бросаются в глаза) связаны с файлами. Начнем с того, что мы открываем файл "c:\tvstart.htm". При этом мы не проверяем, есть ли такой файл вообще по указанному пути. Если его там нет, программ будет принудительно завершена. А как его может там не быть, если мы сами его создали? Почти сразу же после сдачи выпуска 24 я обратил внимание на то, что программа TVStart не работает на моем компьютере, завершаясь с сообщением об ошибке 74 ("Переименование через диски"). Повторный запуск программы приводил к ошибке открытия файла "c:\tvstart.htm", потому что его не было на месте. При написании программы мной была допущена ошибка в процедуре WriteLine в самом, казалось бы неожиданном месте.
ct$=TIME$ tmpfile$ = LEFT$(ct$, 2) + MID$(ct$, 4, 2) + RIGHT$(ct$, 2) + ".tmp" Имя временного файла представляет из себя нечто вида "165600.tmp". Заметьте, что не указан ни диск, ни каталог, где будет создан временный файл. В соответствии с принятыми в DOS соглашениями файл создается на текущем диске, в текущем каталоге. Если вспомнить, при каких обстоятельствах запускается программа TVStart, то можно предположить, что текущим диском будет c:. Я так и предположил. Однако у меня к моменту запуска TVStart текущим диском оказывался h:, а текущим каталогом \work (в этом "виноваты" всякие разные команды, имеющиеся в моем autoexec.bat). Именно там и создавался временный файл. Теперь взгляните на последние строки WriteLine:
KILL f$ '"Убиваем" исходный файл NAME tmpfile$ AS f$ 'Переименовываем временный файл. При попытке переименовать файл возникает ошибка, так как временный и исходный файл находятся на разных дисках, а переименование допустимо только в пределах одного диска (все понимают почему так?). Но еще до попытки переименовании исходный файл удачно удаляется и его, естественно, нельзя открыть при следующем запуске программы. Вот такая, вот, пакость! А исправляется эта ошибка очень просто - нужно создавать временный файл на диске c:.
ct$=TIME$ tmpfile$ = "c:\" + LEFT$(ct$, 2) + MID$(ct$, 4, 2) + RIGHT$(ct$, 2) + ".tmp" Вот так. На этом мне хотелось бы закончить сегодняшний наш разговор и попросить вас хорошенько подумать над тем, что вы сегодня узнали.
Рассылка для серьезных разработчиков: Интересные DOS программы (страница ссылок) http://www.opus.co.tt/dave/index.htm
|
http://subscribe.ru/
E-mail: ask@subscribe.ru |
Отписаться
Убрать рекламу | Рейтингуется SpyLog |
В избранное | ||