|
Разбираем заготовки. (редактор кодинга) Что внутри заготовки модуля. Каждый раз, когда вы начинаете работу с новым проектом - Delphi создает для вас файл проекта, и каждый раз, когда добавляете в проект форму, Delphi формирует для нее файл модуля (unit). Когда вы помещаете в форму какой-либо компонент, Delphi вставляет в вашу программу соответствующий программный код. А когда вы созреваете до написания обработчика событий, Delphi обеспечивает определенную инфраструктуру. Можно подумать, что Delphi выполняет всю работу, не так ли? Не волнуйтесь, и на ваш век хватит, и скоро вы в этом убедитесь. Но если сначала хорошенько разобраться в том, что за вас делает Delphi, вы получите приличную фору в познании того, как писать свои собственные гениальные программы. В тот момент, когда на вашем экране появляется новый пустой проект, работяга-Delphi уже написала две страницы программного кода. Первая страница, первоначально называемая Unit1, - это код, который отвечает за основную форму и все ее компоненты. Вторая, по умолчанию имеющая название Project 1, остается невидимой до тех пор, пока вы не выберете пункт Project Source из меню View. Project - это основной файл проекта, который объединяет все формы вашей программы. Итак, откройте новый проект и приготовьте скальпель, так как сейчас мы приступим к препарированию файлов, из которых состоит заготовка программы. Эта операция намного чище, чем вскрытие лягушек, и, кроме того, вам не придется страдать от тягостного запаха формальдегида. Щелкните на закладке Unit1 в Code Editor и раздвиньте рамки окна настолько, чтобы вы могли видеть весь (или почти весь) программный код. Он будет выглядеть примерно, так как показано на рисунке. Первая строка в файле Delphi обычно несет информацию о типе файла, с которым вы имеете дело, В данном случае это unit - программный модуль, который всегда зависит от других модулей. Модуль существует лишь для того, чтобы им воспользовалась основная программа или другой модуль, поэтому он никогда не прозябает в одиночестве. За ключевым словом unit сразу следует имя модуля (в данном случае Unit1), Когда Delphi создает модуль для поддержания новой формы, она всегда старается дать ему имя Unit1. Если это имя занято, она делает попытку присвоить имена Unit2, Unit3 и т.д. Юлий Цезарь сказал, что вся Галлия делится на три части. То же справедливо и в отношении каждого модуля (но чтобы сделать такое сравнение, требуется много таких Галлий). Таким образом, каждый модуль включает следующие разделы: интерфейса, реализации и инициализации. Раздел интерфейса - это как улыбающееся лицо, которое данный модуль обращает к миру. Любая программа или модуль, использующий данный модуль, может получить доступ ко всему, что есть в этом разделе, (Поэтому не следует помещать сюда ничего сомнительного) С другой стороны все, что находится в разделе реализации, является "частной собственностью" данного раздела и доступно только лишь внутри него самого. И, наконец, команды, которые вы помешаете в разделе инициализации, автоматически выполняются при запуске любой программы, которая использует данный модуль. Раздел интерфейса начинается со слова interface и продолжается до начала раздела реализации - строки, содержащей слово implementation. Каждый модуль должен содержать эти два раздела, даже если они пустые. Это сказано не для красного словца, это закон! Раздел инициализации необязателен, и Delphi не встраивает его в генерируемые модули. Если раздел инициализации существует, то он начинается со слова begin и заканчивается словом end. (точка обязательна). Модуль, у которого нет раздела инициализации, все равно заканчивается словом end., хотя ему и не предшествует слово begin. Раздел интерфейса: секретные сведения Первой строкой раздела интерфейса является оператор uses - список других модулей, с которыми связан данный модуль. Модули, которые Delphi строит для вас, не такие уж и неприступные - они вполне могут поддержать вас "с небольшой помощью своих друзей" (почти по Битлз). Модули из оператора uses являются стандартными и поставляются вместе с Delphi, причем каждый из них обладает своими собственными специфическими способностями. SysUtils - набор системных утилит, таких как подпрограммы обработки строк. преобразований даты/времени и функций управления файлами WinProcs - обеспечивает доступ к функциям в библиотеках Windows GDI, USER и KERNEL (ведущий триумвират Windows) WinTypes - типы данных и значения констант, которые используются в функциях Windows, определенных в WinProcs Messages - числовые константы, относящиеся к сообщениям Windows, и типы данных, связанные с отдельными сообщениями Classes - низкоуровневые элементы системы компонентов Delphi, т.е. ее винтики и гаечки Graphics - графические элементы типа шрифтов, перьев, кистей, растровых изображений и т.п. Controls - элементы среднего уровня системы компонентов Delphi Forms - компонент Form, невидимое приложение (Application) и компоненты обслуживания экрана Dialogs - общие диалоговые компоненты За оператором uses следует блок объявления типов, представленный словом type. Блок объявления типов содержит одно или несколько объявлений типов данных. С чем их едят? Сейчас все станет ясно, если, конечно, копнуть немного глубже. Так вот, все данные в вашем компьютере хранятся в виде битов и байтов. Например, одни и те же четыре байта могли бы представлять следующее: 78, 101, 105 и 108 В частности, рассматриваемый модуль объявляет только один тип данных - класс объектов с именем TForm1. Если не очень углубляться в детали, то объект какого-либо класса представляет собой набор данных и функций, которые выполняют операции над этими данными. Припоминаете? Да, формы и компоненты являются примерами объектов. Процесс заворачивания данных вместе с соответствующими функциями в красивую упаковку (объект), называется инкапсуляцией. Первая строка блока объявления типов TForml Class(TForm) говорит о том, что объект TForm1 наследует все свойства и возможности общего объекта TForm. Со всем, что умеет делать TForm, справится и TForm1. Например, TForm обладает свойствами Caption и Color. Следовательно, и объекту TForm1 также присуши эти свойства. А если в арсенале TForm есть метод Close, то, значит, и TForm1 также может им похвастаться. В данном случае объявление класса представляет собой только пустую заготовку. Оно содержит секции private и public, но каждая из них состоит только из комментариев (мы еще вкратце поговорим о комментариях). Если бы здесь были еще какие-нибудь строки программного кода, то они касались бы отличий TForml от TForm. TForml мог бы расширить возможности TForm путем добавления новых полей данных, объявлением новых методов или удалением существующих. Поля и методы из секции private доступны только самим объектам, а обитатели секции public "видны" любой программе или модулю, который использует данный модуль. Программисты любят говорить, что TForm1 является потомком ТForm, а TForm - это предок TForm1. Если бы у TForm1 были свои собственные потомки, они являлись бы и потомками TForm, а TForm был бы предком. Услышав такие теплые домашние словечки, вы, наверное удивитесь, когда узнаете, что объекты, по выражению программист имеют родословную (по-научному это называется иерархией объектов). Далее следует блок объявления переменных, обозначенный ключевым словом var. Переменная представляет собой некоторую область памяти, которую программа резервирует для хранения данных. Каждая переменная имеет имя, поэтому она не потеряется в толпе, а также тип данных, который позволит Delphi yзнать, какого сорта информацию хранит данная переменная. Далее переменным будет уделено много внимания. Объявления типа и переменной, о которых только что шла речь, находятся после строки interface и перед строкой implementation, т.е. они находятся внутри раздела интерфейса конкретного модуля. Это значит, они доступны любой программе или модулю, которые используют данный модуль. Раздел реализации: тайна, покрытая мраком Раздел реализации любого модуля можно сравнить с "задней комнатой", где и выполняется настоящая работа - никакие внешние раздражители сюда не проникают. Все контакты с другими программными модулями происходят через раздел интерфейса. Переменную, объявленную в разделе реализации, нельзя проверить или изменить с помощью программы, являющейся внешней по отношению к данному модулю. Точно так же любую функцию, объявленную только в разделе реализации, нельзя вызвать из внешней программы. Раздел реализации заготовки модуля содержит лишь одну строку, а именно директиву компилятора {$R *.DRM}. Эта строка указывает Delphi на включение всей вашей работы в разрабатываемую форму в файле .DRM содержится информация о свойствах заданной формы и каждого ее компонента. Delphi заменяет звездочку на имя модуля, т.е. в данном случае с помощью этой строки в программу будет включен файл UNIT1.DRM, Никогда не удаляй не изменяйте строки {$R *.DRM}! Вы слышите - никогда! Директивы компилятора представляют собой приказы, которые относятся непосредственно к Delphi, в то время как команды переводят машинный код для получения результирующей программы. Каждому из этих параметров соответствует какая-то директива компилятора. Например, {$S+} в исходном файле Delphi включает проверку стека, невзирая на то, как вы установили параметры компилятора. Кроме того, в начало раздела реализации вы можете добавить второй оператор uses, предназначенный для модулей, которые нужны только программному коду в разделе реализации. Вы должны делать это только в случае, когда два модуля зависят друг от друга. Если модуль Альфонс использует модуль Гастон. а модуль Гастон использует модуль Альфонс, то этот вариант может заставить Delphi зациклиться. Раздел инициализации: последние приготовления Некоторые модули вначале должны "прогреть мотор" или выполнить какие-то другие подготовительные действия. Программные операторы, содержащиеся в разделе инициализации, выполняются в начале любой программы, которая использует этот модуль, или использует другой модуль, который, в свою очередь, использует данный модуль, или же использует некоторый модуль, который использует модуль, который, наконец, использует данный. Тут и запутаться не долго. Если в данном модуле есть нечто, что каким-то образом нужно инициализировать перед использованием, то это нечто должно находиться в разделе инициализации. Поскольку сама по себе Delphi не нуждается в создании этого раздела, он не проиллюстрирован примером. А если бы он был, то занимал бы всего несколько строк. Мы практически никогда не будем использовать этот раздел. Внутренности, заготовки программы А теперь щелкните на закладке Project1 в Code Editor, чтобы рассмотреть смехотворно крошечную основную программу, сгенерированную Delphi. Из первой строки мы узнаем, что этот файл определяет программу с именем Project1. В отличие от модуля, программа может стоять особняком, и, действительно, она является независимым элементом. Сразу после строки program идет оператор uses, напоминающий своего коллегу в модуле. Данная программа использует два модуля: Forms и Unit1. Forms - это встроенный модуль Delphi, который объявляет компоненты Form и Application. Unit1 - это, конечно, тот модуль, который вы только что видели выше. Следует заметить, что в комментарии после имени модуля Delphi великодушно указала имя формы, соответствующей Unit1. Подобно всем другим комментариям, он не влияет на программный код, но Delphi использует его, чтобы помочь связать модуль с его формой. Никогда не модифицируйте комментарии, которые Delphi вставляет в оператор uses основной программы. Любой текст, заключенный в фигурные скобки {подобно этим} или в круглые скобки со звездочкой (*подобно этим*), будет проигнорирован Delphi во время компиляции программы. За исключением случая, описанного выше, комментарии предназначены только для вас. Вы можете и должны вставлять комментарии в свои программы. В противном случае, когда шесть месяцев спустя вам захочется внести изменения в текст программы, вы, возможно, начисто забудете, для чего была предназначена эта программа. Редактор Delphi отображает комментарии особым цветом и шрифтом, поэтому их легко заметить. Кроме того, вы можете использовать символы комментариев, чтобы закомментировать фрагменты своей программы. Например, если некоторая часть вашей программы вызывает проблемы, и вы не знаете их причины, можно попытаться закомментировать различные ее части, чтобы выяснить, что именно заставляет эти проблемы исчезнуть. Для согласованности я предлагаю обращаться к фигурным скобкам {подобно этим} в случае настоящих (постоянных) комментариев и использовать круглые скобки со звездочкой (*подобно этим*) для временного комментирования программного кода. Хотя, закомментировать одну строку удобнее вот так //далее следует комментарий. Текстовая строка, ограниченная символами комментария, почти всегда означает комментарий. Я говорю почти, так как единственным исключением является случай, когда в качестве самого первого символа строки стоит знак доллара, В этом случае вы имеете дело с директивой компилятора, а не с комментарием. За оператором uses следует директива компилятора, по которой для данной программы загружается файл ресурсов. Файл ресурсов содержит данные, которые встраиваются компилятором в результирующий выполняемый программный файл, Файл .DFM, включаемый во встроенный модуль Delphi, является разновидностью файла ресурсов. В новой программе этот файл не содержит ничего, кроме назначенной по умолчанию пиктограммы. Далее идет основное тело программы, начинающееся со слова begin и заканчивающееся словом end. (После слова end обязательно наличие точки.) Тело программы очень напоминает раздел инициализации модуля, но в данном случае оно обязательно. В типичной Delphi-программе основное тело, как правило, крошечное, так как все действия разворачиваются в модулях. В этой программе содержится только две строки, одна из которых предназначена для инициализации Form1 и подключения ее к приложению, а вторая - для запуска приложения. Один из способов использования типов переменных в Delphi заключается
в том, чтобы предотвратить передачу методам некорректных типов переменных.
Если определенный метод работает с числами, то Delphi не позволит передавать
ему строки символов. Если метод работает только с целыми числами. Delphi
не разрешит передачу числа с дробной частью. Но если метод имеет дело
с объектами, происходят несколько странные вещи. Методы Delphi, которые
ожидают TForm, не возмущаются, если вместо этого они получают TForm1.
Почему? Программа с любым другим именем... Как указывалось ранее, Unitl - это имя, которое Delphi присваивает модулю по умолчанию. Можно легко заменить его на что-то более интересное (и мнемоническое). Выберите пункт Save Project из меню File и введите в качестве имени модуля One, а в качестве имени проекта - OneProject. Взгляните на окно Code Editor и увидите, что Delphi обновила закладки - теперь это One и OneProject. Первая строка модуля выглядит сейчас следующим образом: unit One, а вот первая строка файла проекта:program OneProject: Кроме того, относящийся к проекту оператор uses содержит One и ONE.PAS, где обычно значились Unit1 и UNIT1.PAS. Для того чтобы отразить новые имена, Delphi обновила исходные файлы. А теперь переключитесь в окно Object Inspector и замените свойства формы Name с Form1 на FormMain. И снова в соответствии с этим Delphi изменит исходный код. При этом объектный тип для формы изменится на TFormMain, а в основной программе изменятся даже комментарии. Поскольку заголовок формы в точности совпадает с ее именем. Delphi изменила также и заголовок. Все, что Delphi делает для вас, не должно выполняться вами в программе самостоятельно. Например, если вы хотите дать модулю или программе новое имя, не редактируйте его вручную. Вместо этого используйте пункты меню Save As-. или Save Project As,.., чтобы сохранить его или ее под новым именем. И не изменяйте имени формы в коде модуля - это следует делать в окне Object Inspector. Если же вы выполните эти изменения самостоятельно, Delphi не будет компилировать вашу программу! Какие изменения вносит компонент Построение заготовки основной программы и модуля - это только начало программистской работы Delphi. Каждый раз, когда вы добавляете или переименовываете компонент, Delphi сразу выполняет все необходимое, чтобы внести этот компонент в вашу программу. Расположите окна Form и Code Editor на экране таким образом, чтобы можно было видеть оба окна сразу. Если нужно, можете перекрыть окно Object Inspector, Находясь в окне Code Editor, щелкните на странице One и убедитесь, что раздел объявления типов для TFormMain находится в поле зрения. Щелкните на пиктограмме Button на странице Standard палитры компонентов и переместите мышь на форму, но пока не размешайте кнопку. Устремите свой взгляд на объявление TFormMain и щелкните мышью. Вы заметили, что произошло? Объявление TFormMain расширилось, чтобы включить
строку Эта строка представляет собой декларацию независимости объектного типа TFormMain. Теперь он отличается от простого TForm тем, что включает поле данных с именем Button1 с типом TButton. Delphi занимается взаимосвязями Когда вы добавляете компонент, Delphi вставляет строку в объявление объектов формы. Это позволяет вашей программе обращаться к компоненту по имени, если она захочет его вызвать. Но требуется сделать еще массу приготовлений, которыми Delphi не займется до тех пор, пока вы не сохраните программу. Пролистайте файл в Code Editor таким образом, чтобы стал видимым оператор uses. He отрывайте от него глаз. пока будете нажимать <Ctrl+S>, чтобы сохранить файл. А теперь удалите кнопку из файла. Во время сохранения файла в пункте uses появилось имя модуля StdCtrls. Это имя принадлежит модулю, который поддерживает такие стандартные средства управления, как кнопки, строки ввода, списки и т.п. А при удалении кнопки из формы строка Button1 в объявлении объектного типа TFormMain исчезает. Но в пункте uses модуль StdCtrls все еще присутствует. Почему же Delphi все-таки не удаляет имени модуля? На это есть две причины. Во-первых, модуль StdCtrls поддерживает много компонентов Delphi, а не только те из них, которые имеют тип TButton (тем более, не конкретный компонент типа TButton no имени Button1). Для того чтобы решить, не опасно ли удалять этот модуль из оператора uses, Delphi пришлось бы проверить все без исключения остальные компоненты формы. Во-вторых, и что более важно, возможно, вы написали код, которому требуется этот модуль. Delphi знает все о своем собственном коде, но не занимается психоанализом вашего кода. Таким образом, удалять этот модуль из оператора uses - просто опасно. Конечно, если вы точно знаете, что там этот модуль не нужен, удаляйте. Ведь это лишние килограммы в вашем изящном кодинге. Любой компонент, который вы размешаете на форме, будет расширять перечень модулей в операторе uses, если, конечно, еще нет поддерживающего модуля для данного компонента. Приступая к новой программе, вы, возможно, войдете в состояние созидательного безумия, добавляя и удаляя компоненты с крайней непринужденностью. По всей вероятности, ваша окончательная версия будет включать ненужные вам модули, и, вероятно, некоторые фрагменты программного кода, и данные просто будут зря занимать место в вашей откомпилированной программе. Чтобы избежать разбухания программы, удалите из оператора uses все, за исключением модулей, которые Delphi поместила туда при зарождении программы, а также кроме тех модулей, которые вы добавили сами. В следующий момент, когда вы будете сохранять файл, те модули, которые вам все еще необходимы, как по мановению волшебной палочки, появятся снова. Существует небольшая вероятность того, что если следовать этому совету, можно нарваться на возмущение Delphi. Если Delphi вдруг начнет жаловаться, что, мол, ваш программный код содержит неизвестный идентификатор, это означает, что вы удалили на один модуль больше, чем нужно. Без паники! Просто обратитесь к справочной системе по поводу идентификатора-нарушителя, чтобы определить, какой модуль его поддерживает, а затем верните его имя обратно в оператор uses. Так же, вам поможет отмена удаления всех модулей сразу (а удобнее не удалять а закомментировать), а затем, последовательное удаление модулей с последующей компиляцией. Где застопорится, там и ошибка. Если вы изменяете свойство Name какого-то компонента (а проще - его имя), используя окно Object Inspector, вы заметите изменения и в окне Code Editor. Delphi даже переименует все обработчики событий, чьи действующие по умолчанию имена основаны на этом компоненте. Если вы замените Button1 на TikTok, то имя Button1Click будет заменено на TikTokClick. Но Delphi не станет "совать свой нос" в код, который написан вами. И если ваша программа вызывает компонент по его имени, она просто перестанет работать после изменения данного имени. Читайте ее жалобы внимательно и научитесь ее понимать достаточно быстро. Все жалуются на одно и тоже. (Народная примета). Простейший способ обойти эту проблему состоит в использовании имен, поддерживаемых Delphi по умолчанию, С другой стороны, присваивая компонентам имена, которые связаны с их функциями, вам труднее забыть, что происходит в вашей программе. Назначение кнопки FormatHardDiskButton очевидно, a Button42 - нет. Если вы все-таки решили использовать имена, которые что-то обозначают, то одно простое правило предотвратит описанную выше проблему: всегда присваивайте имя компоненту при его создании, причем это должно происходить до написания любого кода, который обращается к этому компоненту. Если позже вы захотите изменить это имя, не забудьте изменить его во всех программах, которые вы написали сами! И в тоже время, если никто и никогда не будет обращаться к этому объекту, зачем его переименовывать. Предлагаю альтернативу. Если вы хотите обратиться к компоненту по имени, а оно имеет имя по умолчанию (например Button42), значит пора его переименовывать, но не ранее. Существует другой способ избежать этой проблемы. Каждый обработчик событий содержит параметр, называемый Sender, который идентифицирует компонент, вызвавший событие. Вместо обращения, скажем, к Edit1, ваш код может использовать следующее: (Sender AS TEdit) В этом случае вы получите двойную выгоду. Во-первых, нет никакой зависимости от имени компонента Button. И, во-вторых, один и тот же обработчик событий может теперь легко обслуживать несколько компонентов одного и того же типа (в данном случае TEdit). Как поется в песне: "Чтоб ты ни делал, мой дружок, впросак не попадай //По имени свой компонент нигде не вызывай!' Функционирование обработчиков событий К этому моменту вы написали несколько десятков обработчиков событий, и, вероятно, считаете само собой разумеющимся, что Delphi генерирует соответствующую заготовку для каждого типа события. Давайте поближе познакомимся с этими заготовками и подумаем, что нам может подойти. Delphi создаст заготовку обработчика событий, даже если вы просто бросите шляпу на стол (во всяком случае, если щелкнете мышью на компоненте). И если вы их не используете - они пропадают зря. Приступив к новому проекту, выберите страницу Events из окна Object Inspector и дважды щелкните на событии OnClick основной формы. Находясь в окне Code Editor, пролистайте текст и найдите объявление класса. Следует отметить, что, помимо поддержки, которая оказывается вам в связи с построением логической структуры для обработчика событий OnCIick, Delphi добавляет его к определению основного объекта TForm1. Как по волшебству, появилась заготовка обработчика событий и соответствующая строка в объявлении объектных типов. He отрывая глаз от окна Code Editor, нажмите <Clrl+F9> (компилирование без запуска программы) или выберите пункт Compile из меню Compile. Когда вы скомпилировали программу, заготовки также быстро и исчезли. Дело в том, что если обработчик событий не содержит программного кода или хотя бы комментария, Delphi удаляет его перед компиляцией программы. Поместите на форму кнопку, дважды щелкните на ней и поместите пустой комментарий // в обработчик событий OnCIick который Delphi обрабатывает по умолчанию для данного компонента. Теперь удалите кнопку и скомпилируйте программу. Существует ряд моментов, на которые следует обратить внимание. Во-первых, обработчик событий представляет собой часть определения объектов формы, он и в самом деле принадлежит форме, а не кнопке. Когда вы удаляете кнопку, обработчик событий остается, и это является еще одним признаком того, что он не принадлежит кнопке. А поскольку обработчик не был пуст на все 100%. Delphi не уничтожает его во время компиляции. Если вы хотите удалить обработчик событий, удалите весь код, за исключением заготовки, которую первоначально обеспечила Delphi. Delphi сама позаботится об удалении и этой заготовки, и строки объявления при следующей компиляции программы. Никогда не удаляйте заготовку самостоятельно, кроме головной боли, вы ничего не добьетесь. В нескольких примерах программ, над которыми вы работали раньше, к одному и тому же обработчику событий подключалось более одного компонента. Вы могли подключить любой обработчик к событиям из нескольких компонентов или несколько событий от одного и того же компонента, в зависимости от того, насколько были совместимы события. Что же представляют собой совместимые события? Давайте разберемся. Снова приступая к новой форме, создайте обработчик событий OnClick. Теперь выберите OnDblClick со страницы Events и нажмите кнопку с направленной вниз стрелкой, которая находится рядом со значением этого события. Появится список, содержащий единственный элемент FormClick. Проделайте то же самое с OnClose, и список окажется пустым. Что же происходит? Продвигаясь вниз по списку событий, создайте обработчик событий для каждого одиночного события. Не стоит помещать какой-либо код в обработчики; мы просто интересуемся основами, которые строит Delphi. Увеличьте окно Code Editor, чтобы можно было увидеть как можно больший фрагмент программного кода. Следует заметить, что существует несколько групп обработчиков, которые
имеют один и тот же список параметров (содержимое в круглых скобках после
имени процедуры). Наиболее популярным списком параметров является следующий: FormDragDrop и FormDragOver имеют один и тот же набор параметров, равно как FormKey Down и FormKey Up. Если теперь щелкнуть на клавише со стрелкой рядом с методом OnCIick в окне Object Inspector, вы получите список возможных вариантов. Посмотрите снова на окно Code Editor и увидите, что все обработчики из списка имеют один и тот же набор параметров. Это все, что нужно Delphi. Если списки параметров совпадают, значит, обработчик событий является совместимым, даже если вы обошли Delphi и построили его вручную. Но запомните, что если сама Delphi создает списки параметров для обработчиков событий, вы никогда не должны их изменять. На сегодня все, разбирайтесь со всем этим. На досуге, попробуйте написать простенькую на первый взгляд программу. Калькулятор. Чем больше возникнет ошибок, тем лучше. Ни в коем случае не бросайте программирование из-за ошибок. Это тоже самое, что во время прыжка передумать. Обязательно свалитесь, а потом будете ломать голову: допрыгнули бы, или нет.
|