Программирование на персональном компьютере: Практическое руководство с примерами по языкам программирования высокого уровня

Комаров Н. А.

Программирование на персональном компьютере: Практическое руководство с примерами по языкам программирования высокого уровня - специальное
изд. - М.: Альянс-пресс, 2003. - 688 с. - (Серия книг «ПК c «нуля»).

ISBN 5-88548-092-3

Первая редакция этой книги была написана еще в 1997 году. Прошло 6 лет. За эти годы произошли существенные изменения в объектно-ориентированном программировании. Во специальном издании этой книги представлена информация, на основании которой читатель сможет «войти» в современный мир программирования на языках 3-го и 4-го уровней.

Языки программирования служат нескольким связанным между собой целям: они не только дают аппарат для задания действий, но и могут сформировать соответствующие концепции, на базе которых «пишется» та или иная прикладная программа. Основной цели идеально отвечает тот язык, который «близок к ПК». Другой цели идеально отвечает язык высокого уровня. Связь между теми или иными языками программирования весьма близка. Современная концепция языков программирования предоставляет набор достаточно удобных инструментов, которые и рассмотрены в этой книге.

В издании приведено исчерпывающее описание языков программирования Си, Perl и Java, описаны среды программирования Delphi и Microsoft FoxPro, а также изложены этапы программирования реляционных баз данных посредством языка SQL. Все фрагменты кодов, приведенные в книге, были проверены профессиональными программистами, которые участвовали в создании этого специального издания.

Эта книга может быть использована для самостоятельного изучения языков программирования высокого уровня, а также будет полезна студентам, преподавателям, разработчикам программного обеспечения и инженерам, желающим быстро и в полной мере освоить возможности языков программирования Си, Perl, Java и SQL.

От автора

Первая редакция этой книги была написана еще в 1997 году. Прошло 5 лет. За эти годы произошли существенные изменения в объектно-ориентированном программировании, а именно:

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

u Произошла стандартизация языка программирования Си++.

u Появился язык программирования Java.

u Канул в прошлое язык программирования Object Pascal.

u Исчез Objective-Cи.

u Появились объектно-ориентированные языки CLOS и Java.

Так появилась вторая редакция этой книги.

Классификация языков программирования

Языки первого уровня

Это языки, которые понимают центральные процессоры компьютеров (разные типы процессоров — разные команды). Команды машинного языка имеют двоичный формат. Машинный язык — это самый сложный, но самый мощный и «гибкий» язык ПК.

Языки второго уровня

Это более приемлемая для человека форма машинного языка.

Языки третьего уровня

К ним относятся такие известные языки, как: FORTRAN, COBOL, BASIC, Cи, Cи++, Pascal. Fortran — язык был создан для облегчения компьютерных вычислений. COBOL — наилучший язык для создания бизнес-программ. Cи и Cи++ — довольно сложные и недружественные языки, предназначенные для создания операционных систем. Именно на Си его создателями (Керниганом и Ричи) была написана знаменитая сетевая ОС Unix. BASIC — подходящий язык для изучения основ программирования. PASCAL — пригодится как и новичкам, так и профессиональным программистам.

Cи++ — универсальный язык программирования, задуманный так, чтобы сделать программирование более приятным для серьезного программиста. За исключением второстепенных деталей Cи++ является надмножеством языка программирования Cи. Помимо возможностей, которые дает Cи, Cи++ предоставляет гибкие и эффективные средства определения новых типов. Используя определения новых типов, точно отвечающих концепциям приложения, программист может разделять разрабатываемую программу на легко поддающиеся контролю части. Такой метод построения программ часто называют абстракцией данных. Информация о типах содержится в некоторых объектах типов, определенных пользователем. Такие объекты просты и надежны в использовании в тех ситуациях, когда их тип нельзя установить на стадии компиляции. Программирование с применением таких объектов часто называют объектно-ориентированным. При правильном использовании этот метод дает более короткие, проще понимаемые и легче контролируемые программы.

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

Языки четвертого уровня

К ним относятся: dBASE, FoxPro, Forth, Perl, SQL, Clipper, Visual BASIC, Java.

Язык программирования Java был тепло встречен мировым сообществом программистов и провайдеров Internet-услуг. Его преимущество для пользователей Internet и World Wide Web заключается в появлении безопасных, платформонезависимых приложений, которые могут использоваться в любом узле Internet. Программисты, создающие приложения на Java, пишут свой код всего один раз — им не приходится «переносить» свои приложения на все возможные программные и аппаратные платформы.

Многие рассматривают Java в первую очередь как средство создания аплетов для World Wide Web. Термином «аплет» в Java обозначается мини-приложение, работающее внутри Web-страницы. После того как аплет загружен на компьютер пользователя, он может выполнять определенные задачи и взаимодействовать с пользователем через броузер, не требуя ресурсов Web-сервера. Разумеется, некоторые аплеты могут взаимодействовать с сервером для решения своих внутренних задач, но это их личное дело.

Java является действительно ценным инструментом разработки в распределенных сетевых средах, к которым относится World Wide Web. Тем не менее Java не ограничивается этими рамками и представляет собой мощный универсальный язык программирования, подходящий для создания самых разнообразных приложений, которые либо не зависят от сетевых средств, либо используют их нестандартным образом. Способность Java безопасно выполнять код на удаленных хост-машинах оказалась очень кстати во многих крупных организациях.

Язык Java проектировался с расчетом на максимальную переносимость и на минимальную зависимость от конкретной системы. Например, во всех реализациях Java тип int представляет собой 32-разрядное число со знаком, дополняемое по модулю 2, независимо от архитектуры процессора, на котором выполняется Java-программа. Определение всех стандартов, касающихся языка и его runtime-среды, позволяет выполнять скомпилированный код в каком угодно месте и переносить его на любую машину, где присутствует среда Java.

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

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

Программистам на Cи и Cи++ язык Java должен показаться знакомым, поскольку в нем используются многие конструкции этих языков.

В этой книге представлена информация, на основании которой вы можете «войти» в мир программирования на языках 3-го и 4-го уровней.

Часть 1.
Введение в информатику

Глава 1.
Понятие инфоpматики

Термин «информатика» (франц. informatique) происходит от французских слов information (информация) и automatique (автоматика) и дословно означает «информационная автоматика».

Широко распространен также англоязычный вариант этого термина — «Сomputer science», что означает буквально «компьютерная наука».

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

Инфоpматика — комплексная научная дисциплина с широчайшим диапазоном применения. Ее приоритетные направления:

u pазpаботка вычислительных систем и пpогpаммного обеспечения;

u теоpия инфоpмации, изучающая процессы, связанные с передачей, приемом, преобразованием и хранением информации;

u математическое моделирование, методы вычислительной и прикладной математики и их применение к фундаментальным и прикладным исследованиям в различных областях знаний;

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

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

u биоинформатика, изучающая информационные процессы в биологических системах;

u социальная информатика, изучающая процессы информатизации общества;

u методы машинной графики, анимации, средства мультимедиа;

u телекоммуникационные системы и сети, в том числе, глобальные компьютерные сети, объединяющие все человечество в единое информационное сообщество;

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

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

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

Программированию задачи всегда предшествует разработка способа ее решения в виде последовательности действий, ведущих от исходных данных к искомому результату, иными словами, разработка алгоритма решения задачи. Для обозначения части информатики, связанной с разработкой алгоритмов и изучением методов и приемов их построения, применяют термин Brainware (англ. brain — интеллект).

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

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

Глава 2.
Понятие информации

Термин «информация» происходит от латинского слова «informatio», что означает сведения, разъяснения, изложение. Несмотря на широкое распространение этого термина, понятие информации является одним из самых дискуссионных в науке. В настоящее время наука пытается найти общие свойства и закономерности, присущие многогранному понятию информация, но пока это понятие во многом остается интуитивным и получает различные смысловые наполнения в различных отраслях человеческой деятельности:

u в обиходе информацией называют любые данные или сведения, которые кого-либо интересуют. Например, сообщение о каких-либо событиях, о чьей-либо деятельности и т.п. «Информировать» в этом смысле означает «сообщить нечто, неизвестное раньше»;

u в технике под информацией понимают сообщения, передаваемые в форме знаков или сигналов;

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

Клод Шеннон, американский ученый, заложивший основы теории информации — науки, изучающей процессы, связанные с передачей, приемом, преобразованием и хранением информации, — рассматривает информацию как снятую неопределенность наших знаний о чем-то. Современное научное представление об информации очень точно сформулировал Норберт Винер, «отец» кибернетики. А именно: «Информация — это обозначение содержания, полученного из внешнего мира в процессе нашего приспособления к нему и приспособления к нему наших чувств».

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

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

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

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

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

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

Глава 3.
Виды представления информации

Информация может существовать в виде:

u текстов, рисунков, чертежей, фотографий;

u световых или звуковых сигналов;

u радиоволн;

u электрических и нервных импульсов;

u магнитных записей;

u жестов и мимики;

u запахов и вкусовых ощущений;

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

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

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

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

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

В качестве единицы информации Клод Шеннон предложил принять один бит (англ. bit — binary digit — двоичная цифра).

Бит в теории информации — количество информации, необходимое для различения двух равновероятных сообщений (типа «орел» — «решка», «чет» — «нечет» и т.п.).

В вычислительной технике битом называют наименьшую «порцию» памяти компьютера, необходимую для хранения одного из двух знаков «0» и «1», используемых для внутримашинного представления данных и команд.

Бит — слишком мелкая единица измерения. На практике чаще применяется более крупная единица — байт, равная восьми битам. Именно восемь битов требуется для того, чтобы закодировать любой из 256 символов алфавита клавиатуры компьютера (256=28).

Широко используются также еще более крупные производные единицы информации:

1 Килобайт (Кбайт) = 1024 байт = 210 байт.

1 Мегабайт (Мбайт) = 1024 Кбайт = 220 байт.

1 Гигабайт (Гбайт) = 1024 Мбайт = 230 байт.

В последнее время в связи с увеличением объемов обрабатываемой информации входят в употребление такие производные единицы, как:

1 Терабайт (Тбайт) = 1024 Гбайт = 240 байт.

1 Петабайт (Пбайт) = 1024 Тбайт = 250 байт.

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

Что можно делать с информацией? Информацию можно:

u создавать;

u передавать;

u воспринимать;

u иcпользовать;

u запоминать;

u принимать;

u копировать;

u формализовать;

u распространять;

u преобразовывать;

u комбинировать;

u обрабатывать;

u делить на части;

u упрощать;

u собирать;

u хранить;

u искать;

u измерять;

u разрушать;

u и др.

Все эти процессы, связанные с определенными операциями над информацией, называются информационными процессами.

Свойства информации:

u достоверность;

u полнота;

u ценность;

u своевременность;

u понятность;

u доступность;

u краткость;

u и др.

Информация достоверна, если она отражает истинное положение дел. Недостоверная информация может привести к неправильному пониманию или принятию неправильных решений.

Достоверная информация со временем может стать недостоверной, так как она обладает свойством устаревать, то есть перестает отражать истинное положение дел.

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

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

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

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

Если ценная и своевременная информация выражена непонятным образом, она может стать бесполезной.

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

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

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

Глава 4.
Обработка информации

Обработка информации — получение одних информационных объектов из других информационных объектов путем выполнения некоторых алгоритмов.

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

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

Компьютеры обрабатывают информацию путем выполнения некоторых алгоритмов. Живые организмы и растения обрабатывают информацию с помощью своих органов и систем.

Информационные ресурсы — это идеи человечества и указания по их реализации, накопленные в форме, позволяющей их воспроизводство.

Это книги, статьи, патенты, диссертации, научно-исследовательская и опытно-конструкторская документация, технические переводы, данные о передовом производственном опыте и др. . Информационные ресурсы (в отличие от всех других видов ресурсов — трудовых, энергетических, минеральных и т.д.) тем быстрее растут, чем больше их расходуют.

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

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

В настоящее время термин «информационная технология» употребляется в связи с использованием компьютеров для обработки информации. Информационные технологии охватывают всю вычислительную технику и технику связи и, отчасти, — бытовую электронику, телевидение и радиовещание.

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

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

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

Глава 5.
Системы счисления

Система счисления — это совокупность приемов и правил, по которым числа записываются и читаются.

Существуют позиционные и непозиционные системы счисления.

В непозиционных системах счисления вес цифры (т.е. тот вклад, который она вносит в значение числа) не зависит от ее позиции в записи числа. Так, в римской системе счисления в числе ХХХII (тридцать два) вес цифры Х в любой позиции равен просто десяти. В позиционных системах счисления вес каждой цифры изменяется в зависимости от ее положения (позиции) в последовательности цифр, изображающих число. Например, в числе 757,7 первая семерка означает 7 сотен, вторая — 7 единиц, а третья — 7 десятых долей единицы.

Сама же запись числа 757,7 означает сокращенную запись выражения

700 + 50 + 7 + 0,7 = 7 * 102 + 5 * 101 + 7 * 100 + 7 * 10—1 = 757,7

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

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

За основание системы можно принять любое натуральное число — два, три, четыре и т.д. Следовательно, возможно бесчисленное множество позиционных систем: двоичная, троичная, четверичная и т.д.

В каждой системе счисления цифры упорядочены в соответствии с их значениями: 1 больше 0, 2 больше 1 и т.д. Продвижением цифры называют замену ее следующей по величине.

Продвинуть цифру 1 значит заменить ее на 2, продвинуть цифру 2 значит заменить ее на 3 и т.д. Продвижение старшей цифры (например, цифры 9 в десятичной системе) означает замену ее на 0. В двоичной системе, использующей только две цифры — 0 и 1, продвижение 0 означает замену его на 1, а продвижение 1 — замену ее на 0.

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

Какие системы счисления используют специалисты для общения с компьютером? Кроме десятичной широко используются системы с основанием, являющимся целой степенью числа 2, а именно:

u двоичная (используются цифры 0, 1);

u восьмеричная (используются цифры 0, 1, ..., 7);

u шестнадцатеричная (для первых целых чисел от нуля до девяти используются цифры 0, 1, ..., 9, а для следующих чисел — от десяти до пятнадцати — в качестве цифр используются символы A, B, C, D, E, F).

Из всех систем счисления особенно проста и поэтому интересна для технической реализации в компьютерах двоичная система счисления. Люди предпочитают десятичную систему, вероятно, потому, что с древних времен считали по пальцам, а пальцев у людей по десять на руках и ногах. Не всегда и не везде люди пользуются десятичной системой счисления. В Китае, например, долгое время пользовались пятеричной системой счисления. А компьютеры используют двоичную систему потому, что она имеет ряд преимуществ перед другими системами:

u для ее реализации нужны технические устройства с двумя устойчивыми состояниями (есть ток — нет тока, намагничен — не намагничен и т.п.), а не, например, с десятью, — как в десятичной;

u представление информации посредством только двух состояний надежно и помехоустойчиво;

u возможно применение аппарата булевой алгебры для выполнения логических преобразований информации;

u двоичная арифметика намного проще десятичной.

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

Числа в этих системах читаются почти так же легко, как десятичные, требуют соответственно в три (восьмеричная) и в четыре (шестнадцатеричная) раза меньше разрядов, чем в двоичной системе (ведь числа 8 и 16 — соответственно, третья и четвертая степени числа 2). Перевод восьмеричных и шестнадцатеричных чисел в двоичную систему очень прост: достаточно каждую цифру заменить эквивалентной ей двоичной триадой (тройкой цифр) или тетрадой (четверкой цифр).

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

Глава 6.
Логические основы компьютеров

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

Алгебра логики возникла в середине ХIХ века в трудах английского математика Джорджа Буля. Ее создание представляло собой попытку решать традиционные логические задачи алгебраическими методами.

Логическое высказывание — это любoе повествовательное пpедлoжение, в oтнoшении кoтopoгo мoжно oднoзначнo сказать, истиннo oнo или лoжнo. Так, например, предложение «6 — четное число» следует считать высказыванием, так как оно истинное. Предложение «Рим — столица Франции» тоже высказывание, так как оно ложное.

Разумеется, не всякое предложение является логическим высказыванием. Высказываниями не являются, например, предложения «ученик десятого класса» и «информатика — интересный предмет». Первое предложение ничего не утверждает об ученике, а второе использует слишком неопределенное понятие «интересный предмет». Вопросительные и восклицательные предложения также не являются высказываниями, поскольку говорить об их истинности или ложности не имеет смысла.

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

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

Алгебра логики рассматривает любое высказывание только с одной точки зрения — является ли оно истинным или ложным. Заметим, что зачастую трудно установить истинность высказывания. Так, например, высказывание «площадь поверхности Индийского океана равна 75 млн кв. км» в одной ситуации можно посчитать ложным, а в другой — истинным. Ложным — так как указанное значение неточное и вообще не является постоянным. Истинным — если рассматривать его как некоторое приближение, приемлемое на практике.

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

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

Так, например, из элементарных высказываний «Петров — врач», «Петров — шахматист» при помощи связки «и» можно получить составное высказывание «Петров — врач и шахматист», понимаемое как «Петров — врач, хорошо играющий в шахматы».

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

Истинность или ложность получаемых таким образом составных высказываний зависит от истинности или ложности элементарных высказываний.

Чтобы обращаться к логическим высказываниям, им назначают имена. Пусть через А обозначено высказывание «Тимур поедет летом на море», а через В — высказывание «Тимур летом отправится в горы». Тогда составное высказывание «Тимур летом побывает и на море, и в горах» можно кратко записать как А и В. Здесь «и» — логическая связка, А, В — логические переменные, которые мoгут принимать только два значения — «истина» или «ложь», обозначаемые, соответственно, «1» и «0».

Каждая логическая связка рассматривается как операция над логическими высказываниями и имеет свое название и обозначение:

u НЕ — операция, выражаемая словом «не», называется отрицанием и обозначается чертой над высказыванием. Высказывание истинно, когда A ложно, и ложно, когда A истинно.

u И — операция, выражаемая связкой «и», называется конъюнкцией (лат. conjunctio — соединение) или логическим умножением и обозначается точкой «.» (может также обозначаться знаком &).

u ИЛИ — операция, выражаемая связкой «или» (в неисключающем смысле этого слова), называется дизъюнкцией (лат. disjunctio — разделение) или логическим сложением.

u ЕСЛИ-ТО — операция, выражаемая связками «если..., то», «из... следует», «... влечет...», называется импликацией (лат. implico — тесно связаны).

u РАВНОСИЛЬНО — операция, выражаемая связками «тогда и только тогда», «необходимо и достаточно», «... равносильно ...», называется эквиваленцией или двойной импликацией

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

Порядок выполнения логических операций задается круглыми скобками. Но для уменьшения числа скобок договорились считать, что сначала выполняется операция отрицания («не»), затем конъюнкция («и»), после конъюнкции — дизъюнкция («или») и в последнюю очередь — импликация.

С помощью логических переменных и символов логических операций любое высказывание можно формализовать, то есть заменить логической формулой. Всякая логическая переменная и символы «истина» («1») и «ложь» («0») — формулы.

Некоторые формулы принимают значение «истина» при любых значениях истинности входящих в них переменных. Такие формулы называются тождественно истинными формулами или тавтологиями. Высказывания, которые формализуются тавтологиями, называются логически истинными высказываниями.

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

Из этого следует два вывода:

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

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

Данные и команды представляются в виде двоичных последовательностей различной структуры и длины.

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

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

Глава 7.
Алгоритмы. Алгоритмизация. Алгоритмические языки

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

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

Алгоpитм — заранее заданное понятное и точное пpедписание возможному исполнителю совеpшить определенную последовательность действий для получения решения задачи за конечное число шагов. Это — не определение в математическом смысле слова, а, скорее, описание интуитивного понятия алгоритма, раскрывающее его сущность.

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

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

u сpеда;

u элементаpные действия;

u cистема команд;

u отказы.

Система команд

Каждый исполнитель может выполнять команды только из некотоpого стpого заданного списка — системы команд исполнителя. Для каждой команды должны быть заданы условия пpименимости (в каких состояниях сpеды может быть выполнена команда) и описаны pезультаты выполнения команды.

После вызова команды исполнитель совеpшает соответствующее элементаpное действие. Отказы исполнителя возникают, если команда вызывается пpи недопустимом для нее состоянии сpеды.

Обычно исполнитель ничего не знает о цели алгоpитма. Он выполняет все полученные команды, не задавая вопросов «почему» и «зачем». В информатике универсальным исполнителем алгоритмов является компьютер.

Основные свойства алгоритмов следующие:

u Понятность для исполнителя — исполнитель алгоритма должен понимать, как его выполнять. Иными словами, имея алгоритм и произвольный вариант исходных данных, исполнитель должен знать, как надо действовать для выполнения этого алгоритма.

u Дискpетность (прерывность, раздельность) — алгоpитм должен пpедставлять пpоцесс pешения задачи как последовательное выполнение пpостых (или pанее опpеделенных) шагов (этапов).

u Опpеделенность — каждое пpавило алгоpитма должно быть четким, однозначным и не оставлять места для пpоизвола. Благодаpя этому свойству выполнение алгоpитма носит механический хаpактеp и не тpебует никаких дополнительных указаний или сведений о pешаемой задаче.

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

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

На практике наиболее распространены следующие формы представления алгоритмов:

u словесная (запись на естественном языке);

u графическая (изображения из графических символов);

u псевдокоды (полуформализованные описания алгоритмов на условном алгоритмическом языке, включающие в себя как элементы языка программирования, так и фразы естественного языка, общепринятые математические обозначения и др.);

u программная (тексты на языках программирования).

Словесный способ записи алгоритмов представляет собой описание последовательных этапов обработки данных. Алгоритм задается в произвольном изложении на естественном языке. Словесный способ не имеет широкого распространения, так как такие описания:

u строго не формализуемы;

u страдают многословностью записей;

u допускают неоднозначность толкования отдельных предписаний.

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

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

Алгоритмизация

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

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

Ниже приведены графические обозначения (обозначения на блок-схемах) ОАС.

Структура «следование»

Полная развилка

Неполная развилка

Цикл с предусловие (цикл ПОКА)

Цикл с постусловием (цикл ДО)

Цикл с параметром

На схемах:

u СЕРИЯ обозначает один или несколько любых операторов;

u УСЛОВИЕ есть логическое выражение (ЛВ) (если его значение ИСТИНА, переход происходит по ветви ДА, иначе — по НЕТ).

На схеме цикла с параметром использованы обозначения:

u ПЦ — параметр цикла

u НЗ — начальное значение параметра цикла

u КЗ — конечное значение параметра цикла

u Ш — шаг изменения параметра цикла.

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

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

Линейные алгоритмы

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

Пример 1. Пешеход шел по пересеченной местности. Его скорость движения по равнине v1 км/ч, в гору — v2 км/ч и под гору — v3 км/ч. Время движения соответственно t1, t2 и t3 ч. Какой путь прошел пешеход?

1. Ввести v1, v2, v3, t1, t2, t3.

2. S1 := v1 * t1.

3. S2 := v2 * t2.

4. S3 := v3 * t3.

5. S := S1 + S2 + S3.

6. Вывести значение S.

7. Конец.

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

Пример 2. Дано натуральное трехзначное число n, в записи которого нет нулей. Составить алгоритм, который возвращает значение ИСТИНА, если верно утверждение: «число n кратно каждой своей цифре», и ЛОЖЬ — в противном случае.

1. Ввести число n

2. A := n mod 10 {разряд единиц}

3. B := n div 100 {разряд сотен}

4. C := n div 10 mod 10 {десятки}

5. L := (n mod A=0) and (n mod B=0) and (n mod C=0)

6. Вывод L

7. Конец

На приведенной выше схеме DIV и MOD соответственно операции деления нацело и получения остатка от целочисленного деления. В фигурных скобках записаны пояснения (комментарии) к операторам.

Развилка

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

Пример 1. Вычислить значение функции

1. Ввести x.

2. Если x<=–12, то y:=–x2

3. Если x<0, то y:=x4

4. y := x–2

5. Вывести y

6. Конец

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

Пример 2. Дано натуральное число n. Если число нечетное и его удвоение не приведет к выходу за 32767 (двухбайтовое целое число со знаком), удвоить его, иначе — оставить без изменения. Чтобы удовлетворить условию удвоения, число n должно быть нечетным и меньше 16384.

1. Ввести число n

2. Если число n нечетное и меньше 16384, то n := n * 2

3. Вывод n

4. Конец

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

Циклы

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

Пример 1. Подсчитать количество нечетных цифр в записи натурального числа n.

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

1. Ввести число n

2. K := 0 {подготавливаем счетчик}

3. Если n = 0, переход к п. 7

4. Если n mod 10 mod 2 = 1, то K := K +1

5. n := n div 10

6. Переход к п. 3

7. Вывод K

8. Конец

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

Пример 2. Дана последовательность, общий член которой определяется формулой

Вычислить при n>2 сумму тех ее членов, которые больше заданного числа e. При решении задачи находится очередной член последовательно и, если он больше e, добавляется к сумме.

1. Ввести e

2. S := 0

3. A := 1/4

4. n := 3

5. Сравнить А с e. Если A>=e, переход к п. 10

6. S := S + A

7. A := (n-1)/(n*n)

8. n := n + 1

9. Переход к п. 5

10. Вывод S

11. Конец

В рассмотренных выше примерах количество повторений заранее неизвестно. В первом оно зависит от количества цифр в записи натурального числа, во втором — от числа e.

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

Пример 3. Найти произведение первых k натуральных чисел, кратных трем. При составлении алгоритма учтем, что первое натуральное число, кратное 3, есть тройка, а все последующие больше предыдущего на 3.

1. Ввод k

2. P := 1 {здесь накапливаем произведение}

3. T := 0 {здесь будут числа, кратные 3}

4. I := 1

5. Если I > k, переход к п. 10

6. T := T + 3

7. P := P * T

8. I := I + 1

9. Перейти к п. 5

10. Вывод P

11. Конец

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

Часть 2.
Программирование на языке Си

Глава 1.
Особенности языка Си

С («Си») является языком функций, типов данных, операторов присваивания и управления последовательностью вычислений. Программируя на С, вы осуществляете обращение к функциям, и большинство функций возвращают некоторые значения. Значение, возвращаемое функцией, будь то значение переменной или константа, может использоваться в операторе присваивания, который изменяет значение другой переменной.

Дополненный операторами управления последовательностью вычислений (while, for, do, switch), Си превращается в язык высокого уровня, способствующий хорошему стилю программирования. Си имеет небольшой набор типов данных: целые числа, числа с плавающей запятой, битовые поля и перечислимый тип. В языке Си вы можете описать переменную типа указатель, который связывается с объектом, принадлежащим к любому типу данных.

Адресная арифметика языка Си является чувствительной к типу данных того объекта, с которым связан используемый указатель. Разрешены также указатели к функциям. Вы можете расширить список типов данных путем создания структур с иерархической зависимостью входящих в него типов данных. Каждый тип данных может принадлежать либо к основному типу, либо к ранее описанному структурному типу. Объединения напоминают структуры, но определяют различные виды иерархических зависимостей, в которых данные разных типов располагаются в памяти. Допустимо описание массивов данных различных типов, включая структуры и объединения. Массивы могут быть многомерными.

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

Программа функции на языке Си разбивается на блоки, в каждом из которых могут быть определены свои собственные локальные переменные. Блоки могут выбираться для исполнения по результату выполнения оператора управления последовательностью вычислений. Блоки могут быть вложенными друг в друга. Переменные и функции могут быть глобальными для программы, глобальными для исходного модуля или локальными для блока, в котором они описаны. Локальные переменные могут быть описаны таким образом, что они будут сохранять свои значения при всех обращениях внутри данного блока (статические переменные) или же будут восприниматься как новые объекты при каждом обращении (автоматические переменные).

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

Глава 2.
Достоинства языка Си

Переносимость программ, написанных на Си, является наиболее разрекламированным преимуществом этого языка. Если вы пишете программу на Си и избегаете при этом использовать расширения библиотеки, зависящие от конкретного компилятора, или машинно-зависимые операции, то вы получаете неплохие шансы (значительно большие, чем при любом другом языке) на успешный перенос вашей программы в другую программно-аппаратную среду, включая смену компилятора, операционной системы и ЭВМ. Библиотека функций для работы с окнами привязана к архитектуре видеопамяти IBM PC, а функции резидентных в памяти утилит используют систему прерываний IBM PC, специфические особенности DOS и библиотеку расширений компилятора Турбо Си, которая облегчает разработку программ обработки прерываний. Некоторые функции включают небольшие фрагменты на языке ассемблера.

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

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

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

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

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

Глава 3.
Переменные

Переменная — это подобный «ящичек» с именем, в котором может храниться некое значение. Имя у переменной постоянно и неизменно, значение же может меняться. Переменную возможно изменять при помощи операции присваивания. В языке Си она обозначается знаком равенства.

x = 12;

Это читается не как «икс равно 12», а как «присвоить переменной икс значение 12», то есть «Положить в ящик с надписью ИКС число 12».

Такая строка является простейшим оператором, то есть действием. В конце операторов ставится точка с запятой.

Рассмотрим оператор

x = x + 3;

Это не уравнение. В случае, если рассматривать эту строку как математическое уравнение, оно не имеет решений. На самом деле здесь написано:

1. «взять значение переменной ИКС»

2. «прибавить к нему 3»

3. «положить новое значение в переменную ИКС», стерев в ней прежнее значение.

У оператора присваивания имеется две части: левая и правая.

ЛЕВАЯ_ЧАСТЬ = ПРАВАЯ_ЧАСТЬ;

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

Если имя переменной встречается в правой части, то это означает «подставить сюда текущее значение этой переменной». При этом текущее значение самой переменной НЕ изменяется, берется его копия. То есть, «вынутое из ящика значение» не оставляет ящик пустым! Вынимается копия, другая копия остается в ящике.

Итак:

x = x + 3;

Пусть сейчас значение x есть 12. Сначала вычисляется правая часть оператора присваивания. Вместо x подставляем число 12 и складываем с 3. Сложение дает 15.

В этот момент x все еще равно 12 (в ящике лежит число 12). Теперь вычисляется само присваивание:

x = 15;

В переменной с тем же именем теперь находится новое значение, она изменилась. Потому и «переменная».

В некоторых языках программирования, к примеру в Pascal или Modula, операция присваивания обозначается символом :=, а не =. Это уменьшает путаницу, но к смыслу = возможно привыкнуть довольно быстро.

В правой части значение переменной может использоваться несколько раз:

z = x * x + 2 * x;

Тут имеются две переменные: z — для результата и x — уже имеющая какое-то значение. x * x означает «умножить икс на икс» (при этом само значение, лежащее в ящике икс не изменяется). x * 2 означает «взять два значения икс». + означает сложение.

Переменные надо объявлять. Это необходимо потому, что иначе, если бы переменные вводились просто использованием имени переменной, и мы вдруг допустили бы опечатку, например:

индекс = 1;

вместо

индекс = 1;

то у нас появилась бы «лишняя» переменная «индекс», а ожидаемое действие не произошло бы. Такую ошибку найти чрезвычайно тяжело. В случае, если переменные надо объявлять, то необъявленные переменные будут выявлены еще на стадии компиляции программы.

Переменные, которые будут хранить целые числа (..., -2, -1, 0, 1, 2, 3, ...), объявляют так:

int переменная1;

int переменная2;

Или сразу несколько в одной строке:

int переменная1, переменная2;

int означает сокращение от слова integer — «целый».

Глава 4.
Программа

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

/* ОБЪЯВЛЯЕМ ДВЕ ПЕРЕМЕННЫЕ */

int x, y; /* 0 */

/* Это еще не операторы, хотя при этом создаются 2 ящика для целых чисел */

/* А ТЕПЕРЬ — ОПЕРАТОРЫ */

/* Мы начнем с простых операторов присваивания и арифметики */

x = 3; /* 1 */

y = 4; /* 2 */

x = x + y; /* 3 */

y = y — 1; /* 4 */

x = y; /* 5 */

Значения переменных (то, что лежит в ящиках) меняются таким образом:

x y

/* 0 */ мусор мусор

/* 1 */ 3 мусор

/* 2 */ 3 4

/* 3 */ 7 4

/* 4 */ 7 3

/* 5 */ 3 3

Как вы видите, переменные, которые не участвуют в левой части оператора присваивания, этим оператором не меняются. Последняя операция x = y; не делает имена x и y синонимами.

Такой вещи, как «перевешивание табличек с именами с ящика на ящик» не происходит.

Вместо этого, два ящика с именами x и y содержат одинаковые значения, то есть две копии одного и того же числа.

Значение целой переменной возможно вывести на экран оператором печати:

printf("%d\n", x);

Над целыми числами возможно производить такие арифметические операции:

x + y

сложение

x — y

вычитание

x * y

умножение

x / y

деление нацело (то есть с остатком; результат — целое)

x % y

вычислить остаток от деления нацело

5 / 2 даст 2

5 % 2 даст 1

В операторах присваивания применяются такие сокращения:

Длинная запись Смысл Сокращается до

x = x + 1; "увеличить на 1" x++; (или ++x; )

x = x — 1; "уменьшить на 1" x--; (или --x; )

x = x + y; "прибавить y" x += y;

x = x * y; "умножить на y" x *= y;

x = x / y; "поделить на y" x /= y;

В том числе x++; возможно записать как x += 1;

Глава 5.
Операторы

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

оператор1;

оператор2;

оператор3;

оператор4;

Условный оператор:

if(условие) оператор;

продолжение

работает так: вычисляется условие. В случае, если оно истинно, то выполняется оператор, после этого выполняется продолжение. В случае, если оно ложно, то сразу выполняется продолжение, а оператор не выполняется.

Если нам надо выполнить при истинности условия несколько операторов, мы должны заключить их в скобки { ... } — это так называемый «составной оператор».

if(условие) {

оператор1;

оператор2;

...

}

продолжение

После } точка с запятой не ставится (можно и поставить, но не нужно).

Имеется вторая форма условного оператора, с частью «иначе»:

if(условие) оператор_если_истинно;

else оператор_если_ложно;

То есть, «или то, или другое» (но не оба сразу).

Пример1:

if(x > 10)

printf("Икс больше десяти\n");

Пример2:

int x, y, z;

if(x < y) z = 1;

else z = 2;

Глава 6.
Условия

В качестве условий могут использоваться операторы сравнения (сравнивать возможно переменные, выражения, константы):

x < y

меньше

x > y

больше

x <= y

меньше или равно

x >= y

больше или равно

x == y

равно

x != y

не равно

Все эти операторы в качестве результата операции сравнения выдают 1 в случае, если сравнение истинно; 0 в случае, если оно ложно.

Таким образом, на самом деле условный оператор работает так:

if(условие) ....

Если условие есть ноль — то условие считается ложным.

Если условие есть не ноль, а ..., -2, -1, 1, 2, 3, ... — то условие истинно.

Это определение. Из него в частности вытекает, что сравнение с целым нулем возможно опускать:

if(x != 0) ...;

сокращается до

if(x) ...;

if(x == 0) ...;

сокращается до

if(!x) ...;

Пример:

int x, y, z;

if(x == 1){ y = 2; z = x + y; }

else { y = 1; z = x — y; }

Пример со вложенными условными операторами:

if(x == 1){

printf("Икс равен 1\n");

if(y == 2){

printf("Игрек равен 2\n");

}

} else {

printf("Икс не равен 1\n");

}

Часто применяется последовательность условных операторов, перебирающая различные варианты:

if(x == 1)

printf("Икс равен 1\n");

else if(x == 2)

printf("Икс равен 2\n");

else if(x == 3){

printf("Икс равен 3\n");

y = 1;

} else

printf("Непредусмотренное значение икс\n");

Самое сложное — привыкнуть к тому, что сравнение обозначается знаком ==, а не =. Знак = означает «присвоить значение», а не «сравнить на равенство».

Цикл while («до тех пор, пока истинно»)

while(условие)

оператор;

...продолжение...

или

while(условие){

операторы;

...

}

...продолжение...

Пример:

int x;

x = 10;

while(x > 0){

printf("x=%d\n", x);

x = x — 1;

}

printf("Конец.\n");

printf("x стало равно %d.\n", x); /* печатает 0 */

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

Логические операторы

Условия могут быть сложными.

В случае, если красный И вес < 10 ТО ...;

В случае, если красный ИЛИ синий ТО ...;

В случае, если НЕ красный ТО ...;

На языке Си такие условия записываются так:

if(условие1 && условие2) ...; /* "И" */

if(условие1 || условие2) ...; /* "ИЛИ" */

if(! условие1) ...; /* "НЕ" */

Например:

if(4 < x && x <= 12) ...;

Было бы неправильно записать:

if(4 < x <= 12) ...;

ибо язык программирования Си не понимает двойное сравнение!

Еще примеры:

if(x < 3 || y > 4) ...;

if( ! (x < 3 || y > 4)) ...;

Цикл for («для каждого»)

Этот цикл является просто иной записью одного из вариантов цикла while. Он служит как правило, для выполнения определенного действия несколько раз, не «пока истинно условие», а «выполнить N-раз».

У такого цикла имеется «переменная цикла» или «счетчик повторений».

int i;

i = a; /* начальная инициализация */

while(i < b){

тело_цикла;

i += c; /* увеличение счетчика */

}

...продолжение...

переписывается в виде

int i;

for(i=a; i < b; i += c)

тело_цикла;

тело_цикла будет выполнено для значений i: a, a+c, a+c+c, пока i < b.

В простейшем случае:

for(i=1; i <= N; i++)

printf("i=%d\n", i);

i означает «номер повторения».

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

Оператор break («вывалиться из цикла»)

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

while(условие1){

операторы1;

if(условие2)

break;

операторы2;

}

...продолжение...

и

for(i=0; условие1; i++){

операторы1;

if(условие2)

break;

операторы2;

}

...продолжение...

Этот оператор позволяет организовывать дополнительные точки выхода из цикла (при дополнительных условиях).

Пример:

for(i=0; i < 20; i++){

printf("i=%d\n", i);

if(i == 7){

printf("break loop!\n");

break; /* вывалиться из цикла */

}

printf("more\n");

}

printf("finished, i=%d\n", i); /* печатает 7 */

В частности, с его помощью возможно организовывать бесконечный цикл:

for(;;){ /* заголовок бесконечного цикла */

операторы1;

if(условие2)

break;

операторы2;

}

...продолжение...

Здесь в самом заголовке цикла не повторяется никаких условий, такой цикл продолжается бесконечно. Условие продолжения считается всегда истинным. Единственный способ выйти из него — это сделать break (при каком-то условии) в теле цикла, что и написано.

Бесконечный цикл возможно также организовать при помощи:

while(1){

...

}

Оператор вывода (Печати)

printf("текст");

Печатает на экран текст.

printf("текст\n");

Печатает на экран текст и переходит к новой строке.

printf("слово1 слово2 ");

printf("слово3\n");

Печатает «слово1 слово2 слово3» и переходит на новую строку.

Если переход на новую строку не задан явно, символом \n, то текст продолжает печататься в текущей строке.

printf("%d", x);

Печатает в текстовом виде значение переменной x. Специальная конструкция %d означает: «взять переменную из списка после запятой и напечатать ее значение в виде целого числа».

printf("икс равен %d — ого-го\n", x);

Печатает сначала текст

икс равен

затем значение переменной x как целое число, затем текст

- ого-го

и переходит на новую строку (поскольку указан символ \n).

Этот оператор может печатать и несколько значений переменных:

int x, y;

x = 12; y = 15;

printf("икс есть %d, игрек есть %d, все.\n", x, y);

~~~~~~

Данный оператор работает так.

Строка «икс есть %d, игрек есть %d\n» называется форматом. Компьютер читает формат слева направо и печатает текст до тех пор, пока не встретит символ %d.

Курсор изображен символом _

икс есть _

Далее он берет первую переменную из списка ~~~~ и печатает ее как целое число.

икс есть 12_

далее он снова печатает текст пока не встретит %d

икс есть 12, игрек есть _

Теперь он берет вторую переменную из списка и печатает ее:

икс есть 12, игрек есть 15_

Снова печатает текст, включая перевод строки \n.

Как исключительно строка формата кончилась, оператор printf завершен.

икс есть 12, игрек есть 15, все.

_

Печатать возможно не исключительно значения переменных, но и значения арифметических выражений:

printf("равно: %d\n", 12 + 3 * 5);

Пример

int x, y, z;

x = 13;

y = 23;

z = 34;

printf("x=%d xx=%d\nzzz=%d\n", x, y — 1, z * 2 + 1);

Так как в формате имеется два перевода строки, поэтому будет напечатано:

x=13 xx=22

zzz=69

_

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

Глава 7.
Функции

Функцией называется фрагмент программы, в который передаются параметры, и который возвращает значение (или ничего).

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

Функция состоит из:

u объявления — описания того, как она что-то вычисляет. Объявление бывает ровно одно.

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

Объявление простейшей функции выглядит так:

int func(int x){

/* Один или несколько операторов,

завершающихся оператором return(нечто);

*/

return x+1;

}

int func(...

задает функцию с именем func (имя выдумывает программист, как и имена переменных). int означает, что функция возвращает целое значение.

(int x)

задает список аргументов (или параметров) функции.

{ ... }

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

return выражение;

задает оператор выхода из функции в точку ее вызова с возвратом значения выражения.

Покажем простой пример вызова этой функции:

int y;

...

y = func(5); /* a */

...продолжение... /* b */

Этот фрагмент работает следующим образом:

y = func(5);

В этой точке мы:

1. «записываем на бумажке», что вызов произошел в такой-то строке, таком-то месте нашей программы.

2. Смотрим на определение функции func.

int func(int x){...

Мы вызвали функцию как func(5).

Это значит, что в теле функции x получает начальное значение 5.

То есть для данного вызова наша функция (ее тело) превращается в:

int x;

x = 5;

return x+1;

3. x+1 есть 6.

Далее должен выполниться оператор return.

Он выполняется так:

Мы «читаем с бумажки» — откуда была вызвана функция func, и смотрим на это место.

Это было:

y = func(5);

Вычеркиваем func(5) и заменяем его значением выражения, вычисленного в операторе return;

y = 6;

4. Выполняем этот оператор и переходим к продолжению.

int y, z, w;

y = func(5);

z = func(6);

w = func(7) + func(8) + 1;

Превратится в

y = 6;

z = 7;

w = 8 + 9 + 1;

При этом мы четыре раза «прыгнем» на определение функции func(), пройдем все ее операторы с разными значениями параметра x и вернемся обратно в точку вызова.

Глава 8.
Программа в целом

Программа в целом состоит из функций.

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

Часто main() — единственная функция в программе.

Структура программы такова:

#include /* магическая строка */

/* ГЛОБАЛЬНЫЕ ПЕРЕМЕННЫЕ */

int a = 7;

int b; /* по умолчанию 0 */

/* ФУНКЦИИ */

f1(){....}

f2(){....}

/* НАЧАЛЬНАЯ (ГЛАВНАЯ) ФУНКЦИЯ */

void main(){

...

}

Пример программы:

#include

int f1(int x, int y){

return (x + y*2);

}

int f2(int x){

int z;

z = x+7;

return 2*z;

}

void main(){

/* Объявления переменных */

int a, b, c;

/* Операторы */

a = 5; b = 6;

c = f1(a, b+3);

b = f1(1, 2);

a = f2(c);

printf("A есть %d B есть %d C есть %d\n", a, b, c);

}

Она печатает:

A есть 60 B есть 5 C есть 23

Глава 9.
Ошибки в программировании циклов

int i;

for(i=0; i < 4; i++){

if(i == 0) func0();

else if(i == 1) func1();

else if(i == 2) func2();

else if(i == 3) func3();

}

В данном примере цикл абсолютно не нужен. То, что здесь делается, есть просто последовательность операторов:

func0();

func1();

func2();

func3();

Цикл имеет смысл лишь тогда, когда много раз вызывается одно и то же действие, может быть зависящее от параметра, вроде func(i). Но не разные функции для разных i.

Аналогично, рассмотрим подобный пример:

int i;

for(i=0; i < 10; i++){

if(i==0) func0();

else if(i == 1) func1();

else if(i == 2) func2();

else funcN(i);

}

Тут funcN(i) берет на себя роль «а в остальных случаях».

Однако, этот пример более естественно может быть записан так:

int i;

func0();

func1();

func2();

for(i = 3; i < 10; i++)

funcN(i);

Заметьте, что цикл теперь начинается с индекса 3.

А теперь — случай, где смесь цикла и условного оператора оправдана:

int i;

for(i=0; i < 100; i++){

if((i % 2) == 0) even(); /* четный */

else odd(); /* нечетный */

}

Тут в цикле проверяется четность индекса i.

Глава 10.
Массивы

Массив — это несколько пронумерованных переменных, объединенных общим именем. Все переменные имеют один и тот же тип.

Рассмотрим полку с N ящиками, пусть имя полки — var. Тогда каждый ящик-ячейка имеет имя:

var[0]

var[1]

...

var[N-1]

Нумерация идет с нуля.

Массив объявляется так:

int var[N];

Здесь N — его размер, число ячеек.

Это описание как бы объявляет N переменных типа int с именами var[0] ...
var[N-1].

В операторах для обращения к n-ому ящичку (где 0 <= n < N) используется имя ящика var[n], где n — целое значение (или значение целой переменной, или целочисленного выражения), «индекс в массиве».

Эта операция [] называется «индексация массива».

Индексация — есть выбор одного из N ящиков при помощи указания целого номера.

var — массив (N ячеек)

n — выражение (формула), выдающая целое значение в интервале 0...N-1

var[n] — взят один из элементов массива. Один из всех.

n — номер ящика — называется еще и «индексом» этой переменной в массиве.

Пример:

int var[5]; /* 1 */

var[0] = 2; /* 2 */

var[1] = 3 + var[0]; /* 3 */

var[2] = var[0] * var[1]; /* 4 */

var[3] = (var[0] + 4) * var[1]; /* 5 */

printf("var третье есть %d\n", var[3]);

В ходе этой программы элементы массива меняются таким образом:

var[0] var[1] var[2] var[3] var[4]

/* 1 */ мусор мусор мусор мусор мусор

/* 2 */ 2 мусор мусор мусор мусор

/* 3 */ 2 5 мусор мусор мусор

/* 4 */ 2 5 10 мусор мусор

/* 5 */ 2 5 10 30 мусор

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

Массив — набор переменных, которые не именованы разными именами, вроде var0, var1, var2, ..., а пронумерованы под одним именем:

var[0], var[1], var[2], ...

Индекс — часть имени переменной.

На самом деле индексация — это:

1. выбор элемента в массиве

2. справа от присваиваний и в выражениях — еще и разыменование, то есть взятие вместо имени переменной — значения, в ней хранящегося.

Если в переменную не было занесено значение, а мы используем эту переменную, то в ней лежит МУСОР (любое, непредсказуемое значение).

printf("var4 есть %d\n", var[4]);

напечатает все что угодно.

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

Массивы нельзя присваивать целиком, язык Си этого не умеет.

int a[5];

int b[5];

a = b; /* ошибка */

Также нельзя присвоить значение сразу всем элементам (ячейкам) массива:

a = 0; /* ошибка */

не делает того, что нами ожидалось, а является ошибкой.

Для обнуления всех ячеек стоит использовать цикл:

int i;

for(i=0; i < 5; i++) /* для каждого i присвоить a[i] = 0; */

a[i] = 0;

Глава 11.
Связь массивов и циклов

Массивы приходится копировать (и инициализировать) поэлементно, в цикле перебирая все (или часть) ячейки массива.

int i;

for(i=0; i < 5; i++)

a[i] = b[i];

В данном случае индекс цикла служит также и индексом в массиве. Индексы в массиве идут с нуля.

Пример инициализации:

int index, array[5];

for(index=0; index < 5; index++)

array[index] = index * 2 + 1;

или

int index, array[5];

index = 0;

while(index < 5){

array[index] = index * 2 + 1;

index++;

}

/* В массиве будет: { 1, 3, 5, 7, 9 } */

Индекс для массивов — номер «ящика/ячейки» в массиве; для циклов — номер повторения цикла, счетчик. Мы должны изменять его сами. Обычно массивы и циклы совмещаются так: индекс цикла есть индекс в массиве; то есть индекс цикла применяется для перебора всех элементов массива:

int a[N], i;

for(i=0; i < N; i++)

...a[i]...

Пример

int a[5];

a[0] = 17;

a[0] += 4;

a[0]++;

Пример

Числа Фибоначчи задаются математическими формулами:

f[1] = 1

f[2] = 1

f[n+2] = f[n+1] + f[n]

Вот программа:

#include /* магическая строка */

#define N 20 /* сколько первых чисел посчитать */

void main(){

int fibs[N], index;

fibs[0] = 1; /* индексы отсчитываются с нуля!!! */

fibs[1] = 1;

/* здесь показано, что индекс элемента массива может вычисляться */

for(index=2; index < N; index++)

fibs[index] = fibs[index-1] + fibs[index-2];

/* Распечатка в обратном порядке */

for(index = N-1; index >= 0; index--)

printf("%d-ое число Фибоначчи есть %d\n",

index+1, fibs[index]);

}

Здесь мы видим новый для нас оператор #define. Он задает текстуальную замену слова N на слово 20, в данном случае просто являясь эквивалентом:

const int N = 20;

К несчастью размер массива не может быть задан при помощи переменной, а вот при помощи имени, определенного в #define — может.

Глава 12.
Строки

Строки есть массивы букв типа char, оканчивающиеся спецсимволом \0.

char string[20];

string[0] = 'П';

string[1] = 'р';

string[2] = 'и';

string[3] = 'в';

string[4] = 'е';

string[5] = 'т';

string[6] = '\0';

printf("%s\n", string);

%s — формат для печати строк.

Никакие другие массивы не могут быть напечатаны целиком одним оператором.

char string[20];

string[0] = 'П';

string[1] = 'р';

string[2] = 'и';

string[3] = 'в';

string[4] = 'е';

string[5] = 'т';

string[6] = '\n'; /* Перевод строки — тоже буква */

string[7] = '\0';

printf("%s", string);

или даже просто

printf(string);

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

char string[20] = "Привет\n";

Оставшиеся неиспользованными символы массива от string[8] до string[19] содержат мусор.

Почему для строк изобрели символ «признак конца»?

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

1. В отдельной переменной. Ее стоит передавать во все функции обработки данной строки (причем она может изменяться).

char str[32]; /* массив для строки */

int slen; /* брать первые slen букв в этом массиве */

...

func(str, &slen); /* ДВА аргумента для передачи ОДНОЙ строки */

...

Этот подход работоспособен, но строка разбивается на два объекта: сам массив и переменную для его длины. Неудобно.

2. Хранить текущую длину в элементе str[0], а буквы — в str[1] и т.д.

Плохо тем, что в str[0] возможно хранить лишь числа от 0 до 255, и в случае, если строка длиннее — то подобный подход неприменим.

3. Не хранить длину нигде, а ввести символ-признак конца строки.

Теперь в:

func(str); /* ОДИН аргумент — сам массив */

передается исключительно сам массив, а его текущая длина может быть при нужде вычислена при помощи некоей функции, вроде такой:

int strlen(char s[]){ /* функция от массива букв */

int counter = 0; /* счетчик и одновременно индекс */

while(s[counter] != '\0')

/* пока не встретился признак конца текста */

counter++; /* посчитать символ */

return counter; /* сколько символов, отличных от '\0' */

}

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

На самом деле в языке имеется такая стандартная функция strlen(s) (вам не надо писать ее самому, ее уже написали за вас).

Глава 13.
Инициализация глобального массива

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

int array[5] = { 12, 23, 34, 45, 56 };

char string[7] = { 'П', 'р', 'и', 'в', 'е', 'т', '\0' };

Если размер массива указан больше, чем мы перечислим элементов, то остальные элементы заполнятся нулями (для int) или '\0' для char.

int array[5] = { 12, 23, 34 };

Если мы перечислим больше элементов, чем позволяет размер массива — это будет ошибкой.

int a[5] = { 177, 255, 133 };

Операция индексации массива a[]: при n значение выражения a[n] есть:

u -1 — не определено (ошибка: «индекс за границей массива»)

u 0 — 177

u 1 — 255

u 2 — 133

u 3 — 0

u 4 — 0

u 5 — не определено (ошибка)

Глава 14.
Вызов функции

Пусть у нас описана функция, возвращающая целое значение.

/* ОПРЕДЕЛЕНИЕ ФУНКЦИИ func(). */

/* Где func — ее имя. Назвать мы ее можем как нам угодно. */

int func(int a, int b, int c){

int x, y;

...

x = a + 7;

...

b = b + 4;

...

return(некое_значение);

}

Здесь a, b, c — аргументы функции (параметры), x, y — локальные переменные. Точка вызова — находится внутри какой-то другой функции, к примеру функции main().

main(){

int zz, var;

...

var = 17;

zz = func(33, 77, var + 3) + 44;

...

}

Когда выполнение программы доходит до строки:

zz = func(33, 77, var + 3) + 44;

1. Происходит вызов функции func(). Создаются переменные с именами a, b, c, x, y. Переменным-аргументам присваиваются начальные значения, которые берутся из точки вызова.

В точке вызова перечислен список (через запятую) выражений (формул):

func(выражение1, выражение2, выражение3)

Вычисленные значения этих выражений соответственно будут присвоены 1-му, 2-му и 3-му аргументам (параметрам) из определения функции:

int func(a, b, c){ /* a = номер 1, b = 2, c = 3 */

Первый параметр:

a = 33;

Второй параметр:

b = 77;

Третий параметр:

c = var + 3;

то есть, вычисляя,

c = 20;

Локальные переменные x и y содержат неопределенные значения, то есть мусор (мы не можем предсказать их значения, пока не присвоим им явным образом какое-либо значение сами).

2. Выполняется тело функции, то есть вычисления, записанные внутри { ... } в определении функции. Например:

x = a + 7;

И параметры, и локальные переменные — это переменные, то есть их возможно изменять.

b = b + 4;

При этом никакие переменные вне этой функции не изменяются.

3. Производится возврат из функции.

...

return(некое_значение);

}

Например, это может быть

...

return(a + 2 * x);

}

Рассмотрим, что при этом происходит в точке вызова:

zz = func(33, 77, var + 3) + 44;

1. Вычеркиваем func(.....)

zz = XXXXXXX + 44;

2. Вычисляем значение «некое_значение» в операторе return, и берем копию этого значения. Пусть при вычислении там получилось 128.

3. Подставляем это значение на место вычеркнутого func(.....). У нас получается:

zz = 128 + 44;

4. Автоматически уничтожаются локальные переменные и аргументы функции:

u a — убито

u b — убито

u c — убито

u x — убито

u y — убито

Таких переменных (и их значений) больше нет в природе.

5. Продолжаем вычисление:

zz = 128 + 44;

Вычисляется в:

zz = 172; /* оператор присваивания */

Рассмотрим пример:

int func1(int x){

printf("func1: x=%d\n", x); /* 1 */

x = 77;

printf("func1: x=%d\n", x); /* 2 */

return x;

}

void main(){

int var, y;

var = 111;

y = func1(var); /* @ */

printf("main: var=%d\n", var); /* 3 */

}

В данном случае в точке @ мы передаем в функцию func1() значение переменной var, равное 111. Это значит, что при вызове функции будет создана переменная x и ей будет присвоено начальное значение 111.

x = 111;

Поэтому первый оператор printf() напечатает 111. Затем мы изменяем значение переменной x на 77. Мы меняем переменную x, но не переменную var!!! Использовав значение (его копию) из переменной var для x, мы о переменной var забыли — она нас не касается (а мы — ее). Поэтому второй оператор printf() напечатает 77. В переменной же var осталось значение 111, что и подтвердит нам третий оператор printf, который напечатает 111.

Глава 15.
Временное сокрытие переменных

int func1(int x){ /* f.1 */

printf("func1: x=%d\n", x); /* f.2 */

x = 77; /* f.3 */

printf("func1: x=%d\n", x); /* f.4 */

return x; /* f.5 */

}

void main(){

int x, y; /* 1 */

x = 111; /* 2 */

y = func1(x); /* 3 */

printf("main: x=%d y=%d\n", x, y); /* 4 */

}

А теперь мы и переменную внутри main(), и аргумент функции func1() назвали одним и тем же именем. Что будет? Будет то же самое, что в предыдущем примере.

В момент вызова функции func1() будет создана новая переменная с именем x, а старая (прежняя) переменная и ее значение будут временно спрятаны (скрыты). Можно было бы уточнить эти переменные именами функций, в которых они определены:

main::x

и

func1::x

(но это уже конструкции из языка Си++, а не Си).

Механизм сокрытия имен позволяет писать функции main() и func1() разным программистам, позволяя им не заботиться о том, чтобы имена локальных переменных в функциях не совпадали. Допустим совпадают — хуже не будет, механизм сокрытия имен разрешит конфликт. Зато программист может использовать любое понравившееся ему имя в любой функции — хотя бы x или i.

Тоже самое происходит с локальными переменными, а не с аргументами функции.

int func1(int arg){ /* локальная переменная-параметр func1::arg */

int x; /* локальная переменная func1::x */

x = arg;

printf("func1: x=%d\n", x);

x = 77;

printf("func1: x=%d\n", x);

return x;

}

void main(){

int x, y; /* переменные main::x и main::y */

x = 111;

y = func1(x);

printf("main: x=%d y=%d\n", x, y);

}

Действует тот же самый механизм временного сокрытия имени x.

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

Глава 16.
Глобальные переменные

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

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

int x = 12; /* ::x — ей возможно заранее присвоить константу */

int globvar; /* ::globvar */

int f1(){

int x; /* f1::x */

x = 77;

printf("x=%d\n", x); /* 4 */

return x;

}

int f2(){

printf("x=%d\n", x); /* 5 */

return 0;

}

void main(){

int x, y; /* main::x */

x = 111; /* 1 */

printf("x=%d\n", x); /* 2 */

printf("glob=%d\n", globvar); /* 3 */

y = f1();

y = f2();

}

В данном примере мы видим: во-первых, функции без параметров. (Это нормальная ситуация.), во-вторых, здесь применяются три переменные с именем «x».

Как выполняется программа?

/* 1 */ main::x = 111;

Это локальный x, а не глобальный. Глобальный x по-прежнему включает в себя 12.

/* 2 */ Напечатает значение переменной main::x, то есть 111.

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

К переменной же globvar у нас доступ есть.

/* 3 */ Печатает ::globvar. Мы обнаруживаем, что ее значение 0.

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

/* 4 */ При вызове f1() переменная f1::x заслоняет собой как main::x так и ::x. В данном случае напечатается 77, но ни ::x, ни main::x не будут изменены оператором x = 77. Это изменялась f1::x.

/* 5 */ При вызове f2() история интереснее. Тут нет своей собственной переменной x. Но какая переменная печатается здесь — ::x или main::x? Ответ: ::x, то есть 12.

Переменные названы локальными еще и потому, что они невидимы в вызываемых функциях. Это определение локальных переменных. То есть, в случае, если мы имеем:

funca(){

int vara;

...

...funcb();... /* вызов */

...

}

то из функции funcb() мы не имеем доступа к переменной vara.

funcb(){

int z;

z = vara + 1; /* ошибка, vara неизвестна внутри funcb() */

}

Если, в свою очередь, funcb() вызывает funcc(), то и из funcc() переменная vara невидима. Это правило служит все той же цели — разные функции могут быть написаны разными программистами, которые могут использовать одни и те же имена для разных переменных, не боясь их взаимопересечения.

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

Глава 17.
Процедуры

Бывают функции, которые не возвращают никакого значения. Такие функции обозначаются void («пустышка»). Такие функции называют еще процедурами.

void func(){

printf("Приветик!\n");

return; /* вернуться в вызывающую функцию */

}

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

int glob;

void func(int a){

glob += a;

}

Оператор return здесь необязателен, он в автоматическом режиме выполняется перед последней скобкой }.

Вызов таких функций не может быть использован в операторе присваивания:

main(){

int z;

z = func(7); /* ошибка, а что мы присваиваем ??? */

}

Корректный вызов таков:

main(){

func(7);

}

Просто вызов и все.

Зачем функции? Чтобы вызывать их с разными аргументами!

int res1, res2;

...

res1 = func(12 * x * x + 177, 865, 'x');

res2 = func(432 * y + x, 123 * y — 12, 'z');

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

Функция описывает последовательность действий, которую возможно выполнить много раз, но с разными исходными данными (аргументами). В зависимости от данных она будет выдавать разные результаты, но выполняя одни и те же действия. В том то и состоит ее прелесть: мы не дублируем один кусок программы много раз, а просто «вызываем» его.

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

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

int f(int x){

int y;

y = x + 4;

if(y > 10) return (x — 1);

y *= 2;

return (x + y);

}

Глава 18.
Рекурсивные функции. Стек

Рекурсивной называется функция, вызывающая сама себя.

int factorial(int arg){

if(arg == 1)

return 1; /* a */

else

return arg * factorial(arg — 1); /* b */

}

Эта функция при вызове factorial(n) вычислит произведение:

n * (n-1) * ... * 3 * 2 * 1

называемое «факториал числа n».

В данной функции переменная arg будет отведена (а после и уничтожена) много раз. Так что переменная factorial::arg должна получить еще и номер вызова функции:

factorial::arg[уровень_вызова]

И на каждом новом уровне новая переменная скрывает все предыдущие. Такая конструкция называется стек (stack).

В любой момент времени у стека доступно для чтения (копирования) или выбрасывания исключительно данное, находящееся на вершине стека. Так и в нашей рекурсивной функции переменная factorial::arg ведет себя именно как стек (этот ящик-стек имеет имя arg) — она имеет одно имя, но разные значения в разных случаях.

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

int stack[10];

int in_stack = 0; /* сколько элементов в стеке */

/* Занесение значения в стек */

void push(int x){

stack[in_stack] = x;

in_stack++;

}

/* Снять значение со стека */

int pop(){

if(in_stack == 0){

printf("Стек пуст, ошибка.\n");

return (-1);

}

in_stack--;

return stack[in_stack];

}

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

void main(){

push(1);

push(2);

push(3);

while(in_stack > 0){

printf("top=%d\n", pop());

}

}

Глава 19.
Стек и функции

Будем рассматривать любой вызов функции как помещение в специальный стек большого «блока информации», включающего в частности аргументы и локальные переменные вызываемой функции.

Оператор return из вызванной функции выталкивает со стека весь подобный блок. В качестве примера рассмотрим такую программу:

int x = 7; /* глобальная */

int v = 333; /* глобальная */

int factorial(int n){

int w;

/* лишняя переменная, исключительно для демонстрационных целей */

w = n;

if(n == 1) return 1; /* #a */

else return n * factorial(n-1); /* #b */

}

void func(){

int x; /* func::x */

x = 777; /* #c */

printf("Вызвана функция func()\n");

}

void main(){

int y = 12; /* main::y */

int z;

/* A */

z = factorial(3); /* B */

printf("z=%d\n", z);

func(); /* C */

}

Выполнение программы начнется с вызова функции main().

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

В данном случае мы смотрим «сверху» и видим:

main::z, main::y, ::x, ::v

В точке /* B */ мы вызываем factorial(3).

При новом взгляде видимы:

factorial(3)::w, factorial(3)::n, ::x, ::v

Стали невидимы:

main::z, main::y

Когда выполнение программы в функции factorial(3) дойдет до точки /* #b */ будет выполняться return 3 * factorial(2). В итоге будет вызвана функция factorial(2).

Когда выполнение программы в функции factorial(2) дойдет до точки /* #b */ будет выполняться return 2 * factorial(1).

В итоге будет вызвана функция factorial(1).

Затем в factorial(1) выполнение программы дойдет до точки /* #a */ и будет производиться return 1.

При return вычеркивается один блок информации со стека вызовов функций, и возобновляется выполнение «текущего оператора» в функции, ставшей новой вершиной стека вызовов. Заметьте, что в данной ситуации вызванные функции factorial(m) еще не завершились. В них еще есть что сделать: вычислить выражение в return, и собственно выполнить сам return. Вычисления будут продолжены.

Начинается выталкивание функций со стека и выполнение операторов return;.

Наконец, в точке /* C */ будет вызвана функция func(). Рассмотрим точку /* #c */ в ней. В данном месте нас интересует — какие переменные видимы? Видимы:

func::x = 777

::v = 333

И все. ::x заслонен локальной переменной. main::y, main::z невидимы, так как находятся не на вершине стека вызовов функций.

Многие функции более естественно выражаются через рекурсию. Хотя, часто это приводит к излишним вычислениям по сравнению с итерациями (то есть циклами). Вот к примеру, — числа Фибоначчи.

int fibonacci(int n){

if(n==1 || n==2) return 1;

/* else */

return fibonacci(n-1) + fibonacci(n-2);

}

void main(){

printf("20-е число Фибоначчи равно %d\n", fibonacci(20));

}

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

int called = 0;

int fibonacci(int n){

if(n==1){

called++;

return 1;

} else if(n==2)

return 1;

return fibonacci(n-1) + fibonacci(n-2);

}

void main(){

printf("20-е число Фибоначчи равно %d\n", fibonacci(20));

printf("fibonacci(1) была вызвана %d раз\n", called);

}

Она была вызвана... 2584 раза!

Глава 20.
Указатели

void f(int x){

x = 7;

}

main(){

int y = 17;

f(y);

printf("y=%d\n", y); /* печатает: y=17 */

}

В аргументе x передается копия значения y, поэтому x=7; не изменяет значения у.

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

Отбросим два способа:

u объявление y как глобальной (много глобальных переменных — плохой стиль);

u y=f(y); (а что, если надо изменить МНОГО переменных? return, к несчастью, может вернуть лишь одно значение).

Используется новая для нас конструкция: указатель.

Пример:

void f(int *ptr){ /* #2 */

*ptr = 7; /* #3 */

}

main (){

int y=17;

f(&y); /* #1 */

printf("y=%d\n", y); /* печатает: y=7 */

}

Ну как, нашли три отличия от исходного текста?

Мы вводим две новые конструкции:

u &y — указатель на переменную y или «адрес переменной y»

u *ptr — означает «разыменование указателя ptr». int *ptr; означает объявление переменной ptr, которая может содержать в себе указатель на переменную, хранящую int-число.

Для начала определим, что такое указатель.

int var1, var2, z; /* целочисленные переменные */

int *pointer; /* указатель на целочисленную переменную */

var1 = 12;

var2 = 43;

pointer = &var1;

Таким образом, указатель — это «стрелка, указывающая на некий ящик-переменную». Начало этой стрелки возможно (в свою очередь) хранить в какой-нибудь переменной. При этом, в случае, если стрелка указывает на переменную типа int, то тип переменной, хранящей начало стрелки, есть int *. Если типа char, то тип — char *.

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

Использование указателей

Указатели несколько различно ведут себя слева и справа от оператора присваивания.

Нас интересует новая операция, применяемая исключительно к указателям:

*pointer

Справа от присваиваний и в формулах *pointer означает «взять значение переменной (лежащее в ящике), на которую указывает указатель, хранящийся в переменной pointer». То есть *pointer означает «пройти по стрелке и взять указываемое ею значение».

printf("%d\n", *pointer);

Печатает 12.

z = *pointer; /* равноценно z = 12; */

z = *pointer + 66; /* равноценно z = 12 + 66; */

Заставим теперь указатель указывать на другую переменную (иначе говоря, «присвоим указателю адрес другой переменной»):

pointer = &var2;

После этого z = *pointer; означает z = 43;

Таким образом, конструкция:

z = *pointer;

означает

z = *(&var2);

означает

z = var2;

То есть * и & взаимно стираются.

Слева от присваивания: *pointer = 123; означает «положить значение правой части (т.е. 123) в переменную (ящик), на который указывает указатель, хранящийся в переменной pointer». Пройти по стрелке и положить значение в указываемую переменную.

В данном случае *pointer обозначает не значение указываемой переменной, а саму указываемую переменную.

pointer = &var2;

*pointer = 123;

означает

*(&var2) = 123;

означает

var2 = 123;

То есть снова * и & взаимно стирают друг друга.

Еще пример:

*pointer = *pointer + 66;

или

*pointer += 66;

Вернемся к примеру с функцией.

Пример:

void f(int *ptr){ /* #2 */

*ptr = 7; /* #3 */

}

main (){

int y=17;

f(&y); /* #1 */

printf("y=%d\n", y); /* печатает: y=7 */

}

Как он работает?

В строке /* #1 */ мы вызываем функцию f(), передавая в нее указатель на переменную y («адрес переменной y»).

В строке /* #2 */ отводится локальная переменная с именем ptr, которая в качестве начального значения получает значение первого аргумента функции в точке вызова — то есть указатель на y.

В строке /* #3 */ мы видим:

*ptr = 7;

что стоит рассматривать как:

*(&y) = 7; точнее *(&main::y)=7;

то есть как:

y = 7;

точнее

main::y=7;

Что и хотелось.

При этом отметим, что само имя «y» этой переменной внутри функции f() невидимо и неизвестно!

Пример: обмен значений двух переменных

void main(){

int x, y;

int temporary; /* вспомогательная переменная */

x=1; y=2;

temporary=x; x=y; y=temporary;

printf("x=%d y=%d\n", x, y); /* Печатает x=2 y=1 */

}

Теперь то же с использованием адресов и указателей:

void swap(int *a, int *b){

int tmp;

tmp = *a; *a = *b; *b = tmp;

}

void main(){

int x, y;

x = 1; y = 2;

swap(&x, &y);

printf("x=%d y=%d\n", x, y);

}

Еще пример:

int x;

int *ptr1, *ptr2;

ptr1 = &x; ptr2 = &x;

*ptr1 = 77;

printf("%d\n", *ptr2); /* Печатает 77 */

То есть на одну переменную могут указывать несколько указателей.

Еще пример:

int x;

int *ptr1; /* Не инициализирована */

x = *ptr1;

В ptr1 нет указателя ни на что, там имеется мусор. Указатель указывает «в никуда» (пальцем в небо). Скорее всего произойдет сбой в работе программы.

Отсюда вывод: всегда инициализируйте переменные, указатели в том числе.

Указатели в массивах

Язык Си работает с именами массивов специальным образом.

Имя массива «a» для

int a[5];

является на самом деле указателем на его нулевой элемент. То есть у нас имеются переменные (ящики) с именами a[0] a[1] ... a[4].

При этом само имя a при его использовании в программе означает &a[0].

Поэтому

int a[5];

/* Передается не КОПИЯ самого массива, а копия УКАЗАТЕЛЯ на его начало */

void f(int *a){ /* или f(int a[]), что имеется равноценная запись */

printf("%d\n", a[1]);

a[2] = 7;

}

main (){

a[1] = 777;

f(a); /* аргумент — массив */

printf("%d\n", a[2]);

}

Вызов f(a); сделает именно ожидаемые вещи.

В этом примере мы видим два правила:

Правило 1

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

Правило 2

Указатель на начало массива возможно индексировать как сам массив. Это вторая операция, помимо *pointer, применимая к указателям: pointer[n].

Второе правило влечет за собой ряд следствий.

int a[5]; /* массив */

int *ptr; /* указательная переменная */

ptr = a; /* законно, означает ptr = &a[0]; */

Теперь

ptr[0] = 3; /* означает a[0] = 3; */

ptr[1] = 5; /* означает a[1] = 5; */

Более того. Возьмем теперь

ptr = &a[2];

Мы как бы «приложили» к массиву a[] массив ptr[]. В котором:

u ptr[0] есть a[2]

u ptr[1] есть a[3]

u ptr[2] есть a[4]

u ptr[3] находится за концом массива a[], т.е. мусор

Более того, допустимы отрицательные индексы!

u ptr[-1] есть a[1]

u ptr[-2] есть a[0]

u ptr[-3] находится перед началом массива a[], т.е. мусор

Итак: индексировать возможно и массивы, и указатели.

Кстати, для имени массива a[] — *a означает то же самое, что и a[0]. Это обратное следствие из схожести массивов и указателей.

Глава 21.
Турбо Си

Турбо Си обозначает два программных изделия: пакет программ, обеспечивающий выполнение последовательности команд в стиле Unix: make/compiler/linker, и интегрированную программную среду для разработки программ. Пакет программ содержит утилиту make, компилятор tcc и настраиваемый компоновщик. Компилятор, входящий в пакет, похож на большинство других компиляторов Си для IBM PC, но является более быстрым. Интегрированная программная среда представляет собой программу под названием tc, которая объединяет в себе текстовый редактор, ориентированный на создание текстов программ на языке Си, построитель задач, ориентированный на реализацию программного проекта, и утилиты исполнения программ. Наличие интегрированной среды выделяет Турбо Си среди конкурентов (также, как и ее безусловно блестящая реализация). Интегрированную среду можно считать витриной Турбо Си. Ее большим достоинством является достигнутый уровень интеграции между редактором, компилятором и компоновщиком.

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

Настройка интегрированной среды

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

u Модель памяти

u соглашение о вызываемых функциях

u плавающая арифметика

u сопроцессор или эмуляция

u уровень оптимизации

u уровень контроля за ошибками.

Можно выбирать и большее число параметров Турбо Си. Турбо Си способен осуществлять строгий контроль за ошибками и подозрительными местами в программе и выдавать предупреждающие сообщения. Можно использовать Установочное Меню для подавления предупреждающих сообщений. Можно установить необходимость соответствия жестким требованиям ANSI. Можно потребовать выдачи предупреждающего сообщения при любом несоответствии описания функции и прототипа или можете разрешить неявное описание функции и определение случайных параметров, как это делается в так называемых K&R-компиляторах.

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

Редактор

В первом приближении редактор Турбо Си похож на Word, работающий в режиме, альтернативном к режиму «документ». Архитектура этого редактора характерна для многих других программных изделий Borland, включая программу Sidekick Notepad и редактор Турбо-Паскаля. Если вы умеете работать с редактором Турбо-Паскаля, то вы умеете работать и с редактором Турбо Си. Однако в случае приобретения именно этого редактора пользователи получат некоторые преимущества.

Путем использования программы TCINST вы можете изменить размер окна по умолчанию и назначение клавиш команд редактирования. Программисты, которые раньше работали с другим редактором, оценят предоставляемую им редактором Турбо Си возможность работы с двумя наборами команд редактирования, что позволяет избежать многих затруднений. Диапазон изменения параметров редактора ограничен: определенные функциональные клавиши и комбинации различных клавиш с клавишей ALT зарезервированы под «горячие клавиши» и не могут быть задействованы под команды редактирования.

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

Компоновщик

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

Утилита построителя задач (Make)

Компилятор Турбо Си имеет утилиту Make, характерную для ОС Unix и других компиляторов Си для IBM PC. Интегрированная Среда дает уникальную возможность связывать при разработке программ исходные и объектные модули с соответствующими им загрузочными модулями. В этом отношении утилита Make является традиционной. Однако утилита Make является частью интегрированной среды и использует файл сопровождения, называемый «файлом проекта», который является более легким для чтения и понимания, чем у командной утилиты MAKE.

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

myprogram (keys.h, twindow.h)

Если версия модуля myprogram.c старше версии модуля myprogram.obj либо версии модулей key.h или twindow.h старше версии модуля myprogram.c, то модуль myprogram.c транслируется в модуль myprogram.obj. Если версия модуля myprogram.obj старше версии модуля myprogram.exe, то модуль myprogram.obj компонуется с соответствующими (зависящими от модели памяти) начальным объектным файлом и исполняющей библиотекой. Использование проектной утилиты Make становится насущно необходимым, когда при формировании загрузочного модуля используются многочисленные исходные модули на Си, зависящие от различных файлов заголовков. Вы можете указывать объектные файлы и объектные библиотеки в проектном файле MAKE. Интегрированная среда будет включать объектные файлы без попытки их компиляции и будет отыскивать соответствующие библиотечные модули для разрешения вызовов внешних функций.

Обнаружение ошибок при компиляции и компоновке

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

Программные средства низкого уровня

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

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

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

Начальная установка

И командный компилятор, и интегрированная среда используют специальные файлы, в которых пользователь описывает требуемую ему конфигурацию. Каждому из компиляторов соответствует свой файл. После того, как вы произвели начальные установки в интегрированной среде, вы должны запустить процесс инсталляции еще раз и установить значения параметров по умолчанию, включая путь доступа, по которому компилятор tc будет искать библиотеки, стартовую программу, включаемые файлы и себя самого. Эти параметры могут быть установлены также из интегрированной среды путем выбора меню Options (Параметры). После установки всех параметров такими, как вы хотели, выберите в меню строку «Запомнить», завершая тем самым создание файла tcconfig.tc.

Функции общего назначения

void clear_screen()

Эта функция очищает экран и устанавливает курсор в левый верхний угол. Экран заполняется символами пробела, и атрибуты символов извлекаются из символьной переменной attrib, описанной в программе ibmpc.c. Значение этого атрибута соответствует байту атрибута в видеопамяти, сопутствующему каждому байту ASCII-кода при записи символа в видеопамять. Переменная attrib принимает значение, которое соответствует установке черного цвета для фона символа и белого цвета для самого символа. Если вы желаете другое значение атрибута, то должны изменить значение этой переменной перед вызовом функции clear_screen.

int vmode()

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

void cursor(int x,int y)

Эта функция устанавливает курсор в позицию на экране, определяемую координатами X и Y. Координаты (0,0) соответствуют левому верхнему углу экрана. Значение координаты X изменяется в диапазоне от 0 до 79, а координаты Y — в диапазоне от 0 до 24.

void curr_cursor(int *x, int *y)

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

int set_cursor_type(int t)

Эта функция устанавливает текущий размер курсора. Размер обозначается целочисленной переменной, которая содержит в старшем байте номер начальной растровой линии курсора и в младшем байте — номер конечной растровой линии курсора. Редактор текстов и программа ввода данных, представленные в следующих разделах, используют возможность изменения размера курсора для обозначения того, какой из режимов установлен: Вставки или Замены. Курсор прямоугольной формы обозначает, что установлен режим Вставки, он определяется значением переменной, равным 0x0106. При этом курсор занимает растровые линии с 1 по 6 того знакоместа, в котором он находится. Курсор в виде знака подчеркивания обозначает режим Замены и определяется значением переменной, равным 0x0607. При этом курсор занимает растровые линии 6 и 7 знакоместа.

int get_char()

Эта функция является очень важной, так как выполняет несколько крайне необходимых действий в вызывающих ее программах. get_char принимает поступающий от клавиатуры символ. Ее главное назначение состоит в приеме одиночного символа от клавиатуры без эха, без преобразования и без обращения к функциям DOS. Кроме того, она выполняет следующие дополнительные функции. В то время, как система ожидает нажатия клавиши, функция get _char вызывает программные прерывания по вектору 0x28. Когда вы нажимаете функциональную клавишу, ROM BIOS возвращает двухбайтный код. Первый байт имеет нулевое значение и обозначает, что следующий за ним код символа соответствует функциональной клавише. Этот второй байт содержит 7-битный ASCII- код, который является уникальным для каждой функциональной клавиши. Если не учитывать первый нулевой байт, то реакция на нажатие функциональных клавиш сходна с реакцией на нажатие клавиш, соответствующих ASCII-символам, в частности, буквам. Функция get_char преобразует двухбайтную последовательность, возвращаемую ROM-BIOS в ответ на нажатие функциональной клавиши, в 8-битный код, позволяющий отличать функциональные клавиши от нефункциональных. Функция осуществляет это преобразование путем установки старшего разряда в байте, содержащем АSCII-код символа и следующем за нулевым байтом.

void vpoke(unsigned vseg,unsigned adr,unsigned chr) int vpeek (unsigned vseg,unsigned adr)

Эти две функции считывают из видеопамяти коды символов и атрибуты символов и записывают их в видеопамять. Для того, чтобы использовать эти функции, вы должны разобраться в организации видеопамяти IBM PC, а также принципах формирования изображения. В тело функций vpoke и vpeek включены фрагменты на ассемблере. Для того, чтобы оттранслировать эти функции, вы должны иметь программу Macro Assembler (MASM), поскольку именно она используется в Турбо Си для трансляции ассемблерных фрагментов.

Примеры

/* позиция курсора */

void cursor(int x,int y) {

rg.x.ax = 0x0200;

rg.x.bx = 0;

rg.x.dx = ((y << 8) & 0xff00) + x;

int86( 16, &rg, &rg);

}

/* возвратить позицию курсора */

void curr_cursor( int *x, int *y )

{

rg.x.ax = 0x0300;

rg.x.bx = 0;

int86( 16, &rg, &rg );

*x = rg.h.dl;

*y = rg.h.dh;

}

/* установить тип курсора */

void set_cursor_type( int t )

{

rg.x.ax = 0x0100;

rg.x.bx = 0;

rg.x.cx = t;

int86( 16, &rg, &rg );

}

char attrib = 7;

/* очистить экран */

void clear_screen()

{

cursor(0, 0);

rg.h.al = ' ';

rg.h.ah = 9;

rg.x.bx = attrib;

rg.x.cx = 2000;

int86( 16, &rg, &rg );

}

/* проверить клавишу Scroll Lock */

int scroll_lock()

{

rg.x.ax = 0x0200;

int86( 0x16, &rg, &rg);

return rg.h.al & 0x10;

}

void (* helpfunc)();

int helpkey = 0;

int helping = 0;

/* принять символ от клавиатуры */

int get_char()

{

int c;

while (1) {

rg.h.ah = 1;

int86(0x16, &rg, &rg);

if (rg.x.flags & 0x40) {

int86(0x28, &rg, &rg);

continue;

}

rg.h.ah = 0;

int86(0x16, &rg, &rg);

if (rg.h.al == 0)

c = rg.h.ah | 128;

else

c = rg.h.al;

if (c == helpkey && helpfunc) {

if (!helping) {

helping = 1;

(*helpfunc)();

helping = 0;

continue;

}

}

break;

}

return c;

}

Экранные окна

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

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

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

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

Программные системы могут использовать окна для отображения информации независимо от содержания других окон и размера области пространства экрана, занимаемой другими окнами. Благодаря этому можно обозревать на экране несколько окон, даже если каждое из них занимает больше половины экрана, потому что окна могут как бы «всплывать» друг над другом. Свойство всплывания окон часто объясняют путем сравнения экрана дисплея с поверхностью стола. Допустим, на вашем столе лежит множество документов, но в каждый момент времени вы можете работать только с одним из этих документов, возможно, наиболее важным для вас. То, что можно делать с бумагами, можно выполнять и на экране дисплея.

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

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

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

Библиотека оконных функций

Библиотека оконных функций поддерживает широкий диапазон экранных оконных операций. Функции подразделены на подсистемы, использование которых позволяет организовать меню, контекстно-управляемые подсказки, редактирование текста и форматирование данных в прикладных системах. Эти подсистемы поддерживаются общецелевой оконной библиотекой, которая может использоваться в прикладных программах так же, как и подсистемы. Оконные функции могут применяться для организации окон в одной из двух конфигураций: стековой и слоеной; одна конфигурация является с точки зрения использующей их программы подмножеством другой. Слоеные окна обладают большими возможностями, чем стековые, однако стековые более эффективны, то есть выдача на экран и уничтожение их происходит быстрее.

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

Стековые окна

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

Если вы сначала создали окно А, а затем окно В, которое закрывает часть окна А, то при записи текста в окно А может случиться, что часть текста попадет в часть окна В, закрывающую окно А. Далее, если вы уничтожите окно A до уничтожения окна В, то часть буфера сохранения окна А запишется в начало части окна В. Большинство коммерческих оконных пакетов поддерживает только стековые окна, поскольку большинство приложений, использующих окна, могут успешно функционировать в среде стековых окон.

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

Слоеные окна

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

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

Независимо от использования стековых или слоеных окон, работа их будет зависеть от требований вашей системы. Каждый подход имеет свои преимущества и недостатки. Если вы начали со стековых окон и обнаружили позже, что вам нужны дополнительные свойства слоеной оконной архитектуры, вы можете выполнить изменение путем перекомпиляции оконных функций без определения FASTWINDOWS и перередактировать связи ваших программ.

Оконные функции

Эти функции включены в оконную библиотеку.

WINDOW *establish_window(x,y,h,w)

Эта функция создает окно, но не отображает его. (Для показа окна воспользуйтесь функцией display_window). Параметры x и y являются координатами верхнего левого угла окна. Эти параметры выражаются в символьных позициях экрана, где координаты левого верхнего угла самого экрана равны (0,0). Параметры h и w являются высотой и шириной окна в символьных позициях. Эта функция не вызовет никакого изменения экрана. Если вы создадите окно, позиция и размеры которого не позволяют ему быть полностью видимым, программное обеспечение установит позицию, при которой оно полностью поместится на экране. Если ширина больше 80 или высота больше 25, функция преобразует соответствующий размер до максимально допустимого значения.

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

Функция establish_window возвращает указатель на структуру WINDOW, которая определена во включаемом файле twindow.h. Этот указатель используется при последующих вызовах оконных функций для идентификации окна, к которому относится вызов. Передавайте указатель NULL другим оконным функциям, если хотите работать с окном, образованным последним.

void set_border(WINDOW *wnd, int btype)

Эта функция устанавливает тип оконной рамки. Целочисленный параметр btype должен принимать одно из следующих значений:

u 0 — одинарные линии (по умолчанию);

u 1 — двойные линии;

u 2 — одинарные верх и низ, двойные боковые;

u 3 — двойные верх и низ, одинарные боковые;

u 4 — специальное проталкиваемое вниз окно меню с одинарными линиями и t-блоком от верхнего левого до верхнего правого угла.

void set_colors(WINDOW *wnd, int area, int bg, int fg, int inten)

Эта функция устанавливает цвета для окна. Параметр area может принимать следующие значения:

u ALL

u BORDER

u TITLE

u ACCENT

u NORMAL

Этот параметр определяет части окна, на которые распространяется действие. ALL соответствует всем частям. BORDER устанавливает цвета рамки окна, которая занимает односимвольный ряд позиций вокруг него. TITLE устанавливает цвета заголовка, размещаемого на верхней границе окна. ACCENT — это область, используемая для блоков меню и другого выделенного текста, который появляется в качестве временно подсвечиваемых частей области NORMAL, где отображается весь текст.

Целые числа bg и fg задают цвета для фона и переднего плана различных частей окна. Допускаются следующие цвета:

u RED — алый;

u GREEN — зеленый;

u BLUE — голубой;

u WHITE — белый;

u YELLOW — желтый;

u AQUA — аквамариновый;

u MAGENTA — красный;

u BLACK — черный.

Целое число inten определяет интенсивность символов переднего плана и может принимать два значения:

u BRIGHT — яркая

u DIM — обычная

Все значения определены в исходном файле twindow.h.

void set_title(WINDOW *wnd, char *title)

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

void set_intensity(WINDOW *wnd, int inten)

Эта функция устанавливает интенсивность фона для всех частей окна. Значениями inten могут быть BRIGHT или DIM.

void display_window(WINDOW *wnd)

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

void delete_window(WINDOW *wnd)

Эта функция уничтожает созданное ранее окно. Если окно является видимым (выдано с помощью display_window), то оно очищается, и экран восстанавливается к его предыдущему образу.

void clear_window(WINDOW *wnd)

Эта функция заполняет текстовую область окна пробелами.

void hide_window(WINDOW *wnd)

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

void wcursor(WINDOW *wnd, int x, int y)

Каждое окно обладает логической позицией курсора, которая изменяется от 0,0 (верхний левый угол текстовой области окна) до граничных размеров окна. Эта функция переставляет курсор в окне. Функции wputehar и wprintf выдают текст относительно данной позиции курсора. Они изменяют позицию курсора точно также, как это происходило бы, если бы управление курсором осуществлялось с помощью клавиатуры дисплея. Символ новой строки (\n) устанавливает курсор в нулевой столбец следующей строки, перемещая текст в окне вверх на одну строку, если курсор уже находится в нижней строке окна. Символ табуляции (\t) перемещает курсор к следующей позиции табуляции в окне. Табуляции располагаются с интервалом в четыре символа.

void wprintf(WINDOW *wnd, char *fmt, ...)

Эта функция является оконной версией стандартной функции printf языка Си. Она использует стандартную функцию sprintf для построения строки, выдаваемой в окно. Убедитесь, что результирующая строка не длиннее 100 символов, или измените длину в массиве dlin функции wprintf в исходном файле twindow.c.

void wputchar(WINDOW *wnd, int ch)

Эта функция является оконной версией putchar. Она записывает символ из ch в окно в текущую позицию курсора. Курсор при этом продвигается дальше. Если символ является символом новой строки (\n), курсор переставляется в нулевую позицию следующей строки. Если символ является табуляцией (\t), курсор продвигается к следующей позиции табуляции окна. Оконные позиции табуляции располагаются в каждой четвертой позиции окна.

void reverse_video(WINDOW *wnd)

После обращения к этой функции все вызовы wprintf и wputchar будут при отображении использовать цвета ACCENT вместо NORMAL.

void normal_video(WINDOW *wnd)

После обращения к этой функции все вызовы wprintf и wputchar будут при отображении использовать цвета NORMAL. Эта функция используется для возврата к нормальному отображению после вызова reverse_ video.

void close_all()

Эта функция уничтожает все образованные окна.

void move_windom(WINDOW *wnd, int x, int y)

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

void rmove_window(WINDOW *wnd, int x, int y)

Эта функция перемещает окно путем добавления к значениям x и y текущих координат левого верхнего угла окна. Используйте эту функцию только для слоеных окон.

void forefront(WINDOW *wnd)

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

void rear_window(WINDOW *wnd)

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

int get_selection(WINDOW *wnd, int sel, char *keys)

Эта функция позволяет использовать окно в качестве меню. Вы должны создать окно и записать в него несколько строк текста, скажем, функцией wprintf. Вы можете использовать set_colors для установки значений цветов ACCENT в окне (по умолчанию — черные буквы на белом фоне). Затем вы вызываете get_selection. Функция использует окно в качестве меню, каждая строка которого представляет альтернативу выбора. Альтернативы подсвечиваются цветом ACCENT.

Целочисленное значение sel используется для первоначального размещения яркого блока курсора меню на одной из альтернатив. Значение 1 соответствует первой альтернативе, 2 — второй и т.д. Пользователь может перемещать блок меню вверх и вниз клавишами управления курсором и производить выбор в меню клавишей Enter. Клавиша <Esc> применяется для выхода из этого процесса. Ссылка keys указывает на строку значений клавиш, которые могут использоваться для выбора из меню. Некоторые системы меню разрешают пользователю применять нажатия клавиш наряду с перемещением блока курсора. Для выключения этой возможности передайте параметру keys ссылку NULL. Эта функция возвращает целочисленное значение, равное номеру выбранной в меню альтернативы. Значение обычно равняется единице и более, однако, если пользователь нажимает клавишу <Esc>, функция возвращает нуль. Функция будет также возвращать значения FWD или BS, если пользователь нажимает клавиши управления курсором вправо или влево. Эти значения определены в keys.h.

void error_message(char *s)

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

void clear_message()

Эта функция удаляет сообщение об ошибке, если оно было выдано функцией error_message.

Примеры

/* установить рамку окна */

void set_border(WINDOW *wnd, int btype)

{

if (verify_wnd(&wnd)) {

BTYPE = btype;

redraw(wnd);

}

}

/* установить цвета */

void set_colors(WINDOW *wnd,int area, int bg, int fg, int inten)

{

if (vmode() == 7) {

if (bg != WHITE && bg != BLACK)

return;

if (fg != WHITE && fg != BLACK)

return;

}

if (verify_wnd(&wnd)) {

if (area == ALL)

while (area)

WCOLOR [--area] = clr(bg, fg, inten);

else

WCOLOR [area] = clr(bg, fg, inten);

redraw(wnd);

}

}

/* установить яркость окна */

void set_intensity(WINDOW *wnd, int inten)

{

int area = ALL;

if (verify_wnd(&wnd)) {

while (area) {

WCOLOR [--area] &= ~BRIGHT;

WCOLOR [area] |= inten;

}

redraw(wnd);

}

}

/* установить заголовок */

void set_title(WINDOW *wnd, char *title)

{

if (verify_wnd(&wnd)) {

WTITLE = title;

redraw(wnd);

}

}

/* перевыдать окно при изменении атрибута */

static redraw(WINDOW *wnd)

{

#ifndef FASTWINDOWS

int x, y, chat, atr;

for (y = 1; y < HEIGHT-1; y++)

for (x = 1; x < WIDTH-1; x++) {

chat = dget(wnd, x, y);

atr = (((chat>>8)&255) ==

PNORMAL ? WNORMAL : WACCENT);

displ(wnd, x, y, chat&255, atr);

}

wframe(wnd);

#endif

PNORMAL = WNORMAL;

}

/* выдать созданное окно */

void display_window(WINDOW *wnd)

{

if (verify_wnd(&wnd) && !VISIBLE) {

VISIBLE = 1;

#ifdef FASTWINDOWS

if (HIDDEN) {

HIDDEN = 0;

vrstr(wnd);

}

else {

vsave(wnd);

clear_window(wnd);

wframe(wnd);

}

#else

vswap(wnd);

#endif

}

}

/* закрыть все окна */

void close_all()

{

WINDOW *sav, *wnd = listtail;

while (wnd) {

sav = PREV;

delete_window(wnd);

wnd = sav;

}

}

/* удалить окно */

void delete_window(WINDOW *wnd)

{

if (verify_wnd(&wnd)) {

hide_window(wnd);

free(SAV);

remove_list(wnd); /* удалить окно из списка */

free(wnd);

}

}

/* скрыть окно */

void hide_window(WINDOW *wnd)

{

if (verify_wnd(&wnd) && VISIBLE) {

#ifndef FASTWINDOWS

vswap(wnd);

#else

vrstr(wnd);

#endif

HIDDEN = 1;

VISIBLE = 0;

}

}

/* очистить область окна */

void clear_window(WINDOW *wnd)

{

register int x1, y1;

if (verify_wnd(&wnd))

for (y1 = 1; y1 < HEIGHT-1; y1++)

for (x1 = 1; x1 < WIDTH-1; x1++)

displ(wnd,x1, y1, ' ', WNORMAL);

}

/* изобразить окно */

static wframe(WINDOW *wnd)

{

register int x1, y1;

if (!verify_wnd(&wnd))

return;

/* заголовок окна */

displ(wnd,0, 0, NW, WBORDER);

dtitle(wnd);

displ(wnd,WIDTH-1, 0, NE, WBORDER);

/* боковые стороны окна */

for (y1 = 1; y1 < HEIGHT-1; y1++) {

displ(wnd,0, y1, SIDE, WBORDER);

displ(wnd,WIDTH-1, y1, SIDE, WBORDER);

}

/* низ окна */

displ(wnd,0, y1, SW, WBORDER);

for (x1 = 1; x1 < WIDTH-1; x1++)

displ(wnd,x1, y1, LINE, WBORDER);

displ(wnd,x1, y1, SE, WBORDER);

}

/* выдать заголовок окна */

static dtitle(WINDOW *wnd)

{

int x1 = 1, i, ln;

char *s = WTITLE;

if (!verify_wnd(&wnd))

return;

if (s) {

ln = strlen(s);

if (ln > WIDTH-2)

i = 0;

else

i = ((WIDTH-2-ln) / 2);

if (i > 0)

while (i--)

displ(wnd, x1++, 0, LINE, WBORDER);

while (*s && x1 < WIDTH-1)

displ(wnd, x1++, 0, *s++, WTITLEC);

}

while (x1 < WIDTH-1)

displ(wnd, x1++, 0, LINE, WBORDER);

}

/* записать символ в окно */

void wputchar(WINDOW *wnd, int c)

{

if (!verify_wnd(&wnd))

return;

switch (c) {

case '\n':

if (SCROLL == HEIGHT-3)

scroll(wnd, UP);

else

SCROLL++;

WCURS = 0;

break;

case '\t':

do displ(wnd,(WCURS++)+3,SCROLL+1,'',WNORMAL);

while ((WCURS%TABS) && (WCURS+1) < WIDTH-1);

break;

default: ;

if ((WCURS+1) < WIDTH-1) {

displ(wnd, WCURS+1, SCROLL+1, c, WNORMAL);

WCURS++;

}

break;

}

}

/* установить курсор окна */

void wcursor(WINDOW *wnd, int x, int y)

{

if (verify_wnd(&wnd) && x < WIDTH-1 && y < HEIGHT-1) {

WCURS = x;

SCROLL = y;

cursor(COL+x+1, ROW+y+1);

}

}

/* выдать символ в окно */

void displ(WINDOW *wnd, int x, int y, int ch, int at)

{

int *vp;

int vch = (ch&255)|(at<<8);

if ((vp = waddr(wnd, x, y)) != NULL)

*vp = vch;

else

vpoke(VSG,vad(x+COL,y+ROW),vch);

}

/* получить отображенный символ из окна */

static int dget(WINDOW *wnd, int x, int y)

{

int *vp;

if ((vp = waddr(wnd, x, y)) != NULL)

return *vp;

return vpeek(VSG,vad(x+COL,y+ROW));

}

Перемещение окна

При использовании слоеных окон вам доступны функции, позволяющие перемещать окно в абсолютную или относительную позицию на экране. Заметим, что эти функции — move_window и rmove _window — недоступны для стековых окон. Каждое нажатие клавиши со стрелкой вызывает перемещение окна на одну символьную позицию в направлении стрелки. Функция rmove_window используется для перемещения окна. Обратите внимание на то, как центральное окно перемещается между двумя остальными. Когда вы нажимаете клавишу <Esc>, открытое первым окно уничтожается. Еще одно нажатие вызывает уничтожение верхнего окна. Заключительное нажатие уничтожает среднее окно с цитатой и завершает программу. Перемещение окна иллюстрирует использование оконных буферов сохранения, запрограммированное в библиотеке. Поскольку требуется определенная обработка для анализа каждого буфера при записи символа в окно, работа функций усложняется по мере увеличения размера окон и их количества.

Подъем и опускание окон

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

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

Контекстно-управляемые окна подсказки

Первой проблемой, обычно возникающей при запуске новой программы, является незнакомство программы с языком своего пользователя. Какая клавиша должна быть нажата? Какое действие будет следующим? Что выполняет данный элемент меню? Независимо от того, сколько усилий вложено в разработку самообъясняемого пользовательского языка, у пользователей всегда возникают вопросы, поскольку язык новой системы для него всегда иностранный. Дело не только в знакомстве пользователей с языком, часто они даже не знают, что система может, а чего не может делать. По традиции эта проблема решается обращением к печатным руководствам пользователя и, возможно, автоматизированным справочникам. Недостатком этих решений является необходимость переключения внимания пользователя от работы с системой к чтению руководства или запуску справочника. Если бы экраны были достаточно большими, система могла бы поддерживать постоянный показ руководства пользователя. Как только пользователю потребовалась бы информация, руководство было бы доступно. Это решение не продержалось бы долго; когда пользователь уже знает систему, информация подсказки не нужна и нежелательна. Значимая для новичка информация — это мусор для ветерана.

Большинство интерактивных систем имеют общее свойство: когда пользователю необходима информация, он смотрит на экран и гадает, какую клавишу нажать. Представляется вполне естественным, что в число нажатий клавиш, которые система будет распознавать в любое время, нужно включить функциональную клавишу <Help> (Подсказка). Нажмите требуемую прикладную клавишу, и программа выполнит ваш запрос; нажмите функциональную клавишу подсказки, и программа сообщит вам кое-что о том, какие клавиши она ожидает и что случится при их нажатии.

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

Окно подсказки содержит текст, который объясняет некоторую часть программы. Когда пользователи работают с программой и переходят от возможности к возможности, содержимое и расположение окон подсказки изменяются для отражения текущего контекста. Эти изменения не видны, поскольку они происходят внутри программ. Когда пользователь нажимает функциональную клавишу подсказки, соответствующее сообщение-подсказка выдается в выскакивающем окне. Поскольку сообщение-подсказка зависит от текущего положения в программе, окно подсказки называется контекстно-управляемым. Опытные пользователи могут игнорировать возможности подсказки программы; новички могут нажимать функциональную клавишу подсказки при каждом изменении состояния программы и получать советы, напоминания или детальные инструкции. Разработчик системы решает, как много и какого вида подсказки система будет предусматривать для пользователя.

Некоторые системы обладают несколькими уровнями подсказки в зависимости от опыта пользователя. Программа обработки слов Word Star применяла эту технологию на протяжении многих лет. Уровни подсказки изменяются от простых сообщений типа «нажмите <Esc> для возврата к...» до полных пользовательских руководств. Многие разработчики программ предпочитают передавать руководство пользователя таким способом, а не в виде больших громоздких документов. Эта процедура имеет два последствия: пользователи связывают экстравагантные руководства с качеством программ, а большие книги способствуют борьбе с «программными пиратами». Как бы там ни было, пользователи также пришли к ожиданию систем, требующих минимального использования пользовательских руководств. Пользователи хотят интерактивную подсказку.

Программирование окон подсказки

Каждая прикладная программа сообщает функциям подсказки, какой файл подсказки использовать и какое окно подсказки является текущим. Файл подсказки содержит текст для каждого окна подсказки. Функции подсказки отслеживают сигналы от клавиатуры и выдают текущее окно подсказки при нажатии функциональной клавиши <Help>. Программа, использующая программное обеспечение подсказки, должна предусматривать следующие интерфейсы с функциями подсказки:

u функциональный вызов для задания имени текстового файла подсказки;

u значение функциональной клавиши подсказки;

u функциональные вызовы для идентификации текущего окна подсказки;

u использование функции клавиатурного ввода get_char для любого ввода с клавиатуры.

Последнее из этих четырех требований может потребовать некоторых разъяснений. Во время работы программы программное обеспечение подсказки перехватывает каждое прерывание от клавиатуры для того, чтобы убедиться, не нажата ли функциональная клавиша подсказки. Если это так, то управление передается оконному процессу подсказки. Если нет, значение клавиши пересылается в ожидающую его программную функцию. Программное обеспечение подсказки может управлять этим перехватом только в том случае, если вы используете функцию get_char для ввода с клавиатуры. Как разъяснено выше, get-char следит за нажатием на клавиатуре функциональной клавиши подсказки. Если вы используете другие способы чтения символов с клавиатуры, то переключение функциональной клавишей подсказки не будет сделано.

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

u вы не сможете использовать стандартные библиотечные функции scanf или getchar, поскольку ни одна из этих функций не использует функцию get_char;

u вы не сможете воспользоваться стандартной функцией ввода с консоли (getch), предусмотренной в Турбо Си;

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

Потеря функций scanf и getchar — это небольшая потеря для программирования диалога. Эти функции бесполезны в интерактивной среде. Они являются функциями буферизованного ввода, требующими нажатия клавиши <Enter> для завершения ввода символа или строки; кроме того, они нечувствительны к расположению курсора и длине поля. Эти функции дублируют свой ввод в стандартный вывод, соблюдая соглашения командной строки ДОС, и они будут дублировать изображения двойных символов при вводе управляющего символа.

Если введена клавишная комбинация Ctrl+С, функции могут аварийно завершить программу. Функции плохо работают с функциональными или курсорными клавишами. Турбо Си включает их для поддержки совместимости с UNIX. Эти функции происходят из компьютерных систем с телетайпными терминалами. Может показаться, что вы теряете ряд возможностей, когда лишаетесь устройства stdin, однако это устройство предназначено для программ, которые могут принимать входные данные из файлов и выходов других программ так же, как и с клавиатуры. Эти программы (или, по крайней мере, stdin используется для ввода данных в них) обычно не предназначены для использования в интерактивной среде.

Текстовый файл окна подсказки

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

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

Функции подсказки

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

void load_help(char *filename)

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

void set_help(char *helpname, int x, int y)

Эта функция задает текущее окно подсказки с восьмисимвольным массивом, соответствующим наименованию окна в файле подсказки. Наименование окружается в файле подсказки угловыми скобками, причем не включает в себя эти скобки. Целые числа х и y задают координаты верхнего левого угла (в символьных позициях) окна подсказки, что позволяет пользователям использовать одно и то же окно в различных контекстах, но в различных листах экрана.

Изменение функциональной клавиши подсказки

Если вам нужно задать значение функциональной клавиши, отличной от <F1> (по умолчанию), вы должны изменить значение глобальной целочисленной переменной helpkey.Следующий фрагмент изменяет функциональную клавишу подсказки на <F2>.

#include "keys.h"

extern int helpkey;

helpkey = F2;

Изменение функции подсказки

Когда используются функции подсказки, указатель инициализируется адресом функции с именем help. Если вам нужно использовать другую функцию, скажем, вместо стандартной функции help, вы можете изменить значение указателя helpfunc. Для применения функциональной клавиши подсказки для вызова вашей функции используйте следующие операторы:

extern void (*helpfunc)();

void yourfunc();

helpfunc = yourfunc;

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

extern void (*helpfunc)();

extern void help();

helpfunc = help;

Выключение подсказки

Имеются три пути для выключения подсказки: вы можете установить значение функциональной клавиши подсказки равным 0; можно установить указатель функции подсказки равным NULL; можно вызвать set_help, передавая указатель на строку нулевой длины. Для включения подсказки необходимо отменить выбранное действие.

Использование данных в окнах

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

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

Шаблон ввода данных

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

Поле ввода данных

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

Позиция

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

Атрибуты

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

Буфер

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

Проверка допустимости значений

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

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

Help-информация

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

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

Маска вводимых данных

Когда вы определяете поле для ввода данных, вы можете задать маску для всех вводимых в это поле значений. Эта маска специфицируется в массиве символов, включающем в себя символы нижнего подчеркивания и пунктуации. Символ нижнего подчеркивания соответствует позиции маски, в которой возможен ввод данных, а пунктуация служит для обозначения других (различных) отображаемых символов кода ASCII. Длина элементов данных описывается количеством символов нижнего подчеркивания, а длина буфера, выделяемого под вводимые данные, описываемые маской, должна быть не меньше, чем длина элемента данных плюс 1. Символы пунктуации в буфер не перекачиваются. Например, маска вводимых данных, касающихся номера телефона (включая код местности и расширение) может иметь следующий вид:

char phone_mask [] = "(____)____-____ ext:_____";

Приглашения к вводу в поле (Prompts)

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

Ввод данных

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

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

Функции сбора данных

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

void init_template(WINDOW *wnd)

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

FIELD *establish_field (WINDOW *wnd,int x,int y,char *msk,char *bf,int t)

Эта функция устанавливает поля ввода данных в окне, которое было инициализировано функцией init_template как окно с шаблоном ввода данных. Символы x и y суть координаты, специфицирующие размещение маски поля ввода, причем предпочтительнее указывать координаты относительно окна, а не экрана. Указатель bf суть указатель на определяемый при обращении буфер сбора данных. Параметр t (значение типа integer) указывает тип поля ввода данных.

Различают следующие типы полей:

u А = алфавитно-цифровое

u N = цифровое, незначащие разряды заполнены пробелами

u Z = цифровое, незначащие разряды заполнены нулями

u С = поле денежной единицы

u D = поле даты

Указатель msk является указателем на символ маски, который определяет длину поля и управляет отображением символа пунктуации на экране (причем последние не контролируются в буфер сбора данных поля). Маска включает в себя обычно несколько символов. Символ подчеркивания указывает позицию символа, который должен быть введен в данном месте. Массив bf должен содержать столько символов, сколько символов подчеркивания содержит маска, плюс один символ (для символа конца строки \0). Поле денежной единицы может иметь произвольное число цифр слева от десятичной точки и две цифры справа от нее.

Дата должна соответствовать формату «ММДДГГ». Если пользователь неправильно ввел дату, программа выдает сообщение об ошибке и требует от пользователя повторного ввода даты. Функция establish_field возвращает указатель на структуру FIELD. Вы можете использовать этот указатель при обращении к field_window, field_help, field_protect и field_validate.

void wprompt(WINDOW *wnd, int x, int y, char *s)

Эта функция осуществляет выдачу приглашения на ввод данных в окно. Приглашение на ввод данных является строкой s, которая выдается, начиная с координат окна, задаваемых значениями х и у.

void field_tally(WINDOW *wnd)

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

void field_window(FIELD *fld, char *helpname, int x, int y)

Эта функция обеспечивает выдачу контекстно-зависимого help-окна для каждого поля шаблона. Параметр helpname является строкой, содержащей мнемонику help-окна в текущем help-файле, и специфицируется предварительно путем обращения к функции load_help. Точно так же, как функция data_entry осуществляет переход от поля к полю, обращение к функции set_help для каждого из полей позволяет привязать конкретное help-окно для каждого из них. Параметры х и у позволяют специфицировать позицию экрана, с которой будет осуществляться выдача help-окна. Заметьте, что эта функция, а также еще три, рассмотренные ниже, не требуют наличия в списке параметров указателя WINDOW. Для этих функций достаточно указателей FIELD, так как эти указатели представляют собой указатели на цепочку связного списка, который и осуществляет привязку функции к конкретному окну. Эти функции модифицируют определенные ранее поля, не обращая внимания на то, что они принадлежат конкретному шаблону окна.

void clear_template(WINDOW *wnd)

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

void field_validate(FIELD *fld, int (*validfn)())

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

Ваша функция контроля допустимости данных может содержать вызов функции error_message для обработки ситуации, когда будет обнаружено недопустимое значение. При этом определенная вами функция контроля данных должна возвращать лишь два значения ОК или ERROR. Если функция возвращает значение ОК, то функция data_ entry будет обрабатывать следующее поле шаблона. Если же функция контроля возвра щает значение ERROR, то функция data_entry «остановится» на поле, в данных которого обнаружена ошибка.

void field_protect(FIELD *fld, int prot)

Эта функция также относится к макросам. Она устанавливает или отменяет (в зависимости от значения параметра prot) защиту характеристик поля. Защищенное поле не обрабатывается (игнорируется) функцией data_entry. Используя функцию field_protect, вы можете управлять доступом пользователей к полям шаблона, разрешая или запрещая запись в них данных. Эта функция может использоваться совместно с функциями field _validate, clear_template и field_fally для контроля над изменениями, производимыми в записях базы данных, в случае, если элементы записи базы данных отображаются в шаблоне.

void field_help(FIELD *fld, int (*helpfn)())

Эта функция относится к макросам. Она позволяет вам установить специальную help-функцию, которая будет вызываться вместо стандартной help-функции. Эта функция используется для отдельных полей шаблона, когда пользователю требуется получить по ним более полную help-информацию, чем ему предоставлено текстом, отображаемым в окне. В процессе ввода данных в окно у пользователя могут возникнуть различные вопросы относительно как предназначения окна, так и вида входных данных. Для получения ответов на свои вопросы пользователю достаточно нажать на специально выделенную help-клавишу, которая инициирует обращение к специальной help-функции. Функция data_entry передаст адрес буфера накопления данных поля, что впоследствии даст возможность специальной help-функции вернуть текущее значение в поле. Эта отличительная черта функции field_help используется, когда имеется список возможных значений, из которых пользователь должен осуществлять выбор, но отображение всего списка в окне постоянно нецелесообразно. Вы можете открыть еще одно окно для выдачи help-информации с помощью вашей специальной help-функции, отобразить в нем весь список возможных значений и затем использовать функцию get_selection для обеспечения выбора пользователем нужного ему значения. Вы также можете записывать текущую дату в буферы накопления данных полей. Вам предоставлена возможность осуществлять запросы к базе данных.

int data_entry(WINDOW *wnd)

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

Курсор устанавливается в первой позиции поля, и функция переходит в режим ожидания ввода данных. Пользователь может начать ввод данных или в текущее поле, или установить текущим другое поле, используя клавишу <Enter>, клавишу табуляции <Tab> или клавиши управления курсором. Клавиши <ESC>, <PgUp>, <PgDn>, <End>, перемещения курсора в первую позицию первой строки (<Home>) и функциональные клавиши (с <F1> до <F10>, исключая клавишу, за которой закреплен вызов help-функции) предназначены для завершения процесса ввода данных и возврата из функции data_entry, к которой перед этим было осуществлено обращение, в точку вызова.

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

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

После того, как происходит возврат из функции data_entry в точку ее вызова, все буферы полей содержат значения данных, которые ввел пользователь. Эта функция возвращает управление в точку вызова при нажатии клавиш, означающих конец ввода данных в шаблон. Таковыми являются клавиша <ESC>, клавиши листания страниц (<PgUp>, <PgDn>) или функциональные клавиши, приводящие к завершению процесса ввода данных. Прикладное программное обеспечение анализирует значение последней нажатой пользователем клавиши для определения его намерений. Нажатие клавиши <ESC>, например, может означать «Игнорируй все внесенные мной изменения и верни меня к предыдущему содержанию шаблона». Клавиши <PgUp> и <PgDn> означают (как определено в keys.h), что пользователь хочет перейти к следующей или предыдущей записи базы данных. Когда пользователь завершает ввод данных, то код завершения процесса ввода передается в программу, вызвавшую функцию data_entry, для дальнейшего использования.

Оконный текстовый редактор

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

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

Для использования текстового редактора программа вначале устанавливает окно, а затем резервирует буфер, в котором будут храниться и редактироваться данные, вводимые пользователем. Текстовый буфер редактора представляет собой массив, резервирующий память, достаточную для хранения фиксированного числа строк, каждая из которых равна ширине области окна, отводимой для ввода и редактирования текста. Например, если вы установили ширину окна в 42 символа, то область текста будет иметь ширину 40 символов (рамка окна занимает 2 позиции). Если вы в этом случае отвели под буфер 4000 символов (длина массива), то редактор сможет обрабатывать одновременно только 100 строк текста, так как буфер может содержать не более 100 строк текста по 40 символов в строке.

При вводе пользователем текста он накапливается в буфере без специальных символов новой строки (\n) и табуляции (\t). Если текстовый буфер уже содержит текст, который был занесен в него при первом обращении к редактору, то этот старый текст отображается на экране дисплея в виде строк фиксированной длины в соответствии с описанием окна.

Команды тестового редактора

Текстовый редактор содержит полный набор команд редактирования. Команды сведены в один перечень и могут быть отображены на экране дисплея. Ранее была рассмотрена программа, которая использовала help-окно для выдачи различной информации. Help-окно отображается при нажатии клавиши <F1>. Точно такой же прием используется для отображения набора команд редактора. Большинство из команд редактирования закреплено за функциональными клавишами, остальные используют комбинации клавиш <Ctrl> или <ALT> с другими клавишами.

Управление курсором

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

u <Ctrl/Т> и <Ctrl/В> перемещают буфер в начало и конец экрана;

u <Ctrl/(курсор вправо)> перемещает курсор в начало следующего слова в буфере хранения текста;

u <Ctrl/(курсор влево)> перемещает курсор в начало предыдущего слова в буфере хранения текста;

u <Home> позиционирует курсор в начало текущей строки;

u <End> позиционирует курсор в конец текущей строки;

u <Tab> перемещает курсор к следующей позиции табуляции;

u <Shift/Tab> перемещает курсор к предыдущей позиции табуляции.

Постраничная работа

u <Page Up> и <Page Down> перемещает текст на страницу вперед или назад;

u <Ctrl/(Home)> устанавливает текст на первую страницу, позиционируя курсор в первой позиции первой строки текста;

u <Ctrl/End> устанавливает текст на последнюю страницу в буфере и позиционирует курсор в первой позиции строки, которая следует за последней строкой текста в буфере.

Команды работы с блоками текста

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

u <F4> формирует параграф из текста, помеченного как блок;

u <F5> помечает первую строку блока. Строка текста, в которой находится курсор, становится первой строкой блока. Текст, образующий блок, отображается в цвете, определяемом значением параметра ACCENT для данного окна;

u <F6> помечает последнюю строку блока текста;

u <F7> перемещает весь блок текста к строке текста, в которой расположен в данный момент курсор. Перемещение блока происходит без его разрушения; пространство для размещения блока выделяется автоматически;

u <F8> копирует блок к текущей строке текста. Отличие этой команды от предыдущей в том, что настоящая команда не приводит к разрушению блока-оригинала на его прежнем месторасположении в тексте.

u <F9> уничтожает блок. Строки текста, следующие за удаленным блоком, перемещаются вверх, заполняя освободившееся пространство в тексте;

u <F10> отменяет маркировку фрагмента текста как блока.

Команды редактирования

После того, как вы ввели текст и осуществили перемещение курсора, редактор автоматически переформатирует текущий параграф. Редактор определяет параграф как группу строк текста, начинающуюся с первой строки, состоящей из пробелов. Переформатирование параграфов может подавляться путем нажатия перечисленных ниже клавиш в режиме Scroll Lock:

u <F3> уничтожает весь текст, хранящийся в буфере «Команда». Команда требует подтверждения;

u <INS> переключает режимы работы редактора Вставка/Замена, а также изменяет форму курсора, индицирующего режим работы редактор;

u <DEL> уничтожает символ, находящийся после курсора в тексте, перемещая весь текст в строке на одну позицию влево;

u <Backspace> (длинная стрелка в правом верхнем углу клавиатуры) уничтожает символ, находящийся левее курсора, перемещая сам курсор и весь следующий за ним текст строки на одну позицию влево;

u <Ctrl/D> уничтожает слово, перед которым позиционирован курсор;

u <ALT/D> уничтожает строку текста, в которой был предварительно позиционирован курсор;

u <F2> или <ESC> приводит к выходу из текстового редактора в точку его вызова из главной программы.

Функция, реализующая текстовый редактор

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

void text_editor (WINDOW *wnd, char *bf, unsigned bsize)

Эта функция обрабатывает текстовые данные, вводимые в окно с помощью текстового редактора. Указатель wnd специфицирует предварительно установленное вами окно. Указатель bf определяет текстовый буфер, а целое число bsize специфицирует размер буфера. Количество строк в буфере является функцией, зависящей от размера буфера и размеров (ширины) окна, которые устанавливаются, когда вы организуете окно.

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

Оконные меню

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

Часто перечень возможностей системы отображается пользователю в виде списка, из которого он может выбрать нужные ему функции. Наиболее часто этот подход носит название меню. Перечень выполняемых функций является одной из основных частей пользовательского интерфейса системы. Один из видов меню может содержать список опций окна. Пользователь может осуществлять выбор из этого меню путем нажатия клавиш, соответствующих одной из опций меню, или путем перемещения курсора по элементам меню и нажатия клавиши <Enter> в позиции соответствующего элемента. Такой тип меню является наиболее часто используемым в интерактивных системах, но в то же время относится к разряду наиболее эффективных и понимаемых пользователем приемов.

Другим популярным типом меню, который часто используется в компьютерах, снабженных графическим пользовательским интерфейсом и манипулятором типа «мышь», является меню в виде скользящей строки (sliding bar menu). Этот тип меню представляет собой горизонтальное меню, расположенное в верхней части, с «всплывающими» вертикальными меню, раскрывающими и конкретизирующими содержание выбранных из горизонтального меню элементов. Вертикальные меню «всплывают» под соответствующими элементами горизонтального меню, и после этого пользователь получает возможность осуществлять выборку из вертикального меню, перемещаясь по нему вверх и вниз. К преимуществам такого типа меню относится то, что оно занимает минимальную область экрана (обычно — одна строка) и наиболее полно отражает взгляд пользователя на конкретное приложение программы.

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

Процесс, образующий оконное меню

Функции-драйверы меню осуществляют обслуживание основного (целевого) процесса выполнения вашей программы. Они управляют пользовательским интерфейсом путем контроля процесса отображения меню и ввода пользователем выбранного элемента меню. Процесс ввода управляется набором таблиц меню, которые кодируются в вашей программе. Основной таблицей меню является массив MENU-структур. Этот массив является входным для каждого процесса выборки из скользящей меню-строки. Каждый ввод выбранного элемента меню содержит отображаемое пользователю имя элемента меню и ряд указателей, описывающих содержание «всплывающего» вертикального меню, соответствующего указанному пользователю элементу горизонтального меню. Эти указатели включают в себя указатель на массив имен элементов «всплывающего» меню и указатель на массив указателей функций на языке Си. Имена отображаются во «всплывающем» меню, а функции входят в состав системы, использующей это меню, и выполняются, когда пользователь отмечает в качестве выбранных соответствующие имена элементов меню.

Иерархическая система меню состоит из двух уровней. Первый уровень включает в себя выборку из горизонтального меню и ограничен лишь шестью элементами вследствие того, что элементы этого уровня располагаются на одной строке. Любая выборка элемента на этом уровне приводит к появлению соответствующего «всплывающего» меню на втором уровне. Размер «всплывающего» меню ограничен 21 элементами, что соответствует максимальному числу строк, которое может быть включено в «всплывающее» меню. Каждая выборка на этом уровне приводит к вызову определенной Си-функции из закрепленных за элементами меню. Если вы хотите получить дополнительные уровни в вашем иерархическом меню, то должны описать дополнительные управляющие таблицы меню, присвоить им значения и определить рекурсивные вызовы функций поддержки системы меню.

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

Функции поддержки меню

Для использования оконных меню вы должны вначале сгенерировать массив MENU, затем массив, на который ссылается массив MENU, а также написать и оттранслировать прикладные функции, которые будут выполняться, когда пользователь выберет конкретный элемент «всплывающего» меню. Затем вы так или иначе обращаетесь к функции menu_ select.

void menu_select(char *name, Menu *mn)

Эта функция активизирует меню-процесс, отображая на экране дисплея горизонтальное скользящее меню-строку и переходя в состояние ожидания использования пользователем клавиатуры для выборки из меню. Указатель name является именем заголовка скользящей меню-строки. Указатель mn содержит адрес массива структур MENU в вызывающей программе. Этот массив и указатель на массив MENU определяют иерархию меню для меню-процесса. После того, как функция menu_select отобразила горизонтальное меню, пользователь, используя клавиши управления курсором вправо и влево, может перемещаться по меню. Если пользователь нажал клавишу <ESC>, то меню-процесс прерывается, и управление передается в точку вызова функции menu_ select. Если же пользователь нажал клавишу <Enter>, то осуществляется привязка конкретного «всплывающего» вертикального меню к текущему элементу горизонтального меню (определяется текущим положением маркера меню или курсора) и отображение нужного вертикального меню. Во время отображения вертикального «всплывающего» меню пользователь может использовать клавиши передвижения курсора вправо и влево для выбора других элементов горизонтального меню-строки. В этом режиме в соответствии с движением курсора в меню-строке осуществляется последовательное отображение вертикальных меню, причем перед отображением очередного вертикального меню предыдущее вертикальное меню уничтожается.

Если пользователь нажмет клавишу <ESC>, то текущее «всплывающее» меню уничтожается, и процесс возвращается к обработке скользящего меню-строки аналогично описанному выше. Пользователь может использовать клавиши перемещения курсора вверх и вниз для навигации по вертикальному меню и выбора нужного элемента меню. После того, как нужный элемент выбран, нужно нажать клавишу <Enter>. Нажатие этой клавиши приведет к исчезновению как вертикального, так и горизонтального меню и вызову функции, соответствующей указателю, хранимому в массиве указателей функций «всплывающего» меню и, в свою очередь, соответствующего выбранному элементу меню.

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

Глава 22.
Утилита make

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

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

Правила

Основным «строительным элементом» make-файла являются правила (rules). В общем виде правило выглядит так:

<цель_1> <цель_2> ... <цель_n>: <зависимость_1> <зависимость_2> ...
<зависимость_n>

<команда_1>

<команда_2>

...

<команда_n>

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

iEdit: main.o Editor.o TextLine.o

gcc main.o Editor.o TextLine.o -o iEdit

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

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

clean:

rm *.o iEdit

Подобного рода цели называются псевдоцели (pseudotargets) или абстрактные цели (phony targets).

Зависимость (dependency) — это некие «исходные данные», необходимые для достижения указанной в правиле цели. Можно сказать что зависимость — это «предварительное условие» для достижения цели. Зависимость может представлять собой имя файла. Этот файл должен существовать, для того чтобы можно было достичь указанной цели. В следующем правиле:

iEdit: main.o Editor.o TextLine.o

gcc main.o Editor.o TextLine.o -o iEdit

файлы main.o, Editor.o и TextLine.o являются зависимостями. Эти файлы должны существовать для того, чтобы стало возможным достижение цели — построение файла iEdit.

Зависимость также может быть именем некоторого действия. Это действие должно быть предварительно выполнено перед достижением указанной в правиле цели. В следующем примере зависимость clean_obj является именем действия (удалить объектные файлы программы):

clean_all: clean_obj

rm iEdit

clean_obj:

rm *.o

Для того чтобы цель clean_all была достигнута, нужно сначала выполнить действие (достигнуть цели) clean_obj.

Команды — это действия, которые необходимо выполнить для обновления либо достижения цели. В следующем примере:

iEdit: main.o Editor.o TextLine.o

gcc main.o Editor.o TextLine.o -o iEdit

командой является вызов компилятора GCC. Утилита make отличает строки, содержащие команды, от прочих строк make-файла по наличию символа табуляции (символа с кодом 9) в начале строки. В приведенном выше примере строка:

gcc main.o Editor.o TextLine.o -o iEdit

должна начинаться с символа табуляции.

Алгоритм работы make

Типичный make-файл проекта содержит несколько правил. Каждое из правил имеет некоторую цель и некоторые зависимости. Смыслом работы make является достижение цели, которую она выбрала в качестве главной цели (default goal). Если главная цель является именем действия (то есть абстрактной целью), то смысл работы make заключается в выполнении соответствующего действия. Если же главная цель является именем файла, то программа make должна построить самую «свежую» версию указанного файла.

Выбор главной цели

Главная цель может быть прямо указана в командной строке при запуске make. В следующем примере make будет стремиться достичь цели iEdit (получить новую версию файла iEdit):

make iEdit

А в этом примере make должна достичь цели clean (очистить директорию от объектных файлов проекта):

make clean

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

В следующем примере:

iEdit: main.o Editor.o TextLine.o

gcc main.o Editor.o TextLine.o -o iEdit

main.o: main.cpp

gcc -c main.cpp

Editor.o: Editor.cpp

gcc -c Editor.cpp

TextLine.o: TextLine.cpp

gcc -c TextLine.cpp

clean:

rm *.o

из четырех перечисленных в make-файле целей (iEdit, main.o, Editor.o, TextLine.o, clean) по умолчанию в качестве главной будет выбрана цель iEdit. Схематично, «верхний уровень» алгоритма работы make можно представить так:

make()

{

главная_цель = ВыбратьГлавнуюЦель()

ДостичьЦели( главная_цель )

}

Достижение цели

После того как главная цель выбрана, make запускает «стандартную» процедуру достижения цели. Сначала в make-файле ищется правило, которое описывает способ достижения этой цели (функция НайтиПравило). Затем, к найденному правилу применяется обычный алгоритм обработки правил (функция ОбработатьПравило).

ДостичьЦели( Цель )

{

правило = НайтиПравило( Цель )

ОбработатьПравило( правило )

}

Обработка правил

Обработка правила разделяется на два основных этапа. На первом этапе обрабатываются все зависимости, перечисленные в правиле (функция ОбработатьЗависимости). На втором этапе принимается решение — нужно ли выполнять указанные в правиле команды (функция НужноВыполнятьКоманды). При необходимости, перечисленные в правиле команды выполняются (функция ВыполнитьКоманды).

ОбработатьПравило( Правило )

{

ОбработатьЗависимости( Правило )

если НужноВыполнятьКоманды( Правило )

{

ВыполнитьКоманды( Правило )

}

}

Обработка зависимостей

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

ОбработатьЗависимости( Правило )

{

цикл от i=1 до Правило.число_зависимостей

{

если ЕстьТакаяЦель( Правило.зависимость[ i ] )

{

ДостичьЦели( Правило.зависимость[ i ] )

}

иначе

{

ПроверитьНаличиеФайла( Правило.зависимость[ i ] )

}

}

}

Обработка команд

На стадии обработки команд решается вопрос — нужно ли выполнять описанные в правиле команды или нет. Считается, что нужно выполнять команды если:

u Цель является именем действия (абстрактной целью)

u Цель является именем файла и этого файла не существует

u Какая-либо из зависимостей является абстрактной целью

u Цель является именем файла и какая-либо из зависимостей, являющихся именем файла, имеет более позднее время модификации чем цель.

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

НужноВыполнятьКоманды( Правило )

{

если Правило.Цель.ЯвляетсяАбстрактной()

return true

// цель является именем файла

если ФайлНеСуществует( Правило.Цель )

return true

цикл от i=1 до Правило.Число_зависимостей

{

если Правило.Зависимость[ i ].ЯвляетсяАбстрактной()

return true

иначе

// зависимость является именем файла

{

если ВремяМодификации( Правило.Зависимость[ i ] ) >

ВремяМодификации( Правило.Цель )

return true

}

}

return false

}

Абстрактные цели и имена файлов

Каким образом make отличает имена действий от имен файлов? Традиционные варианты make поступают просто. Сначала ищется файл с таким именем. Если файл найден, то считается что цель или зависимость являются именем файла.

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

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

Некоторые версии make предлагают свои варианты решения этой проблемы. Так, например, в утилите GNU Make имеется механизм (специальная цель .PHONY), с помощью которого можно указать, что данное имя является именем действия.

Пример работы make

Рассмотрим, как утилита make будет обрабатывать такой make-файл:

iEdit: main.o Editor.o TextLine.o

gcc main.o Editor.o TextLine.o -o iEdit

main.o: main.cpp

gcc -c main.cpp

Editor.o: Editor.cpp

gcc -c Editor.cpp

TextLine.o: TextLine.cpp

gcc -c TextLine.cpp

clean:

rm *.o

Предположим, что в директории с проектом находятся следующие файлы:

main.cpp

Editor.cpp

TextLine.cpp

Предположим также, что программа make была вызвана следующим образом:

make

Цель не указана в командной строке, поэтому запускается алгоритм выбора цели (функция ВыбратьГлавнуюЦель). Главной целью становится файл iEdit (первая цель из первого правила).

Цель iEdit передается функции ДостичьЦели. Эта функция ищет правило, которое описывает обрабатываемую цель. В данном случае, это первое правило make-файла. Для найденного правила запускается процедура обработки (функция ОбработатьПравило).

Сначала поочередно обрабатываются описанные в правиле зависимости (функция ОбработатьЗависимости). Первая зависимость — объектный файл main.o. Поскольку в make-файле есть правило с такой целью (функция ЕстьТакаяЦель возвращает true), то для цели main.o запускается процедура ДостичьЦели.

Функция ДостичьЦели ищет правило, где описана цель main.o. Эта цель описана во втором правиле make-файла. Для этого правила запускается функция ОбработатьПравило.

Функция ОбработатьПравило запускает процесс обработки зависимостей (функция ОбработатьЗависимости). Во втором правиле указана единственная зависимость — main.cpp. Такой цели в make-файле не существует, поэтому считается, что зависимость main.cpp является именем файла. Далее, проверяется наличие этого файла на диске (функция ПроверитьНаличиеФайла) — такой файл существует. На этом процесс обработки зависимостей завершается.

После обработки зависимостей, функция ОбработатьПравило принимает решение о том, нужно ли выполнять указанные в правиле команды (функция НужноВыполнятьКоманды). Цели правила (файла main.o) не существует, поэтому команды нужно выполнять. Функция ВыполнитьКоманды запускает указанную в правиле команду (компилятор GCC), в результате чего создается файл main.o.

Цель main.o достигнута (объектный файл main.o построен). Теперь make возвращается к обработке остальных зависимостей первого правила. Зависимости Editor.o и TextLine.o обрабатываются аналогично. Для них выполняются те же действия, что и для зависимости main.o.

После того, как все зависимости (main.o, Editor.o и TextLine.o) обработаны, решается вопрос о необходимости выполнения указанных в правиле команд (функция НужноВыполнятьКоманды).

Поскольку цель (iEdit) является именем файла, который в данный момент не существует, то принимается решение выполнить описанную в правиле команду (функция ВыполнитьКоманды).

Содержащаяся в правиле команда запускает компилятор GCC, в результате чего создается исполняемый файл iEdit. Главная цель (iEdit) таким образом достигнута. На этом программа make завершает свою работу.

Еще один пример работы make

Рассмотрим, как будет действовать утилита make, если для обработки make-файла, она будет вызвана следующим образом:

make clean

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

Поскольку в правиле не указано каких-либо зависимостей, make сразу переходит к этапу обработки указанных в правиле команд. Цель является именем действия, поэтому команды нужно выполнять.

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

Переменные

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

obj_list = main.o Editor.o TextLine.o

присваивает переменной obj_list значение «main.o Editor.o TextLine.o» (без кавычек). Пробелы между символом '=' и началом первого слова игнорируются. Следующие за последним словом пробелы также игнорируются. Значение переменной можно использовать с помощью конструкции:

$(имя_переменной)

Например, при обработке такого make-файла:

dir_list = . .. src/include

all:

echo $(dir_list)

на экран будет выведена строка:

. .. src/include

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

optimize_flags = -O3

сompile_flags = $(optimize_flags) -pipe

all:

echo $(compile_flags)

на экран будет выведено:

-O3 -pipe

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

program_name = iEdit

obj_list = main.o Editor.o TextLine.o

$(program_name): $(obj_list)

gcc $(obj_list) -o $(program_name)

...

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

Автоматические переменные

Автоматические переменные — это переменные со специальными именами, которые «автоматически» принимают определенные значения перед выполнением описанных в правиле команд. Автоматические переменные можно использовать для «упрощения» записи правил. Такое, например, правило:

iEdit: main.o Editor.o TextLine.o

gcc main.o Editor.o TextLine.o -o iEdit

с использованием автоматических переменных можно записать следующим образом:

iEdit: main.o Editor.o TextLine.o

gcc $^ -o $@

Здесь $^ и $@ являются автоматическими переменными. Переменная $^ означает «список зависимостей». В данном случае при вызове компилятора GCC она будет ссылаться на строку «main.o Editor.o TextLine.o». Переменная $@ означает «имя цели» и будет в этом примере ссылаться на имя «iEdit».

Иногда использование автоматических переменных совершенно необходимо — например, в шаблонных правилах.

Шаблонные правила

Шаблонные правила (implicit rules или pattern rules) — это правила, которые могут быть применены к целой группе файлов. В этом их отличие от обычных правил — описывающих отношения между конкретными файлами.

Традиционные реализации make поддерживают так называемую «суффиксную» форму записи шаблонных правил:

.<расширение_файлов_зависимостей>.<расширение_файлов_целей>:

<команда_1>

<команда_2>

...

<команда_n>

Например, следующее правило говорит о том, что все файлы с расширением «o» зависят от соответствующих файлов с расширением «cpp»:

.cpp.o:

gcc -c $^

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

Шаблонные правила позволяют упростить make-файл и сделать его более универсальным. Рассмотрим простой проектный файл:

iEdit: main.o Editor.o TextLine.o

gcc $^ -o $@

main.o: main.cpp

gcc -c $^

Editor.o: Editor.cpp

gcc -c $^

TextLine.o: TextLine.cpp

gcc -c $^

Все исходные тексты программы обрабатываются одинаково — для них вызывается компилятор GCC. С использованием шаблонных правил, этот пример можно переписать так:

iEdit: main.o Editor.o TextLine.o

gcc $^ -o $@

.cpp.o:

gcc -c $^

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

Часть 3.
Программирование в среде Delphi

Глава 1.
Основные характеристики

Delphi — это комбинация нескольких важнейших технологий:

u Высокопроизводительный компилятор в машинный код;

u Объектно-ориентированная модель компонент;

u Визуальное (а, следовательно, и скоростное) построение приложений из программных прототипов;

u Масштабируемые средства для построения баз данных.

Компилятор в машинный код

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

В процессе построения приложения разработчик выбирает из палитры компонент готовые компоненты как художник, делающий крупные мазки кистью. Еще до компиляции он видит результаты своей работы — после подключения к источнику данных их можно видеть отображенными на форме, можно перемещаться по данным, представлять их в том или ином виде. В этом смысле проектирование в Delphi мало чем отличается от проектирования в интерпретирующей среде, однако после выполнения компиляции мы получаем код, который исполняется в 10-20 раз быстрее, чем то же самое, сделанное при помощи интерпретатора. Кроме того, компилятор компилятору рознь, в Delphi компиляция производится непосредственно в родной машинный код, в то время как существуют компиляторы, превращающие программу в так называемый p-код, который затем интерпретируется виртуальной p-машиной. Это не может не сказаться на фактическом быстродействии готового приложения.

Объектно-ориентированная модель программных компонент

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

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

В стандартную поставку Delphi входят основные объекты, которые образуют удачно подобранную иерархию из 270 базовых классов. Для начала — неплохо. Но если возникнет необходимость в решении какой-то специфической проблемы на Delphi, советуем, прежде чем попытаться начинать решать проблему «с нуля», просмотреть список свободно распространяемых или коммерческих компонент, разработанных третьими фирмами. Количество этих фирм в настоящее время превышает число 250, хотя, возможно, мы не обо всех знаем. Скептики, возможно, не поверят, что на Delphi можно одинаково хорошо писать как приложения к корпоративным базам данных, так и, к примеру, игровые программы. Тем не менее, это так. Во многом это объясняется тем, что традиционно в среде Windows было достаточно сложно реализовывать пользовательский интерфейс. Событийная модель в Windows всегда была сложна для понимания и отладки. Но именно разработка интерфейса в Delphi является самой простой задачей для программиста.

Быстрая разработка работающего приложения из прототипов

Среда Delphi включает в себя полный набор визуальных инструментов для скоростной разработки приложений (RAD — rapid application development), поддерживающей разработку пользовательского интерфейса и подключение к корпоративным базам данных.

VCL — библиотека визуальных компонент, включает в себя стандартные объекты построения пользовательского интерфейса, объекты управления данными, графические объекты, объекты мультимедиа, диалоги и объекты управления файлами, управление DDE и OLE. Единственное, что можно поставить в вину Delphi, это то, что готовых компонент, поставляемых Borland, могло бы быть и больше. Однако, разработки других фирм, а также свободно распространяемые программистами freeware-компоненты уже восполнили этот недостаток. Постойте, — скажете вы, ведь это уже было. Да, это было в Visual Basic.

Соответствующий стандарт компонент назывался VBX. И этот стандарт так же поддерживается в Delphi. Однако, визуальные компоненты в Delphi обладают большей гибкостью. Вспомним, в чем была проблема в VB. Прикладной программист программировал, вообще говоря, в среде языка бейсик. А компоненты в стандарте VBX готовили ему его коллеги-профессионалы на Си++.

VBX’ы приходили, «как есть», и ни исправить, ни добавить ничего было нельзя.

А для изготовления VBX надо было осваивать «кухню» языка Cи++. В Delphi визуальные компоненты пишутся на объектном паскале, на том же паскале, на котором пишется алгоритмическая часть приложения. И визуальные компоненты Delphi получаются открытыми для надстройки и переписывания. Чувствуете разницу?

Масштабируемые средства для построения баз данных

Объекты БД в Delphi основаны на SQL и включают в себя полную мощь Borland Database Engine. В состав Delphi также включен Borland SQL Link, поэтому доступ к СУБД Oracle, Sybase, Informix и InterBase происходит с высокой эффективностью. Кроме того, Delphi включает в себя локальный сервер Interbase для того, чтобы можно было разработать расширяемые на любые внешние SQL-сервера приложения в офлайновом режиме. Разработчик в среде Delphi, проектирующий информационную систему для локальной машины (к примеру, небольшую систему учета медицинских карточек для одного компьютера), может использовать для хранения информации файлы формата .dbf (как в dBase или Clipper) или .db (Paradox). Если же он будет использовать локальный InterBase for Windows (это локальный SQL-сервер, входящий в поставку), то его приложение безо всяких изменений будет работать и в составе большой системы с архитектурой клиент-сервер.

Вот она — масштабируемость на практике — одно и то же приложение можно использовать как для локального, так и для более серьезного клиент-серверного вариантов.

Глава 2.
Два варианта поставки

Мы уже упомянули о технологиях, которые объединяет Delphi. Теперь можно перейти к описанию собственно продукта. Что лежит внутри в коробке, и чем может воспользоваться программист при разработке прикладной системы? Выпущены две версии Delphi — одна (Delphi Client-Server) адресована для разработчиков приложений в архитектуре «клиент-сервер», а другая (Delphi for Windows) предназначена для остальных программистов.

Клиент-серверная версия Delphi

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

Клиент-серверная версия включает в себя следующие особенности:

u SQL Links: специально написанные драйвера для доступа к Oracle, Sybase, Informix, InterBase.

u Локальный сервер InterBase: SQL-сервер для Windows. СУБД для разработки в корпоративных приложений на компьютере, не подключенном к локальной сети.

u ReportSmith Client/server Edition: генератор отчетов для SQL-серверов.

u Team Development Support: предоставляет версионный контроль при помощи PVCS компании Intersolve (приобретается отдельно) или при помощи других программных продуктов версионного контроля.

u Visual Query Builder — это средство визуального построения SQL-запросов.

u Лицензия на право распространения приложений в архитектуре клиент-сервер, изготовленных при помощи Delphi.

u Исходные тексты всех визуальных компонент.

Delphi for Windows

Delphi for Windows представляет из себя подмножество Delphi Client-Server и предназначен для разработчиков высокопроизводительных персональных приложений, работающих с локальными СУБД типа dBase и Paradox. Delphi Desktop Edition предлагает такую же среду для быстрой разработки и первоклассный компилятор как и клиент-серверная версия (Client/Server Edition). Эта среда позволяет разработчику быстро изготавливать персональные приложения, работающие с персональными СУБД типа dBase и Paradox. Delphi позволяет также создавать разработчику DLL, которая может быть вызвана из Paradox, dBase, C++ или каких-нибудь других готовых программ.

В Delphi for Windows, как и в Delphi Client-Server, входят:

u Компилятор Object Pascal (этот язык является расширением языка Borland Pascal).

u Генератор отчетов ReportSmith.

u Среда визуального построителя приложений.

u Библиотека визуальных компонент.

u Локальный сервер InterBase.

Глава 3.
RAD Pack

Стоит упомянуть еще один продукт, выпущенный компанией Borland для Delphi. В RAD Pack for Delphi входит набор полезных дополнений, которые помогут разработчику при освоении и использовании Delphi. Это учебник по объектному паскалю, интерактивный отладчик самой последней версии, Borland Visual Solutions Pack (набор VBX для реализации редакторов, электронных таблиц, коммуникационные VBX, VBX с деловой графикой), Resource WorkShop для работы с ресурсами Borland Pascal, а также дельфийский эксперт для преобразования ресурсов BP в формы Delphi.

Глава 4.
Для кого предназначен Delphi

В первую очередь, Delphi предназначен для профессионалов-разработчиков корпоративных информационных систем. Может быть, здесь следует пояснить, что конкретно имеется в виду. Не секрет, что некоторые удачные продукты, предназначенные для скоростной разработки приложений (RAD — rapid application development) прекрасно работают при изготовлении достаточно простых приложений, однако, разработчик сталкивается с непредвиденными сложностями, когда пытается сделать что-то действительно сложное. Бывает, что в продукте вскрываются присущие ему ограничения только по прошествии некоторого времени.

Delphi такие ограничения не присущи. Хорошее доказательство тому — это тот факт, что сам Delphi разработан на Delphi. Можете делать выводы. Однако Delphi предназначен не только для программистов-профессионалов. Его используют учителя, врачи, преподаватели ВУЗов, бизнесмены, все те, кто используют компьютер с чисто прикладной целью; используют для того, чтобы быстро решить какие-то свои задачи, не привлекая для этого программистов со стороны. В большинстве случаев им это удается. Поразительный факт — журнал Visual Basic Magazine присудил свою премию Delphi for Windows.

Руководители предприятий, планирующие выделение средств на приобретение программных продуктов, должны быть уверены в том, что планируемые инвестиции окупятся. Поэтому одним из оцениваемых факторов должен быть вопрос — а легко ли найти специалиста по Delphi и сколько будет стоить его обучение, сколько времени специалист затратит на овладение продуктом. Ответ здесь получить весьма просто — любой программист на паскале способен практически сразу профессионально освоить Delphi. Специалисту, ранее использовавшему другие программные продукты, придется труднее, однако самое первое работающее приложение он сможет написать в течение первого же часа работы на Delphi. И, конечно же, открытая технология Delphi является мощным гарантом того, что инвестиции, сделанные в Delphi, будут сохранены в течение многих лет.

Глава 5.
Некоторые особенности

Локальный сервер InterBase

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

Team Development Support

Средство поддержки разработки проекта в группе. Позволяет существенно облегчить управление крупными проектами. Это сделано в виде возможности подключения такого продукта как Intersolve PVCS непосредственно к среде Delphi.

Высокопроизводительный компилятор в машинный код

В отличие от большинства Паскаль-компиляторов, транслирующих в p-код, в Delphi программный текст компилируется непосредственно в машинный код, в результате чего Delphi-приложения исполняются в 10-20 раз быстрее (особенно приложения, использующие математические функции). Готовое приложение может быть изготовлено либо в виде исполняемого модуля, либо в виде динамической библиотеки, которую можно использовать в приложениях, написанных на других языках программирования.

Глава 6.
Открытая компонентная архитектура

Благодаря такой архитектуре приложения, изготовленные при помощи Delphi, работают надежно и устойчиво. Delphi поддерживает использование уже существующих объектов, включая DLL, написанные на Си и Си++, OLE сервера, VBX, объекты, созданные при помощи Delphi. Из готовых компонент работающие приложения собираются очень быстро. Кроме того, поскольку Delphi имеет полностью объектную ориентацию, разработчики могут создавать свои повторно используемые объекты для того, чтобы уменьшить затраты на разработку.

Delphi предлагает разработчикам — как в составе команды, так и индивидуальным — открытую архитектуру, позволяющую добавлять компоненты, где бы они ни были изготовлены, и оперировать этими вновь введенными компонентами в визуальном построителе. Разработчики могут добавлять CASE-инструменты, кодовые генераторы, а также авторские help’ы, доступные через меню Delphi.

Two-way tools — однозначное соответствие между визуальным проектированием и классическим написанием текста программы Это означает, что разработчик всегда может видеть код, соответствующий тому, что он построил при помощи визуальных инструментов и наоборот.

Визуальный построитель интерфейсов (Visual User-interface builder) дает возможность быстро создавать клиент-серверные приложения визуально, просто выбирая компоненты из соответствующей палитры.

Глава 7.
Структурное объектно-ориентированное программирование

Delphi использует структурный объектно-ориентированный язык (Object Pascal). Программисты немедленно могут начать производить работающие приложения, и им не придется для этого изучать особенности программирования событий в Windows. Delphi полностью поддерживает передовые программные концепции, включая инкапсуляцию, наследование, полиморфизм и управление событиями.

Глава 8.
Поддержка OLE, DDE и VBX

Это очень важная особенность для разработчиков в среде Windows, поскольку в уже существующие Windows-приложения программист может интегрировать то, что разработает при помощи Delphi.

Глава 9.
Настраиваемая среда разработчика

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

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

Понятно, что поскольку в Delphi вы визуальным образом строите свою программу, все эти компоненты имеют свое графическое представление в поле форм для того, чтобы можно было бы ими соответствующим образом оперировать. Но для работающей программы видимыми остаются только визуальные компоненты. Компоненты сгруппированы на страницах палитры по своим функциям. К примеру, компоненты, представляющие Windows common dialogs все размещены на странице палитры с названием Dialogs.

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

Заметим, что в Delphi вы можете определить свою группу компонент и разместить ее на странице палитры, а если возникнет необходимость, перегруппировать компоненты или удалить неиспользуемые.

Интеллектуальный редактор

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

Графический отладчик

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

Инспектор объектов

Этот инструмент представляет из себя отдельное окно, где вы можете в период проектирования программы устанавливать значения свойств и событий объектов (Properties & Events).

Менеджер проектов

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

Менеджер проектов показывает имена файлов, время/дату выбранных форм.

Можно немедленно попасть в текст или форму, просто щелкнув мышкой на соответствующее имя.

Навигатор объектов

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

Дизайнер меню

Можно создавать меню, сохранить созданные в виде шаблонов и затем использовать в их в любом приложении.

Эксперты

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

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

Включает в себя:

u Эксперт форм, работающих с базами данных.

u Эксперт стилей и шаблонов приложений.

u Эксперт шаблонов форм

В состав RAD Pack входит эксперт для преобразования ресурсов, изготовленных в Borland Pascal, в формы Delphi. Уже появились эксперты, облегчающие построение DLL и даже написание собственных экспертов

Интерактивная обучающая система

Позволяет более полно освоить Delphi. Она являются не просто системой подсказок, а показывает возможности Delphi на самой среде разработчика.

Глава 10.
Компоненты доступа к базам данных и визуализации данных

Библиотека объектов содержит набор визуальных компонент, значительно упрощающих разработку приложений для СУБД с архитектурой клиент-сервер. Объекты инкапсулируют в себя нижний уровень — Borland Database Engine.

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

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

Разработка приложений БД

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

Библиотека объектных Визуальных Компонент

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

Этот костяк называется Visual Component Library (VCL). В VCL есть такие стандартные элементы управления, как строки редактирования, статические элементы управления, строки редактирования со списками, списки объектов.

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

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

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

Здесь следует отметить, что обычных ограничений, присущих средам визуальной разработки, в Delphi нет. Сам Delphi написан при помощи Delphi, что говорит об отсутствии таких ограничений.

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

Язык программирования Delphi базируется на Borland Object Pascal. Кроме того, Delphi поддерживает такие низкоуровневые особенности, как подклассы элементов управления Windows, перекрытие цикла обработки сообщений Windows, использование встроенного ассемблера.

Глава 11.
Формы, модули и метод разработки «Two-Way Tools»

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

Информация о формах хранится в двух типах файлов — .dfm и .pas, причем первый тип файла — двоичный — хранит образ формы и ее свойства, второй тип описывает функционирование обработчиков событий и поведение компонент. Оба файла автоматически синхронизируются Delphi, так что если добавить новую форму в ваш проект, связанный с ним файл .pas автоматически будет создан, и его имя будет добавлено в проект.

Такая синхронизация и делает Delphi two-way-инструментом, обеспечивая полное соответствие между кодом и визуальным представлением. Как только вы добавите новый объект или код, Delphi устанавливает так называемую «кодовую синхронизацию» между визуальными элементами и соответствующими им кодовыми представлениями.

Например, предположим, вы добавили описание поведения формы, чтобы показывать окно сообщения по нажатию кнопки. Такое описание появляется, если дважды щелкнуть мышкой непосредственно на объект Button в форме или дважды щелкнуть мышью на строчку OnClick на странице Events в Инспекторе объектов. В любом случае Delphi создаст процедуру или заголовок метода, куда вы можете добавить код.

procedure TForm1.Button1Click(Sender: TObject);

begin

end;

Создавая этот код, Delphi автоматически формирует декларацию объекта TForm1, которая содержит процедуру ButtonClick, представляющую из себя собственно обработчик события.

TForm1 = class (TForm)

Button1: Tbutton;

procedure Button1Click(Sender: TObject);

private

{ Private declarations }

public

{ Public declarations }

end;

Конечно, вы запросто можете решить после получения этого кода, что автоматически созданные имена вас не устраивают, и заменить их. Например, Button1 на Warning. Это можно сделать изменив свойство Name для Button1 при помощи Инспектора объектов. Как только вы нажмете Enter, Delphi автоматически произведет соответствующую синхронизацию в коде. Так как объект TForm1 существует в коде, вы свободно можете добавлять любые другие поля, процедуры, функции или object definition. К примеру, вы можете дописать в коде свою собственную процедуру, обрабатывающую событие, а не делать это визуальным методом.

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

TForm1 = class(TForm)

Warning: TButton;

Button1: TButton;

procedure WarningClick(Sender: TObject);

procedure NewHandler(Sender: TObject);

private

{ Private declarations }

public

{ Public declarations }

end;

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

Глава 12.
Добавление новых объектов

Delphi — это прежде всего среда разработки, базирующаяся на использовании компонент. Поэтому вы можете добавлять совершенно новые компоненты в палитру компонент. Вы можете создавать компоненты внутри Delphi, или вводить компоненты, созданные как управляющие элементы VBX или OLE, или же вы можете использовать компоненты, написанные на Си или Си++ в виде dll. Последовательность введения новой компоненты состоит из трех шагов:

u наследование из уже существующего типа компоненты;

u определение новых полей, свойств и методов;

u регистрация компоненты.

Это все делается через меню Install Components.

Глава 13.
Добавление управляющих элементов VBX

Delphi генерирует объектное расширение VBX, которое инсталлируется в качестве компонент.

Например, если вы инсталлируете SaxComm VBX из Visual Solutions Pack компании Borland в Delphi, автоматически генерится тип объекта TComm, который наследуется из стандартного TVBXControl. Когда вы инсталлируете компоненты, Delphi будет компилировать и подлинковывать их к библиотеке компонент.

Глава 14.
Делегирование: события программируются проще

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

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

К примеру, когда вы добавляете кнопку в форму и прикрепляете код, обрабатывающий нажатие, вы фактически используете делегирование кода для ассоциирования кода с событием OnClick. Такая ассоциация происходит для вас автоматически. Если проверить страницу Events в Инспекторе объектов для вашего приложения, можно увидеть ассоциированные с событиями процедуры.

Глава 15.
Ссылки на классы

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

Глава 16.
Обработка исключительных ситуаций

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

В Delphi это устроено в стиле Си++. Исключения представлены в виде объектов, содержащих специфическую информацию о соответствующей ошибке (тип и местонахождение ошибки). Разработчик может оставить обработку ошибки, существовавшую по умолчанию, или написать свой собственный обработчик.

Обработка исключений реализована в виде exception-handling blocks (также еще называется protected blocks), которые устанавливаются ключевыми словами try и end. Существуют два типа таких блоков: try...except и try...finally.

Общая конструкция выглядит примерно так:

try

{ выполняемые операторы }

except

on exception1 do statement1;

{ реакция на ситуации }

on exception2 do statement2;

else

{ операторы по умолчанию }

end;

Конструкция try....finally предназначена для того, чтобы разработчик мог быть полностью уверен в том, что, что бы ни случилось, перед обработкой исключительной ситуации всегда будет выполнен некоторый код (например, освобождение ресурсов).

try

{ выполняемые операторы }

finally

{ операторы, выполняемые безусловно }

end;

Глава 17.
Среда программирования Delphi

Структура среды программирования

Внешний вид среды программирования Delphi отличается от многих других из тех, что можно увидеть в Windows. К примеру, Borland Pascal for Windows, Borland C++, Word for Windows, Program Manager — это все MDI приложения и выглядят по-другому, чем Delphi. MDI (Multiple Document Interface) — определяет особый способ управления нескольких дочерних окон внутри одного большого окна.

Среда Delphi же следует другой спецификации, называемой Single Document Interface (SDI), и состоит из нескольких отдельно расположенных окон. Это было сделано из-за того, что SDI близок к той модели приложений, что используется в Windows.

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

Главные составные части среды программирования

Ниже перечислены основные составные части Delphi:

u Дизайнер Форм (Form Designer)

u Окно Редактора Исходного Текста (Editor Window)

u Палитра Компонент (Component Palette)

u Инспектор Объектов (Object Inspector)

u Справочник (On-line help)

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

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

Дизайнер Форм — то место, где вы создаете визуальный интерфейс программы.

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

Дизайнер Форм в Delphi столь интуитивно понятен и прост в использовании, что создание визуального интерфейса превращается в детскую игру. Дизайнер Форм первоначально состоит из одного пустого окна, которое вы заполняете всевозможными объектами, выбранными на Палитре Компонент.

Несмотря на всю важность Дизайнера Форм, местом, где программисты проводят основное время является Редактор. Логика является движущей силой программы и Редактор — то место, где вы ее «кодируете».

Палитра Компонент позволяет вам выбрать нужные объекты для размещения их на Дизайнере Форм. Для использования Палитры Компонент просто первый раз щелкните мышкой на один из объектов и потом второй раз — на Дизайнере Форм. Выбранный вами объект появится на проектируемом окне и им можно манипулировать с помощью мыши.

Палитра Компонент использует постраничную группировку объектов. Внизу Палитры находится набор закладок — Standard, Additional, Dialogs. Если вы щелкнете мышью на одну из закладок, то можете перейти на следующую страницу Палитры Компонент.

Принцип разбиения на страницы широко используется в среде программирования Delphi и его легко можно использовать в своей программе. (На странице Additional есть компоненты для организации страниц с закладками сверху и снизу).

Предположим, вы помещаете компонент TEdit на форму; вы можете двигать его с места на место. Вы также можете использовать границу, прорисованную вокруг объекта для изменения его размеров. Большинством других компонент можно манипулировать тем же образом. Однако, невидимые во время выполнения программы компоненты (типа TMenu или TDataBase) не меняют своей формы.

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

Инспектор Объектов состоит из двух страниц, каждую из которых можно использовать для определения поведения данного компонента. Первая страница — это список свойств, вторая — список событий. Если нужно изменить что-нибудь, связанное с определенным компонентом, то вы обычно делаете это в Инспекторе Объектов. К примеру, вы можете изменить имя и размер компонента TLabel, изменяя свойства Caption, Left, Top, Height и Width.

Вы можете использовать закладки внизу Инспектора Объектов для переключения между страницами свойств и событий. Страница событий связана с Редактором; если вы дважды щелкнете мышкой на правую сторону какого-нибудь пункта, то соответствующий данному событию код автоматически запишется в Редактор, сам Редактор немедленно получит фокус, и вы сразу же имеете возможность добавить код обработчика данного события.

Последняя важная часть среды Delphi — Справочник (on-line help). Для доступа к этому инструменту нужно просто выбрать в системном меню пункт Help и затем Contents.

Справочник является контекстно-зависимым; при нажатии клавиши F1, вы получите подсказку, соответствующую текущей ситуации. Например, находясь в Инспекторе Объектов, выберите какое-нибудь свойство и нажмите F1 — вы получите справку о назначении данного свойства. Если в любой момент работы в среде Delphi возникает неясность или затруднение — жмите F1 и необходимая информация появится на экране.

Дополнительные элементы

Здесь внимание фокусируется на трех инструментах, которые можно воспринимать как вспомогательные для среды программирования:

u Меню (Menu System)

u Панель с кнопками для быстрого доступа (SpeedBar)

u Редактор картинок (Image Editor)

Меню предоставляет быстрый и гибкий интерфейс к среде Delphi, потому что может управляться по набору «горячих клавиш». Это удобно еще и потому, что здесь используются слова или короткие фразы, более точные и понятные, нежели иконки или пиктограммы. Вы можете использовать меню для выполнения широкого круга задач; скорее всего, для наиболее общих задач вроде открытия и закрытия файлов, управления отладчиком или настройкой среды программирования.

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

Редактор Картинок работает аналогично программе Paintbrush из Windows. Вы можете получить доступ к этому модулю, выбрав пункт меню Tools ê Image Editor.

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

Инструментальные средства

В дополнение к инструментам, обсуждавшимся выше, существуют пять средств, поставляемых вместе с Delphi. Эти инструментальные средства:

u Встроенный отладчик.

u Внешний отладчик (поставляется отдельно).

u Компилятор командной строки.

u WinSight.

u WinSpector.

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

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

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

Внешний компилятор называется DCC.EXE, полезен, в основном, если вы хотите скомпилировать приложение перед отладкой его во внешнем отладчике. Большинство программистов, наверняка, посчитают, что гораздо проще компилировать в среде Delphi, нежели пытаться создать программу из командной строки. Однако, всегда найдется несколько оригиналов, которые будут чувствовать себя счастливее, используя компилятор командной строки. Но это факт — возможно создать и откомпилировать программу на Delphi используя только DCC.EXE и еще одну программу CONVERT.EXE, которая поможет создать формы. Однако, данный подход неудобен для большинства программистов.

WinSight и WinSpector интересны преимущественно для опытных программистов в Windows. Это не значит, что начинающий не должен их запускать и экспериментировать с ними по своему усмотрению. Но эти инструменты вторичны и используются для узких технических целей.

Из этих двух инструментов WinSight определенно более полезен. Основная его функция — позволить вам наблюдать за системой сообщений Windows. Хотя Delphi делает много для того, чтобы спрятать сложные детали данной системы сообщений от неопытных пользователей, тем не менее Windows является операционной системой, управляемой событиями. Почти все главные и второстепенные события в среде Windows принимают форму сообщений, которые рассылаются с большой интенсивностью среди различных окон на экране. Delphi дает вам полный доступ к сообщениям Windows и позволяет отвечать на них, как только будет нужно. В результате, опытным пользователям WinSight становится просто необходим.

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

Глава 18.
Стандартные компоненты

Для дальнейшего знакомства со средой программирования Delphi потребуется рассказать о составе первой страницы Палитры Компонент.

На первой странице Палитры Компонент размещены 14 объектов, определенно важных для использования. Мало кто обойдется длительное время без кнопок, списков, окон ввода. Все эти объекты такая же часть Windows, как мышь или окно.

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

u TMainMenu позволяет вам поместить главное меню в программу. При помещении TMainMenu на форму это выглядит, как просто иконка. Иконки данного типа называют «невидимыми компонентом», поскольку они невидимы во время выполнения программы. Создание меню включает три шага:

u помещение TMainMenu на форму;

u вызов Дизайнера Меню через свойство Items в Инспекторе Объектов;

u определение пунктов меню в Дизайнере Меню.

u TPopupMenu позволяет создавать всплывающие меню. Этот тип меню появляется по щелчку правой кнопки мыши.

u TLabel служит для отображения текста на экране. Вы можете изменить шрифт и цвет метки, если дважды щелкнете на свойство Font в Инспекторе Объектов. Вы увидите, что это легко сделать и во время выполнения программы, написав всего одну строчку кода.

u TEdit — стандартный управляющий элемент Windows для ввода. Он может быть использован для отображения короткого фрагмента текста и позволяет пользователю вводить текст во время выполнения программы.

u TMemo — иная форма TEdit. Подразумевает работу с большими текстами. TMemo может переносить слова, сохранять в Clipboard фрагменты текста и восстанавливать их, и другие основные функции редактора. TMemo имеет ограничения на объем текста в 32Кб, это составляет 10–20 страниц. (Есть VBX и «родные» компоненты Delphi, где этот предел снят).

u TButton позволяет выполнить какие-либо действия при нажатии кнопки во время выполнения программы. В Delphi все делается очень просто. Поместив TButton на форму, вы по двойному щелчку можете создать заготовку обработчика события нажатия кнопки. Далее нужно заполнить заготовку кодом:
procedure TForm1.Button1Click(Sender: TObject);
begin
MessageDlg('Are you there?',mtConfirmation,mbYesNoCancel,0);
end;

u TCheckBox отображает строку текста с маленьким окошком рядом. В окошке можно поставить отметку, которая означает, что что-то выбрано. Например, если посмотреть окно диалога настроек компилятора (пункт меню Options ê Project, страница Compiler), то можно увидеть, что оно состоит преимущественно из CheckBox’ов.

u TRadioButton позволяет выбрать только одну опцию из нескольких. Если вы опять откроете диалог Options ê Project и выберете страницу Linker Options, то вы можете видеть, что секции Map file и Link buffer file состоят из наборов RadioButton.

u TListBox нужен для показа прокручиваемого списка. Классический пример ListBox’а в среде Windows — выбор файла из списка в пункте меню File ê Open многих приложений. Названия файлов или директорий и находятся в ListBox’е.

u TComboBox во многом напоминает ListBox, за исключением того, что позволяет водить информацию в маленьком поле ввода сверху ListBox. Есть несколько типов ComboBox, но наиболее популярен выпадающий вниз (drop-down combo box), который можно видеть внизу окна диалога выбора файла.

u TScrollbar — полоса прокрутки, появляется автоматически в объектах редактирования, ListBox’ах при необходимости прокрутки текста для просмотра.

u TGroupBox используется для визуальных целей и для указания Windows, каков порядок перемещения по компонентам на форме (при нажатии клавиши TAB).

u TPanel — управляющий элемент, похожий на TGroupBox, используется в декоративных целях. Чтобы использовать TPanel, просто поместите его на форму и затем положите другие компоненты на него. Теперь при перемещении TPanel будут передвигаться и эти компоненты. TPanel используется также для создания линейки инструментов и окна статуса.

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

Это полный список объектов на первой странице Палитры Компонент. Если вам нужна дополнительная информация, то выберите на Палитре объект и нажмите клавишу F1 — появится Справочник с полным описанием данного объекта.

Глава 19.
Инспектор Объектов

Ранее мы вкратце рассмотрели Инспектор Объектов (Object Inspector). Теперь нужно исследовать этот важный инструмент глубже. Основное для понимания Инспектора Объектов состоит в том, что он используется для изменения характеристик любого объекта, брошенного на форму. Кроме того, и для изменения свойств самой формы.

Лучший путь для изучения Инспектора объектов — это поработать с ним. Для начала откройте новый проект, выбрав пункт меню File ê New Project. Затем положите на форму объекты TMemo, TButton и TListBox.

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

Вернитесь на форму и поставьте значение Ctl3D в True. Теперь нажмите клавишу Shift и щелкните на TMemo и затем на TListBox. Теперь оба объекта имеют по краям маленькие квадратики, показывающие, что объекты выбраны.

Пункт меню Edit дает вам доступ к двум диалогам для выравнивания выбранного набора компонент. Первый диалог — управление размерами объектов в наборе.

Выбрав два или более объектов одновременно, вы можете выполнить большое число операций над ними. Например, передвигать по форме. Затем попробуйте выбрать пункт меню Edit ê Size и установить оба поля Ширину (Width) и Высоту (Height) в Grow to Largest. Теперь оба объекта стали одинакового размера. Затем выберите пункт меню Edit ê Align и поставьте в выравнивании по горизонтали значение Center.

Диалог Alignment помогает выровнять компоненты на форме.

Поскольку вы выбрали сразу два компонента, то содержимое Инспектора Объектов изменится — он будет показывать только те поля, которые являются общими для объектов. Это означает то, что изменения в свойствах, произведенные вами повлияют не на один, а на все выбранные объекты.

Рассмотрим изменение свойств объектов на примере свойства Color. Есть три способа изменить его значение в Инспекторе Объектов. Первый — просто напечатать имя цвета (clRed) или номер цвета. Второй путь — нажать на маленькую стрелку справа и выбрать цвет из списка. Третий путь — дважды щелкнуть на поле ввода свойства Color. При этом появится диалог выбора цвета.

Свойство Font работает на манер свойства Color. Чтобы это посмотреть, сначала выберите свойство Font для объекта TMemo и дважды щелкните мышкой на поле ввода. Появится диалог настройки шрифта.

Выберите, например, шрифт New Times Roman и установите какой-нибудь очень большой размер, например 72. Затем измените цвет фонта с помощью ComboBox’а в нижнем правом углу окна диалога. Когда вы нажмете кнопку OK, вы увидите, что вид текста в объекте TMemo радикально изменился.

В завершение краткого экскурса по Инспектору Объектов дважды щелкните на свойство Items объекта ListBox. Появится диалог, в котором вы можете ввести строки для отображения в ListBox. Напечатайте несколько слов, по одному на каждой строке, и нажмите кнопку OK. Текст отобразится в ListBox’е.

Глава 20.
Сохранение программы

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

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

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

После создания поддиректории для хранения вашей программы нужно выбрать пункт меню File ê Save Project. Сохранить нужно будет два файла. Первый — модуль (unit), над которым вы работали, второй — главный файл проекта, который «владеет» вашей программой.

Сохраните модуль под именем MAIN.PAS и проект под именем TIPS1.DPR. (Любой файл с расширением PAS и словом unit в начале является модулем.)

Глава 21.
TButton, исходный текст, заголовки и
Z-упорядочивание

Еще несколько возможностей Инспектора Объектов и Дизайнера Форм.

Создайте новый проект. Поместите на форму объект TMemo, а затем TEdit так, чтобы он наполовину перекрывал TMemo. Теперь выберите пункт меню Edi ê Send to Back, что приведет к перемещению TEdit вглубь формы, за объект TMemo. Это называется изменением Z-порядка компонент. Буква Z используется потому, что обычно математики обозначают третье измерение буквой Z. Так, X и Y используются для обозначения ширины и высоты, и Z используется для обозначения глубины.

Объект TEdit перекрывается наполовину объектом TMemo.

Если вы «потеряли» на форме какой-то объект, то найти его можно в списке Combobox’а, который находится в верхней части Инспектора Объектов.

Поместите кнопку TButton в нижнюю часть формы. Теперь растяните Инспектор Объектов так, чтобы свойства Name и Caption были видны одновременно на экране. Теперь измените имя кнопки на Terminate. Заметьте, что заголовок (Caption) поменялся в тот же момент. Такое двойное изменение наблюдается только если ранее не изменялось свойство Caption. Текст, который вы видите на поверхности кнопки — это содержимое свойства Caption, свойство Name служит для внутренних ссылок, вы будете использовать его при написании кода программы. Если вы откроете сейчас окно Редактора, то увидите следующий фрагмент кода:

TForm1 = class(TForm)

Edit1: TEdit;

Memo1: TMemo;

Terminate: TButton;

private

{ Private declarations }

public

{ Public declarations }

end;

В этом фрагменте кнопка TButton называется Terminate из-за того, что вы присвоили это название свойству Name. Заметьте, что TMemo имеет имя, которое присваивается по умолчанию.

Перейдите на форму и дважды щелкните мышкой на объект TButton. Вы сразу попадете в окно Редактора, в котором увидите фрагмент кода вроде этого:

procedure TForm1.TerminateClick(Sender: TObject);

begin

end;

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

TForm1 = class(TForm)

Edit1: TEdit;

Memo1: TMemo;

Terminate: TButton;

procedure TerminateClick(Sender: TObject);

private

{ Private declarations }

public

{ Public declarations }

end;

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

Теперь самое время написать строчку кода. Это очень простой код, состоящий из одного слова Close:

procedure TForm1.TerminateClick(Sender: TObject);

begin

Close;

end;

Когда этот код исполняется, то главная форма (значит и все приложение) закрывается. Для проверки кода запустите программу и нажмите кнопку Terminate. Если все сделано правильно, программа закроется и вы вернетесь в режим дизайна.

Прежде, чем перейти к следующему разделу, перейдите в Инспектор Объектов и измените значение свойства Name для кнопки на любое другое, например OK. Нажмите Enter для внесения изменений.

Посмотрите в Редактор, вы увидите, что код, написанный вами изменился:

procedure TForm1.OkClick(Sender: TObject);

begin

Close;

end;

Заметьте, что аналогичные изменения произошли и в определении класса:

TForm1 = class(TForm)

Edit1: TEdit;

Memo1: TMemo;

Ok: TButton;

procedure OkClick(Sender: TObject);

private

{ Private declarations }

public

{ Public declarations }

end;

Глава 22.
Интерактивные обучающие программы

Delphi предоставляет тьютор, содержащий несколько тем и который можно запустить из пункта меню Help ê Interactive Tutors. Тьютор запускается только если среда Delphi имеет все установки по умолчанию. Если конфигурация была изменена, то проще всего сохранить файл DELPHI.INI под другим именем и скопировать файл DELPHI.CBT в DELPHI.INI.

В первых двух темах дается краткий обзор Delphi и происходит обучение построению простейшего приложения.

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

Глава 23.
Управление проектом

Проект Delphi

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

u Главный файл проекта, изначально называется
PROJECT1.DPR.

u Первый модуль программы unit, который автоматически появляется в начале работы. Файл называется UNIT1.PAS по умолчанию, но его можно назвать любым другим именем, вроде MAIN.PAS.

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

u Файл PROJECT1.RES содержит иконку для проекта, создается автоматически.

u Файл, который называется PROJECT1.OPT по умолчанию, является текстовым файлом для сохранения установок, связанных с данным проектом. Например, установленные вами директивы компилятора сохраняются здесь.

u Файл PROJECT1.DSK содержит информацию о состоянии рабочего пространства.

Разумеется, если сохранить проект под другим именем, то изменят название и файлы с расширением RES, OPT и DSK.

После компиляции программы получаются файлы с расширениями:

u DCU — скомпилированные модули;

u EXE — исполняемый файл;

u DSM — служебный файл для запуска программы в среде, очень большой, рекомендуется стирать его при окончании работы;

u ~PA, ~DP — backup файлы Редактора.

Глава 24.
Пункт меню File

Если нужно сохранить проект, то вы выбираете пункт главного меню File (с помощью мышки или по клавише Alt+F). Пункт меню File выглядит следующим образом:

u New Project

u Open Project

u Save Project

u Save Project As

u Close Project

u New Form

u New Unit

u New Component

u Open File

u Save File

u Save File As

u Close File

u Add File

u Remove File

u Print

u Exit

u 1 PREV1.DPR

u 2 PREV2.DPR

Меню File делится на шесть секций; вот их назначение:

u Первая секция дает возможность управления проектом в целом.

u Вторая секция дает контроль над формами, модулями и компонентами проекта.

u Третья позволяет добавлять и удалять файлы из проекта.

u Четвертая управляет печатью.

u Пятая секция — выход из Delphi

u Шестая секция предоставляет список ранее редактировавшихся проектов; вы можете быстро открыть нужный.

Как вы увидите позже, большинство операций из пункта меню File можно выполнить с помощью Менеджера Проекта (Project Manager), который можно вызвать из пункта меню View. Некоторые операции доступны и через SpeedBar. Данная стратегия типична для Delphi: она предоставляет несколько путей для решения одной и той же задачи, вы сами можете решать, какой из них более эффективен в данной ситуации.

Каждая строка пункта меню File объяснена в Справочнике. Выберите меню File и нажмите F1, появится экран справочника.

Большинство из пунктов первой секции очевидны. New Project начинает новый проект, Open Project открывает существующий проект.

Первые два пункта второй секции позволяют вам создать новую форму или новый модуль. Выбирая New Form, вы создаете новую форму и модуль, связанный с ней; выбирая New Unit, вы создаете один модуль.

New Component вызывает диалог для построения заготовки нового визуального компонента. В результате создается модуль, который можно скомпилировать и включить в Палитру Компонент.

Open File открывает при необходимости любой модуль или просто текстовый файл. Если модуль описывает форму, то эта форма тоже появится на экране.

При создании нового модуля Delphi дает ему имя по умолчанию. Вы можете изменить это имя на что-нибудь более осмысленное (например, MAIN.PAS) с помощью пункта Save File As.

Save File сохраняет только редактируемый файл, но не весь проект.

Close File удаляет файл из окна Редактора.

Нужно обратить внимание: вы должны регулярно сохранять проект через File ê Save Project либо через нажатие Ctrl+S.

Глава 25.
Менеджер проектов

Теперь, когда вы знаете о создании проекта с помощью пункта меню File, перейдем к Менеджеру Проектов, который помогает управлять проектом. Менеджер Проектов разделен на две части. Верхняя — панель с управляющими кнопками. Нижняя — список модулей, входящих в проект.

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

Краткое описание других кнопок:

u Третья слева кнопка — просмотр текста модуля, на котором стоит курсор.

u Четвертая — просмотр формы, если есть таковая для данного модуля.

u Пятая — вызов диалога настройки проекта, сам диалог будет рассмотрен позже.

u Последняя — сохранение изменений на диске.

Глава 26.
Обзор других пунктов меню

Рассмотрим другие важные пункты — Edit, Search, View и Compile.

Пункт меню Edit

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

Отметьте для себя, что Справочник (on-line help) объясняет, как нужно использовать пункт меню Options ê Environment для настройки команды Undo. Возможность ограничить возможное количество команд Undo может пригодиться, если вы работаете на машине с ограниченными ресурсами.

Команды Cut, Copy, Paste и Delete — как во всех остальных приложениях Windows, но их можно применять не только к тексту, но и к визуальным компонентам.

Пункт меню Menu

В Search есть команда Find Error (поиск ошибки), которая поможет отследить ошибку периода выполнения программы. Когда в сообщении об ошибке указан ее адрес, вы можете выбрать пункт меню Search ê Find Error и ввести этот адрес. Если это представится возможным, то среда переместит вас в то место программы, где произошла ошибка.

Пункт меню View

Составляющие пункта меню View:

u Project Manager (Менеджер Проекта).

u Project Source — загружает главный файл проекта (DPR) в Редактор.

u Установка, показывать или нет Object Inspector на экране.

u Установка, показывать или нет Alignment Palette. То же самое доступно из пункт меню Edit ê Align.

u Browser — вызов средства для просмотра иерархии объектов программы, поиска идентификатора в исходных текстах.

u Watch, Breakpoint и Call Stack — связаны с процедурой отладки программы.

u Component List — список компонент, альтернатива Палитре Компонент. Используется для поиска компонента по имени или при отсутствии мыши.

u Window List — список окон, открытых в среде Delphi.

u Toggle Form/Unit, Units, Forms — переключение между формой и соответствующим модулем, выбор модуля или формы из списка.

u New Edit Window — открывает дополнительное окно Редактора. Полезно, если нужно, например, просмотреть две разных версии одного файла.

u SpeedBar и Component Palette — установки, нужно ли их отображать.

Пункт меню Compile

В пункте меню Compile проект можно скомпилировать (compile) или перестроить (build). Если выбрать Compile или Run, то Delphi перекомпилирует только те модули, которые изменились со времени последней компиляции. Build all, с другой стороны, перекомпилирует все модули, исходные тексты которых доступны. Команда Syntax Check только проверяет правильность кода программы, но не обновляет DCU файлы. В самом низу — пункт Information, который выдает информацию о программе: размеры сегментов кода, данных и стека, размер локальной динамической памяти и количество скомпилированных строк.

Пункт меню Run

Можно использовать Run для компиляции и запуска программы и для указания параметров командной строки для передачи в программу. Здесь же имеются опции для режима отладки.

Глава 27.
Меню Options

Options наиболее сложная часть системного меню. Это центр управления, из которого вы можете изменять установки для проекта и для всей рабочей среды Delphi. В Options есть семь пунктов:

u Project

u Environment

u Tools

u Gallery

u Open Library

u Install Components

u Rebuild Library

Первые четыре пункта вызывают диалоговые окна. Ниже приведено общее описание пункта меню Options:

u Project — выбор установок, которые напрямую влияют на текущий проект, это могут быть, к примеру, директивы компилятора проверки стека (stack checking) или диапазона (range checking).

u Environment — конфигурация самой среды программирования (IDE). Например, здесь можно изменить цвета, используемые в Редакторе.

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

u Gallery — позволяет определить специфические установки для Эксперта Форм и Эксперта Проектов и их «заготовок». Эксперты и «заготовки» предоставляют путь для ускорения конструирования интерфейса программы.

u Последние три пункта позволяют сконфигурировать Палитру Компонент.

Диалог из пункта Options ê Project включает пять страниц:

u На странице Forms перечислены все формы, включенные в проект; вы можете указать, нужно ли автоматически создавать форму при старте программы или вы ее создадите сами.

u На странице Application вы определяете элементы программы такие, как заголовок, файл помощи и иконка.

u Страница Compiler включает установки для генерации кода, управления обработкой ошибок времени выполнения, синтаксиса, отладки.

u На странице Linker можно определить условия для процесса линковки приложения.

u Страница Directories/Conditionals — здесь указываются директории, специфичные для данного проекта.

Все установки для проекта сохраняются в текстовом файле с расширением OPT и вы можете вручную их исправить.

Глава 28.
Страница Forms

На странице Forms можно выбрать главную форму проекта. Изменения, которые вы сделаете, отобразятся в соответствующем файле DPR. Например, в нижеследующем проекте, Form1 является главной, поскольку появляется первой в главном блоке программы:

program Project1;

uses

Forms,

Unit1 in 'UNIT1.PAS' {Form1},

Unit2 in 'UNIT2.PAS' {Form2};

{$R *.RES}

begin

Application.CreateForm(TForm1, Form1);

Application.CreateForm(TForm2, Form2);

Application.Run;

end

Если изменить код так, чтобы он читался:

begin

Application.CreateForm(TForm2, Form2);

Application.CreateForm(TForm1, Form1);

Application.Run;

end

то теперь Form2 станет главной формой проекта.

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

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

Глава 29.
Страница Applications

На странице Applications вы можете задать заголовок (Title), файл помощи (Help file) и пиктограмму (Icon) для проекта.

Глава 30.
Страница Compiler

Установки из пункта меню Options ê Project сохраняются в соответствующем файле с расширением OPT. Давайте рассмотрим директивы компилятора на странице Compiler.

Следующая таблица показывает, как различные директивы отображаются в OPT файле, на странице Compiler и внутри кода программы:

Глава 31.
Страница Linker

Если буфер линковщика расположен в памяти, то линковка происходит быстрее.

Размер стека (Stack Size) и локальной динамической памяти (Heap Size) весьма важны. Delphi устанавливает по умолчанию и Stack Size, и Heap Size в 8192 байт каждый. Вам может понадобиться изменить размер стека в программе, но обычно это не более 32Кб. В сумме эти два размера не должны превышать 64Кб, иначе будет выдаваться ошибка при компиляции программы.

Глава 32.
Страница Directories/Conditionals

Страница Directories/Conditionals дает возможность расширить число директорий, в которых компилятор и линковщик ищут DCU файлы. В файле DELPHI.INI содержится еще один список директорий. Запомните, что в OPT файле — список директорий для конкретного проекта, а в файле DELPHI.INI — список относится к любому проекту.

u Output directory — выходная директория, куда складываются EXE и DCU файлы, получающиеся при компиляции.

u Search path — список директорий для поиска DCU файлов при линковке. Директории перечисляются через точку с запятой.

u Conditional defines — для опытного программиста и на первом этапе создания проекта не требуется. Для информации можно вызвать Справочник (on-line help).

Глава 33.
Конфигурация среды программирования (IDE)

Пункт меню Options ê Environment предоставляет вам большой набор страниц и управляющих элементов, которые определяют внешний вид и работу IDE. Delphi позволяет сделать следующие важные настройки:

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

u Можно менять цвета IDE.

u Можно менять подсветку синтаксиса в Редакторе.

u Можно изменить состав Палитры Компонент.

u Указать «горячие клавиши» IDE.

В группе Desktop Contents определяется, что будет сохраняться при выходе из Delphi. Если выбрать Desktop Only — это сохранит информацию о директориях и открытых окнах, если выбрать Desktop And Symbols — это сохранит то же самое плюс информацию для броузера (browser).

В группе Autosave указывается, что нужно сохранять при запуске программы. Если позиция Editor Files выбрана, то сохраняются все модифицированные файлы из Редактора при выполнении команд Run ê Run, Run ê Trace Into, Run ê Step Over, Run ê Run To Cursor или при выходе из Delphi. Если позиция Desktop выбрана — сохраняется рабочая среда при закрытии проекта или при выходе из Delphi. Если вы позже откроете проект, то он будет иметь тот же вид, что и при его закрытии.

В группе Form Designer можно установить, показывать ли сетку (grid) на экране и выравнивать ли объекты по ней, и размер ячеек сетки.

В группе Debugging опция:

u Integrated Debugging — использовать ли встроенный отладчик;

u Step Program Block — отладчик остановится на первой строке модуля, в котором есть отладочная информация;

u Break On Exception — останавливать ли программу при возникновении исключительной ситуации;

u Minimize On Run — свертывать ли Delphi при запуске программы. После закрытия программы среда Delphi восстанавливается;

u Hide Designers On Run — прячет окна Дизайнера (Инспектор Объектов, формы) при запуске приложения;

u Show Compiler Progress — показывать ли окно, в котором отражается процесс компиляции программы;

u Gallery — указывает, в каких случаях нужно предоставлять «галерею» (коллекцию заготовок и экспертов).

Страницы Editor Options, Editor Display и Editor Colors позволяют вам изменить цвета и «горячие» клавиши, используемые IDE.

Существует несколько способов изменить назначение «горячих» клавиш, используемых Редактором. Например, многие пользователи привыкли, что по клавише F5 максимизируется окно Редактора. Для этого им надо использовать расположение клавиш, называемое Classic (Keystroke mapping: Classic). Всего есть четыре вида конфигурации клавиш:

u Default — характерно для Microsoft. Если вы новичок в Windows или уже привыкли к этому расположению клавиш, то это подойдет.

u Classic — более известно ветеранам Borland C++ и Borland Pascal. Поддерживает многие комбинации клавиш WordStar и отладчик управляется старым добрым способом.

u Остальные два вида — имитируют редакторы Epsilon и BRIEF. Подойдут, если вы с ними знакомы.

Точное описание назначения клавиш можно найти в Справочнике (в Help ê Topic Search набрать key mapping).

Цвета IDE можно изменить на странице Editor Colors.

На странице Editor Options можно настроить тонкие детали работы Редактора.

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

u Use syntax highlight — выделять ли цветом синтаксические конструкции в Редакторе Исходного текста.

u Find text at cursor — если включено, то при поиске (Ctrl+F) в качестве подстроки для поиска будет браться то слово, на котором стоит курсор.

Обо всех опциях можно подробнее узнать в Справочнике (F1).

Установки сохраняются в файле DELPHI.INI, который находится в директории Windows.

Глава 34.
Обзор Палитры Компонент

Стандартные компоненты

На первой странице Палитры Компонент размещены 14 объектов, определенно важных для использования. Мало кто обойдется длительное время без кнопок, списков, окон ввода. Все эти объекты такая же часть Windows, как мышь или окно. Набор и порядок компонент на каждой странице являются конфигурируемыми. Так, вы можете добавить к имеющимся компонентам новые, изменить их количество и порядок. Это можно сделать, вызвав всплывающее меню (нажать правую кнопку мыши, когда указатель над Палитрой).

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

Курсор — не компонент, просто пиктограмма для быстрой отмены выбора какого-либо объекта.

TMainMenu позволяет вам поместить главное меню в программу. При помещении TMainMenu на форму это выглядит, как просто иконка. Иконки данного типа называют «невидимыми компонентами», поскольку они невидимы во время выполнения программы. Создание меню включает три шага:

u помещение TMainMenu на форму;

u вызов Дизайнера Меню через свойство Items в Инспекторе Объектов;

u определение пунктов меню в Дизайнере Меню.

TPopupMenu позволяет создавать всплывающие меню. Этот тип меню появляется по щелчку правой кнопки мыши на объекте, к которому привязано данное меню. У всех видимых объектов имеется свойство PopupMenu, где и указывается нужное меню. Создается PopupMenu аналогично главному меню.

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

TEdit — стандартный управляющий элемент Windows для ввода. Он может быть использован для отображения короткого фрагмента текста и позволяет пользователю вводить текст во время выполнения программы.

TMemo — иная форма TEdit. Подразумевает работу с большими текстами. TMemo может переносить слова, сохранять в ClipBoard фрагменты текста и восстанавливать их, и другие основные функции редактора. TMemo имеет ограничения на объем текста в 32Кб, это составляет 10–20 страниц.

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

procedure TForm1.Button1Click(Sender: TObject);

begin

MessageDlg('Are you there?',mtConfirmation,mbYesNoCancel,0);

end;

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

Например, если посмотреть окно диалога настроек компилятора (пункт меню Options ê Project, страница Compiler), то можно увидеть, что оно состоит преимущественно из CheckBox’ов.

TRadioButton позволяет выбрать только одну опцию из нескольких. Если вы опять откроете диалог Options ê Project и выберете страницу Linker Options, то вы можете видеть, что секции Map file и Link buffer file состоят из наборов RadioButton.

TListBox нужен для показа прокручиваемого списка. Классический пример ListBox’а в среде Windows — выбор файла из списка в пункте меню File ê Open многих приложений. Названия файлов или директорий и находятся в ListBox’е.

TComboBox во многом напоминает ListBox, за исключением того, что позволяет водить информацию в маленьком поле ввода сверху ListBox. Есть несколько типов ComboBox, но наиболее популярен спадающий вниз (drop-downcombo box), который можно видеть внизу окна диалога выбора файла.

TScrollbar — полоса прокрутки, появляется автоматически в объектах редактирования, ListBox’ах при необходимости прокрутки текста для просмотра.

TGroupBox используется для визуальных целей и для указания Windows, каков порядок перемещения по компонентам на форме (при нажатии клавиши TAB).

TRadioGroup используется аналогично TGroupBox, для группировки объектов TRadioButton.

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

Это полный список объектов на первой странице Палитры Компонент. Если вам нужна дополнительная информация, то выберите на Палитре объект и нажмите клавишу F1 — появится Справочник с полным описанием данного объекта.

Страница Additional

На странице Standard представлены управляющие элементы, появившиеся в Windows. На странице Additional размещены объекты, позволяющие создать более красивый пользовательский интерфейс программы.

Список компонент:

u TBitBtn — кнопка вроде TButton, однако на ней можно разместить картинку (glyph). TBitBtn имеет несколько предопределенных типов (bkClose, bkOK и другие), при выборе которых кнопка принимает соответствующий вид. Кроме того, нажатие кнопки на модальном окне (Form2.ShowModal) приводит к закрытию окна с соответствующим модальным результатом (Form2.ModalResult).

u TSpeedButton — кнопка для создания панели быстрого доступа к командам (SpeedBar). Пример — SpeedBar слева от Палитры Компонент в среде Delphi. Обычно на данную кнопку помещается только картинка (glyph).

u TTabSet — горизонтальные закладки. Обычно используется вместе с TNoteBook для создания многостраничных окон. Название страниц можно задать в свойстве Tabs. Но проще это сделать в программе при создании формы (OnCreate):
TabSet1.Tabs := Notebook1.Pages;
А для того, чтобы при выборе закладки страницы перелистывались, нужно в обработчике события OnClick для TTabSet написать:
Notebook1.PageIndex := TabSet1.TabIndex;

u TNoteBook — используется для создания многостраничного диалога, на каждой странице располагается свой набор объектов. Используется совместно с TTabSet.

u TTabbedNotebook — многостраничный диалог со встроенными закладками, в данном случае — закладки сверху.

u TMaskEdit — аналог TEdit, но с возможностью форматированного ввода. Формат определяется в свойстве EditMask. В редакторе свойств для EditMask есть заготовки некоторых форматов: даты, валюты. Специальные символы для маски можно посмотреть в Справочнике.

u TOutline — используется для представления иерархических отношений связанных данных. Например — дерево директорий.

u TStringGrid — служит для представления текстовых данных в виде таблицы. Доступ к каждому элементу таблицы происходит через свойство Cell.

u TDrawGrid — служит для представления данных любого типа в виде таблицы. Доступ к каждому элементу таблицы происходит через свойство CellRect.

u TImage — отображает графическое изображение на форме. Воспринимает форматы BMP, ICO, WMF.

u TShape — служит для отображения простейших графических объектов на форме: окружность, квадрат.

u TBevel — элемент для рельефного оформления интерфейса.

u THeader — элемент оформления для создания заголовков с изменяемыми размерами для таблиц.

u TScrollBox — позволяет создать на форме прокручиваемую область с размерами большими, нежели экран. На этой области можно разместить свои объекты.

Страница Dialogs

На странице Dialogs представлены компоненты для вызова стандартных диалогов Windows. Внешний вид диалогов зависит от используемой версии Windows. Объекты, представленные на данной странице невидимы во время выполнения и вызов диалогов происходит программно, например:

if OpenDialog1.Execute then

Image1.Picture.LoadFromFile(OpenDialog1.FileName);

Диалоги Windows в порядке появления на странице Dialogs:

u OpenDialog — выбрать файл;

u SaveDialog — сохранить файл;

u FontDialog — настроить шрифт;

u ColorDialog — выбор цвета;

u PrintDialog — печать;

u PrinterSetupDialog — настройка принтера;

u FindDialog — поиск строки;

u ReplaceDialog — поиск с заменой.

Страница System

Страница представляет набор компонент для доступа к некоторым системным сервисам типа таймер, DDE, OLE.

u TTimer — таймер, событие OnTimer периодически вызывается через промежуток времени, указанный в свойстве Interval. Период времени может составлять от 1 до 65535 мсек.

u TPaintBox — место для рисования. В обработчики событий передаются относительные координаты мышки в TPaintBox.

u TFileListBox — специализированный ListBox, в котором отображаются файлы из указанной директории (свойство Directory). На названия файлов можно наложить маску, для этого служит свойство Mask. Кроме того, в свойстве FileEdit можно указать объект TEdit для редактирования маски.

u TDirectoryListBox — специализированный ListBox, в котором отображается структура директорий текущего диска. В свойстве FileList можно указать TFileListBox, который будет автоматически отслеживать переход в другую директорию.

u TDriveComboBox — специализированный ComboBox для выбора текущего диска. Имеет свойство DirList, в котором можно указать TDirectoryListBox, который будет отслеживать переход на другой диск.

u TFilterComboBox — специализированный ComboBox для выбора маски имени файлов. Список масок определяется в свойстве Filter. В свойстве FileList указывается TFileListBox, на который устанавливается маска.

С помощью вышеперечисленных четырех компонент (TFileListBox, TDirectoryListBox, TDriveComboBox, TFilterComboBox) можно построить свой собственный диалог выбора файла, причем для этого не потребуется написать ни одной строчки кода.

u TMediaPlayer — служит для управления мультимедийными устройствами (типа CD-ROM, MIDI). Выполнен в виде панели управления с кнопками Play, Stop, Record. Для воспроизведения может понадобиться как соответствующее оборудование, так и программное обеспечение. Подключение устройств и установка программного обеспечения

производится в среде Windows. Например, для воспроизведения видео, записанного в формате AVI, в потребуется установить ПО MicroSoft Video.

u TOLEContainer — контейнер, содержащий OLE объекты.

u TDDEClientConv, TDDEClientItem, TDDEServerConv, TDDEServerItem — 4 объекта для организации DDE. С помощью этих объектов можно построить приложение как DDE-сервер, так и DDE-клиент.

Страница VBX

Поскольку формат объектов из MicroSoft Visual Basic (VBX) является своего рода стандартом и существует большое количество библиотек таких объектов, то в Delphi была предусмотрена совместимость с этим форматом. VBX можно включить в Палитру Компонент Delphi и использовать их как «родные» компоненты (в том числе, выбирать их в качестве предков и наследовать свойства и методы).

u TBiSwitch — двухпозиционный переключатель.

u TBiGauge — прогресс-индикатор.

u TBiPict — аналог TImage.

u TChartFX — деловая графика.

Глава 35.
Рисование и закраска

Графические компоненты

В стандартную библиотеку визуальных компонент Delphi входит несколько объектов, с помощью которых можно придать своей программе совершенно оригинальный вид. Это — TImage (TDBImage), TShape, TBevel.

TImage позволяет поместить графическое изображение в любое место на форме. Этот объект очень прост в использовании — выберите его на странице Additional и поместите в нужное место формы. Собственно картинку можно загрузить во время дизайна в редакторе свойства Picture (Инспектор Объектов). Картинка должна храниться в файле в формате BMP (bitmap), WMF (Windows Meta File) или ICO (icon). (TDBImage отображает картинку, хранящуюся в таблице в поле типа BLOB. При этом доступен только формат BMP.)

Как известно, форматов хранения изображений гораздо больше трех вышеназванных (например, наиболее известны PCX, GIF, TIFF, JPEG). Для включения в программу изображений в этих форматах нужно либо перевести их в формат BMP, либо найти библиотеки третьих фирм, в которых есть аналог TImage, «понимающий» данные форматы (есть как VBX объекты, так и «родные» объекты для Delphi).

При проектировании следует помнить, что изображение, помещенное на форму во время дизайна, включается в файл .DPR и затем прикомпилируется к EXE файлу. Поэтому такой EXE файл может получиться достаточно большой.

Как альтернативу можно рассмотреть загрузку картинки во время выполнения программы, для этого у свойства Picture (которое является объектом со своим набором свойств и методов) есть специальный метод LoadFromFile. Это делается, например, так:

if OpenDialog1.Execute then

Image1.Picture.LoadFromFile(OpenDialog1.FileName);

Важными являются свойства объекта Center и Stretch — оба имеют булевский тип. Если Center установлено в True, то центр изображения будет совмещаться с центром объекта TImage. Если Stretch установлено в True, то изображение будет сжиматься или растягиваться таким образом, чтобы заполнить весь объект TImage.

TShape — простейшие графические объекты на форме типа круг, квадрат. Вид объекта указывается в свойстве Shape. Свойство Pen определяет цвет и вид границы объекта. Brush задает цвет и вид заполнения объекта. Эти свойства можно менять как во время дизайна, так и во время выполнения программы.

TBevel — объект для украшения программы, может принимать вид рамки или линии. Объект предоставляет меньше возможностей по сравнению с TPanel, но не занимает ресурсов. Внешний вид указывается с помощью свойств Shape и Style.

Свойство объектов Canvas

У ряда объектов из библиотеки визуальных компонент есть свойство Canvas (канва), которое предоставляет простой путь для рисования на них. Эти объекты — TBitmap, TComboBox, TDBComboBox, TDBGrid, TDBListBox, TDirectoryListBox, TDrawGrid, TFileListBox, TForm, TImage, TListBox, TOutline, TPaintBox, TPrinter, TStringGrid.

Canvas является в свою очередь объектом, объединяющим в себе поле для рисования, карандаш (Pen), кисть (Brush) и шрифт (Font). Canvas обладает также рядом графических методов: Draw, TextOut, Arc, Rectangle. Используя Canvas, вы можете воспроизводить на форме любые графические объекты — картинки, многоугольники, текст без использования компонент TImage, TShape и TLabel (то есть, без использования дополнительных ресурсов), однако при этом вы должны обрабатывать событие OnPaint того объекта, на канве которого вы рисуете. Рассмотрим подробнее свойства и методы объекта Canvas:

u Brush — кисть, является объектом со своим набором свойств:

u Bitmap — картинка размером строго 8x8, используется для заполнения (заливки) области на экране.

u Color — цвет заливки.

u Style — предопределенный стиль заливки; это свойство конкурирует со свойством Bitmap — какое свойство вы определили последним, то и будет определять вид заливки.

u Handle — данное свойство дает возможность использовать кисть в прямых вызовах процедур Windows API.

u ClipRect — (только чтение) прямоугольник, на котором происходит графический вывод.

u CopyMode — свойство определяет, каким образом будет происходить копирование (метод CopyRect) на данную канву изображения из другого места: один к одному, с инверсией изображения.

u Font — шрифт, которым выводится текст (метод TextOut).

u Handle — данное свойство используется для прямых вызовов Windows API.

u Pen — карандаш, определяет вид линий; как и кисть (Brush) является объектом с набором свойств:

u Color — цвет линии.

u Handle — для прямых вызовов Windows API.

u Mode — режим вывода: простая линия, с инвертированием, с выполнением исключающего «Или».

u Style — стиль вывода: линия, пунктир.

u Width — ширина линии в точках.

u PenPos — текущая позиция карандаша, карандаш рекомендуется перемещать с помощью метода MoveTo, а не прямой установкой данного свойства.

u Pixels — двумерный массив элементов изображения (pixel), с его помощью вы получаете доступ к каждой отдельной точке изображения.

Методы Canvas

Методы для рисования простейшей графики — Arc, Chord, LineTo, Pie, Polygon, PolyLine, Rectangle, RoundRect. При прорисовке линий в этих методах используются карандаш (Pen) канвы, а для заполнения внутренних областей — кисть (Brush).

Методы для вывода картинок на канву — Draw и StretchDraw. В качестве параметров указываются прямоугольник и графический объект для вывода (это может быть TBitmap, TIcon или TMetafile). StretchDraw отличается тем, что растягивает или сжимает картинку так, чтобы она заполнила весь указанный прямоугольник.

Методы для вывода текста — TextOut и TextRect. При выводе текста используется шрифт (Font) канвы. При использовании TextRect текст выводится только внутри указанного прямоугольника. Длину и высоту текста можно узнать с помощью функций TextWidth и TextHeight.

Объект TPaintBox

На странице System Палитры Компонент есть объект TPaintBox, который можно использовать для построения приложений типа графического редактора или, например, в качестве места построения графиков (если, конечно, у вас нет для этого специальных компонент третьих фирм).

Никаких ключевых свойств, кроме Canvas, TPaintBox не имеет, собственно, этот объект является просто канвой для рисования. Важно, что координаты указателя мыши, передаваемые в обработчики соответствующих событий (например, OnMouseMove), являются относительными, то есть, это смещение мыши относительно левого верхнего угла объекта TPaintBox, а не относительно левого верхнего угла формы.

Примеры

В первом примере показано, как во время выполнения программы можно изменять свойства объекта TShape. Изменение цвета объекта (событие OnChange для ColorGrid1):

procedure TForm1.ColorGrid1Change(Sender: TObject);

begin

Shape1.Brush.Color:=ColorGrid1.ForeGroundColor;

end;

Во втором примере показано, как осуществить доступ к отдельной точке на изображении (на канве). По нажатию кнопки Fill всем точкам изображения присваивается свой цвет:

procedure TForm1.Button1Click(Sender: TObject);

var

i, j : Longint;

begin

Button1.Enabled:=False;

with Canvas do

for i:=1 to Width do begin

Application.ProcessMessages;

for j:=1 to Height do

Pixels[i,j]:=i*j;

end;

Button1.Enabled:=True;

end;

В третьей программе приведен пример использования методов, выводящих изображение — Draw и StretchDraw. Прорисовка изображений происходит в обработчике события OnPaint для формы:

procedure TForm1.FormPaint(Sender: TObject);

begin

with Canvas do begin

Draw(0,0, Image1.Picture.BitMap);

StretchDraw(Rect(250,0,350,50),Image1.Picture.BitMap)

end;

end;

Глава 36.
Печать текстовая и графическая

Печать в текстовом режиме

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

procedure TForm1.Button1Click(Sender: TObject);

var

To_Prn : TextFile;

begin

AssignPrn(To_Prn);

Rewrite(To_Prn);

Writeln(To_Prn, 'Printer in Text Mode');

CloseFile(To_Prn);

end;

Здесь необходимо пояснить, что по сравнению с Pascal в Delphi изменены названия некоторых функций и переменных в модуле System:

u AssignFile вместо Assign;

u CloseFile вместо Close;

u TextFile вместо Text;

Вывод содержимого формы на печать

Иногда в программе требуется просто получить твердую копию экранной формы. В Delphi это делается более, чем просто — у объекта TForm есть метод Print, который и нужно вызвать в нужный момент.

Графическая печать

И все же, более интересно, как из программы, созданной в Delphi, можно вывести на печать графическую информацию. Для этого есть специальный объект Printer (класса TPrinter). Он становится доступен, если к программе подключить модуль Printers (т.е. добавить имя модуля в разделе uses). С помощью этого объекта печать на принтере графической информации становится не сложнее вывода этой информации на экран. Основным является то, что Printer предоставляет разработчику свойство Canvas и методы, выводящие содержание канвы на принтер. Рассмотрим подробнее свойства и методы объекта Printer:

u Aborted — тип булевский; показывает, прервал ли пользователь работу принтера методом Abort.

u Canvas — канва, место для вывода графики.

u Fonts — список доступных шрифтов.

u Handle — используется при прямых вызовах Windows API.

u Orientation — ориентация страницы, вертикально или горизонтально.

u PageWidth, PageHeight, PageNumber — соответственно ширина, высота и номер страницы.

u Printers перечисляет все установленные в системе принтеры.

u PrinterIndex указывает, какой из подключенных принтеров является текущим. Чтобы печатать на принтере по умолчанию здесь должно быть значение -1.

u Printing — тип булевский; показывает, начата ли печать (методом BeginDoc).

u Title — заголовок для Print Manager и для заголовка перед выводом на сетевом принтере.

Методы Printer

u Abort — прерывает печать, начатую методом BeginDoc.

u BeginDoc — вызывается перед тем, как начать рисовать на канве.

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

u NewPage — переход на новую страницу.

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

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

u Выполняется метод BeginDoc.

u На канве (Canvas) рисуем все, что нужно.

u При необходимости разместить информацию на нескольких листах вызываем метод NewPage.

u Посылаем нарисованное на принтер.

Глава 37.
Свойства

Свойства и создание откликов на события

Каждый компонент, который вы помещаете на форму, имеет свое отражение в окне Инспектора Объектов (Object Inspector). Как вы помните, Object Inspector имеет две «странички» — Properties (Свойства) и Events (События). Создание программы в Delphi сводится к «нанесению» компонент на форму (которая, кстати, также является компонентом) и настройке взаимодействия между ними путем:

u изменения значения свойств этих компонент;

u написания адекватных реакций на события.

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

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

u Простые свойства — это те, значения которых являются числами или строками. Например, свойства Left и Top принимают целые значения, определяющие положение левого верхнего угла компонента или формы. Свойства Caption и Name представляют собой строки и определяют заголовок и имя компонента или формы.

u Перечислимые свойства — это те, которые могут принимать значения из предопределенного набора (списка). Простейший пример — это свойство типа Boolean, которое может принимать значения True или False.

u Вложенные свойства — это те, которые поддерживают вложенные значения (или объекты). Object Inspector изображает знак «+» слева от названия таких свойств. Имеется два вида таких свойств: множества и комбинированные значения. Object Inspector изображает множества в квадратных скобках. Если множество пусто, оно отображается как []. Установки для вложенных свойств вида «множество» обычно имеют значения типа Boolean. Наиболее распространенным примером такого свойства является свойство Style с вложенным множеством булевых значений. Комбинированные значения отображаются в Инспекторе Объектов как коллекция некоторых величин, каждый со своим типом данных. Некоторые свойства, например, Font, для изменения своих значений имеют возможность вызвать диалоговое окно. Для этого достаточно щелкнуть маленькую кнопку с тремя точками в правой части строки Инспектора Объектов, показывающей данное свойство.

Delphi позволяет легко манипулировать свойствами компонент как в режиме проектирования (design time), так и в режиме выполнения программы (run time).

В режиме проектирования манипулирование свойствами осуществляется с помощью Дизайнера Форм (Forms Designer) или, как вы уже видели, на страничке Properties Инспектора Объектов. Например, для того чтобы изменить свойства Height (высоту) и Width (ширину) кнопки, достаточно «зацепить» мышкой за любой ее угол и раздвинуть до нужного представления. Того же результата можно добиться, просто подставив новые значения свойств Height и Width в окне Object Inspector.

С другой стороны, в режиме выполнения пользователь (программист) имеет возможность не только манипулировать всеми свойствами, отображаемыми в Инспекторе Объектов, но и управлять более обширным их списком.

Управление свойствами визуальных компонент в режиме выполнения

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

MyComponent.Width := 35;

Вышеприведенная строка устанавливает ширину (Width) компонента в значение 35. Если свойство Width компонента еще не было равно 35 к моменту выполнения данной строки программы, вы увидите, как компонента визуально изменит свою ширину.

Таким образом, нет ничего магического в Инспекторе Объектов. Object Inspector просто является удобным способом выполнения в режиме проектирования того, что может быть осуществлено программным путем в режиме выполнения. Более того, как уже было сказано выше, у компонента могут быть свойства, не отображаемые в окне Инспектора Объектов.

Объектно-ориентированный язык Паскаль, лежащий в основе Delphi, в качестве базового имеет принцип соответствия визуальных компонент тем вещам, которые они представляют. Разработчики Delphi поставили перед собой цель, чтобы, например, представление компонента Button (кнопка), инкапсулирующее некий код, соответствовало визуальному изображению кнопки на экране и являлось как можно более близким эквивалентом реальной кнопки, которую вы можете найти на клавиатуре. И именно из этого принципа родилось понятие свойства.

Если вы измените свойства Width и Height компонента Button, кнопка соответствующим образом изменит свои ширину и высоту. Вам нет необходимости после изменения свойства Width указывать объекту, чтобы он перерисовал себя, хотя при обычном программировании именно так вы и должны поступать. Свойства — это более чем просто данные. Напротив, они делают эти данные «живыми», и все это происходит перед вашими глазами! Свойства дают вам иллюзию, как будто вы имеете дело с реальными объектами, а не с их программным представлением.

Программа SHAPEDEM.DPR демонстрирует различные способы, с помощью которых можно изменять пользовательский интерфейс при выполнении программы. Эта программа не производит никаких полезных действий кроме демонстрации того, как легко можно создать «дельфийское» приложение с настраиваемым интерфейсом.

Программа SHAPEDEM содержит всего лишь объект TShape, размещенный на форме, вместе с двумя полосами прокрутки и несколькими кнопками. Эта программа интересна тем, что позволяет в режиме выполнения изменять размер, цвет и внешний вид объекта TShape, равно как размер и цвет самой формы.

Листинг А показывает код программы SHAPEDEM. Код головного модуля этой программы мы приведем по частям — по мере его написания.

Листинг А: Исходный код программы SHAPEDEM.DPR

program Shapedem;

uses

Forms,

Mina in 'MAIN.PAS' {Form1};

begin

Application.CreateForm(TForm1, Form1);

Application.Run;

end;

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

В нашем примере полосы прокрутки (ScrollBars) используются для изменения размера фигуры, изображенной в средней части экрана. Для выбора нового вида фигуры используйте выпадающий список (ComboBox), а для изменения цвета фигуры или окна (формы) используйте стандартное диалоговое окно выбора цвета, вызываемое кнопками Цвет фигуры и Цвет формы.

Что нужно сделать пользователю (программисту) для того чтобы получить возможность «в режиме выполнения» изменять цвет какого-либо элемента или всего окна (формы)? Для этого достаточно выполнить всего лишь несколько действий. Убедитесь сами.

Для изменения цвета окна просто выберите компонент ColorDialog из палитры компонентов (она находится на страничке Dialogs) и поместите его на форму. Кроме того, поместите на форму обычную кнопку (компонент Button находится на страничке Standard). Для удобства чтения с помощью Object Inspector измените имя компонента (свойство Name) с Button1 (которое дается по умолчанию) на FormColor, а его заголовок (свойство Caption) — на Цвет формы. Дважды щелкните по кнопке Цвет формы — Delphi сгенерирует заготовку метода, который выглядит следующим образом:

procedure TForm1.FormColorClick(Sender: TObject);

begin

end;

Теперь введите две простые строчки кода:

procedure TForm1.FormColorClick(Sender: TObject);

begin

if ColorDialog1.Execute then

Form1.Color := ColorDialog1.Color;

end;

Диалоговое окно Color дает возможность изменить цвет «во время выполнения».

Данный код во время выполнения при нажатии кнопки Цвет формы вызывает стандартное диалоговое окно выбора цвета. Если в этом диалоговом окне вы щелкните кнопку OK, выполнится следующая строка:

Form1.Color:=ColorDialog1.Color;

Этот код установит свойство Color формы Form1 в цвет, который был выбран с помощью диалогового окна ColorDialog1. Это очень просто!

Та же самая техника может использоваться для изменения цвета фигуры (компонент Shape, объект TShape). Все, что вам нужно сделать — это поместить на форму другую кнопку, изменить (при желании) ее имя на ShapeColor, а заголовок — на Цвет Фигуры, дважды щелкнуть по ней мышкой и создать метод аналогичный следующему:

procedure TForm1.ShapeColorClick(Sender: TObject);

begin

if ColorDialog1.Execute then

Shape1.Brush.Color := ColorDialog1.Color;

end;

Что может быть проще?!

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

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

Весь механизм Windows-сообщений, используемый при взаимодействии компонент во время выполнения, оказывается скрытым от программиста, делая процесс создания программ наиболее легким. Сложное программирование в среде Windows становится доступным «широким» массам программистов. Например, программирование изменения размера фигуры с помощью полос прокрутки, требовавшее в «чистом» Windows сложной обработки сообщений в конструкции типа case, в Delphi сводится к написанию одной-единственной строчки кода.

Для начала, поместите два компонента ScrollBar на форму (находится на страничке Standard) и установите свойство Kind первого компонента в sbHorizontal, а второго — в sbVertical. Переключитесь на страничку Events в Инспекторе Объектов и создайте заготовки метода для отклика на событие OnChange для каждой полосы прокрутки. Напишите в каждом из методов по одной строчке следующим образом:

procedure TForm1.ScrollBar1Change(Sender: TObject);

begin

Shape1.Width := ScrollBar1.Position * 3;

end;

procedure TForm1.ScrollBar2Change(Sender: TObject);

begin

Shape1.Height := ScrollBar2.Position * 2;

end;

Код, показанный здесь, устанавливает свойства Width и Height фигуры TShape в соответствие с положением «бегунка» на полосах прокрутки (сомножители 3 и 2 введены только для лучшего представления).

Последняя часть программы SHAPEDEM демонстрирует большие возможности языка Object Pascal, на основе которого построен Delphi. Вы можете ввести элементы в список компонента ComboBox как в режиме проектирования, так и при выполнении программы. При этом в режиме проектирования вы можете просто ввести нужные элементы в список Items, щелкнув маленькую кнопку с тремя точками в правой части строки Инспектора Объектов, показывающей данное свойство (Items).

Перед вами появится диалоговое окно текстового редактора (String List Editor), в котором вы и введете элементы. Список этих элементов совпадает со списком опций свойства Shape компонента Shape1 (Shape). Другими словами, если вы выделите компонент Shape1 на форме (просто щелкнув по нему) и посмотрите свойство Shape в Инспекторе Объектов, вы увидите список возможных видов фигур, которые может принимать данный компонент. Это как раз те самые виды фигур, которые мы перечисляли в списке у компонента ComboBox1.

Этот список вы можете найти в on-line справочнике по Delphi по контексту TShapeType. Или же, если вы заглянете в исходный код класса TShape, там увидите те же элементы, формирующие перечислимый тип TShapeType:

TShapeType = (stRectangle, stSquare, stRoundRect,

stRoundSquare, stEllipse, stCircle);

Итак, смысл всего сказанного в том, что за всеми объектами, которые вы видите в «дельфийской» программе, стоит некий код на Паскале, к которому вы имеете доступ при «прямом» программировании. Ничто не скрыто от вас. Это значит, что вы можете изменить поведение любой части вашей программы во время выполнения путем написания соответствующего кода.

В нашем конкретном случае, вам нужно написать только одну строчку кода, которая будет выполнена в качестве отклика на щелчок пользователем по выпадающему списку ComboBox1. Чтобы написать код этого отклика, в режиме проектирования выделите компонент ComboBox1 на форме (как всегда, просто щелкнув по нему левой кнопкой мыши), затем перейдите на страничку Events в Инспекторе Объектов. Дважды щелкните по пустому полю напротив события OnClick. В редакторе автоматически сгенерируется следующая заготовка метода:

procedure TForm1.ComboBox1Click(Sender: TObject);

begin

end;

Теперь вставьте одну строчку кода, чтобы метод выглядел следующим образом:

procedure TForm1.ComboBox1Click(Sender: TObject);

begin

Shape1.Shape := TShapeType(ComboBox1.ItemIndex);

end;

Эта строчка кода устанавливает свойство Shape компонента Shape1 в вид, который пользователь выберет в выпадающем списке. Этот код работает благодаря соответствию между порядковыми членами перечислимого типа и числовыми значениями различных элементов в ComboBox. Другими словами, первый элемент перечислимого типа имеет значение 0, что соответствует первому элементу, показанному в ComboBox. Давайте рассмотрим этот подход несколько подробней.

Если вы рассмотрите декларацию перечислимого типа TShapeType, вы увидите, что первый его элемент называется stRectangle. По определению, компилятор назначает этому элементу порядковый номер 0. Следующему по порядку элементу назначается номер 1. Таким образом, слова stRectangle, stSquare, в действительности, просто символизируют порядковые номера в данном перечислимом типе. На элементы в списке ComboBox также можно сослаться по их порядковому номеру, начиная с 0. Именно поэтому так важно (в данном случае) вводить указанные строки в строгом соответствии с декларацией типа TShapeType. Таким образом, используя преобразование типа

TShapeType(ComboBox1. ItemIndex)

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

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

Программа SHAPEDEM проста в написании и в освоении. Однако при изменении пользователем размера окна она будет выглядеть «некрасиво». Давайте изменим ее таким образом, чтобы программа сама обрабатывала изменение размера окна, а заодно изучим компонент меню. Для достижения этих целей сделаем следующее:

u Кнопки и выпадающий список уберем с экрана и вместо них поместим на форму компонент меню (MainMenu);

u «Заставим» полосы прокрутки изменять свое положение в зависимости от размера окна;

u «Заставим» свойство Position полос прокрутки изменяться, чтобы правильно отражать размер формы.

Программа SHAPDEM2 имеет возможность реагировать на изменение пользователем размера окна.

Листинг B: Программа SHAPDEM2 включает метод FormOnResize

Представлен главный модуль.

unit Main;

interface

uses

WinTypes, WinProcs, Classes, Graphics, Forms, Controls, ColorDlg, StdCtrls, Menus, Dialogs, ExtCtrls;

type

TForm1 = class(TForm)

Shape1: TShape;

ColorDialog1: TColorDialog;

ScrollBar1: TScrollBar;

ScrollBar2: TScrollBar;

MainMenu1: TMainMenu;

Shapes1: TMenuItem;

ShapeColor1: TMenuItem;

FormColor1: TMenuItem;

Shapes2: TMenuItem;

Rectangle1: TMenuItem;

Square1: TMenuItem;

RoundRect1: TMenuItem;

RoundSquare1: TMenuItem;

Ellipes1: TMenuItem;

Circle1: TMenuItem;

Exit1: TMenuItem;

procedure NewShapeClick(Sender: TObject);

procedure ShapeColorClick(Sender: TObject);

procedure FormColorClick(Sender: TObject);

procedure ScrollBar2Change(Sender: TObject);

procedure ScrollBar1Change(Sender: TObject);

procedure FormResize(Sender: TObject);

procedure Exit1Click(Sender: TObject);

private

{ Private declarations }

public

{ Public declarations }

end;

var

Form1: TForm1;

implementation

{$R *.DFM}

procedure TForm1.NewShapeClick(Sender: TObject);

begin

Shape1.Shape := TShapeType((Sender as TMenuItem).Tag);

end;

procedure TForm1.ShapeColorClick(Sender: TObject);

begin

if ColorDialog1.Execute then

Shape1.Brush.Color := ColorDialog1.Color;

end;

procedure TForm1.FormColorClick(Sender: TObject);

begin

if ColorDialog1.Execute then

Form1.Color := ColorDialog1.Color;

end;

procedure TForm1.ScrollBar2Change(Sender: TObject);

begin

Shape1.Height := ScrollBar2.Position;

end;

procedure TForm1.ScrollBar1Change(Sender: TObject);

begin

Shape1.Width := ScrollBar1.Position;

end;

procedure TForm1.FormResize(Sender: TObject);

var

Menu,

Caption,

Frame: Integer;

begin

Caption := GetSystemMetrics(sm_cyCaption);

Frame := GetSystemMetrics(sm_cxFrame) * 2;

Menu := GetSystemMetrics(sm_cyMenu);

Scrollbar1.Max := Width;

Scrollbar2.Max := Height;

Scrollbar2.Left := Width — Frame — Scrollbar2.Width;

Scrollbar1.Top := Height — ScrollBar2.Width — Frame — Caption — Menu;

Scrollbar1.Width := Width — Scrollbar2.Width — Frame;

Scrollbar2.Height := Height — Frame — Caption — Menu — Scrollbar1.Height;

end;

procedure TForm1.Exit1Click(Sender: TObject);

begin

Close;

end;

end;

Главное меню для программы создается с помощью компонента MainMenu (находится на страничке Standard палитры компонентов). Поместив его на форму, дважды щелкните по нему мышкой — откроется редактор меню, в котором вы сможете ввести нужные вам названия пунктов меню и, при желании, изменить их имена (задаваемые Delphi по умолчанию) для удобочитаемости. Создадим меню программы SHAPEDEM2 с тремя главными пунктами: Цвета, Фигуры, Выход.

Для первого пункта создадим следующие подпункты:

u Цвет фигуры.

u Цвет окна.

Для второго:

u Прямоугольник.

u Квадрат.

u Закругленный прямоугольник.

u Закругленный квадрат.

u Эллипс.

u Окружность.

Третий пункт меню не будет содержать никаких подпунктов.

После создания всех пунктов и подпунктов меню для работы программы SHAPEDEM2 нужно назначить номера для каждого из подпунктов меню, связанных с типом фигуры. Для этого воспользуемся свойством Tag, имеющимся у каждого пункта меню. Свойство Tag (типа Integer) специально введено в каждый компонент Delphi с тем, чтобы программисты могли использовать его по своему усмотрению. Назначим 0 свойству Tag пункта Прямоугольник, 1 — пункту Квадрат, 2 — пункту Закругленный прямоугольник.

Два метода, созданные для подпунктов изменения цвета аналогичны тем, которые были в программе SHAPEDEM:

procedure TForm1.ShapeColorClick(Sender: TObject);

begin

if ColorDialog1.Execute then

Shape1.Brush.Color := ColorDialog1.Color;

end;

procedure TForm1.FormColorClick(Sender: TObject);

begin

if ColorDialog1.Execute then

Form1.Color := ColorDialog1.Color;

end;

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

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

procedure TForm1.NewShapeClick(Sender: TObject);

begin

Shape1.Shape := TShapeType((Sender as TMenuItem).Tag);

end;

Этот код «работает» правильно благодаря тому, что перечислимый тип TShapeType в качестве начального имеет значение 0 и в свойство Tag подпунктов меню мы также записали порядковые номера, начинающиеся с нуля.

Отметим, что мы использовали оператор as, который позволяет надежно преобразовывать типы из одного в другой: в частности, преобразовать параметр Sender (имеющий общий тип TObject) в тип TMenuItem. Как правило, параметр Sender в Delphi — это управляющий элемент, посылающий сообщения функции, в которой он фигурирует. В данном случае, Sender является пунктом меню, и, следовательно, вы можете работать с этим параметром как если бы он был декларирован с типом TMenuItem.

Главная причина использования оператора as состоит в том, что он обеспечивает очень ясный синтаксис, даже если вы проводите сложное двухуровневое преобразование типов. Более того, оператор as обеспечивает проверку преобразования в режиме выполнения программы. Когда вы используете оператор as, вы можете быть уверены в том, что преобразование Sender в TMenuItem реально будет произведено лишь в том случае, если Sender действительно имеет тип TMenuItem.

Две полосы прокрутки в программе SHAPEDEM2 всегда будут располагаться возле границ окна, независимо от его размеров. Выполнение этих действий требует от вас написания несколько более сложного программирования, чем было ранее. Как было указано ранее, Delphi, хотя и скрывает от программиста детали Windows-программирования, однако не запрещает обращаться к функциям Windows API (прикладного пользовательского интерфейса). Таким образом, Delphi поддерживает низкоуровневое программирование на уровне Windows API. Короче говоря, если вам нужно углубиться в дебри низкоуровневого программирования — пожалуйста!

procedure TForm1.FormResize(Sender: TObject);

var

Menu,

Caption,

Frame: Integer;

begin

Caption := GetSystemMetrics(sm_cyCaption);

Frame := GetSystemMetrics(sm_cxFrame) * 2;

Menu := GetSystemMetrics(sm_cyMenu);

Scrollbar1.Max := Width;

Scrollbar2.Max := Height;

Scrollbar2.Left := Width — Frame — Scrollbar2.Width;

Scrollbar2.Height := Height — Frame — Caption — Menu;

Scrollbar1.Top := Height — Scrollbar2.Width — Frame — Caption — Menu;

Scrollbar1.Width := Width — Scrollbar2.Width — Frame;

end;

Код, показанный здесь, является реакцией на событие OnResize. Это событие перечислено среди других на страничке Events Инспектора Объектов в состоянии, когда выбрана форма (окно). Как вы можете ожидать, событие (сообщение) OnResize посылается форме (окну) каждый раз, когда пользователь «захватывает» мышкой за какой-либо край окна и делает размер окна большим или меньшим. Однако, это же сообщение (событие) посылается окну и тогда, когда происходит максимизация окна (но не минимизация).

Первое, что делается в данном методе — запрашиваются системные параметры, определяющие размеры заголовка окна, огибающей его рамки и меню. Эта информация «добывается» путем вызова функции GetSystemMetrics, являющейся частью Windows API. Функции GetSystemMetrics передается один аргумент в виде константы, определяющей вид запрашиваемой информации. Например, если вы передадите функции константу sm_cyCaption, получите в качестве результата высоту заголовка окна (в пикселах). Полный список этих констант имеется в on-line справочнике Delphi (Help ê Windows API ê Alphabetical functions ê User functions ê GetSystemMetrics), здесь же мы приведем небольшую выдержку из справочника:

u SM_CXBORDER — Ширина огибающей окно рамки, размер которой не может быть изменен.

u SM_CYBORDER — Высота огибающей окно рамки, размер которой не может быть изменен.

u SM_CYCAPTION — Высота заголовка окна, включая высоту огибающей окно рамки, размер которой не может быть изменен (SM_CYBORDER).

u SM_CXCURSOR — Ширина курсора.

u SM_CYCURSOR — Высота курсора.

u SM_CXFRAME — Ширина огибающей окно рамки, размер которой может быть изменен.

u SM_CYFRAME — Высота огибающей окно рамки, размер которой может быть изменен.

u SM_CXFULLSCREEN — Ширина клиентской части для полноэкранного окна.

u SM_CYFULLSCREEN — Высота клиентской части для полноэкранного окна (эквивалентна высоте экрана за вычетом высоты заголовка окна).

u SM_CXICON — Ширина иконки.

u SM_CYICON — Высота иконки.

u SM_CYMENU — Высота полосы меню в одну строку. Это высота меню за вычетом высоты огибающей окно рамки, размер которой не может быть изменен (SM_CYBORDER).

u SM_CXMIN — Минимальная ширина окна.

u SM_CYMIN — Минимальная высота окна.

u SM_CXSCREEN — Ширина экрана.

u SM_CYSCREEN — Высота экрана.

u SM_MOUSEPRESENT — Не 0, если мышь установлена.

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

Scrollbar1.Max := Width;

Scrollbar2.Max := Height;

Scrollbar2.Left := Width — Frame — Scrollbar2.Width;

Scrollbar2.Height := Height — Frame — Caption — Menu;

Scrollbar1.Top :=

Height — Scrollbar2.Width — Frame — Caption — Menu;

Scrollbar1.Width := Width — Scrollbar2.Width — Frame;

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

В программе SHAPEDEM свойство Max каждой полосы прокрутки оставалось равным значению по умолчанию — 100; это означало, что после того как бегунок полосы прокрутки пройдет все доступное расстояние (как для вертикальной, так и для горизонтальной полосы прокрутки), свойство Position будет установлено в 100. Если бегунок возвращался к началу, свойство Position устанавливалось равным свойству Min, которое, по умолчанию, 0.

В программе SHAPEDEM2 вы можете изменять значения свойств Min и Max так, чтобы диапазон значений Position полос прокрутки отражал текущий размер окна (формы), даже при изменении формой своего размера в режиме выполнения. Здесь приведены соответствующие строки из метода FormResize.

procedure TForm1.FormResize(Sender: TObject);

begin

...

Scrollbar1.Max := Width;

Scrollbar2.Max := Height;

...

end;

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

procedure TForm1.Scrollbar2Change (Sender: TObject);

begin

Shape1.Height := Scrollbar2.Position;

end;

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

В конце хотелось бы сделать одно маленькое замечание. Как вы, возможно, уже заметили, каждая форма, по умолчанию, имеет две полосы прокрутки (HorzScrollbar и VertScrollbar), которые появляются автоматически всякий раз, когда размер формы становится меньше, чем область, занимаемая управляющими элементами, расположенными на этой форме. Иногда эти полосы прокрутки могут быть очень полезными, но в нашей ситуации они сделают совсем не то, что хотелось бы. Поэтому, для надежности, вы можете установить их вложенные свойства Visible в False.

Глава 38.
Методы

Создание методов с помощью визуальных средств

Синтаксический «скелет» метода может быть сгенерирован с помощью визуальных средств. Для этого, напомним, нужно в Инспекторе Объектов дважды щелкнуть мышкой на пустой строчке напротив названия интересующего вас события в требуемом компоненте. Заметим, если эта строчка не пуста, то двойной щелчок на ней просто переместит вас в окне Редактора Кода в то место, где находится данный метод.

Для более глубокого понимания дальнейшего изложения кратко остановимся на концепции объектно-ориентированного программирования. Для начала определим базовое понятие объектно-ориентированного программирования — класс.

Класс — это категория объектов, обладающих одинаковыми свойствами и поведением. При этом объект представляет собой просто экземпляр какого-либо класса. Например, в Delphi тип форма (окно) является классом, а переменная этого типа — объектом. Метод — это процедура, которая определена как часть класса и инкапсулирована (содержится) в нем. Методы манипулируют полями и свойствами классов (хотя могут работать и с любыми переменными) и имеют автоматический доступ к любым полям и методам своего класса. Доступ к полям и методам других классов зависит от уровня «защищенности» этих полей и методов. Пока же для нас важно то, что методы можно создавать как визуальными средствами, так и путем написания кода вручную.

Давайте рассмотрим процесс создания программы CONTROL1, которая поможет нам изучить технику написания методов в Delphi.

Для создания программы CONTROL1 поместите с помощью мышки компонент Edit (находится на страничке Standard Палитры Компонентов) на форму.

Чтобы создать метод, просто дважды щелкните справа от слова OnDblClick.

Теперь перейдите в Object Inspector, выберите страничку Events и дважды щелкните в пустой строчке напротив события OnDblClick. После этого в активизировавшемся окне Редактора вы увидите сгенерированный «скелет» метода Edit1DblClick, являющегося реакцией на событие OnDblClick:

procedure TForm1.Edit1DblClick(Sender: TObject);

begin

end;

После генерации процедуры вы можете оставить ее имя таким, каким «установил» Delphi, или изменить его на любое другое (для этого просто введите новое имя в указанной выше строке Инспектора Объектов справа от требуемого события и нажмите Enter).

Теперь в окне Редактора Кода введите смысловую часть метода:

procedure TForm1.Edit1DblClick(Sender: TObject);

begin

Edit1.Text:= 'Вы дважды щелкнули в строке редактирования';

end;

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

Содержимое управляющего элемента TEdit изменяется после двойного щелчка по нему.

Листинг А: Программа CONTROL1: создание и использование методов в Delphi

program Control1;

uses

Forms,

Main in 'MAIN.PAS' {Form1};

begin

Application.CreateForm(TForm1, Form1);

Application.Run;

end;

Листинг B: Головной модуль программы CONTROL1

unit Main;

interface

uses

WinTypes, WinProcs,

Classes, Graphics, Controls,

Printers, Menus, Forms, StdCtrls;

type

TForm1 = class(TForm)

Edit1: TEdit;

procedure Edit1DblClick(Sender: TObject);

end;

var

Form1: TForm1;

implementation

{$R *.DFM}

procedure TForm1.Edit1DblClick(Sender: TObject);

begin

Edit1.Text := 'Вы дважды щелкнули в строке редактирования';

end;

end;

После того, как ваша программа загрузится в память, выполняются две строчки кода в CONTROL1.DPR, автоматически сгенерированные компилятором:

Application.CreateForm(TForm1, Form1);

Application.Run;

Первая строка запрашивает память у операционной системы и создает там объект Form1, являющийся экземпляром класса TForm1. Вторая строка указывает объекту Application, «по умолчанию» декларированному в Delphi, чтобы он запустил на выполнение главную форму приложения.

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

Итак, большинство кода Delphi генерирует автоматически. В большинстве приложений все, что вам остается сделать — это вставить одну или несколько строк кода, как в методе Edit1DblClick:

Edit1.Text := 'Вы дважды щелкнули в строке редактирования';

Хотя внешний интерфейс программы CONTROL1 достаточно прост, программа имеет строгую внутреннюю структуру. Каждая программа в Delphi состоит из файла проекта, имеющего расширение .DPR и одного или нескольких модулей, имеющих расширение .PAS. Модуль, в котором содержится главная форма проекта, называется головным. Указанием компилятору о связях между модулями является предложение Uses, которое определяет зависимость модулей.

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

u Заголовок.

u Секция Interface.

u Секция Implementation.

Таким образом, «скелет» модуля выглядит следующим образом:

unit Main;

{Заголовок модуля}

interface

{Секция Interface}

implementation

{Секция Implementation}

end

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

Передача параметров

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

u создавать свои собственные процедуры;

u добавлять процедуру в класс, формируя метод класса;

u вызывать одну процедуру из другой.

После нажатия кнопки Вызов процедуры WriteAll строка из управляющего элемента EditSource скопируется в шесть управляющих элементов — строк редактирования.

Далее мы не будем подробно останавливаться на том, как размещать компоненты на форме — считаем, что это вы уже умеете. После того как вы разместили на форме семь компонентов Edit, переименуйте с помощью Инспектора Объектов седьмой компонент (Edit7) в EditSource. Положите на форму компонент Button, и в Object Inspector измените его заголовок (свойство Caption) на Вызов процедуры WriteAll (естественно, вы можете заменить его шрифт, цвет).

После завершения проектирования формы класс TForm1 будет выглядеть следующим образом:

TForm1 = class(TForm)

Edit1: TEdit;

Edit2: TEdit;

Edit3: TEdit;

Edit4: TEdit;

Edit5: TEdit;

Edit6: TEdit;

EditSource: TEdit;

Button1: TButton;

end;

Следующий шаг состоит в добавлении метода, вызываемого по нажатию пользователем кнопки Button1. Это можно сделать двумя способами:

u Перейти в Инспекторе Объектов на страничку Events (предварительно выбрав компонент Button1 на форме), выбрать слово OnClick и дважды щелкнуть мышкой на пустой строчке справа от него.

u Просто дважды щелкнуть на компоненте Button1 на форме.

Delphi сгенерирует следующую «заготовку»:

procedure TForm1.Button1Click(Sender: TObject);

begin

end;

Цель программы — научить вас писать процедуры и передавать в них параметры. В частности, программа реагирует на нажатие кнопки Button1 путем вызова процедуры WriteAll и передачи ей в качестве параметра содержимого строки редактирования EditSource (EditSource.Text).

procedure TForm1.Button1Click(Sender: TObject);

begin

WriteAll(EditSource.Text);

end;

Важно понять, что объект EditSource является экземпляром класса TEdit и, следовательно, имеет свойство Text, содержащее набранный в строке редактирования текст. Как вы уже, наверное, успели заметить, по умолчанию свойство Text содержит значение, совпадающее со значением имени компонента (Name) — в данном случае EditSource. Свойство Text вы, естественно, можете редактировать как в режиме проектирования, так и во время выполнения.

Текст, который должен быть отображен в шести строках редактирования, передается процедуре WriteAll как параметр. Чтобы передать параметр процедуре, просто напишите имя этой процедуры и заключите передаваемый параметр (параметры) в скобки — вот так:

WriteAll(EditSource.Text);

Заголовок этой процедуры выглядит следующим образом:

procedure TForm1.WriteAll(NewString: String);

где указано, что передаваемый процедуре параметр NewString должен иметь тип String.

Вспомним, что задача процедуры WriteAll состоит в копировании содержимого строки редактирования EditSource в шесть других строк редактирования Edit1–Edit6. Поэтому процедура должна выглядеть следующим образом:

procedure TForm1.WriteAll(NewString: String);

begin

Edit1.Text := NewString;

Edit2.Text := NewString;

Edit3.Text := NewString;

Edit4.Text := NewString;

Edit5.Text := NewString;

Edit6.Text := NewString;

end;

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

Возвратимся еще раз к заголовку процедуры. Заголовок состоит из пяти частей:

procedure TForm1.WriteAll(NewString: String);

Первая часть — зарезервированное слово «procedure»; пятая часть — концевая точка с запятой «;». Обе эти части служат определенным синтаксическим целям, а именно: первая информирует компилятор о том, что определен синтаксический блок «процедура», а вторая указывает на окончание заголовка (собственно говоря, все операторы в Delphi должны заканчиваться точкой с запятой). Вторая часть заголовка — слово «TForm1», которое квалифицирует то обстоятельство, что данная процедура является методом класса TForm1.

Третья часть заголовка — имя процедуры. Вы можете выбрать его любым, по вашему усмотрению. В данном случае мы назвали процедуру «WriteAll».

Четвертая часть заголовка — параметр. Параметр декларируется внутри скобок и, в свою очередь, состоит из двух частей. Первая часть — имя параметра, вторая — его тип. Эти части разделены двоеточием. Если вы описываете в процедуре более чем один параметр, нужно разделить их точкой с запятой, например:

procedure Example(Param1: String; Param2: String);

После того как вы создали «вручную» заголовок процедуры, являющейся методом класса, вы должны включить его в декларацию класса, например, путем копирования (еще раз напомним, что для методов, являющихся откликами на дельфийские события, данное включение производится автоматически):

TForm1 = class(TForm)

Edit1: TEdit;

Edit2: TEdit;

Edit3: TEdit;

Edit4: TEdit;

Edit5: TEdit;

Edit6: TEdit;

EditSource: TEdit;

Button1: TButton;

procedure Button1Click(Sender: TObject);

procedure WriteAll(NewString: String);

end;

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

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

Листинг C: Исходный код головного модуля программы PARAMS: использование строки редактирования и способы передачи параметов

Unit Main;

interface

uses

WinTypes, WinProcs, Classes,

Graphics, Controls,

Printers, Forms, StdCtrls;

type

TForm1 = class(TForm)

Edit1: TEdit;

Edit2: TEdit;

Edit3: TEdit;

Edit4: TEdit;

Edit5: TEdit;

Edit6: TEdit;

EditSource: TEdit;

Button1: TButton;

procedure Button1Click(Sender: TObject);

procedure WriteAll(NewString: String);

end;

var

Form1: TForm1;

implementation

{$R *.DFM}

procedure TForm1.WriteAll(NewString: String);

begin

Edit1.Text := NewString;

Edit2.Text := NewString;

Edit3.Text := NewString;

Edit4.Text := NewString;

Edit5.Text := NewString;

Edit6.Text := NewString;

end;

procedure TForm1.Button1Click(Sender: TObject);

begin

WriteAll(EditSource.Text);

end;

end;

При экспериментах с программой PARAMS вы можете попробовать изменить имена процедур и параметров. Однако, следует помнить, что ряд слов в Delphi являются зарезервированными, и употреблять их в идентификаторах (именах процедур, функций, переменных, типов, констант) не разрешается — компилятор сразу же обнаружит ошибку. К ним относятся такие слова, как procedure, string, begin, end; полный же список их приведен в on-line справочнике Delphi.

Не старайтесь запомнить сразу все зарезервированные слова — компилятор «напомнит» вам о неправильном их использовании выдачей сообщения типа Identifier expected. (Ожидался идентификатор, а обнаружено зарезервированное слово).

Более сложные методы и управляющие элементы

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

В программе CONTROL1 был сгенерирован метод, являющийся откликом на событие OnClick строки редактирования Edit1. Аналогично, можно сгенерировать метод, являющийся реакцией на событие OnDblClick. В программе CONTROL2 расширен список находящихся на форме компонентов и для многих из них определены события OnClick и OnDblClick. Для исследования вы можете просто скопировать файлы проекта CONTROL1 в новую директорию CONTROL2, изменить имя проекта на CONTROL2. DPR (в этом файле после ключевого слова program также должно стоять название CONTROL2) и добавить компоненты Label, GroupBox, CheckBox, RadioButton, Button на форму (эти компоненты находятся на страничке Standard Палитры Компонентов).

Заметим, что вы должны «положить» компонент GroupBox на форму до того, как вы добавите компоненты CheckBox и RadioButton, которые, в нашем примере, должны быть «внутри» группового элемента. Иначе, объекты CheckBox1, CheckBox2, RadioButton1 и RadioButton2 будут «думать», что их родителем является форма Form1 и при перемещении GroupBox1 по форме не будут перемещаться вместе с ней. Таким образом, во избежание проблем, компонент, который должен быть «родителем» других компонент (Panel, GroupBox, Notebook, StringGrid, ScrollBox), нужно помещать на форму до помещения на нее его «детей». Если вы все же забыли об этом и поместили «родителя» (например, GroupBox) на форму после размещения на ней его «потомков» (например, CheckBox и RadioButton) — не отчаивайтесь! Отметьте все необходимые объекты и скопируйте (с удалением) их в буфер обмена с помощью команд меню Edit ê Cut. После этого отметьте на форме нужный вам объект (GroupBox1) и выполните команду меню Edit ê Paste. После этого все выделенные вами ранее объекты будут помещены на форму, и их «родителем» будет GroupBox1. Описанный механизм является стандартным и может быть использован для всех видимых компонент.

Выберите объект Label1. Создайте для него метод, являющийся откликом на событие OnDblClick. Введите в метод одну строчку, например:

procedure TForm1.Label1DblClick(Sender: TObject);

begin

Edit1.Text := 'Двойной щелчок на Label1';

end;

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

Теперь закройте приложение и возвратитесь в режим проектирования. Добавьте обработчики событий OnClick и OnDblClick для каждого объекта, имеющегося на форме. Текст вашего головного модуля будет выглядеть следующим образом:

Листинг D: Головной модуль программы CONTROL2

Unit Main;

interface

uses

WinTypes, WinProcs, Classes,

Graphics, Controls, StdCtrls,

Printers, Menus, Forms;

type

TForm1 = class(TForm)

Label1: TLabel;

Edit1: TEdit;

Button1: TButton;

GroupBox1: TGroupBox;

CheckBox1: TCheckBox;

CheckBox2: TCheckBox;

RadioButton1: TRadioButton;

RadioButton2: TRadioButton;

procedure Edit1DblClick(Sender: TObject);

procedure Label1DblClick(Sender: TObject);

procedure CheckBox1Click(Sender: TObject);

procedure CheckBox2Click(Sender: TObject);

procedure RadioButton1Click(Sender: TObject);

procedure RadioButton2Click(Sender: TObject);

procedure Button1Click(Sender: TObject);

end;

var

Form1: TForm1;

implementation

{$R *.DFM}

procedure TForm1.Edit1DblClick(Sender: TObject);

begin

Edit1.Text := 'Двойной щелчок на Edit1';

end;

procedure TForm1.Label1DblClick(Sender: TObject);

begin

Edit1.Text := 'Двойной щелчок на Label1';

end;

procedure TForm1.CheckBox1Click(Sender: TObject);

begin

Edit1.Text := 'Щелчок на CheckBox1';

end;

procedure TForm1.CheckBox2Click(Sender: TObject);

begin

Edit1.Text := 'Щелчок на CheckBox2';

end;

procedure TForm1.RadioButton1Click(Sender: TObject);

begin

Edit1.Text := 'Щелчок на RadioButton1';

end;

procedure TForm1.RadioButton2Click(Sender: TObject);

begin

Edit1.Text := 'Щелчок на Radiobutton2';

end;

procedure TForm1.Button1Click(Sender: TObject);

begin

Edit1.Text := 'Щелчок на Button1';

end;

end;

Эта программа служит двум целям:

u Она показывает, как создавать процедуры (методы) и как «наполнять» их содержательной «начинкой».

u Она демонстрирует технику работы с управляющими элементами Windows.

Информация периода выполнения

Как вы, наверное, заметили, методы программы CONTROL2, являющиеся откликами на события OnClick и OnDblClick, во многом похожи друг на друга.

Открытость среды Delphi позволяет получать и оперировать информацией особого рода, называемой информацией периода выполнения (RTTI — run-time type information). Эта информация организована в виде нескольких уровней.

Верхний уровень RTTI представлен как средство проверки и приведения типов с использованием ключевых слов is и as.

Ключевое слово is дает программисту возможность определить, имеет ли данный объект требуемый тип или является одним из наследников данного типа, например, таким образом:

if MyObject is TSomeObj then ...

Имеется возможность использовать RTTI и для процесса приведения объектного типа, используя ключевое слово as:

if MyObject is TSomeObj then

(MyObject as TSomeObj).MyField:=...

что эквивалентно:

TSomeObj(MyObject).MyField:=...

Средний уровень RTTI использует методы объектов и классов для подмены операций as и is на этапе компиляции. В основном, все эти методы заложены в базовом классе TObject, от которого наследуются все классы библиотеки компонент VCL. Для любого потомка TObject доступны, в числе прочих, следующие информационные методы:

u ClassName — возвращает имя класса, экземпляром которого является объект.

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

u ClassParent — возвращает тип родителя объекта.

u ClassType — возвращает тип самого объекта.

u InheritsFrom — возвращает логическое значение, определяющее, является ли объект потомком указанного класса.

u InstanceSize — возвращает размер объекта в байтах.

Эти методы могут использоваться в вашем коде напрямую. Нижний уровень RTTI определяется в дельфийском модуле TypInfo и представляет особый интерес для разработчиков компонент. Через него можно получить доступ к внутренним структурам Delphi, в том числе, к ресурсам форм, инспектору объектов. Итак, доступ к информации периода выполнения в Delphi позволяет динамически получать как имя объекта, находящегося на форме, так и название класса, которому он принадлежит (и еще много другой полезной информации. Для этого используется свойство Name, имеющееся у любого класса-наследника TComponent (а таковыми являются все компоненты, входящие в дельфийскую библиотеку VCL), и метод ClassName, доступный для любого потомка класса базового TObject. А поскольку класс TComponent, в свою очередь, является наследником класса TObject, то он доступен для всех компонент из библиотеки VCL.

Вернувшись к нашим примерам, мы можем заменить целую «кучу» методов двумя, реализующими события OnClick и OnDblClick для всех объектов сразу. Для этого можно скопировать все файлы из CONTROL2 в новый директорий CONTROL3. Создадим стандартным образом методы ControlDblClick и ControlClick для какого-либо объекта (например, для Label1). Введем в них следующие строки:

procedure TForm1.ControlDblClick(Sender: TObject);

begin

Edit1.Text := 'Двойной щелчок на'+

(Sender as TComponent).Name +

'(класс' + Sender.ClassName +')';

end;

procedure TForm1.ControlClick(Sender: TObject);

begin

Edit1.Text := 'Щелчок на' +

(Sender as TComponent).Name +

'(класс' + Sender.ClassName +')';

end;

Теперь назначим данные методы всем событиям OnClick и OnDblClick, имеющимся у расположенных на форме объектов. Мы видим, что размер программы существенно сократился, а функциональность ее значительно выросла. Программа CONTROL3 выводит информацию не только об имени объекта, но и о названии его класса (типа).

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

Глава 39.
Обработка исключительных ситуаций

Структурная обработка исключительных ситуаций

Структурная обработка исключительных ситуаций — это система, позволяющая программисту при возникновении ошибки (исключительной ситуации) связаться с кодом программы, подготовленным для обработки такой ошибки. Это выполняется с помощью языковых конструкций, которые как бы «охраняют» фрагмент кода программы и определяют обработчики ошибок, которые будут вызываться, если что-то пойдет не так в «охраняемом» участке кода. В данном случае понятие исключительной ситуации относится к языку и не нужно его путать с системными исключительными ситуациями (hardware exceptions), такими как General Protection Fault. Эти исключительные ситуации обычно используют прерывания и особые состояния «железа» для обработки критичной системной ошибки; исключительные ситуации в Delphi же независимы от «железа», не используют прерываний и используются для обработки ошибочных состояний, с которыми подпрограмма не готова иметь дело. Системные исключительные ситуации, конечно, могут быть перехвачены и преобразованы в языковые исключительные ситуации, но это только одно из применений языковых исключительных ситуаций.

При традиционной обработке ошибок, ошибки, обнаруженные в процедуре обычно передаются наружу (в вызывавшую процедуру) в виде возвращаемого значения функции, параметров или глобальных переменных (флажков). Каждая вызывающая процедура должна проверять результат вызова на наличие ошибки и выполнять соответствующие действия. Часто, это просто выход еще выше, в более верхнюю вызывающую процедуру: функция A вызывает B, B вызывает C, C обнаруживает ошибку и возвращает код ошибки в B, B проверяет возвращаемый код, видит, что возникла ошибка и возвращает код ошибки в A, A проверяет возвращаемый код и выдает сообщение об ошибке либо решает сделать что-нибудь еще, раз первая попытка не удалась.

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

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

Структурная обработка исключительной ситуации замещает ручную обработку ошибок автоматической, сгенерированной компилятором системой уведомления. В приведенном выше примере, процедура A установила бы «охрану» со связанным обработчиком ошибки на фрагмент кода, в котором вызывается B. B просто вызывает C. Когда C обнаруживает ошибку, то создает (raise) исключительную ситуацию. Специальный код, сгенерированный компилятором и встроенный в Run-Time Library (RTL) начинает поиск обработчика данной исключительной ситуации. При поиске «защищенного» участка кода используется информация, сохраненная в стеке. В процедурах C и B нет такого участка, а в A — есть. Если один из обработчиков ошибок, которые используются в A, подходит по типу для возникшей в C исключительной ситуации, то программа переходит на его выполнение. При этом, область стека, используемая в B и C, очищается; выполнение этих процедур прекращается.

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

Без проверки возвращаемого кода после каждого вызова подпрограммы, код программы должен быть более простым, а скомпилированный код — более быстрым. При наличии исключительных ситуаций подпрограмма B не должна содержать дополнительный код для проверки возвращаемого результата и передачи его в A. B ничего не должна делать для передачи исключительной ситуации, возникшей в C, в процедуру A — встроенная система обработки исключительных ситуаций делает всю работу.

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

Модель исключительных ситуаций

Модель исключительных ситуаций в Object Pascal является невозобновляемой (non-resumable). При возникновении исключительной ситуации вы уже не сможете вернуться в точку, где она возникла, для продолжения выполнения программы (это позволяет сделать возобновляемая (resumable) модель). Невозобновляемые исключительные ситуации разрушают стек, поскольку они сканируют его в поисках обработчика; в возобновляемой модели необходимо сохранять стек, состояние регистров процессора в точке возникновения ошибки и выполнять поиск обработчика и его выполнение в отдельном стеке. Возобновляемую систему обработки исключительных ситуаций гораздо труднее создать и применять, нежели невозобновляемую.

Синтаксис обработки исключительных ситуаций

Теперь, когда мы рассмотрели, что такое исключительные ситуации, давайте дадим ясную картину, как они применяются. Новое ключевое слово, добавленное в язык Object Pascal — try. Оно используется для обозначения первой части защищенного участка кода. Существует два типа защищенных участков:

u try..except

u try..finally

Первый тип используется для обработки исключительных ситуаций. Его синтаксис:

try

Statement 1;

Statement 2;

...

except

on Exception1 do Statement;

on Exception2 do Statement;

...

else

Statements; {default exception-handler}

end;

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

try

Statement1;

Statement2;

...

finally

Statements; { These statements always execute }

end;

Примеры обработки исключительных ситуаций

Ниже приведены процедуры A, B и C, обсуждавшиеся ранее, воплощенные в новом синтаксисе Object Pascal:

type

ESampleError = class(Exception);

var

ErrorCondition: Boolean;

procedure C;

begin

writeln('Enter C');

if (ErrorCondition) then

begin

writeln('Raising exception in C');

raise ESampleError.Create('Error!');

end;

writeln('Exit C');

end;

procedure B;

begin

writeln('enter B');

C;

writeln('exit B');

end;

procedure A;

begin

writeln('Enter A');

try

writeln('Enter A''s try block');

B;

writeln('After B call');

except

on ESampleError do

writeln('Inside A''s ESampleError handler');

on ESomethingElse do

writeln('Inside A''s ESomethingElse handler');

end;

writeln('Exit A');

end;

begin

writeln('begin main');

ErrorCondition := True;

A;

writeln('end main');

end;

При ErrorCondition = True программа выдаст:

begin main

Enter A

Enter A's try block

enter B

Enter C

Raising exception in C

Inside A's ESampleError handler

Exit A

end main

Возможно вас удивила декларация типа:

ESampleError =class

вместо

'=object';

это еще одно новое расширение языка. Delphi вводит новую модель объектов, доступную через декларацию типа '=class'.

Процедура C проверяет наличие ошибки (в нашем случае это значение глобальной переменной) и, если она есть (а это так), C вызывает (raise) исключительную ситуацию класса ESampleError.

Процедура A помещает часть кода в блок try..except. Первая часть этого блока содержит часть кода, аналогично конструкции begin..end. Эта часть кода завершается ключевым словом except, далее следует один или более обработчиков исключительных ситуаций on xxxx do yyyy, далее может быть включен необязательный блок else, вся конструкция заканчивается end;. В конструкции, назначающей определенную обработку для конкретной исключительной ситуации (on xxxx do yyyy), после резервного слова on указывается класс исключительной ситуации, а после do следует собственно код обработки данной ошибки. Если возникшая исключительная ситуация подходит по типу к указанному после on, то выполнение программы переходит сюда (на код после do). Исключительная ситуация подходит в том случае, если она того же класса, что указан в on, либо является его потомком. Например, в случае on EFileNotFound обрабатываться будет ситуация, когда файл не найден. А в случае on EFileIO — все ошибки при работе с файлами, в том числе и предыдущая ситуация. В блоке else обрабатываются все ошибки, не обработанные до этого.

Приведенные в примере процедуры содержат код (строка с writeln), который отображает путь выполнения программы. Когда C вызывает exception, программа сразу переходит на обработчик ошибок в процедуре A, игнорируя оставшуюся часть кода в процедурах B и C.

После того, как найден подходящий обработчик ошибки, поиск оканчивается. После выполнения кода обработчика, программа продолжает выполняться с оператора, стоящего после слова end блока try..except (в примере — writeln('Exit A')).

Конструкция try..except подходит, если известно, какой тип ошибок нужно обрабатывать в конкретной ситуации. Но что делать, если требуется выполнить некоторые действия в любом случае, произошла ошибка или нет? Это тот случай, когда понадобится конструкция try..finally.

Рассмотрим модифицированную процедуру B:

procedure NewB;

var

P: Pointer;

begin

writeln('enter B');

GetMem(P, 1000);

C;

FreeMem(P, 1000);

writeln('exit B');

end;

Если C вызывает исключительную ситуацию, то программа уже не возвращается в процедуру B. А что же с теми 1000 байтами памяти, захваченными в B? Строка FreeMem(P,1000) не выполнится и вы потеряете кусок памяти. Как это исправить? Нужно ненавязчиво включить процедуру B в процесс, например:

procedure NewB;

var

P: Pointer;

begin

writeln('enter NewB');

GetMem(P, 1000);

try

writeln('enter NewB''s try block');

C;

writeln('end of NewB''s try block');

finally

writeln('inside NewB''s finally block');

FreeMem(P, 1000);

end;

writeln('exit NewB');

end;

Если в A поместить вызов NewB вместо B, то программа выведет сообщения следующим образом:

begin main

Enter A

Enter A's try block

enter NewB

enter NewB's try block

Enter C

Raising exception in C

inside NewB's finally block

Inside A's ESampleError handler

Exit A

end main

Код в блоке finally выполнится при любой ошибке, возникшей в соответствующем блоке try. Он же выполнится и в том случае, если ошибки не возникло. В любом случае память будет освобождена. Если возникла ошибка, то сначала выполняется блок finally, затем начинается поиск подходящего обработчика. В штатной ситуации, после блока finally программа переходит на следующее предложение после блока.

Почему вызов GetMem не помещен внутрь блока try? Этот вызов может окончиться неудачно и вызвать exception EOutOfMemory. Если это произошло, то FreeMem попытается освободить память, которая не была распределена. Когда мы размещаем GetMem вне защищаемого участка, то предполагаем, что B сможет получить нужное количество памяти, а если нет, то более верхняя процедура получит уведомление EOutOfMemory.

А что, если требуется в B распределить 4 области памяти по схеме все-или-ничего? Если первые две попытки удались, а третья провалилась, то как освободить захваченную область памяти? Можно так:

procedure NewB;

var

p,q,r,s: Pointer;

begin

writeln('enter B');

P := nil;

Q := nil;

R := nil;

S := nil;

try

writeln('enter B''s try block');

GetMem(P, 1000);

GetMem(Q, 1000);

GetMem(R, 1000);

GetMem(S, 1000);

C;

writeln('end of B''s try block');

finally

writeln('inside B''s finally block');

if P <> nil then FreeMem(P, 1000);

if Q <> nil then FreeMem(Q, 1000);

if R <> nil then FreeMem(R, 1000);

if S <> nil then FreeMem(S, 1000);

end;

writeln('exit B');

end;

Установив сперва указатели в NIL, далее можно определить, успешно ли прошел вызов GetMem.

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

Вызов исключительной ситуации

В процедуре C из примера мы уже могли видеть, как должна поступать программа при обнаружении состояния ошибки — она вызывает исключительную ситуацию:

raise ESampleError.Create('Error!');

После ключевого слова raise следует код, аналогичный тому, что используется для создания нового экземпляра класса. Действительно, в момент вызова исключительной ситуации создается экземпляр указанного класса; данный экземпляр существует до момента окончания обработки исключительной ситуации и затем автоматически уничтожается. Вся информация, которую нужно сообщить в обработчик ошибки передается в объект через его конструктор в момент создания.

Почти все существующие классы исключительных ситуаций являются наследниками базового класса Exception и не содержат новых свойств или методов. Класс Exception имеет несколько конструкторов, какой из них конкретно использовать — зависит от задачи. Описание класса Exception можно найти в on-line Help.

Доступ к экземпляру объекта exception

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

Как уже говорилось, при вызове исключительной ситуации (raise) автоматически создается экземпляр соответствующего класса, который и содержит информацию об ошибке. Весь вопрос в том, как в обработчике данной ситуации получить доступ к этому объекту.

Рассмотрим модифицированную процедуру A в нашем примере:

procedure NewA;

begin

writeln('Enter A');

try

writeln('Enter A''s try block');

B;

writeln('After B call');

except

on E: ESampleError do writeln(E.Message);

on ESomethingElse do

writeln('Inside A''s ESomethingElse handler');

end;

writeln('Exit A');

end;

Здесь все изменения внесены в строку

on ESE: ESampleError do writeln(ESE.Message);

Пример демонстрирует еще одно новшество в языке Object Pascal — создание локальной переменной. В нашем примере локальной переменной является ESE — это тот самый экземпляр класса ESampleError, который был создан в процедуре C в момент вызова исключительного состояния. Переменная ESE доступна только внутри блока do. Свойство Message объекта ESE содержит сообщение, которое было передано в конструктор Create в процедуре C.

Есть еще один способ доступа к экземпляру exception — использовать функцию ExceptionObject:

on ESampleError do

writeln(ESampleError(ExceptionObject).Message);

Предопределенные обработчики исключительных ситуаций

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

u Exception — базовый класс-предок всех обработчиков исключительных ситуаций.

u EAbort — «скрытое» исключение. Используйте его тогда, когда хотите прервать тот или иной процесс с условием, что пользователь программы не должен видеть сообщения об ошибке. Для повышения удобства использования в модуле SysUtils предусмотрена процедура Abort, определенная, как:
procedure Abort;
begin
raise EAbort.CreateRes(SOperationAborted) at ReturnAddr;
end;

u EComponentError — вызывается в двух ситуациях:

u при попытке регистрации компоненты за пределами процедуры Register;

u когда имя компоненты не уникально или не допустимо.

u EConvertError — происходит в случае возникновения ошибки при выполнении функций StrToInt и StrToFloat, когда конвертация строки в соответствующий числовой тип невозможна.

u EInOutError — происходит при ошибках ввода/вывода при включенной директиве {$I+}.

u EIntError — предок исключений, случающихся при выполнении целочисленных операций.

u EDivByZero — вызывается в случае деления на ноль, как результат RunTime Error 200.

u EIntOverflow — вызывается при попытке выполнения операций, приводящих к переполнению целых переменных, как результат RunTime Error 215 при включенной директиве {$Q+}.

u ERangeError — вызывается при попытке обращения к элементам массива по индексу, выходящему за пределы массива, как результат RunTime Error 201 при включенной директиве {$R+}.

u EInvalidCast — происходит при попытке приведения переменных одного класса к другому классу, несовместимому с первым (например, приведение переменной типа TListBox к TMemo).

u EInvalidGraphic — вызывается при попытке передачи в LoadFromFile файла, несовместимого графического формата.

u EInvalidGraphicOperation — вызывается при попытке выполнения операций, неприменимых для данного графического формата (например, Resize для TIcon).

u EInvalidObject — реально нигде не используется, объявлен в Controls.pas.

u EInvalidOperation — вызывается при попытке отображения или обращения по Windows-обработчику (handle) контрольного элемента, не имеющего владельца (например, сразу после вызова MyControl:=TListBox.Create(...) происходит обращение к методу Refresh).

u EInvalidPointer — происходит при попытке освобождения уже освобожденного или еще неинициализированного указателя, при вызове Dispose(), FreeMem() или деструктора класса.

u EListError — вызывается при обращении к элементу наследника TList по индексу, выходящему за пределы допустимых значений (например, объект TStringList содержит только 10 строк, а происходит обращение к одиннадцатому).

u EMathError — предок исключений, случающихся при выполнении операций с плавающей точкой.

u EInvalidOp — происходит, когда математическому сопроцессору передается ошибочная инструкция. Такое исключение не будет до конца обработано, пока вы контролируете сопроцессор напрямую из ассемблерного кода.

u EOverflow — происходит как результат переполнения операций с плавающей точкой при слишком больших величинах. Соответствует RunTime Error 205.

u Underflow — происходит как результат переполнения операций с плавающей точкой при слишком малых величинах. Соответствует RunTime Error 206.

u EZeroDivide — вызывается в результате деления на ноль.

u EMenuError — вызывается в случае любых ошибок при работе с пунктами меню для компонент TMenu, TMenuItem, TPopupMenu и их наследников.

u EOutlineError — вызывается в случае любых ошибок при работе с TOutLine и любыми его наследниками.

u EOutOfMemory — происходит в случае вызовов New(), GetMem() или конструкторов классов при невозможности распределения памяти. Соответствует RunTime Error 203.

u EOutOfResources — происходит в том случае, когда невозможно выполнение запроса на выделение или заполнение тех или иных Windows ресурсов (например таких, как обработчики — handles).

u EParserError — вызывается когда Delphi не может произвести разбор и перевод текста описания формы в двоичный вид (часто происходит в случае исправления текста описания формы вручную в IDE Delphi).

u EPrinter — вызывается в случае любых ошибок при работе с принтером.

u EProcessorException — предок исключений, вызываемых в случае прерывания процессора — hardware breakpoint. Никогда не включается в DLL, может обрабатываться только в «цельном» приложении.

u EBreakpoint — вызывается в случае останова на точке прерывания при отладке в IDE Delphi. Среда Delphi обрабатывает это исключение самостоятельно.

u EFault — предок исключений, вызываемых в случае невозможности обработки процессором тех или иных операций.

u EGPFault — вызывается, когда происходит «общее нарушение защиты» — General Protection Fault. Соответствует RunTime Error 216.

u EInvalidOpCode — вызывается, когда процессор пытается выполнить недопустимые инструкции.

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

u EStackFault — происходит при ошибках работы со стеком, часто вследствие некорректных попыток доступа к стеку из фрагментов кода на ассемблере. Компиляция ваших программ со включенной проверкой работы со стеком {$S+} помогает отследить такого рода ошибки.

u ESingleStep — аналогично EBreakpoint, это исключение происходит при пошаговом выполнении приложения в IDE Delphi, которая сама его и обрабатывает.

u EPropertyError — вызывается в случае ошибок в редакторах свойств, встраиваемых в IDE Delphi. Имеет большое значение для написания надежных property editors. Определен в модуле DsgnIntf.pas.

u EResNotFound — происходит в случае тех или иных проблем, имеющих место при попытке загрузки ресурсов форм — файлов .DFM в режиме дизайнера. Часто причиной таких исключений бывает нарушение соответствия между определением класса формы и ее описанием на уровне ресурса (например,вследствие изменения порядка следования полей-ссылок на компоненты, вставленные в форму в режиме дизайнера).

u EStreamError — предок исключений, вызываемых при работе с потоками.

u EFCreateError — происходит в случае ошибок создания потока (например, при некорректном задании файла потока).

u EFilerError — вызывается при попытке вторичной регистрации уже зарегистрированного класса (компоненты). Является, также, предком специализированных обработчиков исключений, возникающих при работе с классами компонент.

u EClassNotFound — обычно происходит, когда в описании класса формы удалено поле-ссылка на компоненту, вставленную в форму в режиме дизайнера. Вызывается, в отличие от EResNotFound, в RunTime.

u EInvalidImage — вызывается при попытке чтения файла, не являющегося ресурсом, или разрушенного файла ресурса, специализированными функциями чтения ресурсов (например, функцией ReadComponent).

u EMethodNotFound — аналогично EClassNotFound, только при несоответствии методов, связанных с теми или иными обработчиками событий.

u EReadError — происходит в том случае, когда невозможно прочитать значение свойства или другого набора байт из потока (в том числе ресурса).

u EFOpenError — вызывается когда тот или иной специфицированный поток не может быть открыт (например, когда поток не существует).

u EStringListError — происходит при ошибках работы с объектом TStringList (кроме ошибок, обрабатываемых TListError).

Исключения, возникающие при работе с базами данных

Delphi, обладая прекрасными средствами доступа к данным, основывающимися на интерфейсе IDAPI, реализованной в виде библиотеки Borland Database Engine (BDE), включает ряд обработчиков исключительных ситуаций для регистрации ошибок в компонентах VCL работающим с БД. Дадим краткую характеристику основным из них:

u EDatabaseError — наследник Exception; происходит при ошибках доступа к данным в компонентах-наследниках TDataSet. Объявлено в модуле DB. Ниже приведен пример из Delphi On-line Help, посвященный этому исключению:
repeat
{пока не откроем таблицу или не нажмем кнопку Cancel}
try
Table1.Active := True; {Пытаемся открыть таблицу}
Break; { Если нет ошибки — прерваем цикл}
except
on EDatabaseError do
{Если нажата OK — повторяем попытку открытия Table1}
if MessageDlg('Не могу открыть Table1', mtError,
[mbOK, mbCancel], 0) <> mrOK
then
raise;
end;
until False;

u EDBEngineError — наследник EDatabaseError; вызывается, когда происходят ошибки BDE или на сервере БД. Объявлено в модуле DB:
EDBEngineError = class(EDatabaseError)
private
FErrors: TList;
function GetError(Index: Integer): TDBError;
function GetErrorCount: Integer;
public
constructor Create(ErrorCode: DBIResult);
destructor Destroy;
property ErrorCount: Integer;
property Errors[Index: Integer]: TDBError;
end;

Особенно важны два свойства класса EDBEngineError:

u Errors — список всех ошибок, находящихся в стеке ошибок BDE. Индекс первой ошибки 0;

u ErrorCount — количество ошибок в стеке.

Объекты, содержащиеся в Errors, имеют тип TDBError. Доступные свойства класса TDBError:

u ErrorCode — код ошибки, возвращаемый Borland Database Engine;

u Category — категория ошибки, описанной в ErrorCode;

u SubCode — «субкод» ошибки из ErrorCode;

u NativeError — ошибка, возвращаемая сервером БД. Если NativeError 0, то ошибка в ErrorCode не от сервера;

u Message — сообщение, переданное сервером, если NativeError не равно 0; сообщение BDE — в противном случае.

u EDBEditError — наследник Exception; вызывается, когда данные не совместимы с маской ввода, наложенной на поле.

Глава 40.
Событийное программирование

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

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

Delphi предоставляет полный доступ к подструктуре событий, предоставляемой Windows. С другой стороны, Delphi упрощает программирование обработчиков таких событий.

Здесь мы приведем несколько примеров того, как обрабатывать события в Delphi, дадим более детальное объяснение работы системы, ориентированной на события.

Объекты из библиотеки визуальных компонент (VCL) Delphi, равно как и объекты реального мира, имеют свой набор свойств и свое поведение — набор откликов на события, происходящие с ними. Список событий для данного объекта, на которые он реагирует, можно посмотреть, например, в Инспекторе Объектов на странице событий. (На самом деле, на этой странице представлен список свойств, которые имеют тип вроде TMouseMoveEvent и представляют из себя процедуры-обработчики событий. Существует соглашение по названиям данных свойств. Например, OnDblClick соответствует двойному щелчку мыши, а OnKeyUp — событию, когда нажатая клавиша была отпущена.) Среди набора событий для различных объектов из VCL есть как события, портируемые из Windows (MouseMove, KeyDown), так и события, порождаемые непосредственно в программе (DataChange для TDataSource).

Поведение объекта определяется тем, какие обработчики и для каких событий он имеет. Создание приложения в Delphi состоит из настройки свойств используемых объектов и создания обработчиков событий.

Простейшие события, на которые иногда нужно реагировать — это, например, события, связанные с мышкой (они есть практически у всех видимых объектов) или событие Click для кнопки TButton. Предположим, что вы хотите перехватить щелчок левой кнопки мыши на форме. Чтобы сделать это — создайте новый проект, в Инспекторе Объектов выберите страницу событий и сделайте двойной щелчок на правой части для свойства OnClick. Вы получите заготовку для обработчика данного события:

procedure TForm1.FormClick(Sender: TObject);

begin

end;

Напишите здесь следующее:

procedure TForm1.FormClick(Sender: TObject);

begin

MessageDlg('Hello', mtInformation, [mbOk], 0);

end;

Каждый раз, когда делается щелчок левой кнопки мыши над формой будет появляться окно диалога. Код, приведенный выше, представляет из себя простейший случай ответа на событие в программе на Delphi. Он настолько прост, что многие программисты могут написать такой код и без понимания того, что они на самом деле отвечают на сообщение о событии, посланное им операционной системой. Хотя программист получает это событие через третьи руки, тем не менее он на него отвечает. Опытные программисты в Windows знают, что при возникновении события, операционная система передает вам не только уведомление о нем, но и некоторую связанную с ним информацию. Например, при возникновении события «нажата левая кнопка мыши» программа информируется о том, в каком месте это произошло. Если вы хотите получить доступ к такой информации, то должны вернуться в Инспектор Объектов и создать обработчик события OnMouseDown:

procedure TForm1.FormMouseDown(Sender: TObject;

Button: TMouseButton;

Shift: TShiftState;

X, Y: Integer);

begin

Canvas.TextOut(X, Y, 'X='+IntToStr(X)+' Y='+IntToStr(Y));

end;

Запустите программу и пощелкайте мышкой на выводимой форме.

Как видите, в Delphi очень просто отвечать на события. И не только на события, связанные с мышкой. Например, можно создать обработчик для OnKeyDown (нажата клавиша):

procedure TForm1.FormKeyDown(Sender: TObject;

var Key: Word;

Shift: TShiftState);

begin

MessageDlg(Chr(Key), mtInformation, [mbOk], 0);

end;

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

Событийное программирование есть не только в Windows, и данную черту можно реализовать не только в операционной системе. Например, любая DOS программа может быть основана на простом цикле, работающем все время жизни программы в памяти. Ниже вы найдете гипотетический пример, как данный код может выглядеть:

begin

InitializeMemory;

repeat

CheckForMouseEvent(Events);

CheckForKeyPress(Events)

HandleEvents(Events);

until Done := True;

DisposeMemory;

end

Это типичный пример программы, ориентированной на события. Она начинается и заканчивается инициализацией и освобождением памяти. В программе присутствует простой цикл repeat..until, который проверяет появление событий от мыши и клавиатуры и затем дает возможность программисту ответить на эти события. Переменная Events может быть записью с простой структурой:

TEvent = record

X, Y: Integer;

MouseButton: TButton;

Key: Word;

end;

Тип TButton, указанный выше, можно декларировать так:

TButton = (lButton, rButton);

Эти структуры позволяют вам проследить, где находится мышь, каково состояние ее кнопок, и значение нажатой клавиши на клавиатуре. Конечно, это пример очень простой структуры, но заложенные здесь принципы отражают то, что происходит внутри Windows или внутри других систем, ориентированных на события, вроде Turbo Vision. Если бы программа, приведенная выше, была редактором текста, то обработчик HandleEvent для такой программы мог бы иметь вид:

procedure HandleEvent(Events: TEvent);

begin

case Events.Key of ’A’..’z’: Write(Events.Key);

EnterKey: Write(CarriageReturn);

EscapeKey: Done := True;

end;

end;

Согласно коду выше, программа будет печатать букву «a» при нажатии этой клавиши и перейдет на новую строку, если нажата клавиша Enter. Нажатие Esc приведет к завершению программы.

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

Надеемся, что приведенный пример дает некоторое понимание работы ориентированной на события системы. Единственное, что осталось пропущенным — почему система Windows так спроектирована.

Одной из основных причин, почему Microsoft сделал Windows по такой схеме, является тот факт, что множество задач работает в среде одновременно. В многозадачных системах операционная система должна знать, щелкнул ли пользователь мышкой на определенное окно. Если это окно было частично перекрыто другим, то это становится известно операционной системе и она перемещает окно на передний план. Понятно, что неудобно заставлять само окно выполнять эти действия. Операционной системе лучше обрабатывать все нажатия клавиш и кнопок на мыши и затем передавать их в остальные программы в виде событий.

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

Когда пользователь щелкает мышкой, операционная система обрабатывает это событие и передает его в окно, которое должно обработать данное событие. Созданное сообщение, в этом случае, пересылается в некую процедуру DefWindowProc окна (default window procedure). DefWindowProc — аналог процедуры HandleEvent из примера, приведенного выше.

Каждое окно в Windows имеет свою DefWindowProc. Чтобы полностью понять данное утверждение, представьте, что каждая кнопка, каждый ListBox, каждое поле ввода на самом деле являются окнами и имеют свою процедуру DefWindowProc. Это очень гибкая и мощная система, но она может заставить программиста писать очень сложный код. Delphi дает возможность быть защищенным от такой структуры программы.

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

Если посмотреть на DefWindowProc в справочнике по Windows API, то можно увидеть следующее определение:

function DefWindowProc(Wnd: HWnd; Msg, wParam: Word; lParam: LongInt);

Каждое сообщение, посылаемое в окно, состоит из четырех частей: первая часть — handle окна, получающего сообщение, Msg сообщает, что произошло а третья и четвертая части (wParam и lParam) содержат дополнительную информацию о событии. Вместе эти четыре части являются аналогом показанной выше структуры TEvent.

Вторая часть сообщения имеет длину 16 бит и сообщает, что за событие произошло. Например, если нажата левая кнопка на мыши, то переменная Msg содержит значение WM_LBUTTONDOWN. Существуют десятки различного типа сообщений и они называются вроде WM_GETTEXT, WM_HSCROLL, WM_GETTEXTLENGTH. Список всех сообщений можно видеть в справочнике по Windows API (on-line help).

Последние две переменные, длиной 16 и 32 бита, называются wParam и lParam. Они сообщают программисту важную дополнительную информацию о каждом событии. Например при нажатии кнопки мыши, lParam содержит координаты указателя мыши.

Одна из хитростей заключается в том, как выделить нужную информацию из этих переменных. В большинстве случаев Delphi освобождает вас от необходимости выполнять данную задачу. Например, в обработчике события OnMouseDown для формы вы просто используете координаты X и Y. Как программисту, вам не нужно прилагать усилия для получения сообщения и связанных с ним параметров. Все, что связано с событиями, представлено в простом и непосредственном виде:

procedure TForm1.FormMouseDown(Sender: TObject;

Button: TMouseButton;

Shift: TShiftState;

X, Y: Integer);

Итак, если подвести итог, то должно стать ясным следующее:

u Windows является системой, ориентированной на события;

u События в Windows принимают форму сообщений;

u В недрах VCL Delphi сообщения Windows обрабатываются и преобразуются в более простую для программиста форму;

u Обработка событий в Delphi сводится к написанию для каждого объекта своих обработчиков;

u События в программе на Delphi вызываются не только сообщениями Windows, но и внутренними процессами.

Глава 41.
Обработка сообщений Windows

Конечно, нельзя придумать такую библиотеку объектов, которые бы полностью соответствовали потребностям программистов. Всегда возникнет необходимость дополнения или изменения свойств и поведения объектов. В этом случае, так же, как и при создании своих собственных компонент в Delphi, часто требуется обрабатывать сообщения Windows. Поскольку Object Pascal является развитием и продолжением Borland Pascal, то это выполняется сходным с BP способом.

Общий синтаксис для декларации обработчика сообщений Windows:

procedure Handler_Name(var Msg : MessageType);

message WM_XXXXX;

Handler_Name обозначает имя метода; Msg — имя передаваемого параметра; MessageType — какой либо тип записи, подходящий для данного сообщения; директива message указывает, что данный метод является обработчиком сообщения; WM_XXXXX — константа или выражение, которое определяет номер обрабатываемого сообщения Windows.

Рассмотрим обработку сообщений на примере. Например, при нажатии правой кнопки мыши на форме в программе появляется всплывающее меню (pop-up menu, если оно было привязано к этой форме). Программист может захотеть привязать к правой кнопке какое-нибудь другое событие. Это можно сделать так:

type

TForm1 = class(TForm)

PopupMenu1: TPopupMenu;

MenuItem1: TMenuItem;

MenuItem2: TMenuItem;

MenuItem3: TMenuItem;

private

{ Private declarations }

procedure WMRButtonDown(var Msg : TWMMouse);
message WM_RBUTTONDOWN;

public

{ Public declarations }

end;

Подчеркнут код, добавленный в декларацию объекта TForm1 вручную. Далее, в секции implementation нужно написать обработчик:

procedure TForm1.WMRButtonDown(var Msg : TWMMouse);

begin

MessageDlg('Right mouse button click.', mtInformation,

[mbOK], 0);

end;

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

Вообще-то, у класса TForm уже есть унаследованный от дальнего предка обработчик данного события, который называется точно также и вызывает то самое pop-up меню. Если в новом обработчике сообщения нужно выполнить действия, которые производились в старом, то для этого применяется ключевое слово inherited. Если слегка модифицировать наш обработчик, то после диалога будет появляться pop-up меню:

procedure TForm1.WMRButtonDown(var Msg : TWMMouse);

begin

MessageDlg('Right mouse button click.', mtInformation,

[mbOK], 0);

inherited;

end;

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

procedure TForm1.FormCreate(Sender: TObject);

begin

Application.OnMessage:=AOM;

end;

procedure TForm1.AOM(var Msg: TMsg; var Handled: Boolean);

begin

Handled:=False;

if Msg.Message = WM_LBUTTONDBLCLK then begin

MessageDlg('Double click.', mtInformation, [mbOK], 0);

Handled:=True;

end;

end;

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

Глава 42.
Создание мультимедийных приложений

Что такое мультимедиа

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

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

u Показ видео в формате Microsoft's Video for Windows (AVI).

u Воспроизведение звуков и музыки из MIDI и WAVE файлов.

Данную задачу можно выполнить с помощью динамической библиотеки Microsoft Multimedia Extensions для Windows (MMSYSTEM.DLL), методы которой инкапсулированы в компоненте TMediaPlay, находящийся на странице System Палитры Компонент Delphi.

Для проигрывания файлов мультимедиа может потребоваться наличие некоторого оборудования и программного обеспечения. Так, для воспроизведения звуков нужна звуковая карта. Для воспроизведения AVI в Windows (или WFW) требуется установить ПО Microsoft Video.

Мультимедиа в Delphi

В Delphi есть компонент TMediaPlayer, который дает вам доступ ко всем основным возможностям программирования мультимедиа. Данный компонент очень прост в использовании. Фактически, он настолько прост, что многим начинающим программистам будет проще создать свою первую программу, проигрывающую видео или музыку, нежели показывающую классическую надпись «Hello World».

Простоту использования можно воспринимать двояко:

u С одной стороны — это дает возможность любому создавать мультимедиа приложения.

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

Мы не будем описывать подробности внутренних вызовов мультимедийных функций при работе компонента. Все, что нужно знать — это то, что компонент называется TMediaPlayer, и что он дает доступ к набору подпрограмм, созданных Microsoft и называемых Media Control Interface (MCI). Эти подпрограммы дают программисту простой доступ к широкому кругу устройств мультимедиа. Собственно работа с TMediaPlayer интуитивно понятна и очевидна.

Компонент TMediaPlayer

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

Компонент TMediaPlayer оформлен, как панель управления устройством с кнопками. Как и на магнитофоне, здесь есть кнопки «воспроизведение», «перемотка», «запись».

Поместив компонент на форму, вы увидите, что Инспектор Объектов содержит свойство FileName. Щелкните дважды на этом свойстве и выберите имя файла с расширением AVI, WAV или MID. Далее нужно установить свойство AutoOpen в True.

После выполнения этих шагов программа готова к запуску. Запустив программу, нажмите зеленую кнопку «воспроизведение» (крайняя слева) и вы увидите видеоролик (если выбрали AVI) или услышите звук (если выбрали WAV или MID). Если этого не произошло или появилось сообщение об ошибке, то возможны два варианта:

u Вы ввели неправильное имя файла.

u Вы не настроили правильным образом мультимедиа в Windows. Это означает, что либо у вас нет соответствующего «железа», либо не установлены нужные драйверы. Установка и настройка драйверов производится в Control Panel, требования к «железу» приводятся в любой книге по мультимедиа (нужна звуковая карта, например совместимая с Sound Blaster).

Итак, вы имеете возможность проигрывать AVI, MIDI и WAVE файлы, просто указывая имя файла.

Еще одно важное свойство компонента TMediaPlayer — Display. Изначально оно не заполнено и видео воспроизводится в отдельном окошке. Однако, в качестве экрана для показа ролика можно использовать, например, панель. На форму нужно поместить компонент TPanel, убрать текст из свойства Caption. Далее, для TMediaPlayer, в свойстве Display выбрать из списка Panel1. После этого надо запустить программу и нажать кнопку «воспроизведение».

Два вида программ мультимедиа

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

Иногда программист может захотеть скрыть от пользователя существование компонента TMediaPlayer. То есть, воспроизвести звук или видео без того, чтобы пользователь заботился об их источнике. В частности, звук может быть частью презентации. Например, показ какого-нибудь графика на экране может сопровождаться объяснением, записанным в WAV файл. В течении презентации пользователь даже не знает о существовании TMediaPlayer. Он работает в фоновом режиме. Для этого компонент делается невидимым (Visible = False) и управляется программно.

Пример программы с мультимедиа

Ниже мы рассмотрим пример построения приложения с мультимедиа первого типа. Создайте новый проект (File ê New Project). Поместите TMediaPlayer на форму; поместите компоненты TFileListBox, TDirectoryListBox, TDriveComboBox, TFilterComboBox для выбора файла. В свойстве FileList для DirectoryListBox1 и FilterComboBox1 поставьте FileListBox1. В свойстве DirList для DriveComboBox1 поставьте DirectoryListBox1. В свойстве Filter для FilterComboBox1 укажите требуемые расширения файлов:

u AVI File (*.avi) ê *.avi

u WAVE File (*.wav) ê *.wav

u MIDI file (*.MID) ê *.mid

Пусть по двойному щелчку мышкой в FileListBox1 выбранный файл будет воспроизводиться.

В обработчике события OnDblClick для FileListBox1 укажите:

Procedure TForm1.FileListBox1DblClick(Sender:TObject);

begin

with MediaPlayer1 do

begin

Close;

FileName:=FileListBox1.FileName;

Open;

Play;

end;

end;

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

Как уже говорилось выше, видеоролик можно воспроизводить внутри формы, например, на панели. Давайте слегка модифицируем проект и добавим туда панель TPanel. В свойстве Display для MediaPlayer1 укажите Panel1. Нужно убрать надпись с панели (Caption) и свойство BevelOuter = bvNone. Чтобы переключаться при воспроизведении с окна на панель — поместите TСheckBox на форму и в обработчике события OnClick для него запишите:

procedure TForm1.CheckBox1Click(Sender: TObject);

var

Start_From : Longint;

begin

with MediaPlayer1 do begin

if FileName='' then Exit;

Start_From:=Position;

Close;

Panel1.Refresh;

if CheckBox1.Checked then

Display:=Panel1

else

Display:=NIL;

Open;

Position:=Start_From;

Play;

end;

end;

Запустите проект и воспроизведите видеоролик. Пощелкайте мышкой на CheckBox.

Таким образом мы добавили панель для воспроизведения видео и переключатель окно/панель.

Во время выполнения программы может потребоваться отобразить текущее состояние объекта MediaPlayer и самого ролика (время, прошедшее с начала воспроизведения, длину ролика). Для этого у объекта TMediaPlayer есть соответствующие свойства и события: Length, Position, OnNotify. Давайте добавим в проект прогресс-индикатор (TGauge), который отобразит в процентах, сколько прошло времени.

Для обновления показаний индикатора можно воспользоваться таймером. Поместите на форму объект TTimer, установите для него Interval = 100 (100 миллисекунд). В обработчике события OnTimer нужно записать:

procedure TForm1.Timer1Timer(Sender: TObject);

begin

with MediaPlayer1 do

if FileName<>'' then

Gauge1.Progress:=Round(100*Position/Length);

end;

Запустите проект, выберите файл (AVI) и щелкните на нем два раза мышкой. При воспроизведении ролика прогресс-индикатор должен отображать процент, соответствующий прошедшему времени.

Глава 43.
DDE

Аббревиатура DDEML обозначает Dynamic Data Exchange Management Library (библиотека управления динамическим обменом данными). DDEML это надстройка над сложной системой сообщений, называемой Dynamic Data Exchange (DDE). Библиотека, содержащая DDE била разработана для усиления возможностей первоначальной системы сообщений Windows.

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

Dynamic Data Exchange получило свое имя потому, что позволяет двум приложениям обмениваться данными (текстовыми, через глобальную память) динамически во время выполнения. Связь между двумя программами можно установить таким образом, что изменения в одном приложении будут отражаться во втором. Например, если вы меняете число в электронной таблице, то во втором приложении данные обновятся автоматически и отобразят изменения. Кроме того, с помощью DDE можно из своего приложения управлять другими приложениями такими, как Word for Windows, Report Smith, Excel.

Использование DDE

Приложение, получающее данные из другого приложения по DDE и/или управляющее другим приложением с помощью команд через DDE является DDE-клиентом. В этом случае второе приложение является DDE-сервером. Одно и то же приложение может быть одновременно и сервером, и клиентом (например, MicroSoft Word). Построение DDE-серверов и DDE-клиентов удобно рассмотреть на примере, поставляемом с Delphi (каталог x:\delphi\demos\ddedemo).

Сначала давайте рассмотрим логику работы примера. Для начала нужно откомпилировать проекты DDESRVR.DPR и DDECLI.DPR, после этого запустите программу DDECLI.EXE (DDE-клиент) и выберите пункт меню File к New Link. При этом должна запуститься программа DDESRVR (DDE-сервер).

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

Другие возможности DDE:

u пересылка данных из клиента на сервер (Poke Data); наберите любой текст в правом окне DDE-клиента и нажмите кнопку Poke Data, этот текст появится в окне сервера.

u исполнение команд (макросов) на сервере; наберите любой текст в правом окне DDE-клиента и нажмите кнопку Exec Macro, DDE-сервер выдаст соответствующее диалоговое окно.

u установление связи через Clipboard; закройте оба DDE приложения и запустите их заново, затем в DDE-сервере выберите пункт меню Edit ê Copy, далее в клиенте выберите пункт меню Edit ê Paste Link.

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

DDE-серверы

Для построении DDE-сервера в Delphi имеются два объекта, расположенные на странице System Палитры Компонент — TDdeServerConv и TDdeServerItem. Обычно в проекте используется один объект TDdeServerConv и один или более TDdeServerItem. Для получения доступа к сервису DDE-сервера, клиенту потребуется знать несколько параметров:

u имя сервиса (Service Name) — это имя приложения (обычно — имя выполняемого файла без расширения EXE, возможно с полным путем);

u Topic Name — в Delphi это имя компоненты TDdeServerConv;

u Item Name — в Delphi это имя нужной компоненты TDdeServerItem.

Назначение объекта TDdeServerConv — общее управление DDE и обработка запросов от клиентов на выполнение макроса. Последнее выполняется в обработчике события OnExecuteMacro, например, как это сделано в нашем случае:

procedure TDdeSrvrForm.doMacro(Sender: TObject; Msg: TStrings);

var

Text: string;

begin

Text := '';

if Msg.Count > 0 then Text := Msg.Strings[0];

MessageDlg ('Executing Macro — ' + Text, mtInformation, [mbOK], 0);

end;

Объект TDdeServerItem связывается с TDdeServerConv и определяет, что, собственно, будет пересылаться по DDE. Для этого у него есть свойства Text и Lines. (Text имеет то же значение, что и Lines[0].) При изменении значения этих свойств автоматически происходит пересылка обновленных данных во все приложения-клиенты, установившие связь с сервером.

В нашем приложении изменение значения свойства Lines происходит в обработчике события OnChange компонента Edit1:

procedure TDdeSrvrForm.doOnChange(Sender: TObject);

begin

if not FInPoke then

DdeTestItem.Lines := Edit1.Lines;

end;

Этот же компонент отвечает за получение данных от клиента, в нашем примере это происходило при нажатии кнопки Poke Data, это выполняется в обработчике события OnPokeData:

procedure TDdeSrvrForm.doOnPoke(Sender: TObject);

begin

FInPoke := True;

Edit1.Lines := DdeTestItem.Lines;

FInPoke := False;

end;

И последнее — установление связи через Clipboard. Для этого служит метод CopyToClipboard объекта TDdeServerItem. Необходимая информация помещается в Clipboard и может быть вызвана из приложения-клиента при установлении связи. Обычно, в DDE-серверах для этого есть специальный пункт меню Paste Special или Paste Link.

Итак, мы рассмотрели пример полнофункционального DDE-сервера, построенного с помощью компонент Delphi. Очень часто существующие DDE-серверы не полностью реализуют возможности DDE и предоставляют только часть сервиса. Например, ReportSmith позволяет по DDE только выполнять команды (макросы).

DDE-клиенты

Для построения DDE-клиента в Delphi используются два компонента TDDEClientConv и TDDEClientItem. Аналогично серверу, в программе обычно используется один объект TDDEClientConv и один и более связанных с ним TDDEClientItem.

TDDEClientConv служит для установления связи с сервером и общим управлением DDE-связью. Установить связь с DDE-сервером можно как во время дизайна, так и во время выполнения программы, причем двумя способами. Первый способ — заполнить вручную необходимые свойства компонента. Это DdeService, DdeTopic и ServiceApplication. Во время дизайна щелкните дважды на одно из первых двух свойств в Инспекторе Объектов — вы получите диалог для определения DDE-связи.

Укажите в диалоге имена DDE Service и DDE Topic. Эти имена можно узнать из документации по тому DDE-серверу, с которым вы работаете. В случае DDE-сервера, созданного на Delphi, это имя программы (без .EXE) и имя объекта TDdeServerConv. Для установления связи через Clipboard в диалоге есть специальная кнопка Past Link. Ей можно воспользоваться, если вы запустили DDE-сервер, сохранили каким-то образом информацию о связи и вошли в этот диалог. Например, если DDE-сервером является DataBase Desktop, то нужно загрузить в него какую-нибудь таблицу Paradox, выбрать любое поле и выбрать пункт меню Edit ê Copy. После этого войдите в диалог и нажмите кнопку Paste Link. Поля в диалоге заполнятся соответствующим образом.

Свойство ServiceApplication заполняется в том случае, если в поле DDEService содержится имя, отличное от имени программы, либо если эта программа не находится в текущей директории. В этом поле указывается полный путь и имя программы без расширения (.EXE). При работе с Report Smith здесь нужно указать, например: C:\RPTSMITH\RPTSMITH.

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

В нашей демо-программе связь устанавливается во время выполнения программы в пунктах меню File ê New Link и Edit ê Paste Link. В пункте меню File ê New Link программно устанавливается связь по DDE с помощью соответствующего метода объекта TDdeServerConv, OpenLink делать не надо, поскольку свойство ConnectMode имеет значение ddeAutomatic:

procedure TFormD.doNewLink(Sender: TObject);

begin

DdeClient.SetLink(AppName.Text, TopicName.Text);

DdeClientItem.DdeConv := DdeClient;

DdeClientItem.DdeItem := ItemName.Text;

end;

Здесь же заполняются свойства объекта TDdeClietItem.

В пункте меню Edit ê Past Link программно устанавливается связь по DDE с использованием информации из Clipboard:

procedure TFormD.doPasteLink(Sender: TObject);

var

Service, Topic, Item : String;

begin

if GetPasteLinkInfo (Service, Topic, Item) then

begin

AppName.Text := Service;

TopicName.Text := Topic;

ItemName.Text := Item;

DdeClient.SetLink (Service, Topic);

DdeClientItem.DdeConv := DdeClient;

DdeClientItem.DdeItem := ItemName.Text;

end;

end;

После того, как установлена связь, нужно позаботиться о поступающих по DDE данных, это делается в обработчике события OnChange объекта TDdeClietItem:

procedure TFormD.DdeClientItemChange(Sender: TObject);

begin

DdeDat.Lines := DdeClientItem.Lines;

end;

Это единственная задача объекта TDdeClientItem.

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

procedure TFormD.doMacro(Sender: TObject);

begin

DdeClient.ExecuteMacroLines(XEdit.Lines, True);

end;

Пересылка данных на сервер:

procedure TFormD.doPoke (Sender: TObject);

begin

DdeClient.PokeDataLines(DdeClientItem.DdeItem,XEdit.Lines);

end;

Управление ReportSmith по DDE

В прилагаемом примере run-time версия ReportSmith выполняет команду, переданную по DDE.

Имена DDE сервиса для ReportSmith и некоторых других приложений можно узнать в Справочнике в среде ReportSmith.

Перед запуском примера нужно правильно установить в свойстве ServiceApplication путь к run-time версии ReportSmith и в тексте программы в строке:

Cmd:='LoadReport

"c:\d\r\video\summary.rpt","@Repvar1=<40>,@Repvar2=<'#39'Smith'#39'>"'#0;

правильно указать путь к отчету.

Глава 44.
OLE

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

Аббревиатура OLE обозначает Objects Linked and Embedded (Присоединенные И Встроенные Объекты). Данные, разделяемые между приложениями называются OLE объектом. Приложение, которое может содержать OLE объекты, называют OLE контейнером (OLE Container). Приложение, данные из которого можно включить в OLE контейнер в виде OLE объекта, называют OLE сервером.

Например, MicroSoft Word может включать в документ графические объекты, аудио- и видеоклипы и множество других объектов (такой документ иногда называют составным документом — compound document).

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

OLE является дальнейшим развитием идеи разделяемых между приложениями данных. Если с помощью DDE можно было работать с текстом, то OLE позволяет легко встроить в приложение обработку любых типов данных. Как и в случае с DDE, для правильной работы приложения-клиента (OLE контейнера) требуется наличие приложения OLE сервера. Каждый раз, когда в программе-клиенте пользователь обращается к OLE объекту с целью просмотра или редактирования данных (обычно двойной щелчок мышкой на объекте), запускается приложение-сервер, в котором и происходит работа с данными.

В природе существует несколько видов OLE, отличающихся по способу активации OLE сервера. OLE версии 1 запускает сервер в отдельном окне. OLE 2 реализует то, что называется in-place activation and editing. В данном случае сервер запускается «внутри» приложения-клиента, модифицирует вид системного меню, линейки инструментов. Развитие идеи OLE привело к появлению OLE automation — приложение клиент может выполнить часть кода сервера. Тип OLE объекта, помещенного в программу-клиент, определяется тем, какую версию OLE поддерживает сервер.

Объект TOLEContainer

Объект TOLEContainer находится на странице System Палитры Компонент и нужен для создания приложений OLE-контейнеров. TOLEContainer скрывает все сложности, связанные с внутренней организацией OLE и предоставляет программисту достаточно простой интерфейс. Построим простейшее приложение с использованием OLE объекта. Создайте новый проект и поместите на форму TOLEContainer, в Инспекторе Объектов дважды щелкните мышкой на свойство ObjClass или ObjDoc — появится стандартный диалог Windows Insert Object.

В этом диалоге есть список всех зарегистрированных в системе OLE-серверов (регистрация происходит при инсталляции программы). Тип OLE-объекта определяется как раз тем сервером, который вы укажете. Если вы создаете новый объект (Create New), то при нажатии кнопки OK запустится программа OLE-сервер, в которой и формируется новый объект. После выхода из программы-сервера новый OLE объект включается (embedded object) в программу. OLE объект можно создать используя уже имеющийся файл в формате одного из OLE-серверов. Для этого нужно выбрать пункт Create from File.

Выбранный объект можно как включить в приложение, так и присоединить, отметив пункт Link.

Итак, давайте при создании нашего проекта создадим новый объект, выбрав для этого, например, Microsoft Word Document.

Нажмите OK и после того, как запустится MS Word, наберите там любой текст (Например, «Это OLE-объект Microsoft Word document»). Для завершения работы в меню есть специальный пункт File ê Close and Return to Form1. Запустите проект.

Щелкните дважды мышкой на OLE-контейнер — запустится MS Word с документом из OLE-объекта, который можно редактировать, при этом все изменения сохраняются в OLE-объекте.

Если во время дизайна вы выбираете объект для включения в OLE-контейнер, то он полностью записывается в файл формы (FORM1.DFM) и в дальнейшем прикомпилируется к EXE файлу. В случае очень больших объектов это может привести во время дизайна к длительным паузам и даже к возникновению ошибки «Out of resource». Поэтому рекомендуется большие объекты делать присоединенными (linked).

TOLEContainer позволяет отображать в программе объект в его непосредственном виде (с различной степенью увеличения или уменьшения — свойство Zoom) или в виде пиктограммы, определяемой в диалоге Insert Object — Display as Icon. Выбор OLE-объекта может происходить не только во время дизайна, но и во время выполнения программы. Результаты работы с этим объектом можно сохранить в виде файла и в следующий раз восстановить его оттуда, для этого TOLEContainer имеет два метода SaveToFile и LoadFromFile.

Пример OLE приложения

Среди демонстрационных примеров, входящих в Delphi есть два, относящихся к работе с OLE-объектами (в директориях X:\DELPHI\DEMOS\OLE2 и X:\DELPHI\DEMOS\DOC\OLE2). Более полным является второй, который, кроме всего прочего является примером построения MDI приложения. Данная программа демонстрирует все основные возможности TOLEContainer и позволяет:

u создавать новый OLE контейнер во время выполнения программы;

u инициализировать OLE объект либо в стандартном диалоге Windows Insert Object, либо с помощью Clipboard, либо с помощью техники «перенести и бросить» (drag-and-drop);

u сохранить OLE объект в файле и восстановить его оттуда.

Для создания нового OLE объекта нужно выбрать пункт меню File ê New и далее Edit ê Insert Object. Появится стандартный диалог Windows для инициализации OLE объекта. Если приложение OLE-сервер имеет возможность сохранять информацию об OLE объекте в Clipboard, то проинициализировать объект можно с помощью пункта меню Edit ê Paste Special.

Достаточно интересной является возможность применения техники drag-and-drop в применении к OLE объектам. Запустите MS Word (разместите его окно так, чтобы было видно и OLE приложение), наберите какой-нибудь текст, выделите его и с помощью мышки перетащите и бросьте на главное MDI окно приложения. Появится новое дочернее окно с OLE контейнером, содержащим этот текст. Программирование данной возможности достаточно сложно.

Сохранение OLE объекта в базе данных

Иногда необходимо хранить OLE объекты не в файлах, а в базе данных (BLOB поле в таблице). Конечно, в данном случае OLE объект должен быть присоединенным (embedded) в целях переносимости. К сожалению, в стандартной поставке Delphi нет специального объекта типа TDBOLEContainer для данных целей, но OLE объект можно сохранять и восстанавливать с помощью методов SaveToStream и LoadFromStream.

Например:

procedure TOLEForm.SaveOLE(Sender: TObject);

var

BlSt : TBlobStream;

begin

With Table1 do

BlSt:=TBlobStream.Create(BlobField(FieldByName ('OLE')),

bmReadWrite);

OLEContainer.SaveToStream(BlSt as TStream);

BlSt.Free;

end;

Глава 45.
Использование DLL

Понятие DLL

Вспомним процесс программирования в DOS. Преобразование исходного текста программы в машинный код включал в себя два процесса — компиляцию и линковку. В процессе линковки, редактор связей, компоновавший отдельные модули программы, помещал в код программы не только объявления функций и процедур, но и их полный код. Вы готовили таким образом одну программу, другую, третью... И везде код одних и тех же функций помещался в программу полностью.

Программа1 Программа2

… …

MyFunc(…) MyFunc(…)

… …

код функции MyFunc код функции MyFunc

код других функций код других функций

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

Программа1 Библиотека

… динамической

MyFunc(…) компоновки

… …

MyFunc(…)

Программа2 …

…

MyFunc(…) код функции MyFunc

…

Но, чем же отличаются Dynamic Link Library (DLL) от обычных приложений? Для понимания этого требуется уточнить понятия задачи (task), экземпляра (копии) приложения (instance) и модуля (module).

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

Задача 1 Задача 2

Копия 1 приложения Копия 2 приложения

Данные Данные

Стек Стек

Очередь сообщений Очередь сообщений

Модуль приложения

Код

Ресурсы

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

DLL-библиотека

Код

Ресурсы

Данные

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

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

Часто, в виде DLL создаются отдельные наборы функций, объединенные по тем или иным логическим признакам, аналогично тому, как концептуально происходит планирование модулей (в смысле unit) в Pascal. Отличие заключается в том, что функции из модулей Pascal компонуются статически — на этапе линковки, а функции из DLL компонуются динамически, то есть в run-time.

Создание DLL (экспорт)

Для программирования DLL Delphi предоставляет ряд ключевых слов и правил синтаксиса. Главное — DLL в Delphi такой же проект как и программа.

Рассмотрим шаблон DLL:

library MyDll;

uses

<используемые модули>;

<объявления и описания функций>

exports

<экспортируемые функции>

begin

<инициализационная часть>

end

Имя файла проекта для такого шаблона должно быть MYDLL.DPR.

К сожалению, в IDE Delphi автоматически генерируется только проект программы, поэтому вам придется проект DLL готовить вручную.

Как и в программе, в DLL присутствует раздел uses. Инициализационная часть необязательна. В разделе же exports перечисляются функции, доступ к которым должен производится из внешних приложений. Экспортирование функций (и процедур) может производится несколькими способами:

u по номеру (индексу);

u по имени.

В зависимости от этого используется различный синтаксис:

{экспорт по индексу}

procedure ExportByOrdinal; export;

begin

.....

end;

exports

ExportByOrdinal index 10;

{экспорт по имени}

procedure ExportByName; export;

begin

.....

end;

exports

ExportByName name 'MYEXPORTPROC';

{имя для экспорта может не совпадать с именем функции!}

Так как в Windows существует понятие «резидентных функций» DLL, то есть тех функций, которые находятся в памяти на протяжении всего времени существования DLL в памяти, в Delphi имеются средства для организации и такого рода экспорта:

exports

ExportByName name 'MYEXPORTPROC' resident;

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

Если же вы будете экспортировать функции следующим образом:

exports

MyExportFunc1,

MyExportFunc2,

.....;

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

Использование DLL (импорт)

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

Для показанных выше примеров, в вашей программе следует объявить функции, импортируемые из DLL таким образом:

{импорт по специфицированному имени}

procedure ImportByName;external 'MYDLL' name 'MYEXPORTPROC';

{импорт по индексу}

procedure ImportByOrdinal; external 'MYDLL' index 10;

{импорт по оригинальному имени}

procedure MyExportFunc1; external 'MYDLL';

Этот способ называется статическим импортом.

Как вы могли заметить, расширение файла, содержащего DLL, не указывается — по умолчанию подразумеваются файлы *.DLL и *.EXE. Как же тогда быть в случае, если файл имеет другое расширение (например, как COMPLIB.DCL в Delphi), или если требуется динамическое определение DLL и импортируемых функций (например, ваша программа работает с различными графическими форматами, и для каждого из них существует отдельная DLL)?

Для решения такого рода проблем вы можете обратиться напрямую к API Windows, используя, так называемый, динамический импорт:

uses

WinTypes, WinProcs, ... ;

type

TMyProc = procedure ;

var

Handle : THandle;

MyImportProc : TMyProc;

begin

Handle:=LoadLibrary('MYDLL');

if Handle>=32 then { if <=32 — error ! }

begin

@MyImportProc:=GetProcAddress(Handle, 'MYEXPORTPROC');

if MyImportProc<>nil then

...... {using imported procedure}

end;

FreeLibrary(Handle);

end;

Если не говорить о генерируемом компилятором коде (сейчас он более оптимизирован), то все правила синтаксиса остались те же, что и в Borland Pascal.

DLL, использующие объекты VCL для работы с данными

При создании своей динамической библиотеки вы можете использовать вызовы функций из других DLL. Пример такой DLL есть в поставке Delphi (X:\DELPHI\DEMOS\BD\BDEDLL). В эту DLL помещена форма, отображающая данные из таблицы и использующая для доступа к ней объекты VCL (TTable, TDBGrid, TSession), которые, в свою очередь, вызывают функции BDE. Как следует из комментариев к этому примеру, для такой DLL имеется ограничение: ее не могут одновременно использовать несколько задач. Это вызвано тем, что объект Session, который создается автоматически при подключении модуля DB, инициализируется для модуля, а не для задачи. Если попытаться загрузить эту DLL вторично из другого приложения, то возникнет ошибка. Для предотвращения одновременной загрузки DLL несколькими задачами нужно осуществить некоторые действия.

Исключительные ситуации в DLL

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

Код в DLL:

function MyFunc : string;

begin

try

{собственно код функции}

except

on EResult: Exception do

Result:=Format(DllErrorViewingTable,

[EResult.Message]);

else

Result := Format(DllErrorViewingTable,

['Unknown error']);

end;

end;

Код в программе:

StrResult:=MyFunc;

if StrResult<>’’ then

raise Exception.Create(StrResult);

Глава 46.
Основы работы с базами данных

Требования к базам данных

Итак, хорошо спроектированная база данных:

u Удовлетворяет всем требованиям пользователей к содержимому базы данных. Перед проектированием базы необходимо провести обширные исследования требований пользователей к функционированию базы данных.

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

u Обеспечивает естественное, легкое для восприятия структурирование информации. Качественное построение базы позволяет делать запросы к базе более «прозрачными» и легкими для понимания; следовательно, снижается вероятность внесения некорректных данных и улучшается качество сопровождения базы.

u Удовлетворяет требованиям пользователей к производительности базы данных. При больших объемах информации вопросы сохранения производительности начинают играть главную роль, сразу «высвечивая» все недочеты этапа проектирования.

Следующие пункты представляют основные шаги проектирования базы данных:

u Определить информационные потребности базы данных.

u Проанализировать объекты реального мира, которые необходимо смоделировать в базе данных. Сформировать из этих объектов сущности и характеристики этих сущностей (например, для сущности «деталь» характеристиками могут быть «название», «цвет», «вес») и сформировать их список.

u Поставить в соответствие сущностям и характеристикам — таблицы и столбцы (поля) в нотации выбранной Вами СУБД (Paradox, dBase, FoxPro, Access, Clipper, InterBase, Sybase, Informix, Oracle).

u Определить атрибуты, которые уникальным образом идентифицируют каждый объект.

u Выработать правила, которые будут устанавливать и поддерживать целостность данных.

u Установить связи между объектами (таблицами и столбцами), провести нормализацию таблиц.

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

Основные концепции реляционных баз данных

Прежде чем подробно рассматривать каждый из этих шагов, остановимся на основных концепциях реляционных баз данных. В реляционной теории одним из главных является понятие отношения. Математически отношение определяется следующим образом. Пусть даны n множеств D1,D2,...,Dn. Тогда R есть отношение над этими множествами, если R есть множество упорядоченных наборов вида <d1,d2,...,dn>, где d1 — элемент из D1, d2 — элемент из D2, ..., dn — элемент из Dn. При этом наборы вида <d1,d2,...,dn> называются кортежами, а множества D1,D2,...,Dn — доменами. Каждый кортеж состоит из элементов, выбираемых из своих доменов. Эти элементы называются атрибутами, а их значения — значениями атрибутов.

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

u отношение, таблица, файл (для локальных баз данных);

u кортеж, строка, запись;

u атрибут, столбец, поле.

Атрибут (или набор атрибутов), который может быть использован для однозначной идентификации конкретного кортежа (строки, записи), называется первичным ключом. Первичный ключ не должен иметь дополнительных атрибутов. Это значит, что если из первичного ключа исключить произвольный атрибут, оставшихся атрибутов будет недостаточно для однозначной идентификации отдельных кортежей. Для ускорения доступа по первичному ключу во всех системах управления базами данных (СУБД) имеется механизм, называемый индексированием. Грубо говоря, индекс представляет собой инвертированный древовидный список, указывающий на истинное местоположение записи для каждого первичного ключа. Естественно, в разных СУБД индексы реализованы по-разному (в локальных СУБД — как правило, в виде отдельных файлов), однако, принципы их организации одинаковы. Возможно индексирование отношения с использованием атрибутов, отличных от первичного ключа. Данный тип индекса называется вторичным индексом и применяется в целях уменьшения времени доступа при нахождении данных в отношении, а также для сортировки. Таким образом, если само отношение не упорядочено каким-либо образом и в нем могут присутствовать строки, оставшиеся после удаления некоторых кортежей, то индекс (для локальных СУБД — индексный файл), напротив, отсортирован.

Для поддержания ссылочной целостности данных во многих СУБД имеется механизм так называемых внешних ключей. Смысл этого механизма состоит в том, что некоему атрибуту (или группе атрибутов) одного отношения назначается ссылка на первичный ключ другого отношения; тем самым закрепляются связи подчиненности между этими отношениями. При этом отношение, на первичный ключ которого ссылается внешний ключ другого отношения, называется master-отношением, или главным отношением; а отношение, от которого исходит ссылка, называется detail-отношением, или подчиненным отношением. После назначения такой ссылки СУБД имеет возможность автоматически отслеживать вопросы «ненарушения» связей между отношениями, а именно:

u если вы попытаетесь вставить в подчиненную таблицу запись, для внешнего ключа которой не существует соответствия в главной таблице (например, там нет еще записи с таким первичным ключом), СУБД сгенерирует ошибку;

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

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

Существует два подхода к удалению и изменению записей из главной таблицы:

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

u Распространить всякие изменения в первичном ключе главной таблицы на подчиненную таблицу, а именно:

u если в главной таблице удалена запись, то в подчиненной таблице должны быть удалены все записи, ссылающиеся на удаляемую;

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

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

Шаги проектирования базы данных

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

u сможет ли новая система объединить существующие приложения или их необходимо будет кардинально переделывать для совместной работы с новой системой;

u какие данные используются разными приложениями; смогут ли ваши приложения совместно использовать какие-либо из этих данных;

u кто будет вводить данные в базу и в какой форме; как часто будут изменяться данные;

u достаточно ли будет для вашей предметной области одной базы или вам потребуется несколько баз данных с различными структурами;

u какая информация является наиболее чувствительной к скорости ее извлечения и изменения.

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

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

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

u идентификацию объектов, которые осуществляют эту функциональную деятельность, и формирование из их операций последовательности событий, которые помогут вам идентифицировать все сущности и взаимосвязи между ними. Например, процесс «Ведение учета работающих» идентифицирует такие сущности как «Работник», «Профессия», «Отдел».

u идентификацию характеристик этих сущностей. Например, сущность «Работник» может включать такие характеристики как «Идентификатор Работника», «Фамилия, Имя, Отчество», «Профессия», «Зарплата».

u идентификацию взаимосвязей между сущностями. Например, каким образом сущности «Работник», «Профессия», «Отдел» взаимодействуют друг с другом? Работник имеет одну профессию (для простоты!) и значится в одном отделе, в то время как в одном отделе может находиться много работников.

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

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

На четвертом шаге определяются атрибуты, которые уникальным образом идентифицируют каждый объект. Это необходимо для того, чтобы система могла получить любую единичную строку таблицы. Вы должны определить первичный ключ для каждого из отношений. Если нет возможности идентифицировать кортеж с помощью одного атрибута, то первичный ключ нужно сделать составным — из нескольких атрибутов. Хорошим примером может быть первичный ключ в таблице работников, состоящий из фамилии, имени и отчества. Первичный ключ гарантирует, что в таблице не будет содержаться двух одинаковых строк. Во многих СУБД имеется возможность помимо первичного определять еще ряд уникальных ключей. Отличие уникального ключа от первичного состоит в том, что уникальный ключ не является главным идентифицирующим фактором записи и на него не может ссылаться внешний ключ другой таблицы. Его главная задача — гарантировать уникальность значения поля.

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

Эти правила включают:

u определение типа данных;

u выбор набора символов, соответствующего данной стране;

u создание полей, опирающихся на домены;

u установка значений по умолчанию;

u определение ограничений целостности;

u определение проверочных условий.

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

Каждый из различных типов связей должен быть смоделирован в базе данных. Существует несколько типов связей:

u связь «один-к-одному»;

u связь «один-ко-многим»;

u связь «многие-ко-многим».

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

Связь «один-ко-многим» в большинстве случаев отражает реальную взаимосвязь сущностей в предметной области. Она реализуется уже описанной парой «внешний ключ-первичный ключ», то есть, когда определен внешний ключ, ссылающийся на первичный ключ другой таблицы. Именно эта связь описывает широко распространенный механизм классификаторов. Имеется справочная таблица, содержащая названия, имена и некие коды, причем, первичным ключом является код. В таблице, собирающей информацию — назовем ее информационной таблицей — определяется внешний ключ, ссылающийся на первичный ключ классификатора. После этого в нее заносится не название из классификатора, а код. Такая система становится устойчивой от изменения названия в классификаторах. Имеются способы быстрой «подмены» в отображаемой таблице кодов на их названия как на уровне сервера БД (для клиент-серверных СУБД), так и на уровне пользовательского приложения.

Связь «многие-ко-многим» в явном виде в реляционных базах данных не поддерживается. Однако имеется ряд способов косвенной реализации такой связи, которые с успехом возмещают ее отсутствие. Один из наиболее распространенных способов заключается во введении дополнительной таблицы, строки которой состоят из внешних ключей, ссылающихся на первичные ключи двух таблиц. Например, имеются две таблицы: «Клиент» и «Группа интересов». Один человек может быть включен в различные группы, в то время как группа может объединять различных людей. Для реализации такой связи «многие-ко-многим» вводится дополнительная таблица, назовем ее «Клиенты в группе», строка которой будет иметь два внешних ключа: один будет ссылаться на первичный ключ в таблице «Клиент», а другой — на первичный ключ в таблице «Группа интересов». Таким образом в таблицу «Клиенты в группе» можно записывать любое количество людей и любое количество групп.

Итак, после определения таблиц, полей, индексов и связей между таблицами следует посмотреть на проектируемую базу данных в целом и проанализировать ее, используя правила нормализации, с целью устранения логических ошибок. Важность нормализации состоит в том, что она позволяет разбить большие отношения, как правило, содержащие большую избыточность информации, на более мелкие логические единицы, группирующие только данные, объединенные «по природе». Таким образом, идея нормализации заключается в следующем. Каждая таблица в реляционной базе данных удовлетворяет условию, в соответствии с которым в позиции на пересечении каждой строки и столбца таблицы всегда находится единственное значение, и никогда не может быть множества таких значений.

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

u данные легко обновлять или удалять;

u исключается возможность рассогласования копий данных;

u уменьшается возможность введения некорректных данных.

Процесс нормализации заключается в приведении таблиц в так называемые нормальные формы. Существует несколько видов нормальных форм: первая нормальная форма (1НФ), вторая нормальная форма (2НФ), третья нормальная форма (3НФ), нормальная форма Бойса-Кодда (НФБК), четвертая нормальная форма (4НФ), пятая нормальная форма (5НФ). С практической точки зрения, достаточно трех первых форм — следует учитывать время, необходимое системе для «соединения» таблиц при отображении их на экране. Поэтому мы ограничимся изучением процесса приведения отношений к первым трем формам.

Этот процесс включает:

u устранение повторяющихся групп (приведение к 1НФ);

u удаление частично зависимых атрибутов (приведение к 2НФ);

u удаление транзитивно зависимых атрибутов (приведение к 3НФ).

Приведение к первой нормальной форме

Когда поле в данной записи содержит более одного значения для каждого вхождения первичного ключа, такие группы данных называются повторяющимися группами. 1НФ не допускает наличия таких многозначных полей. Рассмотрим пример базы данных предприятия, содержащей таблицу «Отдел» со следующими значениями (атрибут, выделенный курсивом, является первичным ключом):

Для приведения этой таблицы к 1НФ мы должны устранить атрибут (поле) Расположение из таблицы «Отдел» и создать новую таблицу «Расположение отделов», в которой определить первичный ключ, являющийся комбинацией номера отдела и его расположения (Номер отдела+Расположение). Теперь для каждого расположения отдела существуют различные строки; тем самым мы устранили повторяющиеся группы.

Приведение ко второй нормальной форме

Следующий важный шаг в процессе нормализации состоит в удалении всех неключевых атрибутов, которые зависят только от части первичного ключа. Такие атрибуты называются частично зависимыми. Неключевые атрибуты заключают в себе информацию о данной сущности предметной области, но не идентифицируют ее уникальным образом. Например, предположим, что мы хотим распределить работников по проектам, ведущимся на предприятии. Для этого создадим таблицу «Проект» с составным первичным ключом, включающим номер работника и идентификатор проекта (Номер_работника+ИД_проекта).

В этой таблице возникает следующая проблема. Атрибуты «Назв_проекта», «Описание_проекта» и «Продукт» относятся к проекту как сущности и, следовательно, зависят от атрибута «ИД_проекта» (являющегося частью первичного ключа), но не от атрибута «Номер_работника». Следовательно, они являются частично зависимыми от составного первичного ключа.

То же самое можно сказать и об атрибуте «Фамилия», который зависит от атрибута «Номер_работника», но не зависит от атрибута «ИД_проекта».

Для нормализации этой таблицы (приведения ее в 2НФ) удалим из нее атрибуты «Номер_работника» и «Фамилия» и создадим другую таблицу (назовем ее «Работник в проекте»), которая будет содержать только эти два атрибута, и они же будут составлять ее первичный ключ.

Приведение к третьей нормальной форме

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

Предположим, например, что мы добавили поля «Номер_руководителя» и «Телефон» в таблицу «Проект», находящуюся в 2НФ (первичным ключом является поле «ИД_проекта»). Атрибут «Телефон» логически связан с атрибутом «Номер_руководителя», неключевым полем, но не с атрибутом «ИД_проекта», являющимся первичным ключом.

Для нормализации этой таблицы (приведения ее в 3НФ) удалим атрибут «Телефон», изменим «Номер_руководителя» на «Руководитель» и сделаем атрибут «Руководитель» внешним ключом, ссылающимся на атрибут «Номер_работника» (первичный ключ) в таблице «Работники». После этого таблицы «Проект» и «Работники» будут выглядеть следующим образом:

Теперь, когда мы научились проводить нормализацию таблиц с целью устранения избыточного дублирования данных и группирования информации в логически связанных единицах, необходимо сделать ряд замечаний по вопросам проектирования баз данных. Необходимо четко понимать, что разбиение информации на более мелкие единицы с одной стороны, способствует повышению надежности и непротиворечивости базы данных, а с другой стороны, снижает ее производительность, так как требуются дополнительные затраты процессорного времени (серверного или машины пользователя) на обратное «соединение» таблиц при представлении информации на экране. Иногда для достижения требуемой производительности нужно сделать отход от канонической нормализации, при этом ясно осознавая, что необходимо обеспечить меры по предотвращению противоречивости в данных. Поэтому всякое решение о необходимости того или иного действия по нормализации можно принимать только тщательно проанализировав предметную область и класс поставленной задачи. Может потребоваться несколько итераций для достижения состояния, которое будет желаемым компромиссом между четкостью представления и реальными возможностями техники. Здесь уже начинается искусство...

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

u кто будет иметь права (и какие) на использование базы данных;

u кто будет иметь права на модификацию, вставку и удаление данных;

u нужно ли делать различие в правах доступа;

u каким образом обеспечить общий режим защиты информации.

Глава 47.
Настройка BDE

Сущность BDE

Мощность и гибкость Delphi при работе с базами данных основана на низкоуровневом ядре — процессоре баз данных Borland Database Engine (BDE). Его интерфейс с прикладными программами называется Integrated Database Application Programming Interface (IDAPI). В принципе, сейчас не различают эти два названия (BDE и IDAPI) и считают их синонимами. BDE позволяет осуществлять доступ к данным как с использованием традиционного record-ориентированного (навигационного) подхода, так и с использованием set-ориентированного подхода, используемого в SQL-серверах баз данных. Кроме BDE, Delphi позволяет осуществлять доступ к базам данных, используя технологию (и, соответственно, драйверы) Open DataBase Connectivity (ODBC) фирмы Microsoft. Но, как показывает практика, производительность систем с использованием BDE гораздо выше, чем оных при использовании ODBC. ODBC-драйвера работают через специальный ODBC socket, который позволяет встраивать их в BDE.

Все инструментальные средства баз данных Borland — Paradox, dBase, Database Desktop — используют BDE. Все особенности, имеющиеся в Paradox или dBase, «наследуются» BDE, и поэтому этими же особенностями обладает и Delphi.

Алиасы

Таблицы сохраняются в базе данных. Некоторые СУБД сохраняют базу данных в виде нескольких отдельных файлов, представляющих собой таблицы (в основном, все локальные СУБД), в то время как другие состоят из одного файла, который содержит в себе все таблицы и индексы (InterBase). Например, таблицы dBase и Paradox всегда сохраняются в отдельных файлах на диске. Директория, содержащая dBase .DBF файлы или Paradox .DB файлы, рассматривается как база данных. Другими словами, любая директория, содержащая файлы в формате Paradox или dBase, рассматривается Delphi как единая база данных. Для переключения на другую базу данных нужно просто переключиться на другую директорию. InterBase сохраняет все таблицы в одном файле, имеющем расширение .GDB, поэтому этот файл и есть база данных InterBase.

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

Для создания алиаса запустите утилиту конфигурации BDE (программу BDECFG. EXE), находящуюся в директории, в которой располагаются динамические библиотеки BDE.

По умолчанию, при установке Delphi создается директорий IDAPI, в котором и располагаются указанные библиотеки; при этом иконка с BDECFG обязательно будет присутствовать в группе Delphi.

Главное окно утилиты настройки BDE имеет вид, изображенный выше. Для создания алиаса выберите страничку Aliases и нажмите кнопку New Alias. В появившемся диалоговом окне введите имя алиаса и выберите его тип (тип базы данных) из выпадающего списка. Тип алиаса может быть стандартным (STANDARD) для работы с локальными базами в формате dBase или Paradox или соответствовать наименованию SQL-сервера (InterBase, Sybase, Informix, Oracle).

В диалоговом окне добавления нового алиаса можно указать имя алиаса и тип базы данных. После создания нового алиаса его имя появится в списке алиасов на той же страничке Aliases. Однако просто создать алиас не достаточно. Вам нужно указать дополнительную информацию, содержание которой зависит от типа выбранной базы данных. Например, для баз данных Paradox и dBase (STANDARD) требуется указать лишь путь доступа к данным:

TYPE STANDARD

PATH c:\users\data

SQL-сервер InterBase требует задания десяти параметров, многие из которых можно оставить установленными по умолчанию (кроме, обычно, параметров SERVER NAME и USER NAME):

TYPE INTRBASE

PATH

SERVER NAME myserv:g:\users\contacts.gdb

USER NAME SYSDBA

OPEN MODE READ/WRITE

SCHEMA CACHE SIZE 8

LANGDRIVER Pdox ANSI Cyrillic

SQLQRYMODE

SQLPASSTHRU MODE SHARED AUTOCOMMIT

SCHEMA CACHE TIME -1

В этом примере база данных CONTACTS.GDB размещается в директории USERS, находящемся на диске G: Windows NT сервера, называющегося MYSERV. Имя пользователя при связи с базой данных по этому алиасу — SYSDBA. Остальные параметры — LANGDRIVER, SQLQRYMODE, SQLPASSTHRU MODE, SCHEMA CACHE SIZE и SCHEMA CACHE TIME рассмотрим подробней.

Параметр LANGDRIVER определяет языковый драйвер для доступа к базе данных. Для правильной работы с русскими буквами при работе с базой данных формата dBase нужно выбрать значение dBASE RUS cp866, при работе с базами данных формата Paradox и SQL-серверами (в том числе InterBase) — Pdox ANSI Cyrillic. Кроме того, на этапе создания базы данных InterBase необходимо указать CHARACTER SET (набор символов) WIN1251.

Параметр SQLQRYMODE появляется только в случае, если установлен Borland SQL Links для связи с SQL-серверами. Он определяет режим передачи SQL-запросов и может иметь три значения:

u NULL (пустая строка — режим по умолчанию) — запрос сначала посылается на SQL-сервер. Если сервер не может выполнить запрос, последний обрабатывается локально (это актуально для распределенных баз данных);

u SERVER — запрос посылается на SQL-сервер. Если сервер не может выполнить запрос, генерируется ошибка;

u LOCAL — запрос всегда выполняется на рабочей станции.

Параметр SQLPASSTHRU MODE определяет, могут ли запросы, передаваемые для выполнения на сервер (passthrouh SQL, использующие set-ориентированный подход), и стандартные вызовы BDE (использующие record ориентированный навигационный подход) обрабатываться в одном и том же сеансе соединения с базой данных (в одном и том же «коннекте») — быть SHARED. Он также может иметь три значения:

u SHARED AUTOCOMMIT (значение по умолчанию) — для каждой операции по одной строке таблицы автоматически стартует неявная транзакция, которая, в случае успеха, завершается оператором COMMIT (закрепляющим произведенные изменения). Такой подход наилучшим образом подходит для работы с локальными базами, но неэффективен для SQL-серверных баз данных, так как стартующие каждый раз новые транзакции значительно загружают сетевой траффик.

u SHARED NOAUTOCOMMIT — приложение должно явно стартовать и завершать транзакцию. Эта установка может привести к конфликтам в многопользовательской среде, где большое количество пользователей пытаются обновить одну и ту же строку таблицы.

u NOT SHARED — означает, что запросы, передаваемые для выполнения на сервер (passthrouh SQL), и стандартные вызовы BDE (методы Delphi) используют раздельные соединения («коннекты») с базой данных. Для управления транзакциями через passthrouh SQL необходимо устанавливать именно это значение, иначе passthrouh SQL и методы Delphi могут интерферировать друг с другом, что, в свою очередь, может привести к непредсказуемым результатам.

В параметре SCHEMA CACHE SIZE указывается число таблиц базы данных, информация о структуре которых будет кэшироваться, обеспечивая быстрый доступ к метаданным. Значение этого параметра может быть целым числом от 0 до 32. По умолчанию установлено число 8.

Параметр SCHEMA CACHE TIME определяет время, в течение которого будет кэшироваться информация из таблиц базы данных. Может иметь следующие значения:

u -1 (значение по умолчанию) — информация из таблиц кэшируется до самого закрытия базы данных;

u 0 — информация из таблиц вообще не кэшируется;

u 1–2, 147, 483, 647 — информация из таблиц кэшируется в течение указанного времени (в секундах).

Напомним, что установки по умолчанию параметров SQLQRYMODE, SQLPASSTHRU MODE, SCHEMA CACHE SIZE и SCHEMA CACHE TIME обеспечивают достаточно оптимальный режим работы с базой данных. Экспериментировать с ними для достижения наибольшей эффективности работы с конкретной базой данных желательно только после накопления некоторого опыта работы с BDE.

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

u для доступа по протоколу TCP/IP IB_SERVER:PATH\DATABASE.GDB.

Например, путь к базе на Windows NT сервере будет выглядеть следующим образом — mynt:c:\ib\base.gdb, а к базе на UNIX-сервере — myunix:/ib/base.gdb.

u для доступа по протоколу IPX/SPX IB_SERVER@PATH\DATABASE.GDB.
Например: mynw@sys:ib\base.gdb.

u для доступа по протоколу NETBEUI \\IB_SERVER\PATH\
DATABASE.GDB. Например: \\mynt\c:\ib\base.gdb.

В этих примерах mynt — имя сервера Windows NT, myunix — имя сервера UNIX-системы, mynw — имя сервера Novell NetWare, sys — имя тома NetWare, ib — директорий, в котором находится база данных, base.gdb — имя базы данных InterBase. Для того чтобы правильно указать имя сервера Oracle, нужно писать имя по правилам Oracle — перед именем поставить @.

Важно: При доступе к SQL-серверным базам данных параметр PATH должен оставаться пустым, иначе ядро баз данных не сможет определить истинный путь к вашей базе, и будет сгенерирована ошибка.

Системная информация утилиты настройки BDE

Итак, мы познакомились с наиболее важной возможностью утилиты настройки BDE — созданием и редактированием алиасов, определяющих параметры доступа к базам данных. Однако, утилита настройки BDE позволяет специфицировать не только алиасы, но и драйверы для доступа к базам данных, а также различную системную информацию, составляющую операционное окружение этих самых алиасов. Системная информация располагается на страничках System, Date, Time, Number. Рассмотрим подробней эти странички.

u System

Определяет память и технические установки для таблиц в формате Paradox. Установленные по умолчанию значения обеспечивают оптимальные параметры работы с таблицами Paradox. Однако, если у вас возникают проблемы, Вы можете изменить минимальный и максимальный размер кэш-буфера (MINBUFSIZE, MAXBUFSIZE; значения по умолчанию соответственно 128 и 2048 Кб — должны быть меньше размера физической памяти, доступной для Windows), а также максимальную величину стандартной (low) памяти, используемой BDE для доступа к базе (LOW MEMORY USAGE LIMIT, значение по умолчанию — 32 Кб). Вы можете также специфицировать языковый драйвер по умолчанию (LANGDRIVER), однако языковый драйвер, установленный в алиасе, имеет больший приоритет.

Аналогичным образом (и с теми же оговорками относительно приоритета) вы можете изменить параметр SQLQRYMODE, если у вас установлен Borland SQL Links. С помощью параметра LOCAL SHARE можно управлять возможностью одновременного доступа к таблицам из разных приложений через BDE и не через BDE (например, с использованием своей библиотеки доступа). Значение по умолчанию — false, что означает запрет такой работы. Параметр AUTO ODBC определяет режим выборки параметров алиасов, основанных на ODBC-драйверах. Установленное по умолчанию значение false означает, что параметры берутся из конфигурационного файла BDE (IDAPI.CFG). Если вы желаете брать ODBC-алиасы из файла ODBC.INI, установите этот параметр в true. Стоит упомянуть и о параметре DEFAULT DRIVER, который используется всякий раз, когда в названии таблицы отсутствует расширение и таблица имеет формат локальных СУБД.

Остальные параметры (VERSION и SYSFLAGS) являются системными, и их не следует изменять.

u Date

Определяет установки, используемые при конвертации строковых значений в дату и обратно. Основаны на значениях, устанавливаемых для каждой страны и зафиксированных в файле WIN.INI (секция intl). Однако, все параметры формата даты, времени и чисел BDE берет не из конфигурационного файла BDE, куда попадают данные установки, а из соответствующих переменных модуля SysUtils. По-видимому, эта ситуация произошла по недосмотру разработчиков. Поэтому мы перечислим параметры страничек Date, Time, Number и укажем те переменные, с помощью которых действительно можно управлять изменением системной информации.

Среди параметров даты имеются следующие:

u SEPARATOR — символ, используемый для разделения дня, месяца и года в дате. Ему соответствует переменная DateSeparator (тип Char). Обычно имеет значения «.», «-», «/». Значение по умолчанию берется из параметра sDate секции [intl] файла WIN.INI.

u MODE — управляет порядком следования месяца, дня и года в дате и может иметь значения:

u 0 — для MDY (месяц-день-год)

u 1 — для DMY (день-месяц-год)

u 2 — для YMD (год-месяц-день).

Прямого соответствия переменным модуля SysUtils не имеет. Вместо него, а также вместо параметров FOURDIGITYEAR, YEARBIASED, LEADINGZEROM и LEADINGZEROD используются переменные ShortDateFormat (string[15]) и LongDateFormat (string[31]). В этих переменных могут применяться только символ-разделитель дат (DateSeparator) и символьные выражения типа m, mm, d, dd, yy и yyyy, определяющие месяц, день и год. Например, формат «короткой» даты может выглядеть как:

dd.MM.yy

а формат «длинной» даты — как:

d MMMM yyyy 'г.'

Значения по умолчанию берутся из параметров sShortDate и sLongDate секции [intl] файла WIN.INI.

При отображении даты и времени в качестве символа-разделителя можно использовать любой символ, в том числе и отличный от символа DateSeparator (или TimeSeparator).

Однако при попытке вставить в таком формате дату или время BDE «выдаст» ошибку, связанную с неправильным форматом даты/времени. Поэтому для корректной вставки данных в таблицы необходимо, чтобы в переменной ShortDateFormat символ-разделитель совпадал с символом DateSeparator, а в переменной LongTimeFormat (и ShortTimeFormat) — с символом TimeSeparator.

u Time

Определяет установки, используемые при конвертации строковых значений во время и обратно. Также основаны на значениях, устанавливаемых для каждой страны и зафиксированных в файле WIN.INI (секция [intl]). Аналогично дате, для формата времени совместно с ShortDateFormat используются переменные LongTimeFormat (именно LongTimeFormat, а не ShortTimeFormat!) и TimeSeparator. Значения по умолчанию вычисляются по параметрам iTime и iTLZero секции [intl] файла WIN.INI. Кроме указанных переменных, для форматирования можно использовать переменные TimeAMString (основана на параметре s1159 секции [intl]) и TimePMString (основана на параметре s2359 секции [intl]).

u Number

Описывает трактовку чисел BDE. В частности, определяет символ для десятичной точки (переменная DecimalSeparator, основана на параметре sDecimal секции [intl]), разделитель для тысяч (переменная ThousandSeparator, основана на параметре sThousand секции [intl]), количество знаков после запятой (переменная CurrencyDecimals, основана на параметре sCurrDigits секции [intl]) и наличие лидирующих нулей.

Утилита настройки BDE сохраняет всю конфигурационную информацию в файле IDAPI.CFG. Этот файл с предустановленными ссылками на драйверы и некоторыми стандартными алиасами создается при установке Delphi. Кроме того, он создается при установке файлов редистрибуции BDE (т.е. когда вы переносите BDE и SQL Links на другие компьютеры).

Установка драйверов ODBC и других драйверов

При установке SQL Links для какого-либо SQL-сервера вам не требуется «вручную» добавлять драйверы связи с этим сервером — программа установки SQL Links сделает это автоматически. Заметим, что при установке Delphi Client-Server автоматически устанавливаются также и SQL Links, так что отдельно их инсталляцию проводить не требуется. Вам останется только подправить некоторые параметры (например, языковый драйвер) для установленных драйверов. Но кроме «родных» SQL Links, для доступа к SQL-серверам (и локальным базам данных) можно использовать ODBC-драйверы.

Delphi использует Microsoft ODBC Driver Manager. Если вы имеете версию ODBC Driver Manager, отличную от указанной — сохраните существующие файлы ODBC.DLL и ODBCINST.DLL и скопируйте файлы ODBC.NEW и ODBCINST.NEW из директория IDAPI в ваш ODBC-директорий (по умолчанию, это поддиректорий SYSTEM в «виндоусовом» директории) и переименуйте их соответственно в ODBC.DLL и ODBCINST. DLL.

Установка BDE-алиаса, основанного на ODBC-драйвере состоит из трех шагов. На первом шаге нужно создать ODBC-алиас, «опирающийся» на ODBC-драйвер, на втором шаге — создать над ним надстройку в виде BDE-драйвера, а на третьем шаге — определить стандартный BDE-алиас.

Установка ODBC-драйвера начинается с проверки того, какие драйверы уже находятся в вашей системе. Для этого в Панели Управления (Control Panel) найдите иконку ODBC, которая запускает утилиту конфигурации ODBC. В ее диалоговом окне представлен список ODBC-алиасов (Data Source) с указанием в скобках ODBC-драйверов, на которых они основаны.

В диалоговом окне настройки FoxPro ODBC-алиаса нужно назначить имя этого алиаса (Data Source Name) и указать директорий, в котором находятся файлы базы данных.

Для создания нового ODBC-алиаса сначала нужно выбрать ODBC-драйвер. Для этого нажмите кнопку Add, имеющуюся в правой части диалогового окна Data Sources. Если в списке установленных ODBC-драйверов не окажется нужного вам драйвера, вернитесь к окну Data Sources и установите новый драйвер, указав его местонахождение (с помощью кнопки Drivers). После выбора ODBC-драйвера перед вами появится диалоговое окно, содержимое которого зависит от выбранного вами драйвера и в котором вы сможете произвести настройку ODBC-алиаса, определив его имя (Data Source Name), выбрав версию продукта и указав директорий, в котором находятся файлы базы данных. После нажатия кнопки OK ODBC-алиас будет создан, и вы вернетесь в окно Data Sources.

Диалоговое окно Add ODBC Driver позволяет установить драйверы для Access, FoxPro и других баз данных.

Следующий шаг состоит в создании BDE-надстройки над ODBC-алиасом. Для этого вам нужно убедиться, что в вашем директории IDAPI имеется файл IDODBC01. DLL, в противном случае нужно заново установить BDE. После этого можно загрузить утилиту настройки BDE. Если вы нажмете кнопку New ODBC Driver на страничке Drivers, вы увидите диалоговое окно Add ODBC Driver. Название BDE-драйвера, основанного на ODBC-алиасе, по умолчанию, должно начинаться с букв ODBC_. Поэтому такие буквы уже вынесены перед названием драйвера, так что вам не нужно их вводить. Введите любое название драйвера и выберите из выпадающих списков сначала ODBC-драйвер, а затем — созданный вами на его основе ODBC-алиас (Default Data Source Name).

Таким образом, мы создали BDE-драйвер, основанный на ODBC-алиасе. После этого BDE-алиас создается стандартным способом.

Глава 48.
Создание таблиц с помощью Database Desktop

Утилита Database Desktop

Database Desktop — это утилита, во многом похожая на Paradox, которая поставляется вместе с Delphi для интерактивной работы с таблицами различных форматов локальных баз данных — Paradox и dBase, а также SQL-серверных баз данных InterBase, Oracle, Informix, Sybase (с использованием SQL Links).

Исполняемый файл утилиты называется DBD.EXE, расположен он, как правило, в директории, называемом DBD (при установке по умолчанию). Для запуска Database Desktop просто дважды щелкните по ее иконке.

После старта Database Desktop выберите команду меню File ê New ê Table для создания новой таблицы. Перед вами появится диалоговое окно выбора типа таблицы. Вы можете выбрать любой формат из предложенного, включая различные версии одного и того же формата.

После выбора типа таблицы, Database Desktop представит вам диалоговое окно, специфичное для каждого формата, в котором вы сможете определить поля таблицы и их тип.

Имя поля в таблице формата Paradox представляет собой строку, написание которой подчиняется следующим правилам:

u Имя должно быть не длиннее 25 символов.

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

u Имя не должно содержать квадратные, круглые или фигурные скобки [], () или {}, тире, а также комбинацию символов «тире» и «больше» (—>).

u Имя не должно быть только символом #, хотя этот символ может присутствовать в имени среди других символов. Хотя Paradox поддерживает точку (.) в названии поля, лучше ее избегать, поскольку точка зарезервирована в Delphi для других целей.

Имя поля в таблице формата dBase представляет собой строку, написание которой подчиняется правилам, отличным от Paradox:

u Имя должно быть не длиннее 10 символов.

u Пробелы в имени недопустимы.

Таким образом, вы видите, что имена полей в формате dBase подчиняются гораздо более строгим правилам, нежели таковые в формате Paradox. Однако, мы еще раз хотим подчеркнуть, что если перед вами когда-либо встанут вопросы совместимости, то лучше сразу закладывать эту совместимость — давать полям имена, подчиняющиеся более строгим правилам.

Укажем еще правила, которым подчиняется написание имен полей в формате InterBase.

u Имя должно быть не длиннее 31 символа.

u Имя должно начинаться с букв A-Z, a-z.

u Имя поля может содержать буквы (A-Z, a-z), цифры, знак $ и символ подчеркивания (_).

u Пробелы в имени недопустимы.

u Запрещается использовать зарезервированные слова InterBase.

Следующий (после выбора имени поля) шаг состоит в задании типа поля. Типы полей очень сильно различаются друг от друга, в зависимости от формата таблицы. Для получения списка типов полей перейдите к столбцу Type, а затем нажмите пробел или щелкните правой кнопкой мышки. Приведем списки типов полей, характерные для форматов Paradox, dBase и InterBase. Итак, поля таблиц формата Paradox могут иметь следующий тип (для ввода типа поля можно набрать только подчеркнутые буквы или цифры):

Alpha

Строка длиной 1-255 байт, содержащая любые печатаемые символы.

Number

Числовое поле длиной 8 байт, значение которого может быть положительным и отрицательным. Диапазон чисел — от 10-308 до 10308 с 15 значащими цифрами.

$ (Money)

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

Short

Числовое поле длиной 2 байта, которое может содержать только целые числа в диапазоне от -32768 до 32767.

Long Integer

Числовое поле длиной 4 байта, которое может содержать целые числа в диапазоне от -2147483648 до 2147483648.

# (BCD)

Числовое поле, содержащее данные в формате BCD (Binary Coded Decimal). Скорость вычислений немного меньше, чем в других числовых форматах, однако точность — гораздо выше. Может иметь 0-32 цифр после десятичной точки.

Date

Поле даты длиной 4 байта, которое может содержать дату от 1 января 9999 г. до нашей эры — до 31 декабря 9999 г. нашей эры. Корректно обрабатывает високосные года и имеет встроенный механизм проверки правильности даты.

Time

Поле времени длиной 4 байта, содержит время в миллисекундах от полуночи и ограничено 24 часами.

@ (Timestamp)

Обобщенное поле даты длиной 8 байт — содержит и дату и время.

Memo

Поле для хранения символов, суммарная длина которых более 255 байт. Может иметь любую длину. При этом размер, указываемый при создании таблицы, означает количество символов, сохраняемых в таблице (1-240) — остальные символы сохраняются в отдельном файле с расширением .MB.

Formatted Memo

Поле, аналогичное Memo, с добавлением возможности задавать шрифт текста. Также может иметь любую длину. При этом размер, указываемый при создании таблицы, означает количество символов, сохраняемых в таблице (0-240) — остальные символы сохраняются в отдельном файле с расширением .MB. Однако, Delphi в стандартной поставке не обладает возможностью работать с полями типа Formatted Memo.

Graphic

Поле, содержащее графическую информацию. Может иметь любую длину. Смысл размера — такой же, как и в Formatted Memo. Database Desktop «умеет» создавать поля типа Graphic, однако наполнять их можно только в приложении.

OLE

Поле, содержащее OLE-данные (Object Linking and Embedding) — образы, звук, видео, документы — которые для своей обработки вызывают создавшее их приложение. Может иметь любую длину. Смысл размера — такой же, как и в Formatted Memo. Database Desktop «умеет» создавать поля типа OLE, однако наполнять их можно только в приложении. Delphi «напрямую» не умеет работать с OLE-полями, но это легко обходится путем использования потоков.

Logical

Поле длиной 1 байт, которое может содержать только два значения — T (true, истина) или F (false, ложь). Допускаются строчные и прописные буквы.

+ (Autoincrement)

Поле длиной 4 байта, содержащее нередактируемое (read-only) значение типа long integer. Значение этого поля автоматически увеличивается (начиная с 1) с шагом 1 — это очень удобно для создания уникального идентификатора записи (физический номер записи не может служить ее идентификатором, поскольку в Paradox таковой отсутствует. В InterBase также отсутствуют физические номера записей, но отсутствует и поле Autoincrement. Его с успехом заменяет встроенная функция Gen_id, которую удобней всего применять в триггерах).

Binary

Поле, содержащее любую двоичную информацию. Может иметь любую длину. При этом размер, указываемый при создании таблицы, означает количество символов, сохраняемых в таблице (0-240) — остальные символы сохраняются в отдельном файле с расширением .MB. Это полнейший аналог поля BLOb в InterBase.

Bytes

Строка цифр длиной 1-255 байт, содержащая любые данные.

Поля таблиц формата dBase могут иметь следующий тип (для ввода типа поля можно набрать только подчеркнутые буквы или цифры):

Character (alpha)

Строка длиной 1-254 байт, содержащая любые печатаемые символы.

Float (numeric)

Числовое поле размером 1-20 байт в формате с плавающей точкой, значение которого может быть положительным и отрицательным. Может содержать очень большие величины, однако следует иметь в виду постоянные ошибки округления при работе с полем такого типа. Число цифр после десятичной точки (параметр Dec в DBD) должно быть по крайней мере на 2 меньше, чем размер всего поля, поскольку в общий размер включаются сама десятичная точка и знак.

Number (BCD)

Числовое поле размером 1-20 байт, содержащее данные в формате BCD (Binary Coded Decimal). Скорость вычислений немного меньше, чем в других числовых форматах, однако точность — гораздо выше. Число цифр после десятичной точки (параметр Dec в DBD) также должно быть по крайней мере на 2 меньше, чем размер всего поля, поскольку в общий размер включаются сама десятичная точка и знак.

Date

Поле даты длиной 8 байт. По умолчанию, используется формат короткой даты (ShortDateFormat).

Logical

Поле длиной 1 байт, которое может содержать только значения «истина» или «ложь» — T, t, Y, y (true, истина) или F, f, N, n (false, ложь). Допускаются строчные и прописные буквы. Таким образом, в отличие от Paradox, допускаются буквы «Y» и «N» (сокращение от Yes и No).

Memo

Поле для хранения символов, суммарная длина которых более 255 байт. Может иметь любую длину. Это поле хранится в отдельном файле. Database Desktop не имеет возможности вставлять данные в поле типа Memo.

OLE

Поле, содержащее OLE-данные (Object Linking and Embedding) — образы, звук, видео, документы — которые для своей обработки вызывают создавшее их приложение. Может иметь любую длину. Это поле также сохраняется в отдельном файле. Database Desktop «умеет» создавать поля типа OLE, однако наполнять их можно только в приложении. Delphi «напрямую» не умеет работать с OLE-полями, но это легко обходится путем использования потоков.

Binary

Поле, содержащее любую двоичную информацию. Может иметь любую длину. Данное поле сохраняется в отдельном файле с расширением .DBT. Это полнейший аналог поля BLOb в InterBase.

Поля таблиц формата InterBase могут иметь следующий тип:

SHORT

Числовое поле длиной 2 байта, которое может содержать только целые числа в диапазоне от -32768 до 32767.

LONG

Числовое поле длиной 4 байта, которое может содержать целые числа в диапазоне от -2147483648 до 2147483648.

FLOAT

Числовое поле длиной 4 байта, значение которого может быть положительным и отрицательным. Диапазон чисел — от 3.4*10-38 до 3.4*1038 с 7 значащими цифрами.

DOUBLE

Числовое поле длиной 8 байт (длина зависит от платформы), значение которого может быть положительным и отрицательным. Диапазон чисел — от 1.7*10-308 до 1.7*10308 с 15 значащими цифрами.

CHAR

Строка символов фиксированной длины (0-32767 байт), содержащая любые печатаемые символы. Число символов зависит от Character Set, установленного в InterBase для данного поля или для всей базы данных (например, для символов в кодировке Unicode число символов будет в два раза меньше длины строки).

VARCHAR

Строка символов переменной длины (0-32767 байт), содержащая любые печатаемые символы. Число символов также зависит от Character Set, установленного в InterBase для данного поля или для всей базы данных.

DATE

Поле даты длиной 8 байт, значение которого может быть от 1 января 100 года до 11 декабря 5941 года (время также содержится).

BLOB

Поле, содержащее любую двоичную информацию. Может иметь любую длину. Database Desktop не имеет возможности редактировать поля типа BLOB.

ARRAY

Поле, содержащее массивы данных. InterBase позволяет определять массивы, имеющие размерность 16. Поле может иметь любую длину. Однако, Database Desktop не имеет возможности не только редактировать поля типа ARRAY, но и создавать их.

TEXT BLOB

Подтип BLOB-поля, содержащее только текстовую информацию. Может иметь любую длину. Database Desktop не имеет возможности редактировать поля типа TEXT BLOB.

Итак, мы изучили все типы полей, являющиеся «родными» для Delphi.

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

После создания таблицы, с ней можно связать некоторые свойства, перечень которых зависит от формата таблицы. Так, для таблиц формата Paradox можно задать:

u Validity Checks (проверка правильности) — относится к полю записи и определяет минимальное и максимальное значение, а также значение по умолчанию. Кроме того, позволяет задать маску ввода.

u Table Lookup (таблица для «подсматривания») — позволяет вводить значение в таблицу, используя уже существующее значение в другой таблице.

u Secondary Indexes (вторичные индексы) — позволяют получить доступ к данным в порядке, отличном от порядка, задаваемого первичным ключом.

u Referential Integrity (ссылочная целостность) позволяет задать связи между таблицами и поддерживать эти связи на уровне ядра. Обычно задается после создания всех таблиц в базе данных.

u Password Security (парольная защита) — позволяет закрыть таблицу паролем.

u Table Language (язык таблицы) — позволяет задать для таблицы языковый драйвер.

В таблицах dBase не существует первичных ключей. Однако, это обстоятельство можно преодолеть путем определения уникальных (Unique) и поддерживаемых (Maintained) индексов (Indexes). Кроме того, для таблиц dBase можно определить и язык таблицы (Table Language) — языковый драйвер, управляющий сортировкой и отображением символьных данных.

Определения дополнительных свойств таблиц всех форматов доступны через кнопку Define (для таблиц InterBase данная кнопка называется Define Index... и позволяет определять лишь только индекс, но не первичный ключ) в правой верхней части окна (группа Table Properties). Причем, все эти действия можно проделывать не только при создании таблицы, но и тогда, когда она уже существует. Для этого используется команда Table ê Restructure Table (для открытой в данный момент таблицы) или Utilities ê Restructure (с возможностью выбора таблицы). Однако, если вы желаете изменить структуру или добавить новые свойства для таблицы, которая в данный момент уже используется другим приложением, Database Desktop откажет вам в этом, поскольку данная операция требует монопольного доступа к таблице. Но зато все произведенные в структуре изменения сразу же начинают «работать» — например, если вы определите ссылочную целостность для пары таблиц, то при попытке вставить в дочернюю таблицу данные, отсутствующие в родительской таблице, в Delphi возникнет исключительное состояние.

В заключение отметим еще часто используемую очень полезную возможность Database Desktop. Создавать таблицу любого формата можно не только «с чистого листа», но и путем копирования структуры уже существующей таблицы. Для этого достаточно воспользоваться кнопкой Borrow, имеющейся в левом нижнем углу окна. Появляющееся диалоговое окно позволит вам выбрать существующую таблицу и включить/выключить дополнительные опции, совпадающие с уже перечисленными свойствами таблиц. Это наиболее легкий способ создания таблиц.

Глава 49.
Создание таблиц с помощью SQL-запросов

Базы данных и язык SQL

Database Desktop не обладает всеми возможностями по управлению SQL-серверными базами данных. Поэтому с помощью Database Desktop удобно создавать или локальные базы данных или только простейшие SQL-серверные базы данных, состоящие из небольшого числа таблиц, не очень сильно связанных друг с другом. Если же вам необходимо создать базу данных, состоящую из большого числа таблиц, имеющих сложные взаимосвязи, можно воспользоваться языком SQL (вообще говоря, для этих целей лучше всего использовать специализированные CASE-средства, которые позволяют в интерактивном режиме сгенерировать всю структуру базы данных и сформировать все связи; описание двух наиболее удачных CASE-средств — System Architect и S-Designor). При этом можно воспользоваться компонентом Query в Delphi, каждый раз посылая по одному SQL-запросу, а можно записать всю последовательность SQL-предложений в один так называемый скрипт и послать его на выполнение, используя Windows Interactive SQL (WISQL.EXE) — интерактивное средство посылки SQL-запросов к InterBase (в том числе и локальному InterBase), входящее в поставку Delphi. Конечно, для этого нужно хорошо знать язык SQL, но сложного в этом ничего нет! Конкретные реализации языка SQL незначительно отличаются в различных SQL-серверах, однако базовые предложения остаются одинаковыми для всех реализаций. Практика показывает, что если нет необходимости создавать таблицы во время выполнения программы, то лучше воспользоваться WISQL.

Создание таблиц с помощью SQL

Если вы хотите воспользоваться компонентом TQuery, сначала поместите его на форму. После этого настройте свойство DatabaseName на нужный вам алиас (если базы данных еще не существует, удобней создать ее в WISQL командой File ê Create Database..., а затем уже настроить на нее новый алиас). После этого можно ввести SQL-предложение в свойство SQL. Для выполнения запроса, изменяющего структуру, вставляющего или обновляющего данные на сервере, нужно вызвать метод ExecSQL компонента TQuery. Для выполнения запроса, получающего данные с сервера (т.е. запроса, в котором основным является оператор SELECT), нужно вызвать метод Open компонента TQuery. Это связано с тем, что BDE при посылке запроса типа SELECT открывает так называемый курсор, с помощью которого осуществляется навигация по выборке данных.

Как показывает опыт, проще воспользоваться утилитой WISQL. Для этого в WISQL выберите команду File ê Run an ISQL Script... и выберите файл, в котором записан ваш скрипт, создающий базу данных. После нажатия кнопки OK ваш скрипт будет выполнен, и в нижнее окно будет выведен протокол его работы.

Приведем упрощенный синтаксис SQL-предложения для создания таблицы на SQL-сервере InterBase (более полный синтаксис можно посмотреть в online-справочнике по SQL, поставляемом с локальным InterBase):

CREATE TABLE table

(<col_def> [, <col_def> | <tconstraint> ...]);

где

u table — имя создаваемой таблицы;

u <col_def> — описание поля;

u <tconstraint> — описание ограничений и/или ключей (квадратные скобки [] означают необязательность, вертикальная черта | означает «или»).

Описание поля состоит из наименования поля и типа поля (или домена), а также дополнительных ограничений, накладываемых на поле:

<col_def> = col {datatype | COMPUTED BY (<expr>) | domain}

[DEFAULT {literal | NULL | USER}]

[NOT NULL] [<col_constraint>]

[COLLATE collation]

где

u col — имя поля;

u datatype — любой правильный тип SQL-сервера (для InterBase такими типами являются — SMALLINT, INT GER, FLOAT, DOUBLE PRECISION, DECIMAL, NUME IC, DATE, CHAR, VARCHAR, NCHAR, BLOB), символьные типы могут иметь CHARACTER SET — набор символов, определяющий язык страны. Для русского языка следует задать набор символов WIN1251;

u COMPUTED BY (<expr>) — определение вычисляемого на уровне сервера поля, где <expr> — правильное SQL-выражение, возвращающее единственное значение;

u domain — имя домена (обобщенного типа), определенного в базе данных;

u DEFAULT — конструкция, определяющая значение поля по умолчанию;

u NOT NULL — конструкция, указывающая на то, что поле не может быть пустым;

u COLLATE — предложение, определяющее порядок сортировки для выбранного набора символов (для поля типа BLOB не применяется). Русский набор символов WIN1251 имеет 2 порядка сортировки — WIN1251 и PXW_CYRL. Для правильной сортировки, включающей большие буквы, следует выбрать порядок PXW_CYRL.

Описание ограничений и/или ключей включает в себя предложения CONSTRAINT или предложения, описывающие уникальные поля, первичные, внешние ключи, а также ограничения CHECK (такие конструкции могут определяться как на уровне поля, так и на уровне таблицы в целом, если они затрагивают несколько полей):

<tconstraint> = [CONSTRAINT constraint <tconstraint_def>]

<tconstraint>

Здесь

<tconstraint_def> = {{PRIMARY KEY | UNIQUE} (col[,col...])

| FOREIGN KEY (col [, col ...]) REFERENCES other_table

| CHECK (<search_condition>)}

<search_condition> =

{<val> <operator> {<val> | (<select_one>)}

| <val> [NOT] BETWEEN <val> AND <val>

| <val> [NOT] LIKE <val> [ESCAPE <val>]

| <val> [NOT] IN (<val> [, <val> ...] |

<val> = {

col [<array_dim>] | <constant> | <expr> | <function>

| NULL | USER | RDB$DB_KEY } [COLLATE collation]

<constant> = num | "string" | charsetname "string"

<function> = {

COUNT (* | [ALL] <val> | DISTINCT <val>)

| SUM ([ALL] <val> | DISTINCT <val>)

| AVG ([ALL] <val> | DISTINCT <val>)

| MAX ([ALL] <val> | DISTINCT <val>)

| MIN ([ALL] <val> | DISTINCT <val>)

| CAST (<val> AS <datatype>)

| UPPER (<val>)

| GEN_ID (generator, <val>)

}

<operator> = {= | < | > | <= | >= | !< | !> | <> | !=}

<select_one> — выражение SELECT по одному полю, которое возвращает в точности одно значение.

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

Простая таблица с конструкцией PRIMARY KEY на уровне поля

CREATE TABLE REGION (

REGION REGION_NAME NOT NULL PRIMARY KEY,

POPULATION INTEGER NOT NULL);

Предполагается, что в базе данных определен домен REGION_NAME, например, следующим образом:

CREATE DOMAIN REGION_NAME

AS VARCHAR(40) CHARACTER SET WIN1251 COLLATE PXW_CYRL;

Таблица с предложением UNIQUE как на уровне поля, так и на уровне таблицы

CREATE TABLE GOODS (

MODEL SMALLINT NOT NULL UNIQUE,

NAME CHAR(10) NOT NULL,

ITEMID INTEGER NOT NULL, CONSTRAINT MOD_UNIQUE

UNIQUE (NAME, ITEMID));

Таблица с определением первичного ключа, внешнего ключа и конструкции CHECK, а также символьных массивов

CREATE TABLE JOB (

JOB_CODE JOBCODE NOT NULL,

JOB_GRADE JOBGRADE NOT NULL,

JOB_REGION REGION_NAME NOT NULL,

JOB_TITLE VARCHAR(25) CHARACTER SET WIN1251 COLLATE

PXW_CYRL NOT NULL,

MIN_SALARY SALARY NOT NULL,

MAX_SALARY SALARY NOT NULL,

JOB_REQ BLOB(400,1) CHARACTER SET WIN1251,

LANGUAGE_REQ VARCHAR(15) [5],

PRIMARY KEY (JOB_CODE, JOB_GRADE, JOB_REGION),

FOREIGN KEY (JOB_REGION) REFERENCES REGION (REGION),

CHECK (MIN_SALARY < MAX_SALARY));

Данный пример создает таблицу, содержащую информацию о работах (профессиях). Типы полей основаны на доменах JOBCODE, JOBGRADE, REGION_NAME и SALARY. Определен массив LANGUAGE_REQ, состоящий из 5 элементов типа VARCHAR(15). Кроме того, введено поле JOB_REQ, имеющее тип BLOB с подтипом 1 (текстовый блоб) и размером сегмента 400. Для таблицы определен первичный ключ, состоящий из трех полей JOB_CODE, JOB_GRADE и JOB_REGION. Далее, определен внешний ключ (JOB_REGION), ссылающийся на поле REGION таблицы REGION. И, наконец, включено предложение CHECK, позволяющее производить проверку соотношения для двух полей и вызывать исключительное состояние при нарушении такого соотношения.

Таблица с вычисляемым полем

CREATE TABLE SALARY_HISTORY (

EMP_NO EMPNO NOT NULL,

CHANGE_DATE DATE DEFAULT "NOW" NOT NULL,

UPDATER_ID VARCHAR(20) NOT NULL,

OLD_SALARY SALARY NOT NULL,

PERC_CHANGE DOUBLE PRECISION DEFAULT 0 NOT NULL

CHECK (PERC_CHANGE BETWEEN -50 AND 50),

NEW_SALARY COMPUTED BY

(OLD_SALARY + OLD_SALARY * PERC_CHANGE / 100),

PRIMARY KEY (EMP_NO, CHANGE_DATE, UPDATER_ID),

FOREIGN KEY (EMP_NO) REFERENCES EMPLOYEE (EMP_NO));

Данный пример создает таблицу, где среди других полей имеется вычисляемое (физически не существующее) поле NEW_SALARY, значение которого вычисляется по значениям двух других полей (OLD_SALARY и PERC_CHANGE).

Глава 50.
Компоненты работы с базами данных

Класс TDataSet

TDataSet класс — один из наиболее важных объектов БД. Чтобы начать работать с ним, вы должны взглянуть на следующую иерархию:

u TDataSet

u TDBDataSet

u TTable

u TQuery

u TStoredProc

TDataSet содержит абстрактные методы там, где должно быть непосредственное управление данными. TDBDataSet знает, как обращаться с паролями и то, что нужно сделать, чтобы присоединить вас к определенной таблице. TTable знает (то есть, уже все абстрактные методы переписаны), как обращаться с таблицей, ее индексами TQuery имеет определенные методы для обработки SQL запросов.

TDataSet — инструмент, который вы будете использовать чтобы открыть таблицу, и перемещаться по ней. Конечно, вы никогда не будете непосредственно создавать объект типа TDataSet. Вместо этого, вы будете использовать TTable, TQuery или других потомков TDataSet (например, TQBE). Полное понимание работы системы, и точное значение TDataSet, будут становиться все более ясными по мере прочтения этой главы.

На наиболее фундаментальном уровне, Dataset это просто набор записей.

Любой dataset состоит из ряда записей (каждая содержит N полей) и указатель на текущую запись.

В большинстве случаев dataset будет иметь a прямое, один к одному, соответствие с физической таблицей, которая существует на диске. Однако, в других случаях вы можете исполнять запрос или другое действие, возвращающие dataset, который содержит либо любое подмножество записей одной таблицы, либо объединение (join) между несколькими таблицами. В тексте будут иногда использоваться термины DataSet и TTable как синонимы.

Итак, самое время начать исследование TDataSet. Как только вы познакомитесь с его возможностями, вы начнете понимать, какие методы использует Delphi для доступа к данным, хранящимся на диске в виде БД. Ключевой момент здесь — не забывать, что почти всякий раз, когда программист на Delphi открывает таблицу, он будет использовать TTable или TQuery, которые являются просто некоторой надстройкой над TDataSet.

Открытие и закрытие DataSet

Если вы используете TTable для доступа к таблице, то при открытии данной таблицы заполняются некоторые свойства TTable (количество записей RecordCount, описание структуры таблицы).

Прежде всего, вы должны поместить во время дизайна на форму объект TTable и указать, с какой таблицей хотите работать. Для этого нужно заполнить в Инспекторе объектов свойства DatabaseName и TableName. В DatabaseName можно либо указать директорию, в которой лежат таблицы в формате dBase или Paradox (например, C:\DELPHI\DEMOS\DATA), либо выбрать из списка псевдоним базы данных (DBDEMOS). Псевдоним базы данных (Alias) определяется в утилите Database Engine Configuration. Теперь, если свойство Active установить в True, то при запуске приложения таблица будет открываться автоматически.

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

Table1.Open;

Или, если вы предпочитаете, то можете установить свойство Active равное True:

Table1.Active := True;

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

Также, как имеются два способа открыть a таблицу, так и есть два способа закрыть ее. Самый простой способ просто вызывать Close:

Table1.Close;

Или, если вы желаете, вы можете написать:

Table1.Active := False;

Нет никакой существенной разницы между двумя этими способами. Вы должны только помнить, что Open и Close это методы (процедуры), а Active — свойство.

Перемещение по записям

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

Следующий обширный набор методов и свойства TDataSet обеспечивают все, что вам нужно для доступа к любой конкретной записи внутри таблицы:

u procedure First;

u procedure Last;

u procedure Next;

u procedure Prior;

u property BOF: Boolean read FBOF;

u property EOF: Boolean read FEOF;

u procedure MoveBy(Distance: Integer);

Дадим краткий обзор их функциональных возможностей:

u Вызов Table1.First перемещает вас к первой записи в таблице.

u Table1.Last перемещает вас к последней записи.

u Table1.Next перемещает вас на одну запись вперед.

u Table1.Prior перемещает вас на одну запись назад.

u Вы можете проверять свойства BOF или EOF, чтобы понять, находитесь ли вы в начале или в конце таблицы.

u Процедура MoveBy перемещает вас на N записей вперед или назад в таблице.

Нет никакого функционального различия между запросом Table1.Next и вызовом Table1.MoveBy(1). Аналогично, вызов Table1.Prior имеет тот же самый результат, что и вызов Table1.MoveBy(-1).

Чтобы начать использовать эти навигационные методы, вы должны поместить TTable, TDataSource и TDBGrid на форму. Присоедините DBGrid1 к DataSource1, и DataSource1 к Table1. Затем установите свойства таблицы:

u в DatabaseName имя подкаталога, где находятся демонстрационные таблицы (или псевдоним DBDEMOS);

u в TableName установите имя таблицы CUSTOMER.

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

Однако, иногда нужно перемещаться по таблице «программным путем», без использования возможностей, встроенных в визуальные компоненты, это необходимо выполнить следующим образом:

u Поместите две кнопки на форму и назовите их Next и Prior.

u Дважды щелкните на кнопке Next — появится заготовка обработчика события:
procedure TForm1.NextClick(Sender: TObject);
begin
end;

u Теперь добавьте одну строчку кода так, чтобы процедура выглядела следующим образом:

procedure TForm1.NextClick(Sender: TObject);

begin

Table1.Next;

end;

u Повторите те же самые действия с кнопкой Prior, так, чтобы функция связанная с ней выглядела так:

procedure TForm1.PriorClick(Sender: TObject);

begin

Table1.Prior;

end;

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

u Теперь добавьте еще две кнопки и назовите их First и Last.

u Сделайте то же самое для новых кнопок.

procedure TForm1.FirstClick(Sender: TObject);

begin

Table1.First;

end;

procedure TForm1.LastClick(Sender: TObject);

begin

Table1.Last;

end;

Нет ничего более простого чем эти навигационные функции. First перемещает вас в начало таблицы, Last перемещает вас в конец таблицы, а Next и Prior перемещают вас на одну запись вперед или назад.

TDataSet.BOF — read-only Boolean свойство, используется для проверки, находитесь ли вы в начале таблицы. Свойства BOF возвращает true в трех случаях:

u После того, как вы открыли файл;

u После того, как вы вызывали TDataSet.First;

u После того, как вызов TDataSet.Prior не выполняется.

Первые два пункта — очевидны. Когда вы открываете таблицу, Delphi помещает вас на первую запись; когда вы вызываете метод First, Delphi также перемещает вас в начало таблицы. Третий пункт, однако, требует небольшого пояснения: после того, как вы вызывали метод Prior много раз, вы могли добраться до начала таблицы, и следующий вызов Prior будет неудачным — после этого BOF и будет возвращать True.

Следующий код показывает самый общий пример использования Prior, когда вы попадаете к началу файла:

while not Table.Bof do begin

DoSomething;

Table1.Prior;

end;

В коде, показанном здесь, гипотетическая функция DoSomething будет вызвана сперва на текущей записи и затем на каждой следующей записи (от текущей и до начала таблицы). Цикл будет продолжаться до тех пор, пока вызов Table1.Prior не сможет больше переместить вас на предыдущую запись в таблице. В этот момент BOF вернет True и программа выйдет из цикла. (Чтобы оптимизировать вышеприведенный код, установите DataSource1.Enabled в False перед началом цикла, и верните его в True после окончания цикла.)

Все сказанное относительно BOF также применимо и к EOF. Другими словами, код, приведенный ниже показывает простой способ пробежать по всем записям в dataset:

Table1.First;

while not Table1.EOF do begin

DoSomething;

Table1.Next;

end;

Классическая ошибка в случаях, подобных этому: вы входите в цикл while или repeat, но забываете вызывать Table1.Next:

Table1.First;

repeat

DoSomething;

until Table1.EOF;

Если вы случайно написали такой код, то ваша машина зависнет, и вы сможете выйти из цикла, только нажав Ctrl-Alt-Del и прервав текущий процесс. Также, этот код мог бы вызвать проблемы, если вы открыли пустую таблицу. Так как здесь используется цикл repeat, DoSomething был бы вызван один раз, даже если бы нечего было обрабатывать. Поэтому, лучше использовать цикл while вместо repeat в ситуациях подобных этой.

EOF возвращает True в следующих трех случаях:

u После того, как вы открыли пустой файл;

u После того, как вы вызывали TDataSet.Last;

u После того, как вызов TDataSet.Next не выполняется.

Единственная навигационная процедура, которая еще не упоминалась — MoveBy, которая позволяет вам переместиться на N записей вперед или назад в таблице. Если вы хотите переместиться на две записи вперед, то напишите:

MoveBy(2);

И если вы хотите переместиться на две записи назад, то:

MoveBy(-2);

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

Prior и Next — это простые функции, которые вызывают MoveBy.

Поля

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

property Fields[Index: Integer];

function FieldByName(const FieldName: string): TField;

property FieldCount;

Свойство FieldCount возвращает число полей в текущей структуре записи. Если вы хотите программным путем прочитать имена полей, то используйте свойство Fields для доступа к ним:

var

S: String;

begin

S := Fields[0].FieldName;

end;

Если вы работали с записью, в которой первое поле называется CustNo, тогда код, показанный выше, поместит строку CustNo в переменную S. Если вы хотите получить доступ к имени второго поля в вышеупомянутом примере, тогда вы могли бы написать:

S := Fields[1].FieldName;

Короче говоря, индекс, передаваемый в Fields (начинающийся с нуля), и определяет номер поля, к которому вы получите доступ, то есть, первое поле — ноль, второе — один и так далее. Если вы хотите прочитать текущее содержание конкретного поля конкретной записи, то вы можете использовать свойство Fields или метод FieldsByName.

Для того, чтобы найти значение первого поля записи, прочитайте первый элемент массива Fields:

S := Fields[0].AsString;

Предположим, что первое поле в записи содержит номер заказчика, тогда код, показанный выше, возвратил бы строку типа «1021», «1031» или «2058». Если вы хотели получить доступ к этой переменной, как к числовой величине, тогда вы могли бы использовать AsInteger вместо AsString. Аналогично, свойство Fields включают AsBoolean, AsFloat и AsDate.

Если хотите, вы можете использовать функцию FieldsByName вместо свойства Fields:

S := FieldsByName(‘CustNo’).AsString;

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

Давайте посмотрим на простом примере, как можно использовать доступ к полям таблицы во время выполнения программы. Создайте новый проект, положите на форму объект TTable, два объекта ListBox и две кнопки — Fields и Values.

Соедините объект TTable с таблицей CUSTOMER, которая поставляется вместе с Delphi (DBDEMOS), не забудьте открыть таблицу (Active = True).

Сделайте Double click на кнопке Fields и создайте метод который выглядит так:

procedure TForm1.FieldsClick(Sender: TObject);

var

i: Integer;

begin

ListBox1.Clear;

for i := 0 to Table1.FieldCount — 1 do

ListBox1.Items.Add(Table1.Fields[i].FieldName);

end;

Обработчик события начинается с очистки первого ListBox1, затем он проходит через все поля, добавляя их имена один за другим в ListBox1. Заметьте, что цикл, показанный здесь, пробегает от 0 до FieldCount — 1. Если вы забудете вычесть единицу из FieldCount, то вы получите ошибку List Index Out of Bounds, так как вы будете пытаться прочесть имя поля, которое не существует.

Предположим, что вы ввели код правильно, и заполнили ListBox1 именами всех полей в текущей структуре записи.

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

Свойство Fields позволяет вам получить доступ не только к именам полей записи, но также и к содержимому полей. В нашем примере, для второй кнопки напишем:

procedure TForm1.ValuesClick(Sender: TObject);

var

i: Integer;

begin

ListBox2.Clear;

for i := 0 to Table1.FieldCount — 1 do

ListBox2.Items.Add(Table1.Fields[i].AsString);

end;

Этот код добавляет содержимое каждого из полей во второй listbox. Обратите внимание, что вновь счетчик изменяется от нуля до FieldCount — 1.

Свойство Fields позволяет вам выбрать тип результата, написав Fields[N].AsString. Этот и несколько связанных методов обеспечивают простой и гибкий способ доступа к данным, связанным с конкретным полем. Вот список доступных методов, который вы можете найти в описании класса TField:

u property AsBoolean

u property AsFloat

u property AsInteger

u property AsString

u property AsDateTime

Всякий раз (когда это имеет смысл), Delphi сможет сделать преобразования. Например, Delphi может преобразовывать поле Boolean к Integer или Float, или поле Integer к String. Но не будет преобразовывать String к Integer, хотя и может преобразовывать Float к Integer. Если вы хотите работать с полями Date или DateTime, то можете использовать AsString и AsFloat для доступа к ним.

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

S := Table1.FieldByName(‘CustNo’).AsString;

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

Работа с Данными

Следующие методы позволяют вам изменить данные, связанные с TTable:

u procedure Append;

u procedure Insert;

u procedure Cancel;

u procedure Delete;

u procedure Edit;

u procedure Post;

Все эти методы — часть TDataSet, они унаследованы и используются TTable и TQuery.

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

Имеется типичная последовательность, которую вы могли бы использовать при изменении поля текущей записи:

Table1.Edit;

Table1.FieldByName(‘CustName’).AsString := ‘Fred’;

Table1.Post;

Первая строка переводит БД в режим редактирования. Следующая строка присваивает значение Fred полю CustName. Наконец, данные записываются на диск, когда вы вызываете Post.

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

Table1.Edit;

Table1.FieldByName(‘CustNo’).AsInteger := 1234;

Table1.Next;

Общее правило, которому нужно следовать: всякий раз, когда вы сдвигаетесь с текущей записи, введенные вами данные будут записаны автоматически. Это означает, что вызовы First, Next, Prior и Last всегда выполняют Post, если вы находились в режиме редактирования. Если вы работаете с данными на сервере и транзакциями, тогда правила, приведенные здесь, не применяются.

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

Существуют два метода, названные Append и Insert, который вы можете использовать всякий раз, когда хотите добавить новую запись в DataSet. Очевидно имеет больше смысла использовать Append для DataSets которые не индексированы, но Delphi не будет генерировать exception если вы используете Append на индексированной таблице. Фактически, всегда можно использовать и Append, и Insert.

Продемонстрируем работу методов на простом примере. Чтобы создать программу, используйте TTable, TDataSource и TdbGrid. Откройте таблицу COUNTRY. Затем разместите две кнопки на форме и назовите их Insert и Delete. Когда вы все сделаете, то должна получиться программа, которая может вставлять и удалять запись из таблицы COUNTRY.

Следующим шагом вы должны связать код с кнопками Insert и Delete:

procedure TForm1.InsertClick(Sender: TObject);

begin

Table1.Insert;

Table1.FieldByName('Name').AsString := 'Russia';

Table1.FieldByName('Capital').AsString := 'Moscow';

Table1.Post;

end;

procedure TForm1.DeleteClick(Sender: TObject);

begin

Table1.Delete;

end;

Процедура, показанная здесь, сначала переводит таблицу в режим вставки (новая запись с незаполненными полями вставляется в текущую позицию dataset). После вставки пустой записи, следующим этапом нужно назначить значения одному или большему количеству полей. Существует, конечно, несколько различных путей присвоить эти значения. В нашей программе вы могли бы просто ввести информацию в новую запись через DBGrid или разместить на форме стандартную строку ввода (TEdit) и затем установить каждое поле равным значению, которое пользователь напечатал в этой строке:

Table1.FieldByName(‘Name’).AsString := Edit1.Text;

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

Table1.FieldByName('Name').AsString := 'Russia';

Один из интересных моментов в этом примере это то, что нажатие кнопки Insert дважды подряд автоматически вызывает exception ‘Key Violation’. Чтобы исправить эту ситуацию, вы должны либо удалить текущую запись, либо изменить поля Name и Capital вновь созданной записи. Просматривая код, показанный выше, вы увидите, что просто вставка записи и заполнения ее полей не достаточно для того, чтобы изменить физические данные на диске. Если вы хотите, чтобы информация записалась на диск, необходимо вызывать Post. Если после вызова Insert, вы решаете отказаться от вставки новой записи, то можно вызвать Cancel. Если вы сделаете это прежде, чем вызовете Post, то все, что Вы ввели после вызова Insert будет отменено, и dataset будет находиться в состоянии, которое было до вызова Insert.

Одно дополнительное свойство, которое вы должны иметь в виду, называется CanModify. Если CanModify возвращает False, то TTable находится в состоянии ReadOnly. В противном случае, CanModify возвращает True и вы можете редактировать или добавлять записи в нее по желанию. CanModify — само по себе read only свойство. Если вы хотите установить DataSet в состояние только на чтение (Read Only), то вы должны использовать свойство ReadOnly, а не CanModify.

Использование SetKey для поиска в таблице

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

Чтобы создать программу SEARCH, поместите TTable, TDataSource, TDBGrid, TButton, TLabel и TEdit на форму. Назовите кнопку Search, и затем соедините компоненты БД так, чтобы было видно в DBGrid1 таблицу Customer.

Вся функциональность программы SEARCH скрыта в единственном методе, который присоединен к кнопке Search. Эта функция считывает строку, введенную в окно редактора, и ищет ее в колонке CustNo, и наконец помещает фокус на найденной записи. В простейшем варианте код, присоединенный к кнопке Search, выглядит так:

procedure TSearchDemo.SearchClick(Sender: TObject);

begin

Table1.SetKey;

Table1.FieldByName(’CustNo’).AsString := Edit1.Text;

Table1.GotoKey;

end;

Первый вызов в этой процедуре установит Table1 в режим поиска. Delphi должен знать, что вы переключились в режим поиска просто потому, что свойство Fields используется по другому в этом режиме. Далее, нужно присвоить свойству Fields значение, которое вы хотите найти. Для фактического выполнения поиска нужно просто вызывать Table1.GotoKey.

Если вы ищете не по первичному индексу файла, тогда вы должны определить имя индекса, который используете в свойстве IndexName. Например, если таблица Customer имеет вторичный индекс по полю City, тогда вы должны установить свойство IndexName равным имени индекса. Когда вы будете искать по этому полю, необходимо написать:

Table1.IndexName := ’CityIndex’;

Table1.Active := True;

Table1.SetKey;

Table1.FieldByName(’City’).AsString := Edit1.Text;

Table1.GotoKey;

Запомните: поиск не будет выполняться, если вы не назначите правильно индекс (свойство IndexName). Кроме того, вы должны обратить внимание, что IndexName — это свойство TTable, и не присутствует в других прямых потомках TDataSet или TDBDataSet.

Когда вы ищете некоторое значение в БД, всегда существует вероятность того, что поиск окажется неудачным. В таком случае Delphi будет автоматически вызывать exception, но если вы хотите обработать ошибку сами, то могли бы написать примерно такой код:

procedure TSearchDemo.SearchClick(Sender: TObject);

begin

Cust.SetKey;

Cust.FieldByName('CustNo').AsString:= CustNoEdit.Text;

if not Cust.GotoKey then

raise Exception.CreateFmt('Cannot find CustNo %g',

[CustNo]);

end;

В коде, показанном выше, либо неверное присвоение номера, либо неудача поиска автоматически приведут к сообщению об ошибке Cannot find CustNo %g.

Иногда требуется найти не точно совпадающее значение, а близкое к нему, для этого следует вместо GotoKey пользоваться методом GotoNearest.

Использование фильтров для ограничения числа записей в DataSet

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

Например, в БД Customers, поле CustNo имеет диапазон от 1,000 до 10,000. Если вы хотите видеть только те записи, которые имеют номер заказчика между 2000 и 3000, то вы должны использовать метод ApplyRange, и еще два связанных с ним метода.

Данные методы работают только с индексированным полем.

Вот процедуры, которые вы будете чаще всего использовать при установке фильтров:

u procedure SetRangeStart

u procedure SetRangeEnd

u procedure ApplyRange

u procedure CancelRange

Кроме того, у TTable есть дополнительные методы для управления фильтрами:

u procedure EditRangeStart

u procedure EditRangeEnd

u procedure SetRange

Для использования этих процедур необходимо:

u Сначала вызвать SetRangeStart и использовать свойство Fields для определения начала диапазона.

u Затем вызвать SetRangeEnd и вновь использовать свойство Fields для определения конца диапазона.

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

u Когда нужно прекратить действие фильтра — вызовите CancelRange.

Программа RANGE, которая есть среди примеров Delphi, показывает, как использовать эти процедуры. Чтобы создать программу, поместите TTable, TDataSource и TdbGrid на форму.

Соедините их так, чтобы видеть таблицу CUSTOMERS из подкаталога DEMOS.

Затем поместите два объекта TLabel на форму и назовите их Start Range и End Range. Затем положите на форму два объекта TEdit. Наконец, добавьте кнопки ApplyRange и CancelRange.

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

Процедуры SetRangeStart и SetRangeEnd позволяют вам указать первое и последнее значения в диапазоне записей, которые вы хотите видеть. Чтобы начать использовать эти процедуры, сначала выполните double-click на кнопке ApplyRange, и создайте процедуру, которая выглядит так:

procedure TForm1.ApplyRangeBtnClick(Sender: TObject);

begin

Table1.SetRangeStart;

if RangeStart.Text <> '' then

Table1. Fields[0].AsString := RangeStart.Text;

Table1.SetRangeEnd;

if RangeEnd.Text <> '' then

Table1.Fields[0].AsString := RangeEnd.Text;

Table1.ApplyRange;

end;

Сначала вызывается процедура SetRangeStart, которая переводит таблицу в режим диапазона (range mode). Затем вы должны определить начало и конец диапазона. Обратите внимание, что вы используете свойство Fields для определения диапазона:

Table1.Fields[0].AsString := RangeStart.Text;

Такое использование свойства Fields — это специальный случай, так как синтаксис, показанный здесь, обычно используется для установки значения поля. Этот специальный случай имеет место только после того, как вы перевели таблицу в режим диапазона, вызвав SetRangeStart.

Заключительный шаг в процедуре, показанной выше — вызов ApplyRange. Этот вызов фактически приводит ваш запрос в действие. После вызова ApplyRange, TTable больше не находится в режиме диапазона, и свойства Fields функционирует как обычно.

Обработчик события нажатия кнопки CancelRange:

procedure TForm1.CancelRangeBtnClick(Sender: TObject);

begin

Table1.CancelRange;

end;

Обновление

Как вы уже знаете, любая таблица, которую вы открываете всегда «подвержена изменению». Короче говоря, вы должны расценить таблицу скорее как меняющуюся, чем как статическую сущность. Даже если вы — единственное лицо, использующее данную TTable, и даже если вы не работаете в сети, всегда существует возможность того, что программа с которой вы работаете, может иметь два различных пути изменения данных в таблице. В результате, вы должны всегда знать, необходимо ли вам обновить вид таблицы на экране.

Функция Refresh связана с функцией Open, в том смысле, что она считывает данные, или некоторую часть данных, связанных с данной таблицей. Например, когда вы открываете таблицу, Delphi считывает данные непосредственно из файла БД. Аналогично, когда вы регенерируете таблицу, Delphi считывает данные напрямую из таблицы. Поэтому вы можете использовать эту функцию, чтобы перепрочитать таблицу, если вы думаете что она могла измениться. Быстрее и эффективнее вызывать Refresh, чем вызывать Close и затем Open.

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

Закладки

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

u function GetBookmark: TBookmark; (устанавливает закладку в таблице).

u procedure GotoBookmark(Bookmark: TBookmark); (переходит на закладку).

u procedure FreeBookmark(Bookmark: TBookmark); (освобождает память).

Как вы можете видеть, вызов GetBookmark возвращает переменную типа TBookmark. TBookmark содержит достаточное количество информации, чтобы Delphi мог найти местоположение, к которому относится этот TBookmark. Поэтому вы можете просто передавать этот TBookmark функции GotoBookmark, и будете немедленно возвращены к местоположению, связанному с этой закладкой.

Обратите внимание, что вызов GetBookmark распределяет память для TBookmark, так что вы должны вызывать FreeBookmark до окончания вашей программы, и перед каждой попыткой повторного использования Tbookmark (в GetBookMark).

Создание Связанных Курсоров

Связанные курсоры позволяют программистам определить отношение один ко многим (one-to-many relationship). Например, иногда полезно связать таблицы CUTOMER и ORDERS так, чтобы каждый раз, когда пользователь выбирает имя заказчика, то он видит список заказов, связанных с этим заказчиком. Иначе говоря, когда пользователь выбирает запись о заказчике, то он может просматривать только заказы, сделанные этим заказчиком. Программа LINKTBL демонстрирует, как создать программу, которая использует связанные курсоры. Чтобы создать программу заново, поместите два TTable, два TDataSources и два TDBGrid на форму. Присоедините первый набор к таблице CUSTOMER, а второй — к таблице ORDERS.

Программа LINKTBL показывает, как определить отношения между двумя таблицами.

Следующий шаг должен связать таблицу ORDERS с таблицей CUSTOMER так, чтобы вы видели только те заказы, которые связанные с текущей записью в таблице заказчиков. В первой таблице заказчик однозначно идентифицируется своим номером — поле CustNo. Во второй таблице принадлежность заказа определяется также номером заказчика в поле CustNo. Следовательно, таблицы нужно связывать по полю CustNo в обоих таблицах (поля могут иметь различное название, но должны быть совместимы по типу). Для этого, вы должны сделать три шага, каждый из которых требует некоторого пояснения:

u Установить свойство Table2.MasterSource = DataSource1

u Установить свойство Table2.MasterField = CustNo

u Установить свойство Table2.IndexName = CustNo

Если вы теперь запустите программу, то увидите, что обе таблицы связаны вместе, и всякий раз, когда вы перемещаетесь на новую запись в таблице CUSTOMER, вы будете видеть только те записи в таблице ORDERS, которые принадлежат этому заказчику.

Свойство MasterSource в Table2 определяет DataSource, от которого Table2 может получить информацию. То есть, оно позволяет таблице ORDERS знать, какая запись в настоящее время является текущей в таблице CUSTOMERS. Но тогда возникает вопрос: какая еще информация нужна Table2 для того, чтобы должным образом отфильтровать содержимое таблицы ORDERS?

Ответ состоит из двух частей:

u Требуется имя поля, по которому связаны две таблицы.

u Требуется индекс по этому полю в таблице ORDERS (в таблице «многих записей»), которая будет связываться с таблицей CUSTOMER (таблице, в которой выбирается «одна запись»).

Чтобы правильно воспользоваться информацией описанной здесь, вы должны сначала проверить, что таблица ORDERS имеет нужные индексы. Если этот индекс первичный, тогда не нужно дополнительно указывать его в поле IndexName, и поэтому вы можете оставить это поле незаполненным в таблице TTable2 (ORDERS). Однако, если таблица связана с другой через вторичный индекс, то вы должны явно определять этот индекс в поле IndexName связанной таблицы.

В примере, показанном здесь, таблица ORDERS не имеет первичного индекса по полю CustNo, так что вы должны явно задать в свойстве IndexName индекс CustNo.

Недостаточно, однако, просто упомянуть имя индекса, который вы хотите использовать. Некоторые индексы могут содержать несколько полей, так что вы должны явно задать имя поля, по которому вы хотите связать две таблицы. Вы должны ввести имя CustNo в свойство Table2.MasterFields. Если вы хотите связать две таблицы больше чем по одному полю, необходимо внести в список все поля, помещая символ «|» между каждым:

Table1.MasterFields := ‘CustNo | SaleData | ShipDate’;

В данном конкретном случае, выражение, показанное здесь, не имеет смысла, так как хотя поля SaleData и ShipDate также индексированы, но не дублируются в таблице CUSTOMER. Поэтому вы должны ввести только поле CustNo в свойстве MasterFields. Вы можете определить это непосредственно в редакторе свойств, или написать код подобно показанному выше. Кроме того, поле (или поля) связи можно получить, вызвав редактор связей — в Инспекторе Объектов дважды щелкните на свойство MasterFields.

Основные понятия о TDataSource

Класс TDataSource используется в качестве проводника между TTable или TQuery и компонентами, визуализирующими данные, типа TDBGrid, TDBEdit и TDBComboBox (data-aware components). В большинстве случаев, все, что нужно сделать с DataSource — это указать в свойстве DataSet соответствующий TTable или TQuery. Затем, у data-aware компонента в свойстве DataSource указывается TDataSource, который используется в настоящее время.

TDataSource также имеет свойство Enabled, и оно может быть полезно всякий раз, когда вы хотите временно отсоединить, например, DBGrid от таблицы или запроса. Эти требуется, например, если нужно программно пройти через все записи в таблице. Ведь, если таблица связана с визуальными компонентами (DBGrid, DBEdit), то каждый раз, когда вы вызываете метод TTable.Next, визуальные компоненты будут перерисовываться. Даже если само сканирование в таблице двух или трех тысяч записей не займет много времени, то может потребоваться значительно больше времени, чтобы столько же раз перерисовать визуальные компоненты. В случаях подобных этому, лучше всего установить поле DataSource.Eabled в False. Это позволит вам просканировать записи без перерисовки визуальных компонент. Это единственная операция может увеличить скорость в некоторых случаях на несколько тысяч процентов.

Свойство TDataSource.AutoEdit указывает, переходит ли DataSet автоматически в режим редактирования при вводе текста в data-aware объекте.

Использование TDataSource для проверки состояния БД

TDataSource имеет три ключевых события, связанных с состоянием базы данных:

u OnDataChange

u OnStateChange

u OnUpdateData

OnDataChange происходит всякий раз, когда вы переходите на новую запись, или состояние DataSet сменилось с dsInactive на другое, или начато редактирование. Другими словами, если вы вызываете Next, Previous, Insert, или любой другой запрос, который должен привести к изменению данных, связанных с текущей записью, то произойдет событие OnDataChange. Если в программе нужно определить момент, когда происходит переход на другую запись, то это можно сделать в обработчике события OnDataChange:

procedure TForm1.DataSource1DataChange(Sender: TObject; Field: TField);

begin

if DataSource1.DataSet.State = dsBrowse then begin

DoSomething;

end;

end;

Событие OnStateChange событие происходит всякий раз, когда изменяется текущее состояние DataSet. DataSet всегда знает, в каком состоянии он находится. Если вы вызываете Edit, Append или Insert, то TTable знает, что он теперь находится в режиме редактирования (dsEdit или dsInsert). Аналогично, после того, как вы делаете Post, то TTable знает, что данные больше не редактируется, и переключается обратно в режим просмотра (dsBrowse).

Dataset имеет шесть различных возможных состояний, каждое из которых включено в следующем перечисляемом типе:

TDataSetState = (dsInactive, dsBrowse, dsEdit, dsInsert, dsSetKey, dsCalcFields);

В течение обычного сеанса работы, БД часто меняет свое состояние между Browse, Edit, Insert и другими режимами. Если вы хотите отслеживать эти изменения, то вы можете реагировать на них, написав примерно такой код:

procedure TForm1.DataSource1StateChange(Sender: TObject);

var

S: String;

begin

case Table1.State of

dsInactive: S := 'Inactive';

dsBrowse: S := 'Browse';

dsEdit: S := 'Edit';

dsInsert: S := 'Insert';

dsSetKey: S := 'SetKey';

dsCalcFields: S := 'CalcFields';

end;

Label1.Caption := S;

end;

OnUpdateData событие происходит перед тем, как данные в текущей записи будут обновлены. Например, OnUpdateEvent будет происходить между вызовом Post и фактическим обновлением информации на диске. События, генерируемые TDataSource могут быть очень полезны. Иллюстрацией этого служит следующий пример. Эта программа работает с таблицей COUNTRY, и включает TTable, TDataSource, пять TEdit, шесть TLlabel, восемь кнопок и панель. Действительное расположение элементов показано на рисунке ниже. Обратите внимание, что шестой TLabel расположен на панели внизу главной формы. Программа STATE показывает, как отслеживать текущее состояние таблицы.

Для всех кнопок напишите обработчики, вроде:

procedure TForm1.FirstClick(Sender: TObject);

begin

Table1.First;

end;

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

Edits: array[1..5] of TEdit;

Чтобы заполнить массив, вы можете в событии OnCreate главной формы написать:

procedure TForm1.FormCreate(Sender: TObject);

var

i: Integer;

begin

for i := 1 to 5 do

Edits[i] := TEdit(FindComponent('Edit' + IntToStr(i)));

Table1.Open;

end;

Код показанный здесь предполагает, что первый редактор, который вы будете использовать, назовем Edit1, второй Edit2. Существование этого массива позволяет очень просто использовать событие OnDataChange, чтобы синхронизировать содержание объектов TEdit с содержимым текущей записи в DataSet:

procedure TForm1.DataSource1DataChange(Sender: TObject; Field: TField);

var

i: Integer;

begin

for i := 1 to 5 do

Edits[i].Text := Table1.Fields[i — 1].AsString;

end;

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

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

procedure TForm1.DataSource1UpdateData(Sender: TObject);

var

i: Integer;

begin

for i := 1 to 5 do

Table1.Fields[i — 1].AsString := Edits[i].Text;

end;

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

procedure TForm1.Edit1KeyDown(Sender: TObject;

var Key: Word; Shift: TShiftState);

begin

if DataSource1.State <> dsEdit then

Table1.Edit;

end;

Этот код показывает, как вы можете использовать свойство State DataSource, чтобы определить текущий режим DataSet.

Обновление метки в статусной панели происходит при изменении состояния таблицы:

procedure TForm1.DataSource1StateChange(Sender: TObject);

var

s : String;

begin

case DataSource1.State of

dsInactive : s:='Inactive';

dsBrowse : s:='Browse';

dsEdit : s:='Edit';

dsInsert : s:='Insert';

dsSetKey : s:='SetKey';

dsCalcFields : s:='CalcFields';

end;

Label6.Caption:=s;

end;

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

Отслеживание состояния DataSet

Ранее вы узнали, как использовать TDataSource, чтобы узнать текущее состоянии TDataSet. Использование DataSource — это простой путь выполнения данной задачи. Однако, если вы хотите отслеживать эти события без использования DataSource, то можете написать свои обработчики событий TTable и TQuery:

u property OnOpen

u property OnClose

u property BeforeInsert

u property AfterInsert

u property BeforeEdit

u property AfterEdit

u property BeforePost

u property AfterPost

u property OnCancel

u property OnDelete

u property OnNewRecord

Большинство этих свойств очевидны. Событие BeforePost функционально подобно событию TDataSource.OnUpdateData, которое объяснено выше. Другими словами, программа STATE работала бы точно также, если бы вы отвечали не на DataSource1. OnUpdateData, а на Table1.BeforePost. Конечно, в первом случае вы должны иметь TDataSource на форме, в то время, как во втором этого не требуется.

Глава 51.
Компонент TTable

Создание таблиц с помощью компонента TTable

Для создания таблиц компонент TTable имеет метод CreateTable. Этот метод создает новую пустую таблицу заданной структуры. Данный метод (процедура) может создавать только локальные таблицы формата dBase или Paradox.

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

var

Table1: TTable;

...

Table1:=TTable.Create(nil);

...

Перед вызовом метода CreateTable необходимо установить значения свойств:

u TableType — тип таблицы;

u DatabaseName — база данных;

u TableName — имя таблицы;

u FieldDefs — массив описаний полей;

u IndexDefs — массив описаний индексов.

Свойство TableType имеет тип TTableType и определяет тип таблицы в базе данных. Если это свойство установлено в ttDefault, тип таблицы определяется по расширению файла, содержащего эту таблицу:

u Расширение .DB или без расширения: таблица Paradox;

u Расширение .DBF: таблица dBASE;

u Расширение .TXT: таблица ASCII (текстовый файл).

Если значение свойства TableType не равно ttDefault, создаваемая таблица всегда будет иметь установленный тип, вне зависимости от расширения:

u ttASCII: текстовый файл;

u ttDBase: таблица dBASE;

u ttParadox: таблица Paradox.

Свойство DatabaseName определяет базу данных, в которой находится таблица. Это свойство может содержать:

u BDE алиас;

u директорий для локальных БД;

u директорий и имя файла базы данных для Local InterBase;

u локальный алиас, определенный через компонент TDatabase.

Свойство TableName определяет имя таблицы базы данных.

Свойство FieldDefs (имеющее тип TFieldDefs) для существующей таблицы содержит информацию обо всех полях таблицы. Эта информация доступна только в режиме выполнения и хранится в виде массива экземпляров класса TFieldDef, хранящих данные о физических полях таблицы (таким образом, вычисляемые на уровне клиента поля не имеют своего объекта TFieldDef). Число полей определяется свойством Count, а доступ к элементам массива осуществляется через свойство Items:

property Items[Index: Integer]: TFieldDef;

При создании таблицы, перед вызовом метода CreateTable, нужно сформировать эти элементы. Для этого у класса TFieldDefs имеется метод Add:

procedure Add(const Name: string; DataType: TFieldType; Size: Word; Required: Boolean);

Параметр Name, имеющий тип string, определяет имя поля. Параметр DataType (тип TFieldType) обозначает тип поля. Он может иметь одно из следующих значений, смысл которых ясен из их наименования:

TFieldType = (ftUnknown, ftString, ftSmallint, ftInteger, ftWord, ftBoolean, ftFloat, ftCurrency, ftBCD, ftDate, ftTime, ftDateTime, ftBytes, ftVarBytes, ftBlob, ftMemo, ftGraphic);

Параметр Size (тип word) представляет собой размер поля. Этот параметр имеет смысл только для полей типа ftString, ftBytes, ftVarBytes, ftBlob, ftMemo, ftGraphic, размер которых может сильно варьироваться. Поля остальных типов всегда имеют строго фиксированный размер, так что данный параметр для них не принимается во внимание. Четвертый параметр — Required — определяет, может ли поле иметь пустое значение при записи в базу данных. Если значение этого параметра — true, то поле является «требуемым», то есть, не может иметь пустого значения. В противном случае поле не является «требуемым» и, следовательно, допускает запись значения NULL.

Если вы желаете индексировать таблицу по одному или нескольким полям, используйте метод Add для свойства IndexDefs, которое, как можно догадаться, также является объектом, то есть, экземпляром класса TIndexDefs. Свойство IndexDefs для существующей таблицы содержит информацию обо всех индексах таблицы. Эта информация доступна только в режиме выполнения и хранится в виде массива экземпляров класса TIndexDef, хранящих данные об индексах таблицы. Число индексов определяется свойством Count, а доступ к элементам массива осуществляется через свойство Items:

property Items[Index: Integer]: TIndexDef;

Метод Add класса TIndexDefs имеет следующий вид:

procedure Add(const Name, Fields: string;

Options: TIndexOptions);

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

Field1;Field2;Field4

Последний параметр — Options — определяет тип индекса. Он может иметь набор значений, описываемых типом TIndexOptions:

TIndexOptions = set of (ixPrimary, ixUnique, ixDescending, ixCaseInsensitive, ixExpression);

Поясним эти значения. ixPrimary обозначает первичный ключ, ixUnique — уникальный индекс, ixDescending — индекс, отсортированный по уменьшению значений (для строк — в порядке, обратном алфавитному), ixCaseInsensitive — индекс, «нечувствительный» к регистру букв, ixExpression — индекс по выражению. Отметим, что упоминание о последнем значении также отсутствует в документации и online-справочнике. Опция ixExpression позволяет для таблиц формата dBase создавать индекс по выражению. Для этого достаточно в параметре Fields указать желаемое выражение, например:

Field1*Field2+Field3

Следует отметить, что для формата Paradox опция ixUnique может использоваться только вместе с опцией ixPrimary.

Итак, после заполнения всех указанных выше свойств и вызова методов Add для FieldDefs и IndexDefs необходимо вызвать метод класса TTable — CreateTable:

with Table1 do

begin

DatabaseName:='dbdemos';

TableName:='mytest';

TableType:=ttParadox;

{Создать поля}

with FieldDefs do

begin

Add('Surname', ftString, 30, true);

Add('Name', ftString, 25, true);

Add('Patronymic', ftString, 25, true);

Add('Age', ftInteger, 0, false);

Add('Weight', ftFloat, 0, false);

end;

{Сгенерировать индексы}

with IndexDefs do

begin

Add('I_Name', 'Surname;Name;Patronymic', [ixPrimary, ixUnique]);

Add('I_Age', 'Age', [ixCaseInsensitive]);

end;

CreateTable;

end;

Индексы можно сгенерировать и не только при создании таблицы. Для того чтобы сгенерировать индексы для существующей таблицы, нужно вызвать метод AddIndex класса TTable, набор параметров которого полностью повторяет набор параметров для метода Add класса TIndexDefs:

procedure AddIndex(const Name, Fields: string;

Options: TIndexOptions);

При этом для метода AddIndex справедливы все замечания по поводу записи полей и опций индексов, сделанные выше.

Глава 52.
Объект TQuery

Поддержка языка структурированных запросов

Сокращение SQL означает Structured Query Language — Язык Структурированных Запросов, и обычно произносится либо как «Sequel», либо «Ess Qu El». Однако, как бы вы его ни произносили, SQL — это мощный язык БД, который легко доступен из Delphi, но который отличается от родного языка Delphi. Delphi может использовать утверждения SQL для просмотра таблиц, выполнять объединение таблиц, создавать отношения один-ко-многим, или исполнить почти любое действие, которое могут сделать ваши основные инструменты БД. Delphi поставляется с Local SQL, так что вы можете выполнять запросы SQL при работе с локальными таблицами, без доступа к SQL серверу. Delphi обеспечивает поддержку «pass through SQL», это означает то, что вы можете составлять предложения SQL и посылать их непосредственно серверам Oracle, Sybase, Inrterbase и другим. «Pass through SQL» — это мощный механизм по двум причинам:

u Большинство серверов могут обрабатывать SQL запросы очень быстро, а это означает, что используя SQL для удаленных данных, вы получите ответ очень быстро.

u Есть возможность составлять SQL запросы, которые заставят сервер исполнить специализированные задачи, недоступные через родной язык Delphi.

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

Основные понятия о TQuery

Объект TTable служит для доступа к данным. При использовании TTable, возможен доступ ко всему набору записей из одной таблицы. В отличие от TTable, TQuery позволяет произвольным образом (в рамках SQL) выбрать набор данных для работы с ним. Во многом, методика работы с объектом TQuery похожа на методику работы с TTable, однако есть свои особенности.

Вы может создать SQL запрос, используя компонент TQuery следующим способом:

u Назначьте Псевдоним (Alias) DatabaseName.

u Используйте свойство SQL чтобы ввести SQL запрос типа Select * from Country.

u Установите свойство Active в True.

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

Объект TQuery один из наиболее полезных и гибких компонентов, доступных в Delphi. С ним вы сможете воспользоваться всей мощью, предоставляемой лидерами среди промышленных SQL серверов, вроде InrterBase, Oracle или Sybase.

Свойство SQL

Свойство SQL — вероятно, самая важная часть TQuery. Доступ к этому свойству происходит либо через Инспектор Объектов во время конструирования проекта (design time), или программно во время выполнения программы (run time).

Интересней, конечно, получить доступ к свойству SQL во время выполнения, чтобы динамически изменять запрос. Например, если требуется выполнить три SQL запроса, то не надо размещать три компонента TQuery на форме. Вместо этого можно разместить один и просто изменять свойство SQL три раза. Наиболее эффективный, простой и мощный способ — сделать это через параметризованные запросы, которые будут объяснены в дальнейшем. Однако, сначала исследуем основные особенности свойства SQL, а потом рассмотрим более сложные темы типа запросов с параметрами.

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

Query1.Close;

Query1.SQL.Clear;

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

Следующий шаг — добавление новых строк в запрос:

Query1.SQL.Add(‘Select * from Country’);

Query1.SQL.Add(‘where Name = ’’Argentina’’’);

Метод Add используется для добавления одной или нескольких строк к запросу SQL. Общий объем ограничен только количеством памяти на вашей машине. Чтобы Delphi отработал запрос и возвратил курсор, содержащий результат в виде таблицы, можно вызвать метод:

Query1.Open;

Программа THREESQL показывает, как сделать несколько запросов с помощью единственного объекта TQuery.

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

Select * form Country where Name like ’C%’

возвращает DataSet, содержащий все записи, где поле Name начинается с буквы «C». Следующий запрос позволит увидеть все страны, в названии которых встречается буква «C»:

Select * from Country where Name like ‘%C%’;

Вот запрос, которое находит все страны, название которых заканчивается на «ia»:

Select * from Country where Name like ‘%ia’;

TQuery и Параметры

Delphi позволяет составить «гибкую» форму запроса, называемую параметризованным запросом. Такие запросы позволяют подставить значение переменной вместо отдельных слов в выражениях where или insert. Эта переменная может быть изменена практически в любое время. (Если используется локальный SQL, то можно сделать замену почти любого слова в утверждении SQL, но при этом та же самая возможность не поддерживается большинством серверов.)

Перед тем, как начать использовать параметризованные запросы, рассмотрим снова одно из простых вышеупомянутых предложений SQL:

Select * from Country where Name like ’C%’

Можно превратить это утверждение в параметризованный запрос, заменив правую часть переменной NameStr:

select * from County where Name like :NameStr

В этом предложении SQL, NameStr не является предопределенной константой и может изменяться либо во время дизайна, либо во время выполнения. SQL parser (программа, которая разбирает текст запроса) понимает, что он имеет дело с параметром, а не константой потому, что параметру предшествует двоеточие :NameStr. Это двоеточие сообщает Delphi о необходимости заменить переменную NameStr некоторой величиной, которая будет известна позже.

Обратите внимание, слово NameStr было выбрано абсолютно случайно. Использовать можно любое допустимое имя переменной, точно также, как выбирается идентификатор переменной в программе. Есть два пути присвоить значение переменной в параметризованном запросе SQL. Один способ состоит в том, чтобы использовать свойство Params объекта TQuery. Второй — использовать свойство DataSource для получения информации из другого DataSet. Вот ключевые свойства для достижения этих целей:

property Params[Index: Word];

function ParamByName(const Value: string);

property DataSource;

Если подставлять значение параметра в параметризованный запрос через свойство Params, то обычно нужно сделать четыре шага:

u Закрыть TQuery

u Подготовить объект TQuery, вызвав метод Prepare

u Присвоить необходимые значения свойству Params

u Открыть TQuery

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

Вот фрагмент кода, показывающий как это может быть выполнено практически:

Query1.Close;

Query1.Prepare;

Query1.Params[0].AsString := ‘Argentina’;

Query1.Open;

Этот код может показаться немного таинственным. Чтобы понять его, требуется внимательный построчный анализ. Проще всего начать с третьей строки, так как свойство Params является «сердцем» этого процесса.

Params — это индексированное свойство, которое имеет синтаксис, как у свойства Fields для TDataSet. Например, можно получить доступ к первой переменной в SQL запросе, адресуя нулевой элемент в массиве Params:

Params[0].AsString := ‘”Argentina”’;

Если параметризованный SQL запрос выглядит так:

select * from Country where Name = :NameStr

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

select * from Country where Name = “Argentina”

Все, что произошло, это переменной :NameStr было присвоено значение «Аргентина» через свойство Params. Таким образом, вы закончили построение простого утверждения SQL.

Если в запросе содержится более одного параметра, то подступиться к ним можно, изменяя индекс у свойства Params:

Params[1].AsString := ‘SomeValue’;

либо используя доступ по имени параметра:

ParamByName(‘NameStr’).AsString:=’”Argentina”’;

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

Прежде, чем использовать переменную Params, сначала можно вызвать Prepare. Этот вызов заставляет Delphi разобрать ваш SQL запрос и подготовить свойство Params так, чтобы оно «было готово принять» соответствующее количество переменных. Можно присвоить значение переменной Params без предварительного вызова Prepare, но это будет работать несколько медленнее.

После того, как вы вызывали Prepare, и после того, как присвоили необходимые значения переменной Params, вы должны вызвать Open, чтобы закончить привязку переменных и получить желаемый DataSet. В нашем случае, DataSet должен включать записи, где в поле Name стоит Argentina.

Рассмотрим работу с параметрами на примере (программа PARAMS.DPR).

Для создания программы, разместите на форме компоненты TQuery, TDataSource, TDBGrid и TTabSet. Соедините компоненты и установите в свойстве TQuery.DatabaseName псевдоним DBDEMOS.

В обработчике события для формы OnCreate напишем код, заполняющий закладки для TTabSet, кроме того, здесь подготавливается запрос:

procedure TForm1.FormCreate(Sender: TObject);

var

i : Byte;

begin

Query1.Prepare;

for i:=0 to 25 do

TabSet1.Tabs.Add(Chr(Byte('A')+i));

end;

Текст SQL запроса в компоненте Query1:

select * from employee where LastName like :LastNameStr

Запрос выбирает записи из таблицы EMPLOYEE, в которых поле LastName похоже (like) на значение параметра :LastNameStr. Параметр будет передаваться в момент переключения закладок:

procedure TForm1.TabSet1Change(Sender: TObject;

NewTab: Integer;

var AllowChange: Boolean);

begin

with Query1 do begin

Close;

Params[0].AsString:=

'"'+TabSet1.Tabs.Strings[NewTab]+'%"';

Open;

end;

end;

Передача параметров через TDataSource

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

Объект TQuery имеет свойство DataSource, которое может использоваться для того, чтобы создать связь с другим DataSet. Не имеет значения, является ли другой DataSet объектом TTable, TQuery или некоторый другим потомком TDataSet. Все что нужно для установления соединения — это удостовериться, что у того DataSet есть связанный с ним DataSource.

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

Рассмотрите следующий параметризованный запрос:

select * from Orders where CustNo = :CustNo

В этом запросе :CustNo — связывающая переменная, которой должно быть присвоено значение из некоторого источника. Delphi позволяет использовать поле TQuery.DataSource чтобы указать другой DataSet, который предоставит эту информацию автоматически. Другими словами, вместо того, чтобы использовать свойство Params и «вручную» присваивать значения переменной, эти значения переменной могут быть просто взяты автоматически из другой таблицы. Кроме того, Delphi всегда сначала пытается выполнить параметризованный запрос, используя свойство DataSource, и только потом (если не было найдено какое-то значение параметра) будет пытаться получить значение переменной из свойства Params. При получении данных из DataSource считается, что после двоеточия стоит имя поля из DataSource. При изменении текущей записи в главном DataSet запрос будет автоматически пересчитываться.

Создайте новый проект, положите на форму один набор TTable, TDataSource и TDBGrid. Привяжите его к таблице CUSTOMER. Положите на форму второй набор — TQuery, TDataSource и TDBGrid и свяжите объекты между собой.

В свойстве SQL наберите текст запроса:

select * from Orders where CustNo = :CustNo

В свойстве DatabaseName для Query1 укажите DBDEMOS.

В свойстве DataSource для Query1 укажите DataSource1.

Поставьте Active = True и запустите программу.

Выполнение соединения нескольких таблиц

Вы видели, что таблицы CUSTOMERS и ORDERS связаны в отношении один-ко-многим, основанному на поле CustNo. Таблицы ORDERS и ITEMS также связаны отношении один-ко-многим, только через поле OrderNo.

Более конкретно, каждый заказ который существует в таблице ORDERS будет иметь несколько записей в таблице ITEMS, связанных с этим заказом. Записи из таблицы ITEMS определяют тип и количество изделий, связанных с этим заказом.

Имеется существенное различие между связанными курсорами и соединенными таблицами.

Однако они имеют две общие черты:

u И те, и другие используют две или более таблиц;

u Каждая таблица связана с другой по одному или более одинаковых полей.

Соединение таблиц ORDERS и ITEMS может быть выполнено единственным SQL запросом, который выглядит так:

select

O.CustNo, O.OrderNo, O.SaleDate, I.PartNo, I.Qty

from Orders O, Items I

where O.OrderNo = I.OrderNo

Этот запрос состоит из четырех различных частей:

u Выражение Select определяет, что вы хотите получить курсор, содержащий некоторую форму DataSet.

u Затем идет список полей, которые вы хотите включить в dataset. Этот список включает поля CustNo, OrderNo, SaleDate, PartNo и Qty. Первые три поля из таблицы ORDERS, а два других — из таблицы ITEMS.

u Выражение from объявляет, что вы работаете с двумя таблицами, одна называется ORDERS, а другая ITEMS. Для краткости, в запросе используется особенность SQL, которая позволяет вам ссылаться на таблицу ORDERS буквой «O», а на таблицу ITEMS буквой «I».

u Выражение where жизненно важно потому, что оно определяет поля связи для двух таблиц. Некоторые серверы могут вернуть DataSet, даже если вы не включите выражение where в запрос, но почти всегда результирующий набор записей будет не тем, что вы хотели видеть. Чтобы получить нужный результат, убедитесь что вы включили выражение where.

Open или ExecSQL?

После того, как составлен SQL запрос, есть два различных способа выполнить его. Если вы хотите получить курсор, то нужно вызывать Open. Если выражение SQL не подразумевает возвращение курсора, то нужно вызывать ExecSQL. Например, если происходит вставка, удаление или обновление данных (т.е. SQL запросы INSERT, DELETE, UPDATE), то нужно вызывать ExecSQL. Тоже самое можно сказать по-другому: Open вызывается при запросе типа SELECT, а И — во всех остальных случаях. Вот типичный SQL запрос, который используется для удаления записи из таблицы:

delete from Country where Name = ‘Argentina’;

Этот запрос удалил бы любую запись из таблицы COUNTRY, которая имеет значение «Argentina» в поле Имя. Не трудно заметить, что это тот случай, когда удобно использовать параметризованный запрос. Например, неплохо было бы менять имя страны, которую требуется удалить:

delete from Country where Name = :CountryName

В этом случае переменная :CountryName может быть изменена во время выполнения:

Query2.Prepare;

Query2.Params[0] := ‘Argentina’;

Query2.ExecSQL;

Код сначала вызывает Prepare, чтобы сообщить Delphi что он должен разобрать SQL запрос и подготовить свойство Params. Следующим шагом присваивается значение свойству Params и затем выполняется подготовленный SQL запрос. Обратите внимание, что он выполняется через ExecSQL, а не Open.

Специальные свойства TQuery

Есть несколько свойств, принадлежащих TQuery, которые еще не упоминались:

u property UniDirectional: Boolean;

u property Handle: HDBICur;

u property StmtHandle: HDBIStmt;

u property DBHandle: HDBIDB;

Свойство UniDirectional используется для того, чтобы оптимизировать доступ к таблице. Если вы установите UniDirectional в True, то вы можете перемещаться по таблице более быстро, но вы сможете двигаться только вперед. Свойство StmtHandle связано со свойством Handle TDataSet. То есть, оно включено исключительно для того, чтобы вы могли делать вызовы Borland Database Engine напрямую. При нормальных обстоятельствах нет никакой необходимости использовать это свойство, так как компоненты Delphi могут удовлетворить потребностями большинства программистов. Однако, если вы знакомы с Borland Database Engine, и если вы знаете, что существуют некоторые возможности, не поддерживаемые в VCL, то вы можете использовать TQuery.StmtHandle или TQuery.Handle, чтобы сделать вызов напрямую в engine.

Следующий фрагмент кода показывает два запроса к BDE:

var

Name: array[0..100] of Char;

Records: Integer;

begin

dbiGetNetUserName(Name);

dbiGetRecordCount(Query1.Handle, Records);

end;

Глава 53.
Редактор DataSet

Вызов редактора DataSet

Редактор DataSet может быть вызван с помощью объектов TTable или TQuery. Чтобы начать работать с ним, положите объект TQuery на форму, установите псевдоним DBDEMOS, введите SQL запрос select * from customer и активизируйте его (установив свойство Active в True).

Откройте комбобокс Object Selector вверху Инспектора Объектов — в настоящее время там имеется два компонента: TForm и TQuery.

Нажмите правую кнопку мыши на объекте TQuery и в контекстном меню выберите пункт Fields Editor. Нажмите кнопку Add — появится диалог Add Fields.

По умолчанию, все поля в диалоге выбраны. Нажмите на кнопку OK, чтобы выбрать все поля, и закройте редактор. Снова загляните в Object Selector, теперь здесь появилось несколько новых объектов.

Object Selector показывает в списке все объекты, созданные в Редакторе DataSet. Вы можете также найти этот список в определении класса TForm1.

Эти новые объекты будут использоваться для визуального представления таблицы CUSTOMER пользователю.

Вот полный список объектов, которые только что созданы:

Query1CustNo: TFloatField;

Query1Company: TStringField;

Query1Addr1: TStringField;

Query1Addr2: TStringField;

Query1City: TStringField;

Query1State: TStringField;

Query1Zip: TStringField;

Query1Country: TStringField;

Query1Phone: TStringField;

Query1FAX: TStringField;

Query1TaxRate: TFloatField;

Query1Contact: TStringField;

Query1LastInvoiceDate: TDateTimeField;

Происхождение имен, показанных здесь, должно быть достаточно очевидно. Часть Query1 берется по умолчанию от имени объекта TQuery, а вторая половина от имени поля в таблице Customer. Если бы мы сейчас переименовали объект Query1 в Customer, то получили бы такие имена:

CustomerCustNo

CustomerCompany

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

Любой объект, созданный в редакторе DataSet является наследником класса TField. Точный тип потомка зависит от типа данных в конкретном поле. Например, поле CustNo имеет тип TFloatField, а поле Query1City имеет тип TStringField. Это два типа полей, которые вы будете встречать наиболее часто. Другие типы включают тип TDateTimeField, который представлен полем Query1LastInvoiceDate, и TIntegerField, который не встречается в этой таблице.

Чтобы понять, что можно делать с потомками TField, откройте Browser, выключите просмотр полей Private и Protected, и просмотрите свойства и методы Public и Published соответствующих классов.

Наиболее важное свойство называется Value. Вы можете получить доступ к нему так:

procedure TForm1.Button1Click(Sender: TObject);

var

d: Double;

S: string;

begin

d := Query1CustNo.Value;

S := Query1Company.Value;

d:=d+1;

S := 'Zoo';

Query1CustNo.Value := d;

Query1Company.Value := S;

end;

В коде, показанном здесь, сначала присваиваются значения переменным d и S. Следующие две строки изменяют эти значения, а последние две присваивают новые значения объектам. Не имеет большого смысла писать код, подобный этому, в программе, но этот код служит лишь для того, чтобы продемонстрировать синтаксис, используемый с потомками TField.

Свойство Value всегда соответствует типу поля, к которому оно относится. Например, у TStringFields — string, TCurrencyFields — double. Однако, если вы отображаете поле типа TCurrencyField с помощью компонент, «чувствительных к данным» (data-aware: TDBEdit, TDBGrid), то оно будет представлено строкой типа: «$5.00».

Это могло бы заставить вас думать, что у Delphi внезапно отключился строгий контроль типов. Ведь TCurrencyField.Value объявлена как Double, и если вы пробуете присвоить ему строку, вы получите ошибку «type mismatch» (несоответствие типа). Вышеупомянутый пример демонстрирует на самом деле свойства объектов визуализации данных, а не ослабление проверки типов. (Однако, есть возможность получить значение поля, уже преобразованное к другому типу. Для этого у TField и его потомков имеется набор методов типа AsString или AsFloat. Конечно, преобразование происходит только тогда, когда имеет смысл.)

Если нужно получить имена полей в текущем DataSet, то для этого используется свойство FieldName одним из двух способов, показанных ниже:

S := Query1.Fields[0].FieldName;

S := Query1CustNo.FieldName;

Если вы хотите получить имя объекта, связанного с полем, то вы должны использовать свойство Name:

S := Query1.Fields[0].Name;

S := Query1CustNo.Name;

Для таблицы CUSTOMER, первый пример вернет строку CustNo, а любая из строк второго примера строку Query1CustNo.

Вычисляемые Поля

Создание вычисляемых полей — одно из наиболее ценных свойств Редактора DataSet. Вы можете использовать эти поля для различных целей, но два случая выделяются особо:

u выполнение вычислений по двум или более полям в DataSet, и отображение результата вычислений в третьем поле.

u имитация соединения двух таблиц с возможностью редактировать результат соединения.

Программа CALC_SUM.DPR из примеров к данной главе иллюстрирует первый случай использования вычисляемых полей.

Эта программа связывает три таблицы в отношении один ко многим. В частности, ORDERS и ITEMS связаны по полю OrderNo, а ITEMS и PARTS связаны по полю PartNo. (В таблице ORDERS хранятся все заказы; в таблице ITEMS — предметы, указанные в заказах; PARTS — справочник предметов). В программе можно перемещаться по таблице ORDERS и видеть связанный с текущим заказом список включенных в него предметов. Программа CALC_SUM достаточно сложная, но хорошо иллюстрирует мощность вычисляемых полей.

Последовательность создания проекта CALC_SUM:

u Создайте новый проект (File ê New Project) и удалите из него форму (в Менеджере Проекта View ê Project Manager).

u Выберите эксперта форм БД из меню Help.

u На первом экране выберите Create a master/detail form и Create a form using TQuery Objects.

u Нажмите кнопку Next и выберите таблицу ORDERS.DB из псевдонима БД DBDEMOS.

u Нажмите Next и выберите поля OrderNo, CustNo, SaleDate, ShipDate и ItemsTotal из таблицы ORDERS.DB.

u Нажмите Next и выберите Horizontal из расстановки компонентов dbEdit на форме.

u Нажмите Next и выберите таблицу ITEMS.DB.

u В двух следующих экранах выберите все поля из таблицы и поместите их в grid.

u Нажмите Next и выберите поле OrderNo из Master и Detail ListBoxes и нажмите кнопку Add.

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

Выделите первый из двух объектов TQuery и установите свойство Active в True. Для Query2 в свойстве SQL напишите текст запроса:

select * from Items I, Parts P

where (I.OrderNo =:OrderNo) and

(I.PartNo=P.PartNo)

Активизируйте объект Query2 (Active установите в True) и вызовите редактор DataSet (Fields Editor) для него. Вызовите диалог Add Fields и добавьте поля OrderNo, PartNo, Qty и ListPrice.

Нажмите Define и ведите слово Total в поле FieldName. Установите Field Type в CurrencyField. Проверьте, что Calculated CheckBox отмечен. Нажмите Ok и закройте редактор DataSet. Простой процесс описанный в предыдущем абзаце, показывает как создать вычисляемое поле. Если посмотреть в DBGrid, то можно видеть, что там теперь есть еще одно пустое поле. Для того, чтобы поместить значение в это поле, откройте в Инспекторе Объектов страницу событий для объекта Query2 и сделайте двойной щелчок на OnCalcFields. Заполните созданный метод так:

procedure TForm2.Query2CalcFields(DataSet: TDataSet);

begin

Query2NewTotalInvoice.Value := 23.0;

end;

После запуска программы поле Total будет содержит строку $23.00.

Это показывает, насколько просто создать вычисляемое поле, которое показывает правильно сформатированные данные. На самом деле это поле должно показывать нечто другое — произведение полей Qty (количество) и ListPrice (цена). Для этого вышеприведенный код для события OnCalcFields нужно изменить следующим образом:

procedure TForm1.Query2CalcFields(DataSet: TDataset);

begin

Query2Total.Value:=Query2Qty.Value*Query2ListPrice.Value;

end;

Если теперь запустить программу, то поле Total будет содержать требуемое значение.

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

Теперь давайте добавим вычисляемое поле для первой таблицы (Query1, ORDERS), которое будет отображать сумму значений из поля Total второй таблицы (Query2) для данного заказа. Вызовите редактор DataSet для объекта Query1 и добавьте вычисляемое поле NewItemsTotal типа CurrencyField. В обработчике события OnCalcFields для Query1 нужно подсчитать сумму и присвоить ее полю NewItemsTotal:

procedure TForm1.Query1CalcFields(DataSet: TDataset);

var

R : Double;

begin

R:=0;

with Query2 do begin

DisableControls;

Close;

Open;

repeat

R:=R+Query2Total.Value;

Next;

until EOF;

First;

EnableControls;

end;

Query1NewItemsTotal.Value:=R;

end;

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

Метод DisableControls вызывается для того, чтобы отменить перерисовку DBGrid при сканировании таблицы. Запрос Query2 переоткрывается для уверенности в том, что его текущий набор записей соответствует текущему заказу.

Поместите на форму еще один элемент DBEdit и привяжите его к Query1, полю NewItemsTotal. Запустите программу CALC_SUM.

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

Управление TDBGrid во время выполнения

Объект DBGrid может быть полностью реконфигурирован во время выполнения программы. Вы можете прятать и показывать колонки, изменять порядок показа колонок и их ширину.

Вы можете использовать свойство Options объекта DBGrid, чтобы изменить ее представление. Свойство Options может принимать следующие возможные значения:

DgEditing

Установлен по умолчанию в true, позволяет пользователю редактировать grid. Вы можете также установить свойство ReadOnly grid в True или False.

DgTitles

Будут ли видны названия колонок.

DgIndicator

Будут ли видны небольшие иконки слева.

DgColumnResize

Может ли пользователь менять размер колонки.

dgColLines

Показывать ли линии между колонками.

dgRowLines

Показывать ли линии между строками.

dgTabs

Может ли пользователь использовать tab и shift-tab для переключения между колонками.

Как объявлено в этой структуре:

TDBGridOption = (dgEditing, gdAlwaysShowEditor, dgTitles,

dgIndicator, dgColumnResize, dgColLines,

dgRowLines, dgTabs);

Например, вы можете установить опции в Runtime написав такой код:

DBGrid1.Options := [dgTitles, dgIndicator];

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

DBGrid1.Options := DBGrid1.Options + [dgTitles];

Пусть есть переменная ShowTitles типа Boolean, тогда следующий код позволяют включать и выключать параметр одной кнопкой:

procedure TForm1.Button3Click(Sender: TObject);

begin

if ShowTitles then

DBGrid1.Options := DBGrid1.Options + [dgTitles]

else

DBGrid1.Options := DBGrid1.Options — [dgTitles];

ShowTitles := not ShowTitles;

end;

Если вы хотите скрыть поле в run-time, то можете установить свойство visible в false:

Query1.FieldByName(‘CustNo’).Visible := False;

Query1CustNo.Visible := False;

Обе строки кода выполняют идентичную задачу. Чтобы показать поле снова, установите видимый в true:

Query1.FieldByName(‘CustNo’).Visible := True;

Query1CustNo.Visible := True;

Если вы хотите изменить положение колонки в Runtime, можете просто изменить индекс, (первое поле в записи имеет индекс нуль):

Query1.FieldByName(‘CustNo’).Index := 1;

Query1CustNo.Index := 2;

По умолчанию, поле CustNo в таблице Customer является первым. Код в первой строке перемещает это поле во вторую позицию, а следующая строка перемещает его в третью позицию. Помните, что нумерация полей начинается с нуля, так присвоение свойству Index 1 делает поле вторым в записи. Первое поле имеет Index 0.

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

Если вы хотите изменить ширину колонки в Runtime, только измените свойство DisplayWidth соответствующего TField.

Query1.FieldByName(‘CustNo’).DisplayWidth := 12;

Query1CustNo.DisplayWidth := 12;

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

Программа DBGR_RT показывает как работать с DBGrid в Runtime. Программа достаточно проста, кроме двух небольших частей, которые описаны ниже. Первая часть показывает, как создать check box в Runtime, а вторая показывает, как изменить порядок пунктов в listbox в Runtime.

При создании формы (событие OnCreate) ListBox заполняется именами полей, далее создается массив объектов CheckBox, соответствующий полям в таблице. Сперва все CheckBox’ы выбраны и все поля в таблице видимы. Программа узнает через TTable1 имена полей и присваивает их свойству Caption соответствующего CheckBox. Кроме того, обработчику события OnClick всех CheckBox’ов присваивается процедура ChBClick, которая и включает/выключает поля в DBGrid.

procedure TForm1.FormCreate(Sender: TObject);

var

i : Word;

R : Array[0..49] of TCheckBox;

begin

{Fill ListBox}

ListBox1.Clear;

for i:=0 to Table1.FieldCount-1 do

ListBox1.Items.Add(Table1.Fields[i].FieldName);

{Make CheckBoxes}

for i:=0 to Table1.FieldCount-1 do begin

R[I] := TCheckBox.Create(Self);

R[I].Parent := ScrollBox1;

R[I].Caption := Table1.Fields[i].FieldName;

R[I].Left := 10;

R[I].Top := I * CheckBox1.Height + 5;

R[I].Width := 200;

R[I].Checked := True;

R[I].OnClick := ChBClick;

end;

end;

Большая часть кода в этом примере выполняет относительно простые задачи, типа назначения имен и положений check boxes. Вот две ключевых строки:

R[I] := TCheckBox.Create(Self);

R[I].Parent := ScrollBox1;

Первая строка создает CheckBox с заданным Owner (Владельцем). Вторая строка назначает Parent (Родителя) для CheckBox. Чтобы понять различия между Родителем и Владельцем, посмотрите соответствующие свойства в online-help.

Программа содержит ListBox, который показывает текущий порядок полей в DataSet. Для изменения порядка полей в DataSet (а, следовательно, в DBGrid) используются две кнопки. При нажатии на одну из кнопок, выбранное в ListBox’е поле перемещается на одну позицию вверх или вниз. Синхронно с этим меняется и порядок полей в DBGrid. Код, показанный ниже, изменяет Index поля для Table1, изменяя, таким образом, позицию поля в DBGrid. Эти изменения касаются только визуального представления DataSet. Физически данные на диске не изменяются.

procedure TForm1.downButtonClick(Sender: TObject);

var

i : Integer;

begin

with ListBox1 do

if (ItemIndex<Items.Count-1)and(ItemIndex<>-1) then begin

i := ItemIndex;

{move ListBox item}

Items.Move(i, i+1);

ItemIndex := i+1;

{move Field}

Table1.Fields[i].Index:=i+1;

end;

end;

Последняя строка в примере как раз та, которая фактически изменяет индекс колонки, которую пользователь хочет переместить. Две строки кода непосредственно перед ней перемещают текущую строку в ListBox на новую позицию.

Глава 54.
Управление соединением с базой данных

Класс TDataBase

Объект типа TDataBase не является обязательным при работе с базами данных, однако он предоставляет ряд дополнительных возможностей по управлению соединением с базой данных. TDataBase служит для:

u Создания постоянного соединения с базой данных;

u Определения собственного диалога при соединении с базой данных (опрос пароля);

u Создания локального псевдонима базы данных;

u Изменения параметров при соединении;

u Управления транзакциями.

TDataBase является невидимым во время выполнения объектом. Он находится на странице Data Access Палитры Компонент. Для включения в проект TDataBase нужно «положить» его на главное окно вашей программы.

Создание постоянного соединения с базой данных

Если вы работаете с базой данных, то перед началом работы выполняется процедура соединения с этой базой. В процедуру соединения, кроме прочего, входит опрос имени и пароля пользователя (кроме случая работы с локальными таблицами Paradox и dBase через IDAPI). Если в программе не используется TDataBase, то процедура соединения выполняется при открытии первой таблицы из базы данных. Соединение с базой данных обрывается, когда в программе закрывается последняя таблица из этой базы (это происходит в том случае, если свойство KeepConnections объекта Session установлено в False). Теперь, если снова открыть таблицу, то процедура установки соединения повторится и это может быть достаточно неудобно для пользователя. Чтобы соединение не обрывалось даже в том случае, когда нет открытых таблиц данной базы, можно использовать компонент типа TDataBase. В свойстве AliasName укажите псевдоним базы данных, с которой работает программа; в свойстве DatabaseName — любое имя (псевдоним БД), на которое будут ссылаться таблицы вместо старого псевдонима базы. Свойство Connected установите в True — процедура соединения с базой будет выполняться при запуске программы. И, наконец, свойство KeepConnection нужно установить в True.

После задания свойств DataBase1 нужно у всех таблиц, работающих с IBLOCAL в свойстве DatabaseName поставить Loc_IBLOCAL.

Определение собственного диалога при соединении с базой данных

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

При желании можно изменить внешний вид диалога или вообще его отменить. Для этого используются свойства и события класса TDataBase — LoginPrompt, Params и OnLogin.

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

DataBase1.LoginPrompt:=False;

DataBase1.Params.Clear;

DataBase1.Params.Add(‘USER NAME=SYSDBA’);

DataBase1.Params.Add(‘PASSWORD=masterkey’);

DataBase1.Connected:=True;

Чтобы использовать свой собственный диалог, в котором можно опрашивать не только имя и пароль пользователя, но и, например, сетевой протокол — создайте обработчик события OnLogin для DataBase1:

procedure TForm1.Database1Login(Database: TDatabase;

LoginParams: TStrings);

begin

Form2.ShowModal;

if Form2.ModalResult = mrOK then

with LoginParams do begin

Values['USER NAME'] := User_Name;

Values['PASSWORD'] := User_Pass;

end;

end;

Здесь Form2 — новое окно-диалог для ввода имени и пароля, User_Name и User_ Pass — строки, куда сохраняются введенные имя и пароль.

Создание локального псевдонима базы данных

Обычно, псевдоним базы данных (Alias) определяется в утилите конфигурации BDE и информация о нем сохраняется в файле конфигурации IDAPI.CFG. Однако, в программе можно использовать не только ранее определенный в утилите конфигурации BDE псевдоним базы данных, но и так называемый локальный (т.е. видимый только внутри данной программы) псевдоним. Это иногда бывает нужно, например, для того, чтобы обезопасить программу в случае удаления используемого псевдонима из файла конфигурации BDE.

Для того, чтобы создать локальный псевдоним БД, положите на главное окно проекта компонент DataBase1. Дальнейшие действия можно выполнить с помощью Инспектора Объектов, но удобнее это сделать через редактор компонент. Щелкните дважды мышкой на DataBase1 — появится диалог, в котором требуется указать имя базы данных — это будет ее локальный псевдоним, на который ссылаются таблицы (свойство DatabaseName), тип драйвера, а также параметры, используемые при соединении с базой данных. Получить список параметров в поле Parameter Overrides можно по нажатию кнопки Defaults. Набор параметров зависит от типа БД, с которой вы работаете. Этим параметрам нужно присвоить требуемые значения — указать путь к серверу, имя пользователя. После выхода из редактора компонент имя, указанное в поле Name появится в списке имен баз данных для компонент типа TDataSet (TTable, TQuery).

Изменение параметров при соединении

Иногда требуется изменить определенные в утилите конфигурации BDE параметры, используемые при установлении соединения с БД. Это можно сделать во время дизайна с помощью диалога, в поле Parameter Overrides. Либо во время выполнения программы (до попытки соединения) прямым присвоением свойству Params объекта DataBase1:

DataBase1.Params.Add(‘LANGDRIVER=ancyrr’);

Объект Session

Объект Session, имеющий тип TSession создается автоматически в программе, работающей с базами данных (в этом случае Delphi подключает в программу модуль DB). Вам не нужно заботиться о создании и уничтожении данного объекта, но его методы и свойства могут быть полезны в некоторых случаях. В этом компоненте содержится информация обо всех базах данных, с которыми работает программа. Ее можно найти в свойстве DataBases. Со свойством KeepConnections данного объекта мы уже знакомы. Это свойство определяет, нужно ли сохранять соединение с базой, если в программе нет ни одной открытой таблицы из этой базы. NetDir — директория, в которой лежит общий сетевой файл PDOXUSRS.NET, необходимый BDE. PrivateDir — директория для хранения временных файлов.

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

Еще одно важное назначение объекта Session — доступ с его помощью к таблицам Paradox, защищенным паролем. Прежде, чем открыть такую таблицу, требуется выполнить метод AddPassword:

Session.AddPassword(‘my_pass’);

Удалить пароль можно с помощью метода RemovePassword или RemoveAllPasswords.

Указание сетевого протокола при соединении с БД

В случае с InterBase можно в явном виде указать, какой сетевой протокол используется при соединении с базой данных. Эта установка выполняется либо в утилите конфигурации BDE, либо в программе — нужно изменить параметр SERVER NAME, который содержит полный путь к файлу с базой данных.

Итак:

Протокол Параметр SERVER NAME

TCP/IP IB_SERVER:PATH\DATABASE.GDB
(nt:c:\ib\base.gdb )
(unix:/ib/base.gdb )

IPX/SPX IB_SERVER:PATH\DATABASE.GDB
(nw@sys:ib\base.gdb )

NetBEUI IB_SERVER\PATH\DATABASE.GDB
(\\nt\c:\ib\base.gdb )

Глава 55.
Управление транзакциями

Понятие транкзации

Все операции, выполняемые с данными на SQL сервере, происходят в контексте транзакций.

Транзакция — это групповая операция, то есть, набор действий с базой данных; самым существенным для этих действий является правило либо все, либо ни чего. Если во время выполнения данного набора действий, на каком-то этапе невозможно произвести очередное действие, то нужно выполнить возврат базы данных к начальному состоянию (произвести откат транзакции). Таким образом (при правильном планировании транзакций), обеспечивается целостность базы данных. В данной главе объясняется, как начинать, управлять и завершать транзакции с помощью SQL выражений. А так же рассматривается вопрос об использовании транзакций в приложениях, созданных в Delphi.

Вся приведенная информация касается InterBase.

SQL-выражения для управления транзакциями

Для управления транзакциями имеется три выражения:

SET TRANSACTION

Начинает транзакцию и определяет ее поведение.

COMMIT

Сохраняет изменения, внесенные транзакцией, в базе данных и завершает транзакцию.

ROLLBACK

Отменяет изменения, внесенные транзакцией, и завершает транзакцию.

Запуск

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

SET TRANSACTION [Access mode] [Lock Resolution]

[Isolation Level] [Table Reservation]

Значения, принимаемые по-умолчанию:

u выражение SET TRANSACTION равносильно выражению SET TRANSACTION READ WRITE WAIT ISOLATION LEVEL SNAPSHOT

u Access Mode — определяет тип доступа к данным. Может принимать два значения:

u READ ONLY — указывает, что транзакция может только читать данные и не может модифицировать их.

u READ WRITE — указывает, что транзакция может читать и модифицировать данные. Это значение принимается по умолчанию.
Пример:
SET TRANSACTION READ WRITE

u Isolation Level — определяет порядок взаимодействия данной транзакции с другими в данной базе. Может принимать значения:

u SNAPSHOT — значение по умолчанию. Внутри транзакции будут доступны данные в том состоянии, в котором они находились на момент начала транзакции. Если по ходу дела в базе данных появились изменения, внесенные другими завершенными транзакциями, то данная транзакция их не увидит. При попытке модифицировать такие записи возникнет сообщение о конфликте.

u SNAPSHOT TABLE STABILITY — предоставляет транзакции исключительный доступ к таблицам, которые она использует. Другие транзакции смогут только читать данные из них.

u READ COMMITTED — позволяет транзакции видеть текущее состояние базы.

Конфликты, связанные с блокировкой записей происходят в двух случаях:

u Транзакция пытается модифицировать запись, которая была изменена или удалена уже после ее старта. Транзакция типа READ COMMITTED может вносить изменения в записи, модифицированные другими транзакциями после их завершения.

u Транзакция пытается модифицировать таблицу, которая заблокирована другой транзакцией типа SNAPSHOT TABLE STABILITY.

u Lock Resolution — определяет ход событий при обнаружении конфликта блокировки. Может принимать два значения:

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

u NO WAIT — немедленно возвращает ошибку блокировки записи.

Table Reservation — позволяет транзакции получить гарантированный доступ необходимого уровня к указанным таблицам. Существует четыре уровня доступа:

u PROTECTED READ — запрещает обновление таблицы другими транзакциями, но позволяет им выбирать данные из таблицы.

u PROTECTED WRITE — запрещает обновление таблицы другими транзакциями, читать данные из таблицы могут только транзакции типа SNAPSHOT или READ
COMMITTED.

u SHARED READ — самый либеральный уровень. Читать могут все, модифицировать — транзакции READ WRITE.

u SHARED WRITE — транзакции SNAPSHOT или READ COMMITTED READ WRITE могут модифицировать таблицу, остальные — только выбирать данные.

Завершение транзакции

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

u COMMIT — сохраняет внесенные транзакцией изменения в базу данных. Это означает, что транзакция завершена успешно.

u ROLLBACK — откат транзакции. Транзакция завершается и никаких изменений в базу данных не вносится. Данная операция выполняется при возникновении ошибки при выполнении операции (например, при невозможности обновить запись).

Управление транзакциями в Delphi

Прежде всего, транзакции в Delphi бывают явные и неявные.

Явная транзакция — это транзакция, начатая и завершенная с помощью методов объекта DataBase: StartTransaction, Commit, RollBack. После начала явной транзакции, все изменения, вносимые в данные относятся к этой транзакции.

Другого способа начать явную транзакцию, нежели с использованием DataBase, нет. (Точнее говоря, такая возможность есть, но это потребует обращения к функциям API InterBase. Однако, это уже достаточно низкоуровневое программирование.) Следовательно, в рамках одного соединения нельзя начать две транзакции.

Неявная транзакция стартует при модификации данных, если в данный момент нет явной транзакции. Неявная транзакция возникает, например, при выполнении метода Post для объектов Table и Query. То есть, если вы отредактировали запись в DBGrid и переходите на другую запись, то это влечет за собой выполнение Post, что, в свою очередь, приводит к началу неявной транзакции, обновлению данных внутри транзакции и ее завершению. Важно отметить, что неявная транзакция, начатая с помощью методов Post, Delete, Insert, Append заканчивается автоматически.

Для модификации данных может использоваться и PassThrough SQL — SQL-выражение, выполняемое с помощью метода ExecSQL класса TQuery. Выполнение модификации через PassThrough SQL также приводит к старту неявной транзакции. Дальнейшее поведение транзакции, начатой таким путем, определяется значением параметра SQLPASSTHRU MODE для псевдонима базы данных (или тот же параметр в свойстве Params объекта DataBase). Этот параметр может принимать три значения:

u SHARED AUTOCOMMIT — слово SHARED указывает на то, что оба вида транзакций (через Passthrough SQL и через методы TTable и TQuery) разделяют одно и то же соединение к базе данных. Слово AUTOCOMMIT указывает на то, что неявная транзакция, начатая через Passthrough SQL, завершается после выполнения действия по модификации данных (автоматически выполняется COMMIT).

u SHARED NOAUTOCOMMIT — отличается от предыдущего тем, что неявная транзакция, начатая через Passthrough SQL, не завершается после выполнения, ее нужно явно завершить, выполнив SQL-выражение
COMMIT.

u NOT SHARED — транзакции разных типов работают через разные соединения с базой. Данное значение параметра подразумевает также NOAUTOCOMMIT. То есть все неявные PassthroughSQL-транзакции нужно завершать явно — выполняя SQL-выражение COMMIT для Passtrough SQL.

Рассмотрим возможные сценарии поведения транзакций при разных значениях параметра. В первом случае, если нет в данный момент начатой транзакции, то попытка модификация данных методами TTable или TQuery, как и выполнение через Passtrough SQL какой-либо операции приведет к старту неявной транзакции. После выполнения, такая транзакция будет автоматически завершена (если не возникло ошибки по ходу транзакции). Если уже имеется начатая явно (метод StartTransaction объекта DataBase) транзакция, то изменения будут проходить в ее контексте. Все транзакции используют одно и то же соединение. Во втором случае все происходит, как в первом. Отличие в том, что неявная PassthroughSQL-транзакция не завершается, пока не будет выполнена команда COMMIT.

В третьем случае, при выполнении команды Passthrough SQL, будет установлено еще одно соединение, начата неявная транзакция и выполнены действия по модификации данных. Транзакция не будет завершена, пока не будет выполнена команда COMMIT. Наличие транзакции, начатой явно с помощью DataBase никак не отразится на ходе выполнения PassthroughSQL-транзакции. Пока PassthroughSQL-транзакция не завершится, изменения, внесенные ей, не будут видны в объектах Table и Query, работающих через другое соединение. PassthroughSQL-транзакции можно рассматривать в некотором смысле, как транзакции из другого приложения.

Взаимодействие транзакций данной программы с транзакциями из других приложений определяется свойством TransIsolation объекта DataBase. Для InterBase имеет смысл два значения: tiReadCommitted и tiRepeatableRead. Выполнение метода StartTransaction в этих двух случаях равносильно выполнению SQL-выражений, соответственно:

SET TRANSACTION READ WRITE WAIT ISOLATION LEVEL READ COMMITTED

и

SET TRANSACTION READ WRITE WAIT ISOLATION LEVEL SNAPSHOT

Глава 56.
Local InterBase

Система управления реляционными базами данных

InterBase — это система управления реляционными базами данных, поставляемая корпорацией BORLAND для построения приложений с архитектурой клиент-сервер произвольного масштаба: от сетевой среды небольшой рабочей группы с сервером под управлением Novell NetWare или Windows NT на базе IBM PC до информационных систем крупного предприятия на базе серверов IBM, Hewlett-Packard, SUN.

В пакет Delphi версии входит однопользовательская версия InterBase для Windows — Local InterBase. Используя Local InterBase можно создавать и отлаживать приложения, работающие с данными по схеме клиент-сервер, без подключения к настоящему серверу. В дальнейшем потребуется только перенастроить используемый псевдоним базы данных и программа будет работать с реальной базой без перекомпиляции. Кроме того, Local InterBase можно использовать в приложениях для работы с данными вместо таблиц Paradox.

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

Некоторые технические характеристики InterBase

Отличия Local InterBase от InterBase для других платформ, в частности, от InterBase для Windows NT — Local InterBase не поддерживает:

u функции, определяемые пользователем (UDF);

u BLOB фильтры;

u сигнализатор событий (event alerters);

u запись через журнал (Write Ahead Log (WAL));

u «отключение» и «включение» базы данных (database shutdown or restart);

u ведение теневой базы данных (database shadowing).

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

Максимальный размер базы данных

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

Максимальное количество физических файлов, из которых может состоять база

В системных таблицах InterBase поле, описывающее из каких файлов состоит база данных, включая все shadow, имеет тип SHORT. Соответственно не более 65,536.

Максимальное количество таблиц в базе данных

65,536. Таблицы нумеруются с использованием типа данных SHORT.

Максимальное количество записей в таблице и полей в записи

В записи может быть не более 1000 полей. Количество записей в таблице не ограничено.

Максимальный размер записи и поля

Запись не может быть больше 64К байт (не считая размера BLOB). Поле не может быть больше 32К байт, размер поля типа BLOB не ограничен.

Максимальное количество индексов в таблице и базе

В базе может быть 64K индексов. В одной таблице — 64 индекса.

Максимальное количество уровней вложенности SQL запроса

16 уровней вложенности.

Максимальное количество полей в составном индексе

Составной индекс может включать в себя не более 16 полей.

Максимальный размер stored procedure или trigger

Stored procedure или trigger может иметь размер кода не более 48K байт.

Количество UDF, определенных в одной базе

Длина имени UDF не более 31 символа. Соответственно максимальное количество UDF в базе ограниченно количеством уникальных имен в пределах этой длины.

InterBase Interactive SQL

В поставке Delphi есть две утилиты для доступа к базам данных и администрации сервера InterBase. Утилита Windows ISQL позволяет интерактивно выполнять SQL запросы к базе данных и получать результат. Это требуется в двух случаях: для отладки SQL выражения и для управления данными и их структурой.

Кроме того, создать базу данных, хранимые процедуры, триггеры также удобнее с помощью ISQL. ISQL позволяет обращаться как к данным на удаленном сервере, так и к локальным (к Local InterBase).

Рассмотрим порядок работы с этой программой.

Прежде, чем начать работу, нужно либо установить соединение с имеющейся базой данных, либо создать новую базу.

Установка соединения

После запуска ISQL выберите пункт меню File ê Connect to Database…, появится диалог, в котором нужно выбрать сервер (удаленный или локальный, в данном случае мы обращаемся к Local InterBase), файл базы данных, указать имя пользователя (SYSDBA — имя системного администратора) и пароль (masterkey — пароль по умолчанию).

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

Создание новой базы данных

Эту операцию можно выполнить в пункте меню File ê Create Database. В диалоге нужно указать имя файла (c:\bases\new_base.gdb), имя и пароль системного администратора (SYSDBA и masterkey), и, при необходимости, дополнительные параметры.

В данном случае создается база данных, поддерживающая русскую кодовую страницу WIN1251.

Если вы собираетесь работать из ISQL с базой данных в русской кодировке, то перед установкой соединения нужно в пункте меню Session ê Advanced Settings установить Character set on connect в WIN1251.

Получение информации о структуре базы данных

В ISQL можно получить полную информацию о структуре базы данных: список таблиц и их структуры, списки и текст триггеров, хранимых процедур. Эту операцию можно выполнить в пункте меню View или Extract. Например, для базы данных из поставки Delphi (лежит в \IBLOCAL\EXAMPLES\ EMPLOYEE.GDB), попробуем выбрать Extract ê SQL Metadata for Table для таблицы COUNTRY. В окошке ISQL Output появится текст SQL запроса, который создавал данную таблицу:

/* Extract Table COUNTRY */

/* Domain definitions */

CREATE DOMAIN COUNTRYNAME AS VARCHAR(15);

/* Table: COUNTRY, Owner: SYSDBA */

CREATE TABLE COUNTRY (COUNTRY COUNTRYNAME NOT NULL,

CURRENCY VARCHAR(10) NOT NULL,

PRIMARY KEY (COUNTRY));

Выполнение SQL запросов

Текст SQL запроса вводится в окошке SQL Statement. Для запуска его на выполнение, нажмите кнопку Run.

InterBase Server Manager

Утилита предназначена для администрирования InterBase. С ее помощью можно выполнить следующие операции:

u определить пользователей и их пароли;

u произвести резервное копирование;

u удалить «мусор» из базы;

u завершить/откатить зависшие транзакции;

u произвести проверку базы на наличие ошибок.

Резервное копирование

Обычно, операционные системы сами предоставляют возможности по сохранению баз данных в архивах. Однако, при резервном копировании, проведенном с помощью Server Manager, выполняются дополнительные операции. При этом:

u Увеличивается быстродействие базы. В процессе копирования/восстановления происходит «сбор мусора» — в базе данных освобождается место, занятое удаленными записями. Тем самым уменьшается физический размер базы. При восстановлении можно изменить размер страницы и разбить базу на несколько файлов.

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

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

Глава 57.
Создание собственных компонент

Добавление новых объектов в VCL

Поскольку Delphi является открытой средой и позволяет не только использовать объекты из Библиотеки Визуальных Компонент (VCL) в своей программе, но и создавать новые объекты. Причем, ничего другого, кроме Delphi, для этого не требуется. Создание нового объекта в Delphi не является очень сложной задачей, хотя для этого и требуется знание Windows API, объектно-ориентированного программирования и иерархии классов в VCL.

Может возникнуть вопрос; если в Delphi уже есть своя библиотека, то зачем еще создавать какие-то объекты? Ответ прост: нельзя создать библиотеку на все случаи жизни и на все вкусы. Новые компоненты, во-первых, позволяют расширить область применения Delphi: например, с помощью библиотек объектов третьих фирм разрабатывать приложения для работы в Internet. Во-вторых, позволяют дополнить или настроить для себя имеющиеся в VCL объекты (например, переопределить значения свойств, устанавливаемые по умолчанию).

Предположим, что у вас появился уже готовый компонент. Как его добавить в VCL? Для этого выберите пункт меню Options ê Install Components… Появится диалог установки нового компонента.

Нажмите Add и укажите модуль, содержащий процедуру регистрации, нажмите OK и после успешной перекомпиляции новый объект появится в палитре.

Заготовка для нового компонента

В среде Delphi есть специальный эксперт, создающий заготовку для нового компонента. Вызвать его можно в пункте меню File ê New Component…

В диалоге нужно указать имя нового класса (например, TMyButton), предка класса (TButton) и страницу палитры, куда поместить новый компонент (Samples). Если нажать OK, то эксперт создаст модуль — заготовку для нового компонента:

unit Unit1;

interface

uses

SysUtils, WinTypes, WinProcs, Messages, Classes, Graphics, Controls,

Forms, Dialogs, StdCtrls;

type

TMyButton = class(TButton)

private

{ Private declarations }

protected

{ Protected declarations }

public

{ Public declarations }

published

{ Published declarations }

end;

procedure Register;

implementation

procedure Register;

begin

RegisterComponents('Samples', [TMyButton]);

end;

end.

Модуль содержит декларацию нового класса и процедуру его регистрации в Палитре Компонент. В процедуре RegisterComponents первый параметр — имя страницы (можно указать свое имя — появится новая страница); второй параметр — множество объектов для регистрации. Теперь модуль нужно сохранить под новым именем (например, NEW_BTN.PAS) и приступить к дописыванию новых свойств и методов. После того, как эта работа закончена и новый компонент отлажен, можно добавить его в Палитру. Но перед этим желательно создать файл ресурсов, в котором будет лежать пиктограмма для представления данного объекта в Палитре Компонент. Файл ресурсов можно создать с помощью программы Resource Workshop, называться он должен точно так же, как модуль регистрации компонента и иметь расширение .DCR (т.е., если объект регистрируется в модуле NEW_BTN.PAS, то тогда имя файла ресурсов будет NEW_BTN.DCR). В файле ресурсов должен находиться ресурс типа BITMAP — картинка размером 28x28 точки (можно меньше), название картинки должно совпадать с именем класса (в нашем случае TMYBUTTON).

Соглашения по наименованиям

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

Итак:

u Все декларации типов начинаются на букву «T». Еще раз, Delphi не требует этого, но это делает очевидным, что TEdit, например, есть определение типа, а не переменная или поле класса.

u Имена свойствам нужно давать легко читаемые и информативные. Нужно помнить, что пользователь будет их видеть в Инспекторе Объектов. И имя вроде TextOrientation много удобнее, нежели TxtOr. Тоже самое относится к методам. Методы, доступные пользователю, должны иметь удобные названия.

u При создании свойств типа Event, имя такого свойства должно начинаться с On (например, OnClick, OnCreate).

u Имя метода для чтения свойства должен начинаться со слова Get. Например, метод GetStyle должен выполнять чтение для свойства Style.

u Имя метода для записи свойства должен начинаться со слова Set. Например, метод SetStyle должен выполнять запись в свойство Style.

u Внутреннее поле для хранения данных свойства должно носить имя, начинающееся с буквы «F». Например, свойство Handle могло бы храниться в поле FHandle.

Конечно же, есть исключения из правил. Иногда бывает удобнее их нарушить, например, класс TTable имеет свойства типа Event, которые называются BeforePost, AfterPost.

Выбор предка

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

u TObject — Можно использовать в качестве предка, если с этим компонентом не нужно работать во время дизайна. Это может быть, например, класс, содержащий значения переменных среды (environment) или класс для работы с INI-файлами.

u TComponent — Отправная точка для многих невидимых компонент. Данный класс обладает встроенной возможностью сохранять/считывать себя в потоке во время дизайна.

u TGraphicControl — Используйте этот класс для создания видимых компонент, которым не нужен handle. Такие компоненты рисуют прямо на своей поверхности и требуют мало ресурсов Windows.

u TWinControl — Базовый класс для компонент, которые имеют окно. Данное окно имеет свой handle, его используют при доступе к возможностям Windows через API.

u TCustomControl — Потомок TWinControl, вводит понятие канвы (Canvas) и метод Paint() для лучшего контроля за прорисовкой компонента. Именно этот класс используется в качестве базового для построения большинства видимых компонент, имеющих оконный handle.

u TXxxxx — Класс вроде TEdit или TButton. Используются с целью доопределения их свойств и методов или переопределения значения свойств, принимаемых по умолчанию.

Пример создания компонента

Для примера создадим новый класс, мутант TButton, в котором изменим значение по умолчанию свойства ShowHint на True и добавим новое свойство — счетчик нажатий на кнопку. Заготовка модуля для создания нового компонента уже есть. Теперь исходный текст выглядит так:

unit New_btn;

interface

uses

SysUtils, WinTypes, WinProcs, Messages, Classes, Graphics,

Controls, Forms, Dialogs, StdCtrls;

type

TMyButton = class(TButton)

private

{ Private declarations }

FClickCount : Longint;

protected

{ Protected declarations }

public

{ Public declarations }

constructor Create(AOwner : TComponent); override;

procedure Click; override;

property ClickCount : Longint read FClickCount write FClickCount;

published

{ Published declarations }

end;

procedure Register;

implementation constructor TMyButton.Create(AOwner : TComponent);

begin

inherited Create(AOwner);

ShowHint:=True;

FClickCount:=0;

end;

procedure TMyButton.Click;

begin

Inc(FClickCount);

inherited Click;

end;

procedure Register;

begin

RegisterComponents('Samples', [TMyButton]);

end;

end

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

Новое свойство для подсчета нажатий на клавишу называется ClickCount. Его внутреннее поле для сохранения значения — FClickCount имеет тип Longint, емкости поля хватит надолго.

Глава 58.
Редакторы свойств

Возможность изменения среды программирования

Открытость Delphi проявляется наиболее ярко в том, что наряду с расширяемостью Библиотеки Визуальных Компонент можно изменять саму среду программирования. Delphi предоставляет соответствующее API с тем, чтобы программисты могли расширять функциональность среды разработки. С помощью этого API можно создать свои собственные Эксперты (Experts), свою Систему Контроля Версий (Version Control system), Редакторы Компонент (Component Editors) и Редакторы Свойств (Property Editors).

При написании новых объектов часто требуется создавать для них свои Редакторы Свойств и Редакторы Компонент.

Краткое описание инструментов среды Delphi и модулей, в которых реализованы соответствующие API:

u API Экспертов — Позволяет создать свои собственные эксперты; модули EXPINTF.PAS и VIRTINTF.PAS

u API Контроля Версий — Дает возможность создать свою систему Контроля Версий или подключить систему третьей фирмы; модули VCSINTF.PAS и VIRTINTF.PAS

u API Редакторов Компонент — Создание диалогов, связанных с объектом во время дизайна. Пример — Menu Designer для TMenu или Fields Editor для TTable; модуль DSGNINTF.PAS

u API Редакторов Свойств — Создание редакторов для использования их при редактировании свойств в Инспекторе Объектов; модуль DSGNINTF.PAS

Модули можно найти в библиотеке визуальных компонент (в директории X:\DELPHI\SOURCE\VCL).

Как вы знаете, во время дизайна для настройки внешнего вида и поведения объекта нужно пользоваться Инспектором Объектов. Например, можно изменить цвет фона у объекта TLabel на форме.

Перейдем в окно Инспектора Объектов и выберем свойство Color — отметьте, что справа есть маленькая стрелка, она означает, что мы можем выбрать цвет из списка. Нажмите мышкой на эту стрелку.

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

Стандартные Редакторы Свойств

Прежде, чем приступить к созданию своего Редактора Свойств, давайте исследуем уже имеющиеся в среде Delphi редакторы свойств. Вы уже видели редактор для свойства Color. Даже простейшие свойства, вроде Left или Caption, имеют свои редакторы. Причем, компоненты сами по себе даже не знают, что за редакторы используются для их свойств. Это означает, что вы можете свой Редактор Свойств связать с уже существующими свойствами. Например, можно было бы написать Редактор Свойств, который ограничивает свойство, имеющее целый тип (Integer), некоторым максимальным значением и затем связать этот редактор со свойством Width для всех существующих компонент.

Взглянем на иерархию классов Редакторов Свойств. Базовым является класс TPropertyEditor:

TPropertyEditor

TOrdinalProperty

TIntegerProperty

TColorProperty

TModalResultProperty

TTabOrderProperty

TCharProperty

TEnumProperty

TSetProperty

TShortCutProperty

TFloatProperty

TStringProperty

TComponentNameProperty

TFontNameProperty

TCaptionProperty

TSetElementProperty

TClassProperty

TFontProperty

TMethodProperty

TComponentProperty

Названия классов в большинстве своем очевидны. Класс TFloatProperty связан со свойствами, которые имеют тип Float, класс TSetProperty связан со свойствами, которые имеют тип Set.

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

Класс TPropertyEditor

Прежде, чем писать свой собственный Редактор Свойств, нужно разобраться в базовом классе TPropertyEditor:

TPropertyEditor = class

private

FDesigner: TFormDesigner;

FPropList: PInstPropList;

FPropCount: Integer;

constructor Create(ADesigner: TFormDesigner; APropCount: Integer);

function GetPrivateDirectory: string;

procedure SetPropEntry(Index: Integer; AInstance: TComponent;

APropInfo: PPropInfo);

protected

function GetPropInfo: PPropInfo;

function GetFloatValue: Extended;

function GetFloatValueAt(Index: Integer): Extended;

function GetMethodValue: TMethod;

function GetMethodValueAt(Index: Integer): TMethod;

function GetOrdValue: Longint;

function GetOrdValueAt(Index: Integer): Longint;

function GetStrValue: string;

function GetStrValueAt(Index: Integer): string;

procedure Modified;

procedure SetFloatValue(Value: Extended);

procedure SetMethodValue(const Value: TMethod);

procedure SetOrdValue(Value: Longint);

procedure SetStrValue(const Value: string);

public

destructor Destroy; override;

procedure Activate; virtual;

function AllEqual: Boolean; virtual;

procedure Edit; virtual;

function GetAttributes: TPropertyAttributes; virtual;

function GetComponent(Index: Integer): TComponent;

function GetEditLimit: Integer; virtual;

function GetName: string; virtual;

procedure GetProperties(Proc: TGetPropEditProc); virtual;

function GetPropType: PTypeInfo;

function GetValue: string; virtual;

procedure GetValues(Proc: TGetStrProc); virtual;

procedure Initialize; virtual;

procedure SetValue(const Value: string); virtual;

property Designer: TFormDesigner read FDesigner;

property PrivateDirectory: string read GetPrivateDirectory;

property PropCount: Integer read FPropCount;

property Value: string read GetValue write SetValue;

end;

Методы, приведенные ниже, можно переопределять (override) для изменения поведения Редактора свойств. (SetXxxValue используется для представления одного из методов SetFloatValue, SetMethodValue, SetOrdValue или SetStrValue. GetXxxValue обозначает GetFloatValue, GetMethodValue, GetOrdValue или GetStrValue)

Activate

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

AllEqual

Вызывается всякий раз, когда на форме выбирается более чем один объект. Если этот метод возвращает True, то вызывается GetValue, иначе в Инспекторе Объектов показывается пустая строка. AllEqual вызывается при условии, что GetAttributes возвращает paMultiSelect.

Edit

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

GetAttributes

Возвращает необходимую Инспектору Объектов информацию для того, чтобы тот смог отобразить свойство в подходящей манере. GetAttributes возвращает множество (set) значений типа TPropertyAttributes:

u paValueList: Редактор свойств может возвращать список значений для этого свойства. Если этот атрибут установлен, то нужно определить GetValues. В Инспекторе объектов справа от свойства появится кнопка для выпадающего списка.

u paSortList: Инспектор объектов будет сортировать список, полученный от GetValues.

u paSubProperties: Свойство имеет подсвойства, которые будут показываться ниже в виде иерархии (outline). Если GetProperties будет генерировать объекты-свойства, то этот атрибут должен быть установлен.

u paDialog: Показывает, что метод Edit будет вызывать диалог. Если данный атрибут установлен, то появится кнопка '...' справа от свойства в Инспекторе Объектов.

u paMultiSelect: Позволяет свойству оставаться в Инспекторе Объектов, когда на форме выбрано сразу несколько объектов. Некоторые свойства не годятся для множественного выбора, например, Name.

u paAutoUpdate: Если этот атрибут установлен, то метод SetValue будет вызываться при каждом изменении, произведенном в редакторе, а не после завершения редактирования (пример — свойство Caption).

u paReadOnly: Значение менять нельзя.

GetComponent

Возвращает компонент под номером Index в случае множественного выбора объектов (multiselect). GetAttributes должен возвращать paMultiSelect.

GetEditLimit

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

GetName

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

GetProperties

Должен быть переопределен для вызова PropertyProc для каждого подсвойства (или вложенного свойства) редактируемого свойства и передачи нового TPropertyEdtior для каждого подсвойства. По умолчанию, PropertyProc не вызывается и подсвойства не ожидаются. TClassProperty будет передавать новый редактор свойств для каждого свойства, объявленного published в классе. TSetProperty передает новый редактор для каждого элемента множества.

GetPropType

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

GetValue

Возвращает значение свойства в виде строки. По умолчанию возвращает '(unknown)'.

GetValues

Вызывается, если GetAttributes возвращает paValueList. Должно вызвать Proc для каждого значения, которое приемлемо для данного свойства.

Initialize

Вызывается при создании Редактора свойств.

SetValue(Value)

Вызывается для того, чтобы установить значение свойства. Редактор свойств должен уметь разобрать строку (Value) и вызвать метод SetXxxValue. Если строка имеет некорректный формат или неверное значение, то редактор Свойств должен сгенерировать исключительную ситуацию (exception), описывающую данную проблему. SetValue может вообще проигнорировать все изменения и оставить всю обработку изменений методу Edit (как в свойстве Picture).

Свойства и методы, полезные при создании нового класса Редактора свойств:

PrivateDirectory (свойство)

Это директория, в которой находится .EXE, либо рабочая директория, указанная в DELPHI.INI. Если редактор должен сохранить какую-то информацию (установки), то лучше в этой директории.

Value (свойство)

Текущее значение свойства, то же самое возвращает GetValue.

Modified (метод)

Вызывается для того, чтобы показать, что значение свойства изменилось. Методы SetXxxValue вызывают Modified автоматически.

GetXxxValue (метод)

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

SetXxxValue (метод)

Устанавливает значения свойства для всех выбранных объектов.

Создание Редактора Свойств

При создании нового Редактора Свойств, конечно, не нужно всегда переписывать его заново от базового класса TPropertyEditor. Может оказаться достаточным выбрать в качестве предка уже существующий для данного свойства редактор и переопределить некоторые его методы. Давайте рассмотрим простейший пример нового Редактора Свойств. Как вы знаете, у всех видимых объектов есть свойство Hint — подсказка, появляющаяся во время выполнения программы, если задержать на некоторое время мышь на объекте. Это свойство имеет тип String и во время дизайна для его редактирования используется Редактор типа TStringProperty. Обычно, подсказка бывает однострочной, но в некоторых случаях ее нужно сделать многострочной. В принципе, здесь проблемы нет, достаточно во время выполнения программы присвоить свойству Hint нужное значение, например:

Button1.Hint:=’Line1’#13#10’Line2’;

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

В нашем случае будет достаточно выбрать в качестве предка редактор TStringProperty и переписать некоторые методы. Во-первых, нужно переопределить метод Edit, в котором будет вызываться диалог для ввода строк подсказки. Во-вторых, нужно переопределить функцию GetAttributes, которая возвращает набор параметров, описывающих данное свойство. В частности, должен быть установлен атрибут paDialog, при этом в Инспекторе Объектов у свойства появится кнопка ‘…’ для вызова диалога. И вообще-то нужно изменить метод GetValue, который используется для отображения значения свойства в Инспекторе Объектов.

Назовем новый Редактор Свойств THintProperty, декларация нового класса:

THintProperty = class(TStringProperty)

public

function GetAttributes: TPropertyAttributes; override;

function GetValue : String; override;

procedure Edit; override;

end;

Рассмотрим по порядку методы нового класса.

Функция GetAttributes добавляет к унаследованному множеству атрибуты paDialog (появляется кнопка `…`) и paReadOnly (свойство нельзя редактировать непосредственно в Инспекторе Объектов, а только в диалоге, вызываемом через кнопку `…`):

function THintProperty.GetAttributes: TPropertyAttributes;

begin

Result := inherited GetAttributes + [paDialog,

paReadOnly];

end;

Функция GetValue заменяет «неправильные» символы #10 и #13 (перевод каретки и переход на новую строку) на символ «>»:

function THintProperty.GetValue : string;

var

i : Byte;

begin

result:=inherited GetValue;

for i:=1 to Byte(result[0]) do

if result[i]<#32 then result[i]:='>';

end;

Процедура Edit вызывает диалог для ввода строк подсказки. Диалог можно было бы нарисовать свой собственный, однако можно воспользоваться уже готовым. Несколько разных диалогов лежит в директории X:\DELPHI\SOURCE\LIB. Мы воспользуемся модулем STREDIT.PAS, в котором есть необходимый диалог редактирования строк. Итак, процедура Edit:

procedure THintProperty.Edit;

var

HintEditDlg : TStrEditDlg;

s : string;

begin

HintEditDlg:=TStrEditDlg.Create(Application);

with HintEditDlg do

try

Memo.MaxLength := 254;

s:=GetStrValue+#0;

Memo.Lines.SetText(@s[1]);

UpdateStatus(nil);

ActiveControl := Memo;

if ShowModal = mrOk then begin

s:=StrPas(Memo.Lines.GetText);

if s[0]>#2 then Dec(Byte(s[0]),2);

SetStrValue(s);

end;

finally

Free;

end;

end;

Строка if s[0]>#2 then Dec(Byte(s[0]),2) нужна, так как Memo.Lines.GetText возвращает все строки с символами #13#10.

Регистрация Редактора Свойств

Новый Редактор Свойств готов, осталось только его зарегистрировать в среде Delphi. Для этого в интерфейсной части модуля с нашим редактором требуется поместить декларацию процедуры Register, а в части implementation написать следующее:

procedure Register;

begin

RegisterPropertyEditor(TypeInfo(String), TControl, 'Hint',

THintProperty);

end;

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

Если вы зарегистрировали Редактор и указали как класс компоненты, так и имя свойства, то данный редактор «привязывается» ко всем свойствам, которые:

u имеют тип, указанный в первом параметре процедуры;

u принадлежат компоненте, которая относится к классу (или его потомкам), указанному во втором параметре;

u имеют имя, совпадающее с указанным в третьем параметре;

Если вместо типа класса в процедуре регистрации стоит Nil, а вместо имени свойства — пустая строка, то данный редактор «привязывается» ко всем свойствам, которые имеют тип, указанный в первом параметре, независимо от их имени или принадлежности к объекту какого-либо класса.

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

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

В нашем случае Редактор Свойств зарегистрирован для всех свойств, которые имеют тип String, относятся к компоненте класса TControl или наследника от него и имеют имя Hint.

Установка Редактора свойств

После того, как модуль с новым редактором свойств подготовлен, его нужно подключить к среде Delphi. Установка Редактора Свойств абсолютно аналогична установке новых объектов в палитру компонент и происходит следующим образом:

u выберите пункт меню Options ê Install Components...

u нажмите кнопку Add

u укажите имя подключаемого модуля (или воспользуйтесь кнопкой Browse)

u нажмите OK и еще раз OK

После успешной перекомпиляции библиотеки проверьте, как действует новый редактор свойств. Для этого создайте новый проект, положите на форму какой-либо видимый объект, например TButton, установите ShowHint для него в True, вызовите редактор подсказки (кнопка ‘…’ в свойстве Hint).

В диалоге нажмите OK и запустите программу.

Редактор Компонент

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

Давайте взглянем на класс TComponentEditor в модуле DSGNINTF.PAS:

TComponentEditor = class

private

FComponent: TComponent;

FDesigner: TFormDesigner;

public

constructor Create(AComponent: TComponent;

ADesigner: TFormDesigner); virtual;

procedure Edit; virtual;

procedure ExecuteVerb(Index: Integer); virtual;

function GetVerb(Index: Integer): string; virtual;

function GetVerbCount: Integer; virtual;

procedure Copy; virtual;

property Component: TComponent read FComponent;

property Designer: TFormDesigner read FDesigner;

end;

Редактор Компонент создается для каждого выбранного объекта на форме основываясь на классе объекта. При двойном щелчке на объекте вызывается метод Edit Редактора Компонент. При вызове контекстного меню (popup menu) по правой кнопке мыши, то для построения этого меню вызываются методы GetVerbCount и GetVerb. Если в этом меню выбирается пункт, то вызывается метод ExecuteVerb. Copy вызывается при копировании компонента в Clipboard. Редактор Компонент по умолчанию (TDefaultEditor) при двойном щелчке на объекте создает (или переходит на) в Редакторе Исходного Текста заготовку для событий OnCreate, OnChanged или OnClick (какое первым попадется).

При создании Редактора Компонент вы должны переопределить либо метод Edit, либо три следующих метода: GetVerb, GetVerbCount и ExecuteVerb. Можно переопределять все четыре метода. Если Редактор Компонент был вызван и изменил компонент, то всегда обязательно нужно вызвать метод Designer.Modified, чтобы Дизайнер об этом узнал.

Методы и свойства TComponentEditor

u Create(AComponent, ADesigner): Конструктор Редактора Компонент. AComponent — редактируемый компонент. ADesigner — интерфейс к Дизайнеру среды Delphi.

u Edit: Вызывается при двойном щелчке мышью на компоненте. Редактор Компонент может вызвать какой-нибудь диалог или эксперт.

u ExecuteVerb(Index): Выполняется, когда был выбран пункт номер Index из контекстного меню. Считается, что Редактор Компонент знает, как проинтерпретировать это значение.

u GetVerb(Index): Редактор Компонент должен вернуть в этом методе строку, которая будет показана в виде пункта контекстного меню. Можно использовать обычные для пунктов меню символы, например &.

u GetVerbCount: Возвращает число, которое определяет на какие значения будут отвечать методы GetVerb и ExecuteVerb. Например, если это число равно 3, то в меню будет добавлено три пункта со значениями Index от 0 до 2.

u Copy: Вызывается, когда компонент нужно скопировать в Clipboard. На самом деле, образы полей компонента уже находятся в Clipboard. Просто предоставляется возможность скопировать различные типы форматов, которые игнорируются Дизайнером, но которые могут быть распознаны другими приложениями.

Пример Редактора Компонент

В качестве примера давайте создадим Редактор Компонент для класса TButton. Этот Редактор будет показывать сообщение и изменять свойство Caption у объекта TButton. В данном примере это будет срабатывать и при двойном щелчке мыши и через контекстное меню.

Декларация нового класса Редактора Компонент:

TButtonEditor = class(TComponentEditor)

private

procedure HiThere;

public

procedure Edit; override;

procedure ExecuteVerb(Index: Integer); override;

function GetVerb(Index: Integer): string; override;

function GetVerbCount: Integer; override;

end;

Процедура HiThere и будет показывать сообщение и изменять свойство Caption:

procedure TButtonEditor.HiThere;

begin

MessageDlg('Hi! It replaces Default Component Editor.',

mtInformation, [mbOK], 0);

(Component as TButton).Caption:='Hi!';

Designer.Modified;

end;

Процедуры Edit и ExecuteVerb только вызывают HiThere:

procedure TButtonEditor.Edit;

begin

HiThere;

end;

procedure TButtonEditor.ExecuteVerb(Index: Integer);

begin

if Index = 0 then HiThere;

end;

Процедуры GetVerb и GetVerbCount определяют вид контекстного меню:

function TButtonEditor.GetVerb(Index: Integer): string;

begin

result:='&Get message ...'

end;

function TButtonEditor.GetVerbCount: Integer;

begin

result:=1;

end;

Здесь в контекстное меню добавляется один пункт Get message…

Редактор Компонент готов.

Необходимо зарегистрировать новый Редактор Компонент, это делается аналогично регистрации Редактора Свойств, только проще:

procedure Register;

begin

RegisterComponentEditor(TButton, TButtonEditor);

end;

После того, как вы подключите новый Редактор Компонент в среду Delphi, а это делается в пункте меню Options ê Install Components, создайте новый проект, положите на форму объект TButton и щелкните дважды на нем — появится диалог:

После того, как вы нажмете OK, текст на кнопке изменится.

Созданный нами Редактор Компонент заместит Редактор по умолчанию для всех объектов класса TButton и его наследников, например, TBitBtn.

Часть 4.
Язык программирования Perl

Глава 1.
Переменные

Начнем с переменных, они в Perl бывают 3-х типов: скалярные, списковые (массивы) и хэши (ассоциативные массивы).

Для указания компилятору (да и для немалого удобства программиста) перед именем скалярной переменной стоит знак «$» перед массивом — «@», перед хешем — «%».

Например:

$scalar_var,@array_var,%hash_var

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

Например:

”123”+”4” будет 127 (или ”127”),

так как операция «+» действует над числами, а вот если применить операцию конкатенации строк «.», то строковое

”test” . 1 будет ”test1”

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

Например:

$x=’qwerty’;

print ‘my var is $x’; #выведет my var is $x

print ”my var is $x”; #выведет my var is qwerty

Обычно элементы массивов индексируются целыми числами, первый элемент — нулевой. Отрицательное значение индекса обозначает номер позиции элемента с конца. Хеши индексируются строками символов.

Имена скалярных переменных всегда начинаются с символа «$» даже когда обозначают элемент массива.

Например:

$var1 # Простой скаляр 'var1'

$var1[0] # Первый элемент массива 'var1'

$var1{'first'} # Элемент с индексом 'first'

В случае использования имени массива «целиком» или его «среза» перед именем массива ставится символ «@».

Например:

@var1 # Все элементы массива var1 ( $var1[0],
# $var1[1], ... $var1[n])

@var1[1,3,10] # Элементы $var1[1], $var1[3], $var1[10]

@var1{'first','last'} # то же что и ( $var1{'first'},
# $var1{'last'} )

Хеш «целиком» начинается с символа «%».

Пример:

%var, %key, %years

Имена подпрограмм начинаются символом «&», если из контекста не видно, что это подпрограмма.

Например:

&sub1, &test_prog, test(12)

Имена таблиц символов всегда начинаются символом «*».

Каждый тип переменных имеет свою область памяти поэтому $var1 и $var1[0] совершенно разные переменные, хотя $var1[0] часть массива @var1. Так же @var1 и %var1 — разные массивы переменных.

Имена переменных могут содержать любые буквенно-цифровые символы за исключением пробела и табуляции. Эти символы используются в качестве разделителей. Большие и малые буквы различаются поэтому $var1 и $Var1 — разные переменные. В Perl по умолчанию имена меток и указателей файлов пишут большими буквами.

Глава 2.
Контекст

Большое значение для правильного употребления встроенных функций имеет контекст использования результата этих функций, так как в противном случае они возвращают совершенно «непонятный» результат. В Perl имеется два главных контекста: скалярный и список (list). Проще говоря, если в левой части выражения имеется ввиду одно единственное значение, то это скалярный контекст. Если множество значений — список.

Пример:

$var1 = <>; # Прочитать одну строку файла

@var1 = <>; # Прочитать все строки файла в массив @var1

$var1 = (1,2,3); # $var = 3 — количество элементов

@var1 = (1,2,3); # Создание массива @var1 с элементами 1,2,3

Глава 3.
Скалярные значения

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

В Perl не существует типов «строка» или «число» или «файл» или что-то еще. Это контекстно зависимый полиморфный язык для работы с текстами.

Скаляр имеет логическое значение «TRUE» (истина), если это не нулевая строка или число не равное 0.

В Perl существует два типа нулевых (null) скаляров: определенные (defined) и неопределенные (undefined). Неопределенное значение возвращается, когда что-то не существует. Например, неизвестная переменная, конец файла или ошибка. С помощью функции defined() вы можете заранее обнаружить подобное состояние.

Количество элементов массива так же является скаляром и начинается символами $# подобно интерпретатору csh. Фактически $#var1 — это индекс последнего элемента массива. Нужно помнить, что первый элемент имеет индекс 0, поэтому количество элементов определяется как $#var1+1. Присвоение значения $#var1 изменит длину массива и разрушит «оставленные» значения. Присвоение значения элементу массива с индексом больше чем $#var1 увеличит размер массива, а присвоение ему нулевого списка — обнулит.

В скалярном контексте имя массива возвращает его длину (для списка возвращается последний элемент).

Например:

@var1 = (4, 3, 2, 1); # Присвоение значения элементам массива

$i = @var1; # Использование скалярного контекста

print $i; # Печать результата 4 — кол-во элементов

print @var1; # Списковый контекст, печать всех элементов.

Для принудительного получения скалярного значения удобно применять функцию scalar().

Например:

print scalar(@var1); # Вывод длины массива а не его значений

Хеш в скалярном контексте возвращает true, если существует хотя бы одна пара «ключ-значение». Фактически возвращается строка типа 2/8, где 8 — количество выделенных «ячеек» памяти, а 2 — количество использованных.

Глава 4.
Конструкторы скаляров

Числа пишутся стандартно:

123

123.123

0.12

.12E-10

0xABCD # Шестнадцатеричная запись

0377 # Если 0 в начале — восьмеричная

123_456_123 # Так тоже можно для удобства чтения.

Строки ограничиваются одинарными (') или двойными (") кавычками:

'Равняйсь, смирно!'

"Построимся и спасемся."

Способов обозначения строк очень много.

В хеше можно опускать кавычки, если индекс не содержит пробелов.

Например:

$var1{first} то же что и $var1{'first'}

Обратите внимание на то, что перед первой одинарной кавычкой должен стоять пробел, иначе строка воспримется как имя переменной, так как в именах разрешено использование одинарных кавычек. Запрещается в кавычках применять зарезервированные литералы __LINE__ (номер текущей строки программы), __FILE__ (текущий файл). Для обозначения конца программы можно применять литерал __END__ Весь последующий текст игнорируется, но его можно прочитать используя указатель файла DATA.

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

В Perl есть возможность вставлять текст документа прямо в программу. Так называемый «here-doc» (здесь текст) метод. Обозначается символами << за которыми идет слово-ограничитель.

Например:

print <<EOF; # Все строки до EOF — текст для печати.

Эй вы трое, идите двое сюда!

Я где нормальный, а где и беспощадный!

EOF

Глава 5.
Конструкторы списков

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

Например:

@var1 = (1, 2, 'привет', 1.2); # Присвоить значение элементам.

где

$var1[0] = 1,

$var1[1] = 2,

$var1[2] = 'привет'

$var1[3] = 1.2

$var1 = (1, 2, 'привет', 1.2);

а здесь $var1 = 1.2 то есть, последнее значение списка.

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

Например:

@s1 = (1, 2, 3); # Первый список

@s2 = (6, 7, 8); # Второй

@s = (0, @s1, 4, 5, @s2, 9, 10); # Включаем списки @s1 и @s2

print @s; # Результат: 012345678910 - значения без пробелов

Список без элементов обозначается как () и называется нуль-списком. Списковое выражение можно употреблять как имя массива, но при этом его нужно брать в круглые скобки.

Например:

print ('январь','февраль','март')[1];

Результат:

февраль

Список может быть присвоен списку только если каждый элемент в списке в левой части выражения допустим по типу списку в правой части.

Например:

($a, $b, $c) = (1, 2, 3); # $a = 1, $b = 2, $c = 3

Присваивание списков в скалярном контексте возвращает количество присвоенных элементов.

Например:

$x = (($a, $b, $c) = (1,2)); # Результат $x=2

В случае присваивания списка хешу, список рассматривается как пары: ключ-значение.

Например:

%дни_месяца = ('январь', 31, 'февраль', 30);

Результат:

$дни_месяца{январь} = 31, $дни_месяца{февраль} = 30

Для удобства записи можно использовать выражение с =>.

Например:

%дни_месяца = (

январь => 31,

февраль => 30,

);

Глава 6.
Тип typeglobs

В Perl используется специальный внутренний тип typeglog для записи массива всех переменных. Такие массивы начинаются с символа «*». Их удобно применять для передачи ссылок на массивы и хеши, но в данной версии Perl уже есть возможность применять ссылки, поэтому это делается очень редко. Единственно где это необходимо, так это для работы со ссылками на файлы. Например, если вам нужно создать локальную ссылку на файл в процедуре, то это лучше сделать так:

sub new_sub

{ local *IN; # Ссылка на файл

open (IN, "test") || return undef; # Открыть файл. Возврат при ошибке.

.........

return;

}

Глава 7.
Встроенные переменные

Описанные в этой главе переменные имеют в Perl специальные значения. Они обозначаются несколько непривычно для «глаза» программистов, так как состоят обычно только из двух символов, причем первый это «$» символ, с которого начинаются имена всех переменных, и произвольный часто не буквенно-цифровой символ. Если вы хотите пользоваться их «нормальными» буквенными синонимами, то вам нужно указать в начале программы:

use English;

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

use FileHandle;

после этого можно просто писать:

метод указатель выражение

или

указатель -> метод(выражение)

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

$_
$ARG

Переменная — по умолчанию для операторов ввода и поиска. То есть если в качестве аргумента не указана никакая переменная, то используется именно эта.

$цифра

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

$&
$MATCH

Найденная подстрока в последнем поиске по шаблону.

$`

Подстрока, предшествующая найденной подстроке.

$'
$POSTMATCH

Подстрока, последующая за найденной подстрокой.

$+
$LAST_PAREN_MATCH

Подстрока, найденная в поиске с выбором по «или».

$*
$MULTILINE_MATCHING

Если значение этой переменной установить равным 1, то переменная, в которой осуществляется поиск, будет считаться многострочной, то есть, содержащей символы «\n» — перевод строки. Если значение равно 0, то переменная считается однострочной. В Perl версии 5 и выше не рекомендуется использовать эту переменную.

$.
$INPUT_LINE_NUMBER
$NR

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

$/
$RS
$INPUT_RECORD_SEPARATOR

Символ — признак конца входной строки. По умолчанию это «\n».

$|
$OUTPUT_AUTOFLUSH

Если присвоить этой переменной ненулевое значение, то будет сброс буфера вывода после каждой операции вывода. Значение по умолчанию — 0.

$,
$OFS
$OUTPUT_FIELD_SEPARATOR

Символ, добавляемый оператором print после каждого элемента из списка параметров.

$\
$ORS
$OUTPUT_RECORD_SEPARATOR

Символ, добавляемый print после вывода всех параметров.

$"
$LIST_SEPARATOR

Аналогичен «$,», но добавляется после каждого элемента массива, указанного в "....".

$;
$SUBSEP
$SUBSCRIPT_SEPARATOR

Символ — разделитель для эмуляции многомерных массивов в хеш массивах. По умолчанию «\034».

$#
$OFMT

Формат по умолчанию для вывода чисел.

$%
$FORMAT_PAGE_NUMBER

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

$=
$FORMAT_LINES_PER_PAGE

Длина одной страницы. По умолчанию 60 строк.

$-
$FORMAT_LINES_LEFT

Количество оставшихся строк на странице.

$~
$FORMAT_NAME

Имя формата текущего вывода. По умолчанию имя указателя.

$^
$FORMAT_TOP_NAME

Имя текущего формата для заголовка страницы.

$:
$FORMAT_LINE_BREAK_CHARACTERS

Символы переноса строки для многострочных полей. В строке формата такие поля начинаются символом «^». По умолчанию «\n-».

$^L
$FORMAT_FORMFEED

Символ перевода формата (смены листа). По умолчанию «\f».

$^A
$ACCUMULATOR

Текущее значение аккумулятора функции write() для format(). Значение этой переменной можно увидеть только при использовании функции formline(), так как write() очищает ее после каждого вывода.

$?
$CHILD_ERROR

Данная переменная содержит статус завершения таких процессов как: закрытие pipe, завершение функций system(), wait() и «...».

$!
$ERRNO $OS_ERROR

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

$@
$EVAL_ERROR

Сообщение об ошибке последней команды eval().

$$
$PID
$PROCESS_ID

Номер текущего процесса.

$<
$UID
$REAL_USER_ID

Реальный UID текущего процесса.

$>
$EUID
$EFFECTIVE_USER_ID

Эффективный UID текущего процесса.

$(
$GID
$REAL_GROUP_ID

Реальный GID текущего процесса.

$)
$EGID
$EFFECTIVE_GROUP_ID

Эффективный GID текущего процесса.

$O
$PROGRAM_NAME

Имя файла программы. Если этой переменной присвоить какое-нибудь значение, то его можно видеть в команде ps, что удобно для контроля за состоянием программы.

$[

Номер первого элемента массива или символа строки. Значение по умолчанию — нуль.

$]
$PERL_VERSION

Строка — сообщение версии Perl. Печатается по команде perl -v Применяется в программе для определения рабочей версии Perl. В числовом контексте это номер версии плюс номер модификации / 1000.

$^D
$DEBUGGING

Текущее значение ключа отладки «-D».

$^F
$SYSTEM_FD_MAX

Номер максимального системного описателя файлов (system file descriptor). Обычно это 2.

$^I
$INPLACE_EDIT

Текущее значение inplace-edit возможности. Для отключения используйте undef.

$^P
$PERLDB

Внутренний флаг отладки. Применяется для того, чтобы отладчик не отслеживал самого себя.

$^T
$BASETIME

Время в секундах с начала 1970 года старта текущей программы.

$^W
$WARNING

Значение флага «-w». true — если включено и false — выключено.

$^X
$EXECUTABLE_NAME

Команда запуска Perl. Аналогично argv[0] в С.

$ARGV

Имя текущего файла, читаемого оператором «<>».

@ARGV

Массив параметров строки запуска программы.

Важно: @#ARGV — меньше количества параметров на 1, так как $ARGV[0] это первый параметр (не имя программы).

@INC

Список директорий диска, которые просматривает Perl для выполнения команд do, require или use.

%INC

Этот хеш содержит имена директорий для имен использованных файлов командами do или require. Ключ — имя файла, а значение — директория.

$ENV{выражение}

Хеш %ENV содержит значения переменных окружения. Изменение этих значений вызывает изменение окружения для процессов потомков.

$SIG{выражение}

Хеш %SIG содержит имена подпрограмм для таких системных сигналов как INT, QUIT, PIPE, ... Значение «DEFAULT» — для системной обработки. «IGNORE» — игнорировать данный сигнал.

Глава 8.
Регулярные выражения (шаблоны)

Чаще всего регулярные выражения используются в операторах поиска и замены таких как s//, m/, операторах связки =~ или !=. Как правило все эти операторы имеют схожие опции, такие как:

u i — не различать строчные и заглавные буквы.

u m — считать строку многострочной.

u s — однострочная строка.

u x — расширенный синтаксис (использование пробелов и комментариев)

Обычно все эти опции обозначают как «/x». Их можно использовать даже внутри шаблонов, используя новую конструкцию (?...)

Регулярные выражения или шаблоны (pattern) то же самое, что и regexp процедуры в Unix. Выражения и синтаксис заимствованы из свободно распространяемых процедур V8 Генри Спенсера (Henry Spencer).

В шаблонах используются следующие метасимволы (символы обозначающие группы других символов) часто называемые egrep-стандартом:

u \ — считать следующий метасимвол как обычный символ.

u ^ — начало строки

u . — один произвольный символ. Кроме «\n» — конец строки.

u $ — конец строки

u | — альтернатива (или)

u () — группировка

u [] — класс символов

Метасимволы имеют модификаторы (пишутся после метасимвола):

u * — повторяется 0 или большее число раз

u + - -// — 1 или большее число раз

u ? — 1 или 0 раз

u {n} — точно n раз

u {n,} — по меньшей мере раз

u {n,m} — не меньше n, но и не больше m

Во все других случаях фигурные скобки считаются обычными (регулярными) символами. Таким образом «*» эквивалентна {0,} , «+» — {1,} и «?» — {0,1}. n и m не могут быть больше 65536.

По умолчанию действие метасимволов «жадно» (greedy). Совпадение распространяется столько раз, сколько возможно, не учитывая результат действия следующих метасимволов. Если вы хотите «уменьшить их аппетит», то используйте символ «?». Это не изменяет значение метасимволов, просто уменьшает распространение. Таким образом:

u *? — станет 0 и более

u +? — 1 и более

u ?? — 0 или 1 раз

u {n}? — точно n раз

u {n,}? — не меньше n раз

u {n,m}? — больше или равно n и меньше m раз

Шаблоны работают так же, как и двойные кавычки, поэтому в них можно использовать «\» — символы (бэкслэш-символы):

u \t — символ табуляции

u \n — новая строка

u \r — перевод каретки

u \а — перевод формата

u \v — вертикальная табуляция

u \a — звонок

u \e — escape

u \033 — восьмеричная запись символа

u \x1A — шестнадцатеричная

u \c — control символ

u \l — нижний регистр следующего символа

u \u — верхний регистр

u \L — все символы в нижнем регистре до \E

u \U — то же в верхнем регистре

u \E — ограничитель смены регистра

u \Q — отмена действия как метасимвола

Дополнительно в Perl добавлены следующие метасимволы:

u \w — алфавитно-цифровой или '_' символ

u \W — не алфавитно-цифровой или '_' символ

u \s — один пробел

u \S — один не пробел

u \d — одна цифра

u \D — одна не цифра

Обратите внимание, что все это «один» символ. Для обозначения последовательности применяйте модификаторы. Так:

u \w+ — слово

u \d+ — целое число

u [+-]?\d+ — целое со знаком

u [+-]?\d+\.?\d* — число с точкой

Кроме того существуют мнимые метасимволы. Обозначающие не существующие символы в месте смены значения. Такие как:

u \b — граница слова

u \B — не граница слова

u \A — начало строки

u \Z — конец строки

u \G — конец действия m//g

Граница слова (\b) — это мнимая точка между символами \w и \W. Внутри класса символов \b обозначает символ backspace (стирания). Метасимволы \A и \Z — аналогичны «^» и «$», но если начало строки «^» и конец строки «$» действуют для каждой строки в многострочной строке, то \A и \Z обозначают начало и конец всей многострочной строки.

Если внутри шаблона применяется группировка (круглые скобки), то номер подстроки группы обозначается как «\цифра». Заметьте, что за шаблоном в пределах выражения или блока эти группы обозначаются как «$цифра». Кроме этого существуют дополнительные переменные:

u $+ — обозначает последнее совпадение

u $& — все совпадение

u $` — все до совпадения

u $' — все после совпадения

Например:

$s = "Один 1 два 2 и три 3";

if ($s =~ /(\d+)\D+(\d+)/)

{

print "$1\n";# Результат '1'

print "$2\n";# '2'

print "$+\n";# '2'

print "$&\n";# '1 два 2'

print "$`\n";# 'Один '

print "$'\n";# ' и три 3'

}

Perl версии 5 содержит дополнительные конструкции шаблонов:

u (?#комментарий) — комментарий в теле шаблона.

u (?:шаблон) — группировка как и ( ), но без обратной ссылки

u (?=шаблон) — «заглядывание» вперед. Например /\w+(?=\t)/ соответствует слову, за которым идет табуляция, но символ \t не включается в результат.
Например:
$s = "1+2-3*4";
if ($s =~ /(\d)(?=-)/) # Найти цифру за которой стоит '-'
{
print "$1\n"; # Результат '2'
}
else { print "ошибка поиска\n";}

u (?!шаблон) — «заглядывание» вперед по отрицанию.
Например:
$s = "1+2-3*4";
if ($s =~ /(\d)(?!\+)/) # Найти цифру за которой не стоит '+'
{
print "$1\n"; # Результат '2'
}
else { print "ошибка поиска\n";}

u (?ismx) — «внутренние» модификаторы. Удобно применять в шаблонах, где например нужно внутри шаблона указать модификатор.

Глава 9.
Правила регулярного выражения

1. Любой символ обозначает себя самого, если это не метасимвол. Если вам нужно отменить действие метасимвола, то поставьте перед ним «\».

2. Строка символов обозначает строку этих символов.

3. Множество возможных символов (класс) заключается в квадратные скобки [], это значит, что в данном месте может стоять один из указанных в скобках символов. Если первый символ в скобках это «^» — значит ни один из указанных символов не может стоять в данном месте выражения. Внутри класса можно употреблять символ «-», обозначающий диапазон символов. Например, a-z — один из малых букв латинского алфавита, 0-9 — цифра.

4. Все символы, включая специальные, можно обозначать с помощью «\» как в языке С.

5. Альтернативные последовательности разделяются символом «|» Заметьте что внутри квадратных скобок это обычный символ.

6. Внутри регулярного выражения можно указывать «подшаблоны», заключая их в круглые скобки и ссылаться на них как «\номер». Первая скобка обозначается как «\1».

Глава 10.
Операторы и приоритеты

В Perl ассоциативность и приоритетность операторов аналогична языку С. Ниже перечислены все операторы в порядке уменьшения приоритета и указана ассоциативность.

Ассоциативность Операторы

левая термы и левосторонние списковые операторы

левая ->

— ++ --

правая **

правая ! ~ \ унарные + и —

левая =~ !~

левая * / % x

левая + — .

левая << >>

— именованные унарные операторы

— < > <= >= lt gt le ge

— == != <=> eq ne cmp

левая &

левая | ^

левая &&

левая ||

— ..

правая ?:

правая = += -= *=

левая , =>

— правосторонние списковые операторы

левая not

левая and

левая or xor

Термы и левосторонние списковые операторы

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

Если после любого спискового оператора (print()) или унарного оператора (chdir()) следует левая круглая скобка, то операторы внутри скобок имеют наивысший приоритет. Так же как и обычные функции.

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

Например:

@i = ('a ','b ', print 'c ', 'd ');

print "\n",@i,"\n";

Результат:

c d

a b 1

Здесь мы имеем списковый оператор print. Для запятых слева от него он имеет наименьший приоритет, но повышает приоритет правой запятой. Поэтому правая запятая воспринимается как параметр для print и печатается «c d», а левая просто записывает код завершения операции в массив @i и последний print показывает это.

Оператор «->»

Так же как в С или С++ это инфиксный оператор переадресации. Если справа от него стоит [...] или {...} выражение, то правая часть может быть непосредственной или символической ссылкой на массив или хеш. В противном случае правая сторона это метод или простой скаляр, содержащий имя метода, а правая — или объект или имя класса.

Операторы ++ (инкремент) и -- (декремент)

Эти операторы работают так же как и в С. Если оператор стоит перед переменной, то значение переменной изменяется на 1 и полученное значение используется. Если после переменной — то ее величина изменяется после применения.

Употребление инкремента к строковым переменным в Perl имеет одну особенность. Каждый символ остается в своем классе (большие, малые, цифры) и учитывается перенос предыдущего символа. Таким образом строковые переменные с цифрами работают как числовые переменные.

Например:

print ++($i = "09"); # Результат "10"

print ++($i = "a9"); # "b0"

print ++($i = "az"); # "ba"

print ++($i = "aZ"); # "bA"

Оператор ** (возведение в степень)

Например:

print 4**2 # Результат 16

print -4**2 # Результат -16 то есть, -(4**2)

Унарные операторы

u ! — логическое отрицание

u - — арифметический минус

u ~ — побитная инверсия (дополнение до 1)

u + — арифметический плюс

u \ — получение ссылки на переменную (как & в С)

Операторы «привязки» =~ и !=

Эти «необычные», можно даже сказать, оригинальные операторы имеют очень широкое применение в Perl. Можно даже назвать их «оригинальным» решением. Оператор =~ логически связывает левую часть выражения с патерном (pattern — образец, шаблон, модель) в правой. По умолчанию поиск или изменение по патерну выполняется в переменной $_. Операторы привязки позволяют делать это с любой переменной, указанной в левой части. Логическим результатом будет успех операции. Если в правой части вместо патерна присутствует выражение, то результат этого выражения воспринимается как патерн. Однако это не очень эффективно, так как патерн будет компилироваться во время исполнения программы, что заметно снизит быстродействие. Оператор != аналогичен =~, только результат совпадения инвертируется (логическое «нет»).

Мультипликативные операторы

u * — арифметическое умножение

u / — арифметическое деление

u % — арифметический модуль

u x — оператор повторения

В скалярном контексте возвращает строку левой части, повторенную величиной, указанной в правой части. В списковом контексте, если в левой части список, то в круглых скобках — повторенный список.

Например:

print '*' x 5; # Результат '*****'

print (1,2) x 3; # Результат 121212

Аддитивные операторы

u + — арифметический плюс

u - — арифметический минус

u . — конкатенация (объединение) строк

Операторы сдвига

u << — Сдвигает побитно влево значение выражения в левой части на количество бит указанное в правой.

u >> — Сдвигает побитно вправо значение выражения в левой части на количество бит указанное в правой.

Именованные унарные операторы

Фактически это функции с одним аргументом. Круглые скобки можно опускать.

Операторы отношений

u < — арифметическое меньше

u > — арифметическое больше

u <= — арифметическое меньше или равно

u >= — арифметическое больше или равно

u lt — строковое меньше

u gt — строковое больше

u le — строковое меньше или равно

u ge — строковое больше или равно

Операторы равенства

u == — результат true если левая часть равна правой (равно)

u != — не равно

u <=> — -1 если левая часть меньше правой, 0 если равна, 1 если больше

u eq — строковое равно

u ne — строковое не равно

u cmp — как и <=> применительно к строкам

Операторы работы с битами

u & — побитное AND

u | — побитное OR

u ^ — побитное XOR

Логические операторы && (AND) и || (OR)

u && — если левое выражение возвращает false, правое не выполняется.

u || — если левое выражение возвращает true, правое не выполняется.

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

Оператор диапазона «..»

Результат работы данного оператора зависит от контекста. В списковом контексте результат есть список с элементами, первый элемент которого это левое выражение и последнее — правое. Значение каждого элемента внутри списка увеличивается на 1. Данный оператор удобен для небольших циклов, так как память отводится для всего списка целиком. Поэтому будьте внимательны и не задавайте слишком большой диапазон.

Например:

for $i (1..4)

{ print "$i ";

}

Результат:

1 2 3 4

В скалярном контексте результат — логическое значение. Каждая «..» операция устанавливает свое собственное состояние. Это false до тех пор пока левый операнд false. Как только левый операнд стал true результат — true до тех пока правый true, после чего результат опять — false. Если вы не хотите проверять правый операнд, то используйте оператор «...».

Правый операнд не вычисляется пока результат false и левый операнд не вычисляется пока результат true. Приоритетность оператора «..» немного ниже чем «&&» и «||». Возвращаемое значение если flase — нулевая строка, если true — порядковый номер начиная с 1. Порядковый номер обнуляется для каждого нового диапазона. Последний порядковый номер добавляется строкой «E0» которая не изменяет его значение, но позволяет фиксировать последнее значение.

Например:

@алфавит = ('a'..'z'); # Массив малых букв латинского алфавита

@цифры = (0..9); # Массив цифр

Условный оператор «?:»

Этот оператор работает так же как и в С. Если выражение перед «?» истинно, то выполняется аргумент перед «:» — иначе после «:».

Например:

$i = 1;

$i > 1 ? print "больше" : print "меньше";

Результат:

меньше

Операторы присваивания

u = — обычный оператор «присвоить» правое значение переменной слева

Вся эта группа операторов подобна операторам С, то есть,

$i += 2;

эквивалентно

$i = $i + 2;

Остальные операторы этой группы работают аналогично. Допустимы следующие операторы:

u **=

u +=, -=, .=

u *=, /=, %=, x=

u &=, |=, ^=

u <<=, >>=

u &&=, ||=

Приоритет всей этой группы операторов равен приоритету «=».

Оператор «,» (запятая)

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

Операторы not, and, or, xor

Оператор логическое not (отрицание)

Унарный not возвращает противоположное значение, полученное выражением справа. Он эквивалентен «!», но имеет очень низкий приоритет.

Оператор логическое and (И)

Выполняет логическую конъюнкцию двух выражений. Эквивалентен «&&», но имеет очень низкий приоритет и «краткость» действия, т. е. если левое выражение равно false — левое не выполняется.

Логическое or (ИЛИ)

Выполняет логическую дизъюнкцию двух выражений. Эквивалентен «||», но имеет очень низкий приоритет и «краткость» действия, т. е. если левое выражение равно true — левое не выполняется.

Логическое xor (исключающее ИЛИ)

Выполняет логическое исключающие или. Всегда выполняются оба правое и левое выражение.

В Perl отсутствуют операторы языка С такие как:

u унарное & — получить адрес. Для этого применяется «\».

u унарный * — переадресация.

u (TYPE) — совмещение типов.

Операторы ограничители строк

Обычно ограничителями строк мы считаем литералы, но в Perl это операторы выполняющие разного рода интерполяцию и поиск по шаблону. Вы можете сами задавать удобные для вас ограничители. Фигурные скобки «{}» обозначают любой символ, используемый для ограничителя. В случае использования скобок (круглых «()», квадратных «[]», фигурных «{}», угловых «<>») в начале ставится открывающаяся скобка, а в конце закрывающая.

По умолчанию Полное Функция Интерполяция

'' q{} Literal нет

"" qq{} Литерал да

`` qx{} Команда да

qw{} Список слов нет

// m{} Шаблон да

s{}{} Подстановка да

tr{}{} Трансляция нет

В строках допускающих интерполяцию имена переменных, начинающиеся с символов «$» или «@» — интерполируются, то есть, в строку вставляется значение строки или массива. Данные последовательности символов имеют специальное значение:

u \t символ табуляции

u \n символ новой строки

u \r возврат

u \f перевод формата

u \v вертикальная табуляция

u \b backspace (забой)

u \a звонок

u \e escape

u \034 восьмеричный символ

u \x1a шестнадцатеричный символ

u \c[ символ управления

u \l нижний регистр следующего символа

u \u верхний регистр следующего символа

u \L нижний регистр для всех символов до \E

u \U верхний регистр для всех символов до \E

u \E ограничитель смены регистра

u \Q отмена действия метасимволов до \E

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

?PATERN?

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

m/PATERN/gimosx
/PATERN/gimosx

Поиск в строке по патерну (шаблону). В скалярном контексте возвращает логическое значение true (1) или false (''). Если строка не указана с помощью операторов «=~» или «!~», поиск ведется в строке $_. Опции:

u g — Глобальный поиск. Поиск всех вхождений.

u i — Сравнение не зависит от регистра (верхний или нижний).

u m — Строка многострочная.

u o — Однопроходная компиляция.

u s — Однострочная строка.

u x — Используются расширенные регулярные выражения.

Если «/» — ограничитель, то начальное «m» можно опустить. С помощью него в качестве ограничителя может быть любой символ кроме пробела.

PATTERN может содержать переменные, которые будут интерполироваться (перекомпилироваться) каждый раз в момент вычисления. Переменные $) и $| не интерполируются. Если вы хотите, что бы такой шаблон интерполировался один раз — добавьте /o. Это необходимо делать в циклах поиска для увеличения быстродействия, однако, если вы измените значение переменной, Perl этого даже не заметит.

Если PATERN — нулевая строка, то используется последнее регулярное выражение. В скалярном контексте возвращается список, элементы которого — результаты выполнения выражений в скобках патерна ($1, $2, $3...). Обратите внимание что первый элемент $1.

Например:

$a = "/usr/local/perl/perl.bin"; # Анализируемая строка

Цель: Создать массив @dirs с именами директорий.

Решение: Самый простой способ воспользоваться split('\/'), но в качестве примера используем скобки.

@dirs =~ m[/(\w*)/(\w*)/(\w*)/(\w*)]

Здесь m[ — использовать квадратные скобки как ограничители.
(\w*) — шаблон алфавитно-цифровой последовательности.

В результате @dirs равен («usr», «local», «perl»)

q/строка/
'строка'

Строка литералов. Не интерполируется. Внутри строки разрешается использовать \' или \\ для обозначения символов ' и \.

Например:

print q#Привет.#; # Результат Привет.

print 'O\'K'; # O'K

qq/строка/
"строка"

Интерполируемая строка.

Например:

$var = 13;

print "\$var = $var";

Результат:

$var = 13

qx/строка/
`строка`

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

Например:

print `date`;

Результат:

Thu Nov 14 13:36:49 MSK 1996

qw/строка/

Возвращает список, элементы которого — слова строки, разделенные пробелами.

Например:

print qw/Построимся и спасемся!/;
# ('Построимся','и','спасемся!')

Результат:

Построимсяиспасемся!

Часто применяется как:

use POSIX qw( setlocale localeconv )

@EXPORT = qw( proc1 var );

s/шаблон/подстрока/egimosx

Поиск по шаблону и в случае успеха замена подстрокой. Возвращает количество произведенных подстановок, иначе false (0). Если строка в которой ведется поиск не указана (операторы =~ или !=), то используется переменная $_. Если в качестве разделителя «/» использовать одинарную кавычку ('), то интерполяции не будет, иначе можно применять переменные в шаблоне или подстроке.

Опции:

u e — Рассматривать правую часть как выражение.

u g — Глобальный поиск.

u i — Без различия регистра букв

u m — Многострочная переменная.

u o — Компилировать шаблон один раз.

u s — Однострочная переменная.

u x — Расширенное регулярное выражение.

Разделитель «/» можно заменить на любой алфавитно-цифровой символ кроме пробела.

Например:

$var = "12345"; # исходная строка

$var =~ s/1/0/; # Заменить '1' на '0'. Результат 02345

$var =~ s(5)(.); # Заменить '5' на '.' Результат 0234.

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

$var =~ s/\d*/каламбур/; Заменить все цифры. Результат 'каламбур.'

$var =~ s/а/о/g; # Заменить все 'а' на 'о'. Результат 'коломбур.'

$var = "12 34"; # Новое значение

$var =~ s/(\d\d) (\d\d)/$2 $1/; # Поменять местами числа.

Результат:

'34 12'

tr/таблица1/таблица2/cds
y/таблица1/таблица2/cds

Замена всех символов из «таблица1» на соответствующий символ из «таблица2». Результат — количество замен или стираний. Без оператора =~ или != операция выполняется со строкой $_. Для совместимости с программой sed вместо tr можно писать «y».

Опции:

u c — дополнение «таблица1» .

u d — стереть найденные, но не замененные символы.

u s — «сжать» повторяющиеся замененные символы.

Если указана опция /d таблица2 всегда интерпретируется как положено. Другими словами, если таблица2 короче, чем таблица1, то символ из таблицы1 интерпретируется всегда. Если таблица2 — null, то все символы строки остаются неизменными. Это удобно для подсчета количества символов в строке определенного класса или для сжатия повторяющихся символов, например, пробелов.

Например:

$s = "hello"; # Исходная строка

$s =~ tr/a-z/A-Z/; # Заменить малые буквы на большие.

Результат:

# 'HELLO'

$s = 'Hel....lo';

$s =~ tr/a-zA-z/_/c; # Заменить все не буквы на '_'
# Результат 'Hel____lo'

$s =~ tr/_/ /s; # Заменить '_' на ' ' и сжать. # Результат 'Hel lo'

$s =~ tr/a-zA-Z /a-zA-Z/d; # Удалить все не буквы. Результат 'Hello'

Если один и тот же символ несколько раз указан в таблице1, то применяется только первая замена.

Глава 11.
Операторы ввода-вывода

В Perl существует несколько операторов ввода-вывода. Первый это скобки из символа «`» — акцента. Строка в этих скобках воспринимается как системная команда и результат ее действия возвращается как «псевдо» литерал. В скалярном контексте это строка содержащая весь результат, а в списковом — список, элементы которого — строки результата. Статус выполненной команды хранится в переменной $?.

Следующая команда ввода вывода выглядит как <файл>. Вычисление <файл> приводит к чтению строки из файла. Обратите внимание, что «файл» здесь не имя файла, а указатель файла, который создается функцией open(). В скалярном контексте читается одна строка вместе с символом «\n» — перевода строки, а в списковом весь файл читается в список, элементы которого суть строки файла. В случае обнаружения конца файла результат оператора не определен и воспринимается как false. Если не указана переменная результата, то по умолчанию это $_.

Указатель файла по умолчанию STDIN — стандартный ввод.

Например:

while(<>) { print; }; # Прочитать и вывести весь файл STDIN

У оператора <> есть одна отличительная особенность. Если в командной строке нет никаких аргументов, то читается стандартный ввод, если есть аргументы, то они считаются именами файлов, которые последовательно читаются.

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

Например:

while(<*.pl>) { print;}; # То же что и ls *.pl

@files = <*>; # Массив @files содержит имена файлов в директории

Но лучше сделать:

@files = glob("*");

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

Глава 12.
Слияние констант

Как и С Perl выполняет возможные вычисления в период компиляции. Так подстановка символов после «\» , операция конкатенации строк, арифметические выражения, содержащие только одни константы, все это делается в момент компиляции, что существенно увеличивает скорость выполнения программы.

Глава 13.
Целочисленная арифметика

По умолчанию Perl выполняет арифметику с плавающей запятой, но если вы укажете:

use integer;

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

no integer;

Глава 14.
Встроенные функции

Встроенные функции используются как термы выражений и подразделяются на две категории: списковые операторы и унарные операторы. Это влияет на их приоритет по отношению к оператору «,» — запятая. Списковые операторы могут иметь множество (список) аргументов, а унарные только один. Таким образом, запятая завершает аргументы унарного оператора и разделяет аргументы спискового. Аргумент унарного оператора воспринимается обычно в скалярном контексте, а спискового как в скалярном, так и списковом, причем скалярные аргументы идут первыми. В дальнейшем списковые аргументы мы будем обозначать словом LIST, это значит, что функция имеет список аргументов, разделенных запятой. Аргументы функций можно заключать в круглые скобки и таким образом обозначать, что «это функция» и приоритет не имеет значения, иначе это списковый или унарный оператор с определенным фиксированным приоритетом. Пробел после имени функции и скобкой значения не имеет. Поэтому будьте внимательны!

Пример:

print 1 + 2 + 3; # результат 6

print(1+2)+3; # результат 3

print (1+2)+3; # опять 3

print (1+2+3); # 6

Если функция возвращает результат как в скалярном так и в списковом контексте, то код выхода по ошибке — скаляр c неопределенным значением или пустой список.

Запомните правило: Не существует общего правила преобразования списка в скаляр!

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

Оператор '-X'

-X указатель файла

-X выражение

-X

Проверка файла, где «X» — одно из ниже описанных значений. Это унарный оператор с одним аргументом — либо именем файла, либо указателем файла. Проверяет одно из условий. Если аргумент не указан, то берется значение переменной $_. Для ключа -t — STDIN. Результат 1, если true, «''» — если false или неопределенное значение, если файл не найден. Несмотря на странный вид это унарный оператор с соответствующим приоритетом. Аргумент можно заключать в круглые скобки. «X» имеет следующие значения:

u -r — файл разрешен на чтение эффективным uid/gid

u -w — файл разрешен на запись эффективным uid/gid

u -x — файл разрешен на исполнение эффективным uid/gid

u -o принадлежит эффективному uid (идентификатор пользователя)

u -R — файл разрешен на чтение реальным uid/gid

u -W — на запись реальным uid/gid

u -X — исполнение реальным uid/gid

u -O — принадлежит реальному uid

u -e — файл существует

u -z — пустой

u -s — не пустой

u -f — обычный текст

u -d — директория

u -l — символическая ссылка

u -p — pipes (конвейер)

u -S — socket (гнездо)

u -b — специальное блочное устройство

u -c — специальное символьное устройство

u -t — указатель на устройство tty

u -u — установлен бит setuid

u -g — установлен бит setgid

u -k — установлен бит sticky

u -T — текстовой файл

u -B — двоичный

u -M — «возраст» файла в днях на момент старта скрипта

u -A — дней с последнего чтения

u -C — дней с последней модификации inode

abs выражение

Абсолютное значение выражения.

accept NEWSOCKET, GENERICSOCKET

Открыть новый сокет по внешнему запросу. Аналогично системному вызову accept(). Возвращает адрес или false в случае неудачи.

alarm секунды

Послать сигнал SIGALARM текущему процессу по истечении указанного промежутка времени. Не допустимо делать несколько вызовов одновременно в одном промежутке времени. Возвращает остаток времени предыдущего счетчика.

atan2 Y, X

Арктангенс отношения Y к X в пределах от -pi до +pi.

bind сокет, имя

Назначить сетевой адрес сокету. Подобно системному вызову bind. Возвращает true в случае успеха и false иначе. Имя — пакетный адрес сокета.

binmode файл

Открыть файл для двоичного (binary) режима доступа. В обычном режиме при вводе символы CR LF транслируются в LF, а при выводе LF транслируется в CR LF.

bless ссылка, класс
bless ссылка

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

caller выражение
caller

Возвращает контекст вызова текущей подпрограммы. В скалярном контексте true если это подпрограмма или процедура вызванная операторами eval() или require() и false — иначе. В списковом это список ($package, $filename, $line), где

u $package — имя пакета

u $filename — имя файла пакета

u $line — номер строки откуда был сделан вызов.

Если указано выражение то возвращается список:

($package, $filename, $line, $subroutine, $hasargs, $wantargs)

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

u $subroutine — имя подпрограммы

u $hasargs — имеющиеся аргументы

u $wantargs — необходимые аргументы

Применение данной функции в DB пакете возвращает более детальную информацию. К списку аргументов добавляется список @DB::args.

chdir выражение

Перейти в директорию указанную выражением. Если выражение отсутствует то перейти в «домашнюю» директорию. Возвращает true в случае успеха и false — неудачи.

chmod список

Изменить права доступа к файлам указанным в списке. Первый элемент в списке — числовое, обычно восьмеричное значение прав. Возвращает количество файлов которым были изменены права.

Например:

chmod 0666 'f1', 'f2', 'f3';

chomp переменная

chomp список

chomp

Удаляет в конце строки символ указанный переменной $/. Обычно это «LF». Возвращает количество удаленных символов. Если переменная не указана, то используется переменная $_. Если указан список то обрабатывается каждая строка списка и возвращается общее количество удаленных символов.

chop переменная
chop список
chop

Полностью аналогично функции chomp но всегда удаляет последний символ строки.

chown список

Изменить «владельца» и «группу» файлов списка. Первый элемент — номер нового владельца, второй номер новой группы, а далее список файлов. В большинстве Unix-систем вы не сможете сменить владельца если вы не суперпользователь, но можете изменить группу если это одна из ваших «вторичных» групп.

chr число

Возвращает символ с указанным кодом кодировки.

chroot директория

Сделать «корневой» указанную директорию. «Корневая» — значит внутри программы на нее можно ссылаться как «/».

close файл

Закрыть открытый файл или поток. Можно не использовать, если один и тот же файл открывается повторно, так как он будет автоматически закрываться новым вызовом open().

closedir директория

Закрыть директорию, открытую функцией opendir().

connect сокет, имя

Связаться с удаленным сокетом. Данная функция подобна системному вызову connect(). Возвращает true в случае успеха и false при ошибке.

cos выражение

Косинус выражения в радианах.

crypt текст, salt

Шифрация текста. Аналогично системному crypt().

dbmclose хеш

Закрывает связь между хеш и базой данных.

dbmopen хеш, база, режим

Ассоциировать указанный хеш и базу в определенном режиме. Дело в том, что в Perl реализован очень удобный механизм работы с базами данных типа dbm, ndbm, sbdm, gdbm и Berkeley DB. Для этого нужно связать (открыть) базу под именем хеша. Вся работа с записями базы выполняется так, будто это обычный хеш (ассоциативный массив).

define выражение

Возвращает true, если значение определено (существует) и false, если нет. Многие операции завершаются неопределенным значением, например чтение после конца файла, использование неопределенных переменных, системные ошибки. Данная функция позволяет различать нулевое и неопределенные значения, определенность или неопределенность функций или ссылок. При использовании определенности ключа в хеше она возвращает определен данный ключ или нет, но не существует он или нет.

delete выражение

Удалить значение из хеша. Возвращает удаляемое значение или неопределенность, если ничего не удаляется. Удаление из массива окружения $ENV{} — изменяет окружение. Удаление из хеша связанного с базой данных — удаляет запись в базе данных.

die список

Если программа не была запущена из eval(), данная функция выводит список в поток STDERR и завершает работу программы с кодом из переменной $!. Если $! содержит 0, то с кодом ($? >> 8). Если и это 0, то с кодом 255. При вызове из eval() сообщение об ошибке заносится в переменную $@, и eval() завершается с неопределенным значением. Это позволяет отрабатывать ошибочные ситуации, не завершая головной модуль. Если строка из списка не завершается символом «\n», то дополнительно печатается номер строки скрипта, откуда вызвана функция и номер входной строки, если таковой имеется.

do BLOCK

Это не совсем функция. Здесь возвращается значение последнего оператора блока. Когда используется циклический модификатор BLOCK исполняется перед проверкой условия цикла в отличии от обычных циклов.

do подпрограмма

Устаревший метод вызова подпрограмм.

do выражение

Значение выражения воспринимается как имя файла Perl скрипта. Изначально это применялось для включения подпрограмм библиотеки. Сейчас правильней использовать вызовы use() и require(), которые обеспечивают контроль ошибок и завершения.

dump метка

Выполнение «дампа» текущей памяти. Применялось для возможности использовать программу undump, чтобы включить текущий исполняемый код в программу после инициализации всех переменных. Выполнение этой новой программы будет начинаться оператором goto метка. Если метка опущена, то запуск сначала.

Важно: Файлы, открытые до момента дампа, не будут открытыми в момент исполнения новой программой.

each хеш

Возвращает 2-элементный массив. Первый элемент — ключ второй — значение следующего элемента хеша. Позволяет «просматривать» все значения хеша в определенном порядке. Сброс переменной цикла происходит только после прохождения всего массива.

eof файл
eof()
eof

Возвращает 1, если выполняется чтение после конца или чтение из не открытого файла. Аргументом должно быть выражение, возвращающее существующий указатель файла. Данную функцию нельзя использовать для чтения файлов, открытых как терминалы. Если аргумент не указан, то используется последний читаемый файл. Использование в форме eof() целесообразно применять в циклах while(<>) для определения конца только последнего файла из списка. Применяйте eof(ARGV) или eof для определения конца каждого файла из списка.

eval выражение
eval блок

Выражение сканируется и выполняется как обычная Perl программа. Это делается в контексте текущей программы, поэтому можно использовать уже определенные переменные и подпрограммы. Возвращается результат последнего оператора в блоке или значение оператора return. Если обнаружится синтаксическая ошибка или выполнится оператор die, то возвращается неопределенное значение, а переменная $@ содержит сообщение об ошибке. Если ошибки не было, то $@ содержит нулевую строку. При отсутствии аргумента берется значение переменной $_.

exec список

Выполняется системная команда из списка, и выполнение программы завершается. Если вам необходимо вернуться, то лучше применяйте функцию system(). Если в списке количество аргументов больше 1 или список — это массив с количеством элементов больше 1, то выполняется системный вызов execvp() с аргументами из списка. Если один скалярный аргумент, то он сканируется на обработку метасимволов, и если они присутствуют, то полученные аргументы передаются интерпретатору /bin/sh -c для дальнейшей обработки. Если метасимволов нет, то аргумент расщепляется на слова и передается системному вызову execvp(), что более эффективно.

Важно: exec() и system(0) не сбрасывают буфер ввода/вывода, поэтому необходимо установить переменную $| для предотвращения потери выходной информации.

exists выражение

Возвращает true, если существует указанный ключ хеша, даже если не определено его значение.

exit выражение

Выполнение программы завершается с кодом выражения.

exp выражение

Возвращает e (основание натурального логарифма) в степени выражения.

fcntl файл, функция, скаляр

Выполняет системный вызов fcntl(). Для использования обязательно выполнить use Fcntl;

fileno файл

Возвращает описатель файла для данного указателя файла.

flock файл, операция

Системный вызов flock()

fock

Системный вызов fock(). Возвращает pid потомка для родителя и 0 — для потомка в случае успеха, иначе — неопределенное значение.

format

Декларативная функция Perl. Определяет формат вывода для оператора write.

formline формат, список

Внутренняя функция, используемая в format. Форматирует вывод параметров из списка. Результат помещается в переменную $^A. Функция write просто выводит значение этой переменной, но ее так же можно читать, а затем обнулять. Обычно format выполняет один вызов formline для одной строки формы, но formline не разделяет строки формата. Это значит что символы «~» и «~~» рассматривают весь формат как одну строку, поэтому необходимо использовать многострочные формы для описания одной записи формата.

getc файл
getc

Читает и возвращает символ из указанного файла. Если файл не указан, то читается STDIN. После конца файла возвращает пустую строку.

getlogin

Возвращает текущий login из файла /etc/utmp. Не применяйте для идентификации пользователя, он не так сильно «закрыт» как getpwuid().

getpeername сокет

Возвращает упакованный адрес удаленного сокета.

getpgrp PID

Возвращает группу процесса с указанным PID. Если аргумент отсутствует, возвращает группу текущего процесса.

getppid

Возвращает PID родительского процесса.

getpriority WHICH, WHO

Возвращает текущий приоритет процесса, группы процессов или пользователя.

Группа системных вызовов:

u getpwnam NAME

u getgrnam NAME

u gethostbyname NAME

u getnamebyname NAME

u getprotobyname NAME

u getpwuid UID

u getgrid GID

u getservbyname NAME, PROTO

u gethostbyaddr ADDR, ADDRTYPE

u getnetbyaddr ADDR, ADDRTYPE

u getprotobynumber NUMBER

u getservbyport PORT, PROTO

u getpwent

u gethostent

u getnetent

u getprotoent

u getservent

u setpwent

u setgrent

u sethostent STAYOPEN

u setnetent STAYOPEN

u setprotoent STAYOPEN

u setservent STAYOPEN

u endpwent

u endgrent

u endhostent

u endnetent

u endprotoent

u endservent

Все эти функции аналогичны одноименным системным вызовам. В списковом контексте возвращают следующие списки:

($name, $passwd, $uid, $gid, $quota, $comment, $gcos, $dir, $shell) = getpw*

($name, $passwd, $gid, $members) = getgr*

($name, $aliases, $addrtype, $length, @addrs) = gethost*

($name, $aliases, $addrtype, $net) = getnet*

($name, $aliases, $proto) = getproto*

($name, $aliases, $port, $proto) = getserv*

В скалярном контексте возвращается имя или что-то другое, в зависимости от типа функции. Элемент $members в вызове getgr содержит разделенные пробелом имена членов группы. @addrs содержит IP адреса компьютеров в сети Интернет в упакованном виде. Для распаковки применяйте:

($a, $b, $c, $d) = unpack('C4',$addr[0]);

getsockname сокет

Возвращает упакованный адрес сокета локальной машины.

getsockopt сокет, уровень, опция

Возвращает опцию сокета.

glob выражение

Возвращает список файлов, удовлетворяющих выражению с «*», как в команде ls.

gmtime выражение

Переводит местное время, полученное функцией time(), во время по Гринвичу. Обычно используется в виде:

($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst)=gmtime(time);

goto метка
goto выражение
goto &подпрограмма

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

Например:

goto ('метка1', 'метка2', 'метка3')[$i];

Здесь при $i = 0 будет переход на 'метка1', $i = 1 на 'метка2'. Третья форма (goto &подпрограмма) довольно «хитрый» метод подмены имени вызываемой подпрограммы именем текущей. Это используется в методе автозагрузки, когда нужно запустить другую процедуру, но под именем текущей, как будто та была вызвана раньше.

grep блок, список
grep выражение, список

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

hex выражение

Воспринимает выражение как строку шестнадцатеричных цифр и возвращает десятичное значение.

import

В Perl нет встроенной функции import, но есть метод, с помощью которого модули могут экспортировать область имен другим модулям.

index строка, подстрока, позиция
index строка, подстрока

Возвращает позицию первого вхождения подстроки в строке, начиная с указанной позиции. Если позиция отсутствует, то сравнение начинается с начала строки. Отсчет позиции ведется с 0, если только не изменено значение встроенной переменной $[. Если подстрока не найдена, то результат меньше первой позиции на 1. Обычно это -1.

int выражение

Возвращает целую часть выражения.

ioctl FILEHANDLE, FUNCTION, SCALAR

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

require "ioctl.ph";

join выражение, список

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

keys хеш

Возвращает массив всех ключей хеша. В скалярном контексте — количество ключей. Порядок следования ключей аналогичен порядку в each функции.

kill список

Послать сигнал (первый элемент) указанным в списке процессам. Возвращает количество процессов которые сигнал приняли.

last метка
last

Функция аналогичная break в С. Прерывает цикл и переходит на указанную метку. Если метки нет на ближайший внешний цикл. Блок continue не выполняется.

lc выражение

Преобразовать строку в буквы нижнего регистра.

lcfirst выражение

Заменить первую букву строки на малую.

length выражение

Возвращает длину строки выражения.

link OLDFILE, NEWFILE

Создать файл NEWFILE связанный с файлом OLDFILE.

listen SOCKET, QUEUESIZE

Выполнить системный listen().

local список

Объявляет перечисленные переменные как локальные в текущем блоке или подпрограмме. Если указано два и более имени, их необходимо заключить в круглые скобки. Предпочтительней использовать функцию my().

localtime выражение

Преобразовать 9-элементный массив, возвращаемый функцией time, относительно местного времени. Употребляется как:

($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst)=localtime(time);

log выражение

Возвращает натуральный логарифм выражения.

lstat файл
lstat выражение

То же что и системный вызов stat(), но касательно ссылки на файл, а не самого файла.

m//

Оператор совпадения.

map блок, список
map выражение, список

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

mkdir директория, права

Создать директорию с указанными правами доступа. Возвращает 1 при успехе, и 0 при неудаче.

msgctl ID, CMD, ARG

Системный вызов msgctl() System V.

msgget KEY, FLAGS

Системный вызов msgget() System V.

msgsnd ID, MSG, FLAGS

Системный вызов msgsnd() System V.

msgrcv ID, VAR, SIZE, TYPE, FLAGS

Системный вызов msgrcv() System V.

my список

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

next метка
next

Аналогичен continue в С. Переход к следующей итерации в цикле. Если в цикле присутствует блок continue, он так же выполняется. Если метка отсутствует — переход на ближайший внутренний цикл.

no Module список

Исключить использование указанных модулей.

oct выражение

Возвращает десятичное число, представленное восьмеричными цифрами.

open файл, выражение
open файл

Открыть файл с указанным именем. Аргумент «файл» — это фактически указатель на блок описания файла. Если имя (выражение) файла отсутствует, то имя считается таким же как и указатель файла. Если перед именем стоит символ < то файл открывается только на чтение (значение по умолчанию). Если > — на запись. Если >> — на добавление записей. +> или +< — файл открыт на чтение и запись одновременно. Если имя файла начинается с символа |, то имя считается системной командой, на вход которой выводятся данные (поток). Имя — стандартный вход (STDIN), > — стандартный вывод (STDOUT). Функция возвращает true при успехе и false — неудаче.

Если имя (выражение) начинается с символов >&, то остаток имени считается именем указателя файла, который открывается повторно. Символ & может стоять после <, >>, +>, +>> и +<. Режим повторного открытия должен соответствовать первому. Выражение вида <&=N где N — число, эквивалентно системному вызову fdopen(). Если имя |- или -|, то происходит «расщепление» (fork) процесса на потомка и родителя. При этом возвращается номер процесса потомка (PID) внутри родительского процесса и 0 — внутри потомка. При этом запись/чтение в файл родительским процессом воспринимается как чтение/запись потомком. Для потомка это стандартный ввод/вывод. Родительский процесс ждет завершения потомка после закрытия потока и получает код завершения в переменной $?. Имя файла не должно содержать пробелов как в начале, так и в конце.

opendir указатель, выражение

Открыть директорию с именем выражения для обработки функциями readdir(), telldir(), seekdir(), rewinddir() и closedir(). Возвращает true при успехе.

ord выражение

Возвращает ascii код первого символа выражения.

pack шаблон, список

Упаковывает список в двоичную структуру по шаблону. Шаблон — это строка символов, описывающая порядок и тип упакованных значений.

u A — текстовая строка, добавляется пробелами.

u a — текстовая строка, добавляется 0 символом.

u b — битовая строка (возрастающая нумерация бит)

u B — битовая строка (убывающая нумерация бит)

u h — шестнадцатеричная строка (младший байт — первый)

u H — шестнадцатеричная строка (старший байт первый)

u c — символ со знаком

u C — символ без знака.

u s — короткое целое (один байт) со знаком.

u S — короткое целое без знака.

u i — целое (два байта) со знаком.

u I — целое без знака.

u l — длинное целое (4 байта) со знаком.

u L — длинное целое без знака.

u n — короткое в «сетевом» формате.

u N — длинное в «сетевом» формате.

u v — короткое в «VAX» формате.

u V — длинное в «VAX» формате.

u f — single float.

u F — double float.

u p — указатель на строку ограниченную 0.

u P — указатель на структуру с фиксированной длиной.

u u — упаковка uuencode.

u x — нуль-байт

u X — резервный байт.

u @ — заполнить нулями позицию.

За каждой буквой может стоять число означающее количество повторов. Для всех типов за исключением a, A, b, B, h и H, упаковывается максимально возможное количество значений из списка. Символ «*» после типа означает использовать оставшиеся параметры. Тип a и A использует только одно значение из списка и добавляет остаток поля либо нуль-символами, либо пробелами (при распаковке по типу A пробелы и нули отбрасываются, а по a — нет). Типы b и B упаковывают строку в указанное число бит. Так же как и h и H в число ниблов (байт). P — упаковывает указатель на структуру с указанной длиной. Числа с плавающей запятой (floats и double) записываются в стандартном для каждой машины формате и могут быть разными для разных типов машин. Заметьте, что Perl всегда использует двойную точность (double), поэтому упаковка и распаковка по типу f приведет к потере точности. Шаблоны для распаковки и упаковки совершенно одинаковы.

Примеры:

print(pack("ccc",65,66,67)); # Результат ABC

print(pack("A4A3","ab","cdefg")); # "ab cde"

print(pack("a4a3","ab","cdefg")); # "ab\0\0cde"

pipe READHANDLE, WRITEHANDLE

Аналогичен системному pipe().

pop массив

Удаляет и возвращает последний элемент массива. Длина массива уменьшается на 1. Результат не определен, если массив пустой. Если имя массива отсутствует, то извлекает из @ARGV для головной программы и @_ для подпрограммы.

pos скаляр

Возвращает смещение от последнего m//g поиска.

print файл список
print список
print

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

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

printf файл список
printf список

Аналогичен вызову print файл sprintf(список). Первый элемент списка должен быть форматом printf(), как и в Си.

push массив, список

Добавить элементы массива значениями из списка. Длина массива увеличивается на количество элементов списка.

q/строка/
qq/строка/
qx/строка/
qw/строка/

Обычные операторы строк.

quotemeta выражение

Вычисляет выражение, в котором метасимволы рассматриваются как обычные символы.

rand выражение
rand

Возвращает случайное число в диапазоне между 0 и выражением. Если выражение отсутствует — между 0 и 1. Данная псевдослучайная последовательность сбрасывается функцией srand().

read файл, скаляр, длина, смещение
read файл, скаляр, длина

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

readdir директория

Возвращает имя следующего файла в директории открытой командой opendir(). В скалярном контексте возвращает все оставшиеся имена файлов. Если файлов больше нет, то возвращает неопределенность в скалярном контексте и пустую строку в списковом.

readlink выражение

Возвращает значение символической ссылки. При ошибке возвращает неопределенное значение.

recv SOCKIT, SCALAR, LEN, FLAGS

Получить сообщение из сокета. Аналогичен системному recvfrom().

redo метка
redo

Перейти к началу блока, минуя вычисление условия и блока continue. Если метка отсутствует, то переход к ближайшему внешнему блоку.

ref выражение

Возвращает true, если выражение это ссылка, иначе — false. Возвращаемое значение зависит от типа ссылки. Существуют следующие типы ссылок:

u REF

u SCALAR

u ARRAY

u HASH

u CODE

u GLOB

Если аргумент — ссылка на класс, то возвращается имя класса. Функция ref() аналогична функции typeof().

rename старое_имя, новое_имя_файла

Переименовать файл. Возвращает 1 в случае успеха, иначе — 0.

reset выражение
reset

Обычно используется в блоках continue циклов для очистки переменных и поиска для повторного применения. Выражение воспринимается как список символов. Для указания диапазона можно применять символ минус «-». Все переменные и массивы, начинающиеся с этих символов, обнуляются. Если выражение отсутствует, то обнуляется поиск ?шаблон? для повторного поиска. Сбрасываются только переменные текущего модуля.

Пример:

reset 'a'; # очистить все переменные начинающиеся

# буквой 'a'

reset 'a-z' # все переменные с малых букв.

Выражение вида: 'A-Z' употреблять не рекомендуется, так как обнуляются важные массивы @ARGV и @ENV.

return список

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

reverse список

В списковом контексте возвращает список с элементами в обратном порядке указанному списку. В скалярном контексте возвращает строку с переставленными символами первого элемента списка.

rewindir директория

Устанавливает указатель файлов открытой директории в начало для последующего чтения функцией readdir().

rindex строка, подстрока, позиция
rindex строка, подстрока

Подобна функции index, но возвращает позицию последнего вхождения подстроки в строку. Если указана позиция, то просмотр выполняется до нее.

rmdir директория

Удалить указанную директорию, если в ней нет никаких файлов. Возвращает 1 при успехе и 0 — при неудаче. При этом переменная $! содержит код (errno).

s///

Оператор подстановки.

scalar выражение

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

seek файл, позиция, отсчет

Установить указатель на произвольную позицию в файле. Если отсчет равен 0 — позиционировать от начала файла, если 1 — от текущего положения, 2 — от конца файла. Возвращает 1 при успехе и 0 — неудаче.

seekdir директория, позиция

Установить указатель файла в открытой директории для последующего чтения функцией readdir(). Текущая позиция может быть получена функцией telldir().

select файл
select

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

select RBITS, WBITS, EBITS, TIMEOUT

Выполняет системный select()

semctl ID, SEMNUM, CMD, ARG

Системный вызов semctl() для SYSTEM V.

semget KEY, NSEMS, FLAGS

Системный вызов semget() для SYSTEM V.

semop KEY, OPSTRING

Системный вызов semop() для SYSTEM V.

send SOCKET, MSG, FLAGS, TO
send SOCKET, MSG, FLAGS

Послать сообщение через сокет. Аналогично системному send().

setpgrp PID, PGRP

Установить номер группы PGRP процессу с номером PID, 0 — для текущего процесса.

setpriority WHICH, WHO, PRIORITY

Установить приоритет PRIORITY процессу пользователя WHO и группе WHICH.

setsockopt SOCKET, LEVEL, OPTNAME, OPTVAL

Установить параметры сокета. Возвращает неопределенность при ошибке.

shift массив
shift

Удаляет первый элемент массива и возвращает его значение. Если массив пустой, то результат — неопределенность. Если аргумент опущен — обрабатывается массив @ARGV в головной программе или @_ в подпрограмме.

shmctl ID, CMD, ARG

Системный вызов shmctl() System V

shmget KEY, SIZE, FLAGS

Системный вызов shmget() System V

shmread ID, VAR, POS, SIZE
shmread ID, STRING, POS, SIZE

Пишет или читает сегмент общедоступной памяти в System V.

shutdown SOCKET, HOW

Прекращает работу сокета методом HOW.

sin выражение

Возвращает в радианах синус выражения.

sleep выражение
sleep

Приостанавливает работу процесса на указанное число секунд или зацикливается, если аргумент отсутствует. Работа продолжается, если получен сигнал SIGALARM. Возвращает время фактической паузы.

socket SOCKET, DOMAIN, TYPE, PROTOCOL

Открывает сокет и привязывает его к указателю SOCKET.

socketpair SOCKET1, SOCKET2, DOMAIN, TYPE, PROTOCOL

Создает «безымянную» пару сокетов в указанном DOMAIN. Возвращает true при успехе и false — неудаче.

sort подпрограмма список
sort блок список
sort список

Сортирует элементы списка и возвращает полученный список. Несуществующие элементы списка отбрасываются. Если не указано имя подпрограммы или блок, то сортирует в стандартном строковом порядке. Указанная подпрограмма возвращает значения больше, меньше или равное нулю в зависимости от двух соседних элементов списка. Имя подпрограммы может быть указано переменной. Она не должна быть рекурсивной, и два сравниваемых элемента списка передаются как глобальные переменные $main::a и $main::b. Это ссылки, поэтому их изменение приводит к изменению самих элементов списка.

splice массив, смещение, длина, список
splice массив, смещение, длина
splice массив, смещение

Удаляет элементы массива, начиная со смещения и указанной длины. Заменяет их указанным списком. Возвращает удаленный список. Если длина не указана, удаляет все элементы списка, начиная со смещения.

split /шаблон/, выражение, предел
split /шаблон/, выражение
split /шаблон/
split

Разделяет строку, указанную выражением, на массив элементов и возвращает его. В скалярном контексте возвращает количество полученных элементов массива, а сам массив помещает в @_ (в списковом контексте поместить результат в @_ можно если применить ?шаблон?) Если выражение отсутствует, то обрабатывается содержимое переменной $_. Если шаблон отсутствует, то разделителем является пробел. Все, что подходит по шаблону, считается разделителем. Если указан предел, то это максимальное число разделений. Отрицательное значение предела воспринимается как неограниченно большой предел. Если пустая строка походит под шаблон разделителя, то исходное значение разделяется посимвольно. Предел удобно использовать для частичного разделения строки.

Пример:

($a, $b, $остаток) = split(/ /, "Один Два Три Четыре",3);

Здесь $a = 'Один', $b = 'Два' и $остаток = 'Три Четыре'

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

Пример:

@a = split(/([,.])/, "Один,Два.Три");

Здесь @a = ("Один", "," ,"Два", ".", "Три")

sprintf формат, список

Форматирует список по указанному формату аналогично функции sprintf() языка Си.

sqrt выражение

Возвращает корень квадратный выражения.

srand выражение

Задает начальное значение для генератора случайных чисел — оператора rand. Если аргумент отсутствует, то используется текущее машинное время.

stat файл
stat выражение

Возвращает 13 элементный массив параметров указанного файла или пустой список при ошибке. Применяется обычно следующим образом:

($dev,$ino,$mode,$nlink,$uid,$gid,$rdev,$size,$atime,$mtime,
$ctime,$blksize,$blocks) = stat($filename);

где

$dev — имя устройства

$ino — номер i-узла

$mode — права доступа

$nlink — количество связей

$uid — идентификатор владельца

$gid — идентификатор группы

$rdev — тип устройства

$size — размер файла в байтах

$atime — дата последнего обращения

$mtime — дата последней модификации

$ctime — дата последнего изменения статуса

$blksize — размер блока на диске

$blocks — количество блоков в файле.

Если указан аргумент «_», то возвращается результат предыдущего вызова stat().

study скаляр
study

Включение метода частотного сравнения. Целесообразность применения зависит от количества поисков и количества констант в строке в которой ведется поиск. Функция работает следующим образом: Для указанного скаляра (если аргумент отсутствует берется значение переменной $_) строится список указателей на каждый символ строки. Затем, когда начинается поиск, первыми просматриваются символы, которые наиболее редко встречаются в английском языке. Выигрыш по времени очевиден, когда выполняется многократный поиск в одной и той же строке, и время затраченное на индексацию окупается.

substr выражение, смещение, длина
substr выражение, смещение

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

symlink старый_файл, новый_файл

Создает новый файл, символически связанный со старым, то есть, создает ссылку на файл. Возвращает 1 при успехе и 0 — неудаче.

syscall список

Выполняет системную функцию. Первый элемент списка — это имя функции, а остальные элементы — ее аргументы. Если функция не выполнима, то возвращается фатальная ошибка. Аргументы интерпретируются следующим образом. Если аргумент число, то оно считается целым. Если не число, то аргумент считается указателем на строку. Вам необходимо обеспечить достаточную длину аргумента для подстановки значения указателя. В Perl передается максимум 14 параметров. Обычно этого достаточно.

sysread файл, скаляр, длина, смещение
sysread файл, скаляр, длина

Прочитать из файла указанное число байт в скаляр с помощью системного вызова read(). Смещение обозначает позицию в скаляре, после которой будут вставлены прочитанные байты.

system список

Делает то же самое, что и «exec список» с той лишь разницей, что вызов fork() делается первым, и родительский процесс ждет завершения потомка. Возвращает значение аналогичное функции wait(). Для получения действительного кода завершения необходимо разделить на 256.

syswrite файл, скаляр, длина, смещение
syswrite файл, скаляр, длина

Записать в файл указанное число байт скаляра с помощью системного вызова write(). Смещение указывает позицию в скаляре, откуда начинается запись.

tell файл
tell

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

telldir директория

Возвращает текущую позицию указателя в открытой директории. Обычно это параметр для позиционирования указателя директории функцией seekdir().

tie переменная, класс, список

Присваивает переменную классу. Список — это аргументы new метода класса (TIESCALAR, TIEARRAY или TIEHASH). Возвращает указатель на созданный новый объект класса, который удобен для вызова других методов класса.

Важно: Такие функции как keys() и values() могут вернуть в качестве значения огромное количество данных.

Для создания объекта ассоциативного типа необходимо определение следующих методов:

u TIEHASH classname, LIST

u DESTROY this

u FETCH this, key

u STORE this, key, value

u DELETE this, key

u EXISTS this, key

u FIRSTKEY this

u NEXTKEY this, lastkey

Обычного массива:

u TIEHASH classname, LIST

u DESTROY this

u FETCH this, key

u STORE this, key, value

u [others TBD]

Скалярного типа:

u TIEHASH classname, LIST

u DESTROY this

u FETCH this, key

u STORE this, key, value

time

Возвращает количество секунд, начиная с 1 января 1970 г. 00:00:00 UTC. Годы не разделяются на високосные.

times

Возвращает 4-х элементный массив значений:

($user, $system, $cuser, $csystem) = times;

где:

$user — пользовательское время процесса в секундах.

$system — системное время текущего процесса.

$cuser — время процесса-потомка текущего процесса.

$csystem — его системное время.

tr///

Оператор транслятор.

truncate файл, длина
truncate выражение, длина

Усекает длину указанного файла.

uc выражение

Возвращает строку выражения, в которой малые буквы заменены на заглавные.

ucfirst выражение

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

umask выражение
umask

Устанавливает маску umask и возвращает старое значение. Без аргумента возвращает текущее значение.

undef выражение undef

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

unlink список

Удаляет файлы, указанные в списке. Возвращает количество удаленных файлов. Только супервизор может удалять директории с файлами, если Perl запущен с ключом -U.

unpack шаблон, выражение

Функция, обратная pack(). Распаковывает строку выражения по указанному шаблону в массив значений. В скалярном контексте возвращает значение только первого элемента. Шаблоны такие же как и в pack().

Символ «%» в шаблоне означает, что вы хотите получить вместо значения его контрольную сумму. По умолчанию — 16-битную.

untie переменная

Исключает переменную из класса указанного tie().

unshift массив, список

Противоположное shift. Помещает элементы списка в начало массива.

use модуль список
use модуль

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

BEGIN { require модуль; import модуль список; }

Сам модуль должен располагаться в системной области Perl файлов.

utime список

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

values хеш

Возвращает обычный массив со всеми значениями хеша. В скалярном контексте возвращает количество значений хеша.

vec выражение, смещение, бит

Возвращает значение строки как вектор беззнаковых целых.

wait

Ожидать окончание порожденного процесса. Возвращает PID для родительского процесса или -1, если нет — процесса потомка. Код завершения записывается в переменную $?.

waitpid PID, FLAGS

Ожидать окончания процесса потомка с указанным PID. Возвращает либо PID этого процесса либо -1 если нет такого процесса. Код завершения записывается в переменную $?.

wantarray

Возвращает true, если подпрограмма вызывается в списковом контексте и false — в скалярном.

warn список

Выводит в стандартный поток ошибок STDERR сообщение аналогично die(), но не прекращает работу программы.

write файл
write выражение
write

Выводит в файл форматированные данные по оператору format. По умолчанию имя формата совпадает с именем указателя файла. Имя файла по умолчанию можно изменить функцией select(), а имя формата при этом надо присвоить переменной $~. Заголовок листа пишется автоматически. Если на листе не хватает места для вывода, то выводится «перевод формата» и специальная форма заголовка листа, затем сами записи. По умолчанию имя формата заголовка листа состоит из имени указателя файла и добавленной строки «_TOP». Динамически это можно делать, присваивая формат переменной $^. Количество оставшихся строк листа содержится в переменной $- и если присвоить ей 0, то произойдет смена листа.

y///

Оператор транслятор.

Глава 15.
Подпрограммы

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

u sub имя; — Только декларация.

u sub имя (прототипы); — То же но с декларацией параметров.

u sub имя блок; — Декларация и определение.

u sub имя (прототипы) блок; — То же, но с параметрами.

Для определения динамической анонимной подпрограммы можно указать:

$переменная = sub блок;

Для импортирования подпрограмм из других модулей используйте:

use модуль qw(подпрограмма1 подпрограмма2 );

Вызов подпрограммы:

имя(список параметров); # символ '&' можно не указывать.

имя список; # Если подпрограмма уже декларирована.

&имя; # Параметры в @_

Все параметры передаются подпрограмме как массив @_. Соответственно $_[0] — первый параметр, $_[1] — второй. Массив @_ — локальный, но он содержит адреса параметров, поэтому можно изменять значение параметров. Возвращаемое значение подпрограммы — результат последнего оператора. Это может быть как скаляр так и массив. Можно принудительно возвращать результат используя функцию return().

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

Private переменные

Для применения переменных доступных только внутри блока или подпрограммы необходимо определить их с помощью функции my(список).

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

my() декларирует private переменные в пределах текущей подпрограммы, блока, функции eval() или do/require/use файлов. Private переменные аналогичны auto переменным в С.

Пример:

# Программа вычисления факториала.

print fact(3); # вычислить факториал 3*2*1

sub fact # Определяем подпрограмму.

{ my $m; # private переменная но не local !

$m = $_[0];

return 1 if $m <= 1; return($m * fact($m 1)); }

Можно указывать начальные значения private переменных как:

my(список) = выражение;

Так для вышеприведенного примера лучше было написать:

my($m) = $_[0];

Переменные типа local

В общем лучше использовать private переменные, т. к. это надежней и быстрее. private переменные обеспечивают лексическую область применения (видимости), а local — динамическую.

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

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

Прототипы (prototypes)

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

В Perl существуют следующие прототипы:

Декларация Пример вызова

sub mylink($$) mylink $old, $new

sub myvec($$$) myvec $var, $offset, 1

sub myindex($$;$) myindex &getstring, "substr"

sub myreverse(@) myreverse $a, $b, $c

sub myjoin($@) myjoin ":",$a,$b,$c

sub mypop(\@) mypop @array

sub mysplice(\@$$@) mysplice @array, @array, 0, @pushme

sub mykeys(\%) mykeys %{$hashref}

sub myopen(*;$) myopen HANDLE, $name

sub mypipe(**) mypipe READHANDLE, WRITEHANDLE

sub mygrep(&@) mygrep { /foo/ } $a, $b, $c

sub myrand($) myrand 42

sub mytime() mytime

Здесь:

u \'символ' — параметр с типом 'символ'

u @ или % — все оставшиеся параметры как список

u $ — скаляр

u & — безымянная подпрограмма

u * — ссылка на таблицу имен

u ; — разграничитель обязательных и не обязательных параметров.

Ссылка как параметр

Иногда нужно в качестве параметра передать подпрограмме не значение элемента массива, а ссылку на него, чтобы подпрограмма могла изменить значение элемента. Для этого в Perl к имени переменной добавляется символ «*». Подобное выражение называют type glob так же как в Unix символом «*» обозначают «все возможные значения». Поэтому «*» для массива означает «все элементы массива». Для скаляров употреблять «*» не имеет смысла, так как они и так передаются ссылкой и вы можете изменять значение параметра, изменяя, например, переменную $_[0].

Переопределение встроенных функций

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

Для этого нужно перечислить имена этих функций в виде:

use subs 'функция1', 'функция2' ....;

и далее в модуле определить сами функции.

Автозагрузка

Если вы попытаетесь вызвать несуществующую функцию, то Perl немедленно выдаст сообщение об ошибке. Но если вы определите подпрограмму с именем AUTOLOAD, то она будет вызвана с теми же параметрами, а переменная $AUTOLOAD будет содержать имя несуществующей подпрограммы. Данный механизм очень удобен для средств отладки.

Глава 16.
Модули (packages)

В Perl реализован механизм модулей. Модуль это группа подпрограмм и переменных обычно включенных в один файл. Внутри одного модуля можно определить другой модуль. Начало модуля определяется директивой:

packages имя_модуля;

Конец модуля это конец блока или файла. Головной модуль имеет по умолчанию имя main. На имя внутри модуля можно ссылаться, добавляя «::» после имени модуля.

Например:

$main::var1 - переменная в головном модуле.

::var1 - то же самое. Имя main можно опускать.

$модуль1::var1 - переменная в модуле 'модуль1'

$модуль1::модуль2::var1 — Модуль2 содержится в модуле 1.

Только идентификаторы, начинающиеся с буквы или символа «_», хранятся в пространстве имен текущего модуля. Остальные хранятся в пространстве головного модуля main. Кроме этого имена STDIN, STDOUT, STDERR, ARGV, ARGVOUT, ENV, INC и SIG так же хранятся в головном модуле.

Таблицы имен

Все имена модуля хранятся в ассоциативном массиве (хеше) с именем модуля, к которому добавлены символы «::». Таким образом имена головного модуля хранятся в %main::, модуля 'mod1' в %mod1::. Выражение вида *имя указывает значение элемента хеша 'имя', это удобно для определения констант.

Например:

*pi = \3.14159;

Здесь переменная $pi — это константа пи, которую уже нельзя изменить.

Конструкторы и деструкторы

Конструктор — это подпрограмма, которая выполняется в момент создания объекта, а деструктор — удаления объекта. Для модуля это подпрограммы с именами BEGIN и END. При определении этих подпрограмм слово sub можно опускать.

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

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

Классы

В Perl нет специального синтаксиса для классов. Но функционально полноценными классами могут быть модули. При этом подпрограммы модуля становятся методами, а с помощью массива @ISA можно реализовать механизм наследования в классах. Более подробно классы описаны отдельно.

Создание библиотеки

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

package имя_модуля;

# Такое же как и имя этого файла без расширения '.pm'

require Exporter; # Обязательная строка для экспорта имен

@ISA = qw(Exporter); # Обязательная строка для экспорта имен

@EXPORT = qw(func1 func2)

# Перечисляем имена функций. Внимание! нет запятой!

@EXPORT_OK = qw( $переменная @массив );

# Указать публичные переменные, массивы, если необходимо

{

# Начало блока модуля

.....

sub func1

........

sub func2

........

1;

}

Данный файл с расширением .pm должен храниться в одной из библиотечных директорий Perl. Они перечислены в массиве @INC, одна из них обычно /usr/local/ lib/perl/. В головной программе вы указываете:

use имя_модуля;

и вам становятся доступны имена подпрограмм данного модуля.

Perl библиотеки

Стандартный набор библиотек обычно поставляется с дистрибутивом Perl, они разделяются на pragma библиотеки (работают как директивы компилятору) и стандартные библиотеки.

Pragma библиотеки

Данные библиотеки используют как:

use имя;

когда хотят включить действие и

no имя;

когда выключить.

В стандартный набор входят следующие pragma:

diagnostics

Включить режим расширенной диагностики.

integer

Использовать целочисленную арифметику.

less

Режим минимальной загрузки компилятора.

overload

Режим переопределения операторов.

sigtrap

Режим слежения за прерываниями.

strict

Режим ограниченного использования «опасных» операторов.

subs

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

Стандартные библиотеки

AnyDBM_File

Возможность работы с разными типами баз данных.

AutoLoader

Загрузка в память функций только во время вызова.

AutoSplit

Разделить модуль для автозагрузки.

Benchmark

Анализ скорости исполнения программы.

Carp

Предупреждения об ошибках.

Config

Доступ к конфигурации Perl.

Cwd

Получить имя текущей рабочей директории.

DB_File

Работа с базой данных формата Berkley DB.

Devel::SelfStubber

Режим отладки автозагрузки.

DynaLoader

Динамическая загрузка библиотек C.

English

Использовать длинные имена встроенных переменных.

Env

Импортировать имена переменных окружения

Exporter

Обеспечивает экспорт/импорт для модулей.

ExtUtils::LibList

Определяет используемые библиотеки.

ExtUtils::MakeMaker

Создает файл проекта Makefile

ExtUtils::Manifest

Программы для создания и проверки файла MANIFEST

ExtUtils::Mkbootstrap

Применение файла начальной загрузки для DynaLoader.

Fcntl

Определения как и в С Fcntl.h

File::Basename

Синтаксический разбор спецификации файла.

File::CheckTree

Быстрый проход по директориям диска.

File::Find

Быстрый поиск файлов по директориям.

FileHandle

Обеспечивает объектный метод доступа к указателям файлов.

File::Path

Создание/удаление директорий.

Getopt::Long

Расширенная обработка опций.

Getopt::Std

Стандартная обработка опций.

I18N::Collate

Сравнение символов локальной кодировки.

IPC::Open2

Межпроцессорный обмен по чтению и записи.

IPC::Open3

Межпроцессорный обмен по чтению, записи, и обработки ошибок.

Net::Ping

Тест доступа к хосту.

POSIX

Стандартный интерфейс по IEEE Std 1003.1

SelfLoader

Загрузка функций только по вызову.

Socket

Определение структур и констант как и в С socket.h

Test::Harness

Стандартный тест с статистикой.

Text::Abbrev

Создание таблицы сокращений по списку.

CPAN

Программисты всего мира, работающие с Perl, создали общедоступную библиотеку модулей CPAN. Она доступна через Интернет и содержит огромное количество различных по назначению модулей. К ним относятся документаторы, системные интерфейсы, интерфейсы работы с базами данных, работа в сети, с файлами, Интернет-броузеры, системы поиска, огромное количество CGI скриптов для Web серверов и многое-многое другое.

Глава 17.
Форматы

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

Оператор format имеет следующий синтаксис:

format имя =

FORMLIST

.

Обратите внимание на то, что описание формата идет после строки format и заканчивается символом «.» в начале строки. Здесь «имя» — это имя формата, такое же как и имя указателя выходного файла. Если «имя» отсутствует то значение по умолчанию — STDOUT.

FORMLIST — это строки формата. Они бывают трех типов:

u Комментарий. Строка начинается символом '#'.

u Описатель полей данных (picture).

u Строка аргументов используемых описателем.

Описатель — это строка, которая выводится в виде «как есть» за исключением специально обозначенных форматов полей данных. Каждое поле начинается либо символом «@», либо «^». В описательной строке указывается только положение и вид выводимых данных, но не имена полей и переменных. Для этого предназначена следующая строка аргументов которая следует всегда после описателя и содержит имена переменных или целые выражения в порядке указанном описателем.

Размер и вид поля в описателе обозначается следующими символами:

u <<<< — выровнять значение по правому краю.

u >>>> — выровнять значение по левому краю.

u |||| — выровнять значение по центру.

u ####.### — формат числа с точкой.

u @* — многострочная строка. Данные выводятся в колонку.

Размер поля равен количеству указанных символов.

Символ «^» в начале поля имеет специальное значение.

Так:

u ^#### — пусто если переменная не определена.

Для строчного скаляра:

u ^<<<<< — выводится сколько возможно символов, а значение переменной меняется на остаток, вывод которого можно продолжить на следующих строках, которые могут иметь свои поля.

Пример:

#!/usr/local/bin/perl

#

# Программа печати пригласительного билета

#

$кому = "Чапаеву Василию Ивановичу";

$от_кого = "Компания МММ";

$адрес = "Москва, ул. Петровка, д 38";

$текст = "Уважаемый Василий Иванович! Компания МММ имеет честь пригласить Вас и Ваших близких на презентацию наших новых хромовых сапог, сделанных на уровне мировых стандартов качества и дизайна.";

format STDOUT =

П Р И Г Л А С И Т Е Л Ь Н Ы Й Б И Л Е Т

--------------------------------------------------------------

Кому: @<<<<<<<<<<<<<<<<<<<<<<<<<<
| ^||||||||||||||||||||||||||||||| $кому,
$текст | ^|||||||||||||||||||||||||||||||
$текст От кого: @<<<<<<<<<<<<<<<<<<<<<<<
| ^||||||||||||||||||||||||||||||| $от_кого,
$текст | ^|||||||||||||||||||||||||||||||
$текст Адрес:@<<<<<<<<<<<<<<<<<<<<<<<<<<
| ^||||||||||||||||||||||||||||||| $адрес,
$текст | ^|||||||||||||||||||||||||||||||
$текст | ^|||||||||||||||||||||||||||||||
$текст | ^|||||||||||||||||||||||||||||||
$текст Отпечатано в типографии ИТУ 38 .
write(); # Вывод данных.
exit 0; # Конец программы

Результат:

П Р И Г Л А С И Т Е Л Ь Н Ы Й Б И Л Е Т

--------------------------------------------------------------

Кому: Чапаеву Василию Ивановичу | Уважаемый Василий
| Иванович!

| Компания МММ имеет честь
| пригласить

От кого: Компания МММ | Вас и Ваших близких
| на презентацию наших
| новых хромовых сапог,
Адрес:Москва,ул.Петровка,д 38 | сделанных на уровне
| мировых стандартов
| качества и дизайна.

--------------------------------------------------------------

Отпечатано в типографии ИТУ 38

Специальные переменные:

u $~ — построчный формат содержимого.

u $^ — формат заголовка листа.

u $% — номер листа.

u $= — строк в листе.

Если вы хотите использовать одни и те же форматы для разных файлов, то самый простой путь:

use FileHandle; # Указать в начале программы

format_name файл имя_формата; # Формат содержимого листа.

format_top_name файл имя_формата; # Формат заголовка листа.

write(файл); # вывод данных.

Здесь «файл» имеется ввиду указатель файла полученный командой open();

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

Глава 18.
Отладка программ

В Perl реализован превосходный механизм отладки программ. Вам достаточно запустить программу с ключом «-d» и вы начнете работать с интерактивным отладчиком. Вам становятся доступны все переменные и массивы, вы можете вводить выражения и операторы, трассировать, исследовать работу программы в пошаговом режиме и многое другое. Ниже перечислены команды отладчика которые вы можете вводить на его подсказку.

Команды отладчика.

u h — Получить справку по командам.

u T — Просмотреть стек программы.

u s — Выполнить одну команду и остановиться.

u n — Выполнить подпрограмму и остановиться.

u r — Выполнить текущую подпрограмму и остановиться.

u c — Продолжить выполнение до следующей точки остановки.

u c номер — Продолжить выполнение до строки с данным номером.

u l min+incr — Вывести incr+1 строк программы начиная со строки min.

u l min-max — Вывести строки начиная с min до max.

u l номер — Вывести строку с указанным номером.

u l — Вывести следующий экран строк.

u w строк — Вывести экран w строк. Текущая в середине.

u l подпрограмма — Вывести строки указанной подпрограммы.

u /шаблон/ — Найти строку в тексте программы.

u ?шаблон? — Обратный поиск строки в тексте программы.

u L — Вывести строки с точками останова и активами.

u S — Вывести имена всех подпрограмм.

u t — Включить или выключить трассировку.

u b строка [условие] — Установить точку остановки.

u b подпрограмма [условие] — Установить точку остановки в начале указанной подпрограммы и если указано при данном условии.

u d — Убрать точку остановки.

u D — Убрать все точки остановки.

u a строка команда — Установить актив (команду которая выполнится вместе с указанной строкой. «команда» — обычная команда Perl.

u A — Стереть все активы.

u <команда — Выполнять команду перед каждой остановкой.

u > команда — Выполнить команду после остановки отладчика.

u V модуль [имена] — Вывести значение всех или указанных имен в модуле. Важно: Символ «$» не вводится.

u X [имена] — То же что и V , но только для текущего модуля.

u ! номер — Выполнить одну строку программы.

u ! -номер — Выполнить одну команду с номером предшествующим текущей строке.

u H — n — Вывести n последних выполненных команд.

u q или ^D — Выход из программы.

u команда — Выполнить команду Perl.

u p выражение — Вывести значение выражения.

Конструктор BEGIN не отслеживается отладчиком, но можно в теле конструктора указать: $DB::single =1; и отладчик остановится.

Глава 19.
Примеры применения Perl для различных нужд

Следующая программа переводит текстовый файл в формат HTML (вспомните сколько хлопот вам доставит отлов во всем файле «<», «>» и «&», чтобы заменить их на &tl;, &gt; и &amp;, а как неплохо, чтобы автоматически все http://www.... превратились в <A href=»http://www....» >http://www....</A>)

#!/usr/bin/perl

#txt2html

die «Usage: txt2html Infile OutFile\n» unless(@ARGV);

open(IN,»$ARGV[0]»)|| die «Cannot
open $ARGV[0] $! \n»;

open(OUT,»>$ARGV[1]»)|| die «Cannot
open $ARGV[1] $! \n»;

while(<IN>){

s/&/&amp;/g;

s/</&lt;/g;

s/>/&gt;/g;

s/(http:\/\/\S+)/<A href=»$1»>$1<\/A>/g;

print OUT $_;

}

close(IN);

close(OUT);

Часть 5.
Программирование на Java

Глава 1.
Введение в язык Java

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

Начнем с упражнения: оттранслируем и запустим каноническую программу «Hello World». После этого рассмотрим все существенные лексические элементы, воспринимаемые Java-транслятором: пробелы, комментарии, ключевые слова, идентификаторы, литералы, операторы и разделители.

Итак, вот ваша первая Java-программа:

class HelloWorld {

public static void main (String args []) {

System. out. println ("Hello World");

}

}

Приведенный выше текст примера надо записать в файл HelloWorld.java. Обязательно проверьте соответствие прописных букв в имени файла тому же в названии содержащегося в нем класса. Для того, чтобы оттранслировать этот пример необходимо запустить транслятор Java — javac, указав в качестве параметра имя файла с исходным текстом:

С: \> javac HelloWorld.Java

Транслятор создаст файл HelloWorld.class с независимым от процессора байт-кодом нашего примера. Для того, чтобы исполнить полученный код, необходимо иметь среду времени выполнения языка Java (в нашем случае это программа java), в которую надо загрузить новый класс для исполнения. Подчеркнем, что указывается имя класса, а не имя файла, в котором этот класс содержится.

С: > java HelloWorld

Hello World

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

Глава 2.
Шаг за шагом

Конечно, HelloWorld — это тривиальный пример. Однако даже такая простая программа новичку в языке Java может показаться пугающе сложной, поскольку она знакомит вас с массой новых понятий и деталей синтаксиса языка. Давайте внимательно пройдемся по каждой строке нашего первого примера, анализируя те элементы, из которых состоит Java-программа.

Строка 1

class HelloWorld {

В этой строке использовано зарезервированное слово class. Оно говорит транслятору, что мы собираемся описать новый класс. Полное описание класса располагается между открывающей фигурной скобкой в первой строке и парной ей закрывающей фигурной скобкой в строке 5. Фигурные скобки в Java используются точно так же, как в языках Си и Си++.

Строка 2

public static void main (String args []) {

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

public

Это — модификатор доступа, который позволяет программисту управлять видимостью любого метода и любой переменной. В данном случае модификатор доступа public означает, что метод main виден и доступен любому классу.

static

Следующее ключевое слово — static. С помощью этого слова объявляются методы и переменные класса, используемые для работы с классом в целом. Методы, в объявлении которых использовано ключевое слово static, могут непосредственно работать только с локальными и статическими переменными.

void

У вас нередко будет возникать потребность в методах, которые возвращают значение того или иного типа: например, int для целых значений, float — для вещественных или имя класса для типов данных, определенных программистом. В нашем случае нужно просто вывести на экран строку, а возвращать значение из метода main не требуется. Именно поэтому и был использован модификатор void.

main

Наконец, мы добрались до имени метода main. Здесь нет ничего необычного, просто все существующие реализации Java-интерпретаторов, получив команду интерпретировать класс, начинают свою работу с вызова метода main. Java-транслятор может оттранслировать класс, в котором нет метода main. А вот Java-интерпретатор запускать классы без метода main не умеет.

Все параметры, которые нужно передать методу, указываются внутри пары круглых скобок в виде списка элементов, разделенных символами «;» (точка с запятой). Каждый элемент списка параметров состоит из разделенных пробелом типа и идентификатора. Даже если у метода нет параметров, после его имени все равно нужно поставить пару круглых скобок. В примере, который мы сейчас обсуждаем, у метода main только один параметр, правда довольно сложного типа.

Элемент String args[] объявляет параметр с именем args, который является массивом объектов — представителей класса String. Обратите внимание на квадратные скобки, стоящие после идентификатора args. Они говорят о том, что мы имеем дело с массивом, а не с одиночным элементом указанного типа.

Строка 3

System. out. prlntln("Hello World!");

В этой строке выполняется метод println объекта out. Объект out объявлен в классе OutputStream и статически инициализируется в классе System.

Закрывающей фигурной скобкой в строке 4 заканчивается объявление метода main, а такая же скобка в строке 5 завершает объявление класса HelloWorld.

Глава 3.
Лексические основы

Теперь, когда мы подробно рассмотрели минимальный Java-класс, давайте вернемся назад и рассмотрим общие аспекты синтаксиса этого языка. Программы на Java — это набор пробелов, комментариев, ключевых слов, идентификаторов, литеральных констант, операторов и разделителей.

Пробелы

Java — язык, который допускает произвольное форматирование текста программ. Для того, чтобы программа работала нормально, нет никакой необходимости выравнивать ее текст специальным образом. Например, класс HelloWorld можно было записать в двух строках или любым другим способом, который придется вам по душе. И он будет работать точно так же при условии, что между отдельными лексемами (между которыми нет операторов или разделителей) имеется по крайней мере по одному пробелу, символу табуляции или символу перевода строки.

Комментарии

Хотя комментарии никак не влияют на исполняемый код программы, при правильном использовании они оказываются весьма существенной частью исходного текста. Существует три разновидности комментариев: комментарии в одной строке, комментарии в нескольких строках и, наконец, комментарии для документирования. Комментарии, занимающие одну строку, начинаются с символов // и заканчиваются в конце строки. Такой стиль комментирования полезен для размещения кратких пояснений к отдельным строкам кода:

а = 42; // если 42 — ответ, то каков же был вопрос?

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

/*

* Этот код несколько замысловат...

* Попробую объяснить:

* ….

*/

Третья, особая форма комментариев, предназначена для сервисной программы javadoc, которая использует компоненты Java-транслятора для автоматической генерации документации по интерфейсам классов. Соглашение, используемое для комментариев этого вида, таково: для того, чтобы разместить перед объявлением открытого (public) класса, метода или переменной документирующий комментарий, нужно начать его с символов /** (косая черта и две звездочки). Заканчивается такой комментарий точно так же, как и обычный комментарий — символами */. Программа javadoc умеет различать в документирующих комментариях некоторые специальные переменные, имена которых начинаются с символа @. Вот пример такого комментария:

/**

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

* @see Java. applet. Applet

* ©author Patrick Naughton

* @version 1. 2

*/

class CoolApplet extends Applet { /**

* У этого метода два параметра:

* @param key — это имя параметра.

* @param value — это значение параметра с именем key.

*/ void put (String key, Object value) {

Зарезервированные ключевые слова

Зарезервированные ключевые слова — это специальные идентификаторы, которые в языке Java используются для того, чтобы идентифицировать встроенные типы, модификаторы и средства управления выполнением программы. Эти ключевые слова совместно с синтаксисом операторов и разделителей входят в описание языка Java. Они могут применяться только по назначению, их нельзя использовать в качестве идентификаторов для имен переменных, классов или методов. На сегодняшний день в языке Java имеется 59 зарезервированных слов:

abstract

boolean

break

byte

byvalue

case

cast

catch

char

class

const

continue

default

do

double

else

extends

false

final

finally

float

for

future

generic

goto

if

implements

import

inner

instanceof

int

interface

long

native

new

null

operator

outer

package

private

protected

public

rest

return

short

static

super

switch

syncronized

this

throw

throws

transient

true

try

var

void

volatile

while

Отметим, что слова byvalue, cast, const, future, generic, goto, inner, operator, outer, rest, var зарезервированы в Java, но пока не используются. Кроме этого, в Java есть зарезервированные имена методов (эти методы наследуются каждым классом, их нельзя использовать, за исключением случаев явного переопределения методов класса Object).

Зарезервированные имена методов Java

clone

equals

finalize

getClass

hashCode

notify

notifyAll

toString

wait

Идентификаторы

Идентификаторы используются для именования классов, методов и переменных. В качестве идентификатора может использоваться любая последовательность строчных и прописных букв, цифр и символов «_» (подчеркивание) и «$» (доллар). Идентификаторы не должны начинаться с цифры, чтобы транслятор не перепутал их с числовыми литеральными константами. Java — язык, чувствительный к регистру букв. Это означает, что, к примеру, Value и VALUE — различные идентификаторы.

Литералы

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

Целые литералы

Целые числа — это тип, используемый в обычных программах наиболее часто. Любое целочисленное значение, например, 1, 2, 3, 42 — это целый литерал. В данном примере приведены десятичные числа, то есть числа с основанием 10 — именно те, которые мы повседневно используем вне мира компьютеров. Кроме десятичных, в качестве целых литералов могут использоваться также числа с основанием 8 и 16 — восьмеричные и шестнадцатеричные. Java распознает восьмеричные числа по стоящему впереди нулю. Нормальные десятичные числа не могут начинаться с нуля, так что использование в программе внешне допустимого числа 09 приведет к сообщению об ошибке при трансляции, поскольку 9 не входит в диапазон 0…7, допустимый для знаков восьмеричного числа. Шестнадцатеричная константа различается по стоящим впереди символам нуль-х (0х или 0Х). Диапазон значений шестнадцатеричной цифры — 0…15, причем в качестве цифр для значений 10…15 используются буквы от А до F (или от а до f). С помощью шестнадцатеричных чисел вы можете в краткой и ясной форме представить значения, ориентированные на использование в компьютере, например, написав Oxffff вместо 65535.

Целые литералы являются значениями типа int, которое в Java хранится в 32-битовом слове. Если вам требуется значение, которое по модулю больше, чем приблизительно 2 миллиарда, необходимо воспользоваться константой типа long. При этом число будет храниться в 64-битовом слове. К числам с любым из названных выше оснований вы можете приписать справа строчную или прописную букву L, указав таким образом, что данное число относится к типу long. Например, Ox7ffffffffffffffL или 9223372036854775807L — это значение, наибольшее для числа типа long.

Литералы с плавающей точкой

Числа с плавающей точкой представляют десятичные значения, у которых есть дробная часть. Их можно записывать либо в обычном, либо экспоненциальном форматах. В обычном формате число состоит из некоторого количества десятичных цифр, стоящей после них десятичной точки, и следующих за ней десятичных цифр дробной части. Например, 2.0, 3.14159 и .6667 — это допустимые значения чисел с плавающей точкой, записанных в стандартном формате. В экспоненциальном формате после перечисленных элементов дополнительно указывается десятичный порядок. Порядок определяется положительным или отрицательным десятичным числом, следующим за символом Е или е. Примеры чисел в экспоненциальном формате: 6.022е23, 314159Е-05, 2е+100. В Java числа с плавающей точкой по умолчанию рассматриваются, как значения типа double. Если вам требуется константа типа float, справа к литералу надо приписать символ F или f. Если вы любитель избыточных определений — можете добавлять к литералам типа double символ D или d. Значения используемого по умолчанию типа double хранятся в 64-битовом слове, менее точные значения типа float — в 32-битовых.

Логические литералы

У логической переменной может быть лишь два значения — true (истина) и false (ложь). Логические значения true и false не преобразуются ни в какое числовое представление. Ключевое слово true в Java не равно 1, a false не равно 0. В Java эти значения могут присваиваться только переменным типа boolean либо использоваться в выражениях с логическими операторами.

Символьные литералы

Символы в Java — это индексы в таблице символов UNICODE. Они представляют собой 16-битовые значения, которые можно преобразовать в целые числа и к которым можно применять операторы целочисленной арифметики, например, операторы сложения и вычитания. Символьные литералы помещаются внутри пары апострофов (' '). Все видимые символы таблицы ASCII можно прямо вставлять внутрь пары апострофов: — 'a', 'z', '@'. Для символов, которые невозможно ввести непосредственно, предусмотрено несколько управляющих последовательностей.

Управляющая последовательность Описание

\ddd Восьмеричный символ (ddd)

\uxxxx Шестнадцатеричный символ UNICODE (xxxx)

\' Апостроф

\" Кавычка

\\ Обратная косая черта

\r Возврат каретки (carriage return)

\u Перевод строки (line feed, new line)

\f Перевод страницы (form feed)

\t Горизонтальная табуляция (tab)

\b Возврат на шаг (backspace)

Строчные литералы

Строчные литералы в Java выглядят точно также, как и во многих других языках — это произвольный текст, заключенный в пару двойных кавычек (""). Хотя строчные литералы в Java реализованы весьма своеобразно (Java создает объект для каждой строки), внешне это никак не проявляется. Примеры строчных литералов:

“Hello World!”;

"две\строки; \ А это в кавычках\"".

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

Операторы

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

Операторы языка Java

+

+=

-

-=

*

*=

/

/=

|

|=

^

^=

&

&=

%

%=

>

>=

<

<=

!

!=

++

--

>>

>>=

<<

<<=

>>>

>>>=

&&

| |

==

=

~

?:

instanceof

[ ]

Разделители

Лишь несколько групп символов, которые могут появляться в синтаксически правильной Java-программе, все еще остались неназванными. Это — простые разделители, которые влияют на внешний вид и функциональность программного кода.

( ) круглые скобки

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

{ } фигурные скобки

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

[ ] квадратные скобки

Используются в объявлениях массивов и при доступе к отдельным элементам массива.

; точка с запятой

Разделяет операторы.

, запятая

Разделяет идентификаторы в объявлениях переменных, также используется для связи операторов в заголовке цикла for.

. точка

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

Глава 4.
Переменные

Переменная — это основной элемент хранения информации в Java-программе. Переменная характеризуется комбинацией идентификатора, типа и области действия. В зависимости от того, где вы объявили переменную, она может быть локальной, например, для кода внутри цикла for, либо это может быть переменная экземпляра класса, доступная всем методам данного класса. Локальные области действия объявляются с помощью фигурных скобок.

Объявление переменной

Основная форма объявления переменной такова:

тип идентификатор [ = значение] [, идентификатор
[ = значение 7...];

Тип — это либо один из встроенных типов, то есть, byte, short, int, long, char, float, double, boolean, либо имя класса или интерфейса. Ниже приведено несколько примеров объявления переменных различных типов. Обратите внимание на то, что некоторые примеры включают в себя инициализацию начального значения. Переменные, для которых начальные значения не указаны, автоматически инициализируются нулем.

int a, b, с;

Объявляет три целых переменных а, b, с.

int d = 3, e, f = 5;

Объявляет еще три целых переменных, инициализирует d и f.

byte z = 22;

Инициализирует z.

double pi = 3. 14159;

Объявляет число пи (не очень точное, но все таки пи).

char x = 'x';

Переменная х получает значение 'х'.

В приведенном ниже примере создаются три переменные, соответствующие сторонам прямоугольного треугольника, а затем c помощью теоремы Пифагора вычисляется длина гипотенузы, в данном случае числа 5, величины гипотенузы классического прямоугольного треугольника со сторонами 3-4-5.

class Variables {

public static void main (String args []) {

double a = 3;

double b = 4;

double c;

с = Math.sqrt (a* a + b* b);

System.out.println ("c = "+ c); } }

Глава 5.
Типы

Простые типы

Простые типы в Java не являются объектно-ориентированными, они аналогичны простым типам большинства традиционных языков программирования. В Java имеется восемь простых типов: byte, short, int, long, char, float, double и boolean. Их можно разделить на четыре группы:

Целые

К ним относятся типы byte, short, int и long. Эти типы предназначены для целых чисел со знаком.

Типы с плавающей точкой float и double

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

Символьный тип char

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

Логический тип boolean

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

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

Целые числа

В языке Java понятие беззнаковых чисел отсутствует. Все числовые типы этого языка — знаковые. Например, если значение переменной типа byte равно в шестнадцатеричном виде 0х80, то это — число -1.

Единственная реальная причина использования беззнаковых чисел — это использование иных, по сравнению со знаковыми числами, правил манипуляций с битами при выполнении операций сдвига. Пусть, например, требуется сдвинуть вправо битовый массив mask, хранящийся в целой переменной и избежать при этом расширения знакового разряда, заполняющего старшие биты единицами. Стандартный способ выполнения этой задачи в С — ((unsigned) mask) >> 2. В Java для этой цели введен новый оператор беззнакового сдвига вправо. Приведенная выше операция записывается с его помощью в виде mask>>>2.

Отсутствие в Java беззнаковых чисел вдвое сокращает количество целых типов. В языке имеется 4 целых типа, занимающих 1, 2, 4 и 8 байтов в памяти. Для каждого типа — byte, short, int и long, есть свои естественные области применения.

byte

Тип byte — это знаковый 8-битовый тип. Его диапазон — от -128 до 127. Он лучше всего подходит для хранения произвольного потока байтов, загружаемого из сети или из файла.

byte b;

byte с = 0х55;

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

short

short — это знаковый 16-битовый тип. Его диапазон — от -32768 до 32767. Это, вероятно, наиболее редко используемый в Java тип, поскольку он определен, как тип, в котором старший байт стоит первым.

short s;

short t = Ox55aa;

Случилось так, что на ЭВМ различных архитектур порядок байтов в слове различается, например, старший байт в двухбайтовом целом short может храниться первым, а может и последним. Первый случай имеет место в архитектурах SPARC и Power PC, второй — для микропроцессоров Intel x86. Переносимость программ Java требует, чтобы целые значения одинаково были представлены на ЭВМ разных архитектур.

int

Тип int служит для представления 32-битных целых чисел со знаком. Диапазон допустимых для этого типа значений — от -2147483648 до 2147483647. Чаще всего этот тип данных используется для хранения обычных целых чисел со значениями, достигающими двух миллиардов. Этот тип прекрасно подходит для использования при обработке массивов и для счетчиков. В ближайшие годы этот тип будет прекрасно соответствовать машинным словам не только 32-битовых процессоров, но и 64-битовых с поддержкой быстрой конвейеризации для выполнения 32-битного кода в режиме совместимости. Всякий раз, когда в одном выражении фигурируют переменные типов byte, short, int и целые литералы, тип всего выражения перед завершением вычислений приводится к int.

int i;

int j = 0x55aa0000;

long

Тип long предназначен для представления 64-битовых чисел со знаком. Его диапазон допустимых значений достаточно велик даже для таких задач, как подсчет числа атомов во вселенной.

long m;

long n = Ох55аа000055аа0000;

Не надо отождествлять разрядность целочисленного типа с занимаемым им количеством памяти. Исполняющий код Java может использовать для ваших переменных то количество памяти, которое сочтет нужным, лишь бы только их поведение соответствовало поведению типов, заданных вами. Фактически, нынешняя реализация Java из соображений эффективности хранит переменные типа byte и short в виде 32-битовых значений, поскольку этот размер соответствует машинному слову большинства современных компьютеров.

Ниже приведены разрядности и допустимые диапазоны для различных типов целых чисел.

Имя Разрядность Диапазон

long 64 -9, 223, 372, 036, 854, 775, 808…9, 223,
372, 036, 854, 775, 807

Int 32 -2, 147, 483, 648…2, 147, 483, 647

Short 16 -32, 768…32, 767

byte 8 -128…127

Числа с плавающей точкой

Числа с плавающей точкой, часто называемые в других языках вещественными числами, используются при вычислениях, в которых требуется использование дробной части. В Java реализован стандартный (IEEE-754) набор типов для чисел с плавающей точкой — float и double и операторов для работы с ними. Характеристики этих типов приведены ниже.

Имя Разрядность Диапазон

double 64 1. 7е-308.. 1. 7е+ 308

float 32 3. 4е-038.. 3. 4е+ 038

float

В переменных с обычной, или одинарной точностью, объявляемых с помощью ключевого слова float, для хранения вещественного значения используется 32 бита.

float f;

float f2 = 3. 14F; // обратите внимание на F, так как по умолчанию все литералы double

double

В случае двойной точности, задаваемой с помощью ключевого слова double, для хранения значений используется 64 бита. Все трансцендентные математические функции, такие, как sin, cos, sqrt, возвращают результат типа double.

double d;

double pi = 3. 14159265358979323846;

Глава 6.
Приведение типа

Приведение типов (type casting) — одно из неприятных свойств C++, тем не менее приведение типов сохранено и в языке Java. Иногда возникают ситуации, когда у вас есть величина какого-то определенного типа, а вам нужно ее присвоить переменной другого типа. Для некоторых типов это можно проделать и без приведения типа, в таких случаях говорят об автоматическом преобразовании типов. В Java автоматическое преобразование возможно только в том случае, когда точности представления чисел переменной-приемника достаточно для хранения исходного значения. Такое преобразование происходит, например, при занесении литеральной константы или значения переменной типа byte или short в переменную типа int. Это называется расширением (widening) или повышением (promotion), поскольку тип меньшей разрядности расширяется (повышается) до большего совместимого типа. Размера типа int всегда достаточно для хранения чисел из диапазона, допустимого для типа byte, поэтому в подобных ситуациях оператора явного приведения типа не требуется. Обратное в большинстве случаев неверно, поэтому для занесения значения типа int в переменную типа byte необходимо использовать оператор приведения типа. Эту процедуру иногда называют сужением (narrowing), поскольку вы явно сообщаете транслятору, что величину необходимо преобразовать, чтобы она уместилась в переменную нужного вам типа. Для приведения величины к определенному типу перед ней нужно указать этот тип, заключенный в круглые скобки. В приведенном ниже фрагменте кода демонстрируется приведение типа источника (переменной типа int) к типу приемника (переменной типа byte). Если бы при такой операции целое значение выходило за границы допустимого для типа byte диапазона, оно было бы уменьшено путем деления по модулю на допустимый для byte диапазон (результат деления по модулю на число — это остаток от деления на это число).

int a = 100;

byte b = (byte) a;

Глава 7.
Автоматическое преобразование типов в выражениях

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

byte a = 40;

byte b = 50;

byte с = 100;

int d = a* b / с;

Результат промежуточного выражения (а* b) вполне может выйти за диапазон допустимых для типа byte значений. Именно поэтому Java автоматически повышает тип каждой части выражения до типа int, так что для промежуточного результата (а* b) хватает места.

Автоматическое преобразование типа иногда может оказаться причиной неожиданных сообщений транслятора об ошибках. Например, показанный ниже код, хотя и выглядит вполне корректным, приводит к сообщению об ошибке на фазе трансляции. В нем мы пытаемся записать значение 50* 2, которое должно прекрасно уместиться в тип byte, в байтовую переменную. Но из-за автоматического преобразования типа результата в int мы получаем сообщениет об ошибке от транслятора — ведь при занесении int в byte может произойти потеря точности.

byte b = 50;

b = b* 2;

^ Incompatible type for =. Explicit cast needed to convert int to byte

(Несовместимый тип для =. Необходимо явное преобразование int в byte)

Исправленный текст:

byte b = 50;

b = (byte) (b* 2);

что приводит к занесению в b правильного значения 100.

Если в выражении используются переменные типов byte, short и int, то во избежание переполнения тип всего выражения автоматически повышается до int. Если же в выражении тип хотя бы одной переменной — long, то и тип всего выражения тоже повышается до long. He забывайте, что все целые литералы, в конце которых не стоит символ L (или 1), имеют тип int.

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

class Promote {

public static void main (String args []) { byte b = 42;

char с = 'a';

short s = 1024;

int i = 50000;

float f = 5.67f;

double d =.1234;

double result = (f* b) + (i/ c) - (d* s);

System. out. println ((f* b)+ "+ "+ (i / с)+ " - " +
(d* s));

System. out. println ("result = "+ result);

}

}

Подвыражение f* b — это число типа float, умноженное на число типа byte. Поэтому его тип автоматически повышается до float. Тип следующего подвыражения i/с (int, деленный на char) повышается до int. Аналогично этому тип подвыражения d*s (double, умноженный на short) повышается до double. На следующем шаге вычислений мы имеем дело с тремя промежуточными результатами типов float, int и double. Сначала при сложении первых двух тип int повышается до float и получается результат типа float. При вычитании из него значения типа double тип результата повышается до double. Окончательный результат всего выражения — значение типа double.

Глава 8.
Символы

Поскольку в Java для представления символов в строках используется кодировка Unicode, разрядность типа char в этом языке — 16 бит. В нем можно хранить десятки тысяч символов интернационального набора символов Unicode. Диапазон типа char — 0…65536. Unicode — это объединение десятков кодировок символов, он включает в себя латинский, греческий, арабский алфавиты, кириллицу и многие другие наборы символов.

char c;

char c2 = Oxf132;

char c3 = ' a';

char c4 = '\n';

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

int three = 3;

char one = '1';

char four = (char) (three+ one);

В результате выполнения этого кода в переменную four заносится символьное представление нужной нам цифры — '4'. Обратите внимание — тип переменной one в приведенном выше выражении повышается до типа int, так что перед занесением результата в переменную four приходится использовать оператор явного приведения типа.

Глава 9.
Тип boolean

В языке Java имеется простой тип boolean, используемый для хранения логических значений. Переменные этого типа могут принимать всего два значения — true (истина) и false (ложь). Значения типа boolean возвращаются в качестве результата всеми операторами сравнения, например (а < b).

boolean done = false;

Глава 10.
Пример создания переменных различных типов

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

class SimpleTypes {

public static void main(String args []) {

byte b = 0х55;

short s = 0x55ff;

int i = 1000000;

long l = 0xffffffffL;

char с = ' a' ;

float f = .25f;

double d = .00001234;

boolean bool = true;

System.out.println("byte b = " + b);

System.out.println("short s = " +s);

System.out.println("int i = " + i);

System.out.println("long l = " + l);

System.out.println("char с = " + с);

System.out.println("float f = " + f);

System.out.println("double d = " + d);

System.out.println("boolean bool = " + bool);

} }

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

С: \> java SimpleTypes

byte b = 85

short s = 22015

int i = 1000000

long l = 4294967295

char с = а

float f = 0.25

double d = 1.234e-005

boolean bool = true

Обратите внимание на то, что целые числа печатаются в десятичном представлении, хотя мы задавали значения некоторых из них в шестнадцатеричном формате.

Глава 11.
Массивы

Для объявления типа массива используются квадратные скобки. В приведенной ниже строке объявляется переменная month_days, тип которой — «массив целых чисел типа int».

int month_days [];

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

month_days = new int [12];

Итак, теперь month_days — это ссылка на двенадцать целых чисел. Ниже приведен пример, в котором создается массив, элементы которого содержат число дней в месяцах года (невисокосного).

class Array {

public static void main (String args []) {

int month_days[];

month_days = new int[12];

month_days[0] = 31;

month_days[1] = 28;

month_days[2] = 31;

month_days[3] = 30;

month_days[4] = 31;

month_days[5] = 30;

month_days[6] = 31;

month_days[7] = 31;

month_days[8] = 30;

month_days[9] = 31;

month_days[10] = 30;

month_days[11] = 31;

System.out.println("April has " + month_days[3] + " days.");

} }

При запуске эта программа печатает количество дней в апреле, как это показано ниже. Нумерация элементов массива в Java начинается с нуля, так что число дней в апреле — это month_days [3].

С: \> java Array

April has 30 days.

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

class AutoArray

{

public static void main(String args[])

{

int month_days[] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };

System.out.println("April has " + month_days[3] + " days.");

}

}

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

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

Глава 12.
Многомерные массивы

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

double matrix [][] = new double [4][4];

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

double matrix [][] = new double [4][];

matrix [0] = new double[4];

matrix[1] = new double[4];

matrix[2] = new double[4], matrix[3] = { 0, 1, 2, 3 };

В следующем примере создается матрица размером 4 на 4 с элементами типа double, причем ее диагональные элементы (те, для которых х==у) заполняются единицами, а все остальные элементы остаются равными нулю.

class Matrix {

public static void main(String args[]) { double m[][];

m = new double[4][4];

m[0][0] = 1;

m[1][1] = 1;

m[2][2] = 1;

m[3][3] = 1;

System.out.println(m[0][0] +" "+ m[0][1] +" "+ m[0][2] +" "+ m[0][3]);

System.out.println(m[1][0] +" "+ m[1][1] +" "+ m[1][2] +" "+ m[1][3]);

System.out.println(m[2][0] +" "+ m[2][1] +" "+ m[2][2] +" "+ m[2][3]);

System.out.println(m[3][0] +" "+ m[3][1] +" "+ m[3][2] +" "+ m[3][3]);

}

}

Запустив эту программу, вы получите следующий результат:

C : \> Java Matrix

1 0 0 0

0 1 0 0

0 0 1 0

0 0 0 1

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

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

class AutoMatrix

{

public static void main(String args[]) { double m[][] = {

{ 0*0, 1*0, 2*0, 3*0 },

{ 0*1, 1*1, 2*1, 3*1 },

{ 0*2. 1*2, 2*2, 3*2 },

{ 0*3, 1*3. 2*3, 3*3 } }:

System.out.println(m[0][0] +" "+ m[0][1] +" "+ m[0][2] +" "+ m[0][3]);

System.out.println(m[1][0] +" "+m[1][1] +" "+ m[1][2] +" "+ m[1][3]);

System.out.println(m[2][0] +" "+m[2][1] +" "+ m[2][2] +" "+ m[2][3]);

System.out.println(m[3][0] +" "+m[3][1] +" "+ m[3][2] +" "+ m[3][3]);

}

}

Запустив эту программу, вы получите следующий результат:

С: \> Java AutoMatrix

0 0 0 0

0 1 2 3

0 2 4 6

0 3 6 9

Глава 13.
Операторы

Операторы в языке Java — это специальные символы, которые сообщают транслятору о том, что вы хотите выполнить операцию с некоторыми операндами. Некоторые операторы требуют одного операнда, их называют унарными. Одни операторы ставятся перед операндами и называются префиксными, другие — после, их называют постфиксными операторами. Большинство же операторов ставят между двумя операндами, такие операторы называются инфиксными бинарными операторами. Существует тернарный оператор, работающий с тремя операндами.

В Java имеется 44 встроенных оператора. Их можно разбить на 4 класса — арифметические, битовые, операторы сравнения и логические.

Арифметические операторы

Арифметические операторы используются для вычислений так же как в алгебре. Допустимые операнды должны иметь числовые типы. Например, использовать эти операторы для работы с логическими типами нельзя, а для работы с типом char можно, поскольку в Java тип char — это подмножество типа int.

+

Сложение

+ =

Сложение с присваиванием

-

Вычитание (также унарный минус)

-=

Вычитание с присваиванием

*

Умножение

* =

Умножение с присваиванием

/

Деление

/=

Деление с присваиванием

%

Деление по модулю

%=

Деление по модулю с присваиванием

++

Инкремент

--

Декремент

Четыре арифметических действия

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

class BasicMath { public static void int a = 1 + 1;

int b = a * 3;

main(String args[]) {

int c = b / 4;

int d = b - а;

int e = -d;

System.out.println("a = " + а);

System.out.println("b = " + b);

System.out.println("c = " + c);

System.out.println("d = " + d);

System.out.println("e = " + e);

} }

Исполнив эту программу, вы должны получить приведенный ниже результат:

C: \> java BasicMath

a = 2

b = 6

c = 1

d = 4

e = -4

Оператор деления по модулю

Оператор деления по модулю, или оператор mod, обозначается символом %. Этот оператор возвращает остаток от деления первого операнда на второй. В отличие от C++, функция mod в Java работает не только с целыми, но и с вещественными типами. Приведенная ниже программа иллюстрирует работу этого оператора.

class Modulus {

public static void main (String args []) {

int x = 42;

double у = 42.3;

System.out.println("x mod 10 = " + x % 10);

System.out.println("y mod 10 = " + у % 10);

} }

Выполнив эту программу, вы получите следующий результат:

С:\> Modulus

x mod 10 = 2

y mod 10 = 2.3

Арифметические операторы присваивания

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

class OpEquals {

public static void main(String args[]) {

int a = 1;

int b = 2;

int с = 3;

a += 5;

b *= 4;

c += a * b;

с %= 6;

System.out.println("a = " + a);

System.out.println("b = " + b);

System.out.println("c = " + c);

} }

А вот и результат, полученный при запуске этой программы:

С:> Java OpEquals

а = 6

b = 8

с = 3

Инкремент и декремент

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

class IncDec {

public static void main(String args[]) {

int a = 1;

int b = 2;

int c = ++b;

int d = a++;

c++;

System.out.println("a = " + a);

System.out.println("b = " + b);

System.out.println("c = " + c);

System.out.println("d = " + d);

} }

Результат выполнения данной программы будет таким:

C:\ java IncDec

a = 2

b = 3

c = 4

d = 1

Целочисленные битовые операторы

Для целых числовых типов данных — long, int, short, char и byte, определен дополнительный набор операторов, с помощью которых можно проверять и модифицировать состояние отдельных битов соответствующих значений. Операторы битовой арифметики работают с каждым битом как с самостоятельной величиной.

~

Побитовое унарное отрицание (NOT)

&

Побитовое И (AND)

&=

Побитовое И (AND) с присваиванием

|

Побитовое ИЛИ (OR)

|=

Побитовое ИЛИ (OR) с присваиванием

^

Побитовое исключающее ИЛИ (XOR)

^=

Побитовое исключающее ИЛИ (XOR) с присваиванием

>>

Сдвиг вправо

>> =

Сдвиг вправо с присваиванием

>>>

Сдвиг вправо с заполнением нулями

>>>=

Сдвиг вправо с заполнением нулями с присваиванием

<<

Сдвиг влево

<<=

Сдвиг влево с присваиванием

Пример программы, манипулирующей с битами

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

А В OR AND XOR NOT A

0 0 0 0 0 1

1 0 1 0 1 0

0 1 1 0 1 1

1 1 1 1 0 0

class Bitlogic {

public static void main(String args []) {

String binary[] = { "OOOO", "0001", "0010", "0011", "0100", "0101", "0110", "0111", "1000", "1001", "1010", "1011", "1100", "1101",

"1110", "1111" };

int a = 3; // 0+2+1 или двоичное 0011

int b = 6; // 4+2+0 или двоичное 0110

int c = a | b;

int d = a & b;

int e = a ^ b;

int f = (~a & b) | (a & ~b);

int g = ~a & 0x0f;

System.out.println(" a = " + binary[a]);

System.out.println(" b = " + binary[b]);

System.out.println(" ab = " + binary[c]);

System.out.println(" a&b = " + binary[d]);

System.out.println(" a^b = " + binary[e]);

System.out.рrintln("~a&b|а^~Ь = " + binary[f]);

System.out.println(" ~a = " + binary[g]);

} }

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

С: \> Java BitLogic

a = 0011

b = 0110

a | b = 0111

a & b = 0010

a ^ b = 0101

~a & b | a & ~b = 0101

~а = 1100

Сдвиги влево и вправо

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

Оператор >> означает в языке Java сдвиг вправо. Он перемещает все биты своего левого операнда вправо на число позиций, заданное правым операндом. Когда биты левого операнда выдвигаются за самую правую позицию слова, они теряются. При сдвиге вправо освобождающиеся старшие (левые) разряды сдвигаемого числа заполняются предыдущим содержимым знакового разряда. Такое поведение называют расширением знакового разряда.

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

class HexByte {

static public void main(String args[]) {

char hex[] = { '0', '1, '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f };

byte b = (byte) 0xf1;

System.out.println(“b = 0x” + hex[(b >> 4) & 0x0f] + hex[b & 0x0f]);

} }

Ниже приведен результат работы этой программы:

С:\> java HexByte

b = 0xf1

Беззнаковый сдвиг вправо

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

class ByteUShift {

static public void main(String args[]) {

char hex[] = { '0', '1’, '2', '3', '4', '5', '6', '7', '8', '9', 'а', 'b', 'с', 'd', 'e', 'f’ };

byte b = (byte) 0xf1;

byte c = (byte) (b >> 4);

byte d = (byte) (b >> 4);

byte e = (byte) ((b & 0xff) >> 4);

System.out.println(" b = 0x" + hex(b >> 4) & 0x0f] +
hex[b & 0x0f]);

System.out.println(“ b >> 4 = 0x" + hex[(c >> 4) & 0x0f] + hex[c & 0x0f]);

System.out.println(“b >>> 4 = 0x" + hex[(d >> 4) & 0x0f] + hex[d & 0x0f]);

System.out.println(“(b & 0xff) >> 4 = 0x" +
hex[(e >> 4) & 0x0f] + hex[e & 0x0f]);

} }

Для этого примера переменную b можно было бы инициализировать произвольным отрицательным числом, мы использовали число с шестнадцатеричным представлением 0xf1. Переменной с присваивается результат знакового сдвига b вправо на 4 разряда. Как и ожидалось, расширение знакового разряда приводит к тому, что 0xf1 превращается в 0xff. Затем в переменную d заносится результат беззнакового сдвига b вправо на 4 разряда. Можно было бы ожидать, что в результате d содержит 0x0f, однако на деле мы снова получаем 0xff. Это — результат расширения знакового разряда, выполненного при автоматическом повышении типа переменной b до int перед операцией сдвига вправо. Наконец, в выражении для переменной е нам удается добиться желаемого результата — значения 0x0f. Для этого нам пришлось перед сдвигом вправо логически умножить значение переменной b на маску 0xff, очистив таким образом старшие разряды, заполненные при автоматическом повышении типа. Обратите внимание, что при этом уже нет необходимости использовать беззнаковый сдвиг вправо, поскольку мы знаем состояние знакового бита после операции AND.

С: \> java ByteUShift

b = 0xf1

b >> 4 = 0xff

b >>> 4 = 0xff

b & 0xff) >> 4 = 0x0f

Битовые операторы присваивания

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

class OpBitEquals {

public static void main(String args[]) {

int a = 1;

int b = 2;

int с = 3;

a |= 4;

b >>= 1;

с <<= 1;

а ^= с;

System.out.println("a = " + a);

System.out.println("b = " + b);

System.out.println("c = " + c);

} }

Результаты исполнения программы таковы:

С:\> Java OpBitEquals

а = 3

b = 1

с = 6

Операторы отношения

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

Оператор Результат

== равно

!= не равно

> больше

< меньше

>= больше или равно

<= меньше или равно

Значения любых типов, включая целые и вещественные числа, символы, логические значения и ссылки, можно сравнивать, используя оператор проверки на равенство == и неравенство !=. Обратите внимание — в языке Java, так же, как в С и C++ проверка на равенство обозначается последовательностью (==). Один знак (=) — это оператор присваивания.

Булевы логические операторы

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

Оператор Результат

& логическое И (AND)

&= И (AND) с присваиванием

| логическое ИЛИ (OR)

= ИЛИ (OR) с присваиванием

^ логическое исключающее ИЛИ (XOR)

^= исключающее ИЛИ (XOR) с присваиванием

|| оператор OR быстрой оценки выражений (short
circuit OR)

== равно

&& оператор AND быстрой оценки выражений
(short circuit AND)

!= не равно

! логическое унарное отрицание (NOT)

?: тернарный оператор if-then-else

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

А В OR AND XOR NOT A

false false false false false true

true false true false true false

false true true false true true

true true true true false false

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

class BoolLogic {

public static void main(String args[]) {

boolean a = true;

boolean b = false;

boolean с = a | b;

boolean d = a & b;

boolean e = a ^ b;

boolean f = (!a & b) | (a & !b);

boolean g = !a;

System.out.println(" a = " + a);

System.out.println(" b = " + b);

System.out.println(" a|b = " + c);

System.out.println(" a&b = " + d);

System.out.println(" a^b = " + e);

System.out.println("!a&b|a&!b = " + f);

System.out.println(" !a = " + g);

} }

Результат:

С: \> Java BoolLogic

а = true

b = false

a|b = true

a&b = false

a^b = true

!a&b|a&!b = true

!a = false

Операторы быстрой оценки логических выражений (short
logical operators)

Существуют два интересных дополнения к набору логических операторов. Это — альтернативные версии операторов AND и OR, служащие для быстрой оценки логических выражений. Вы знаете, что если первый операнд оператора OR имеет значение true, то независимо от значения второго операнда результатом операции будет величина true. Аналогично в случае оператора AND, если первый операнд — false, то значение второго операнда на результат не влияет — он всегда будет равен false. Если вы в используете операторы && и || вместо обычных форм & и |, то Java не производит оценку правого операнда логического выражения, если ответ ясен из значения левого операнда. Общепринятой практикой является использование операторов && и || практически во всех случаях оценки булевых логических выражений. Версии этих операторов & и | применяются только в битовой арифметике.

Тернарный оператор if-then-else

Общая форма оператора if-then-use такова:

выражение1? выражение2: выражениеЗ

В качестве первого операнда — «выражение1» — может быть использовано любое выражение, результатом которого является значение типа boolean. Если результат равен true, то выполняется оператор, заданный вторым операндом, то есть, «выражение2». Если же первый операнд paвен false, то выполняется третий операнд — «выражениеЗ». Второй и третий операнды, то есть «выражение2» и «выражениеЗ», должны возвращать значения одного типа и не должны иметь тип void.

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

class Ternary {

public static void main(String args[]) {

int a = 42;

int b = 2;

int c = 99;

int d = 0;

int e = (b == 0) ? 0 : (a / b);

int f = (d == 0) ? 0 : (c / d);

System.out.println("a = " + a);

System.out.println("b = " + b);

System.out.println("c = " + c);

System.out.println("d = " + d);

System.out.println("a / b = " + e);

System.out.println("c / d = " + f);

} }

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

С: \>java Ternary

а = 42

b = 2

с = 99

d = 0

a / b = 21

с / d = 0

Глава 14.
Приоритеты операторов

В Java действует определенный порядок, или приоритет, операций. В элементарной алгебре нас учили тому, что у умножения и деления более высокий приоритет, чем у сложения и вычитания.

В программировании также приходится следить и за приоритетами операций. Ниже указаны в порядке убывания приоритеты всех операций языка Java.

Высший

( ) [ ] .

~ !

* / %

+ -

>> >>> <<

> >= < <=

== !=

&

^

|

&&

| |

?:

= op=

Низший

В первой строке приведены три необычных оператора, о которых мы пока не говорили. Круглые скобки () используются для явной установки приоритета. Как вы узнали из предыдущей главы, квадратные скобки [] используются для индексирования переменной-массива. Оператор «.» (точка) используется для выделения элементов из ссылки на объект.

Явные приоритеты

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

а >> b + 3

Какому из двух выражений, а >> (b + 3) или (а >> b) + 3, соответствует первая строка? Поскольку у оператора сложения более высокий приоритет, чем у оператора сдвига, правильный ответ — а>> (b + а).

Так что если вам требуется выполнить операцию (а>>b)+ 3 без скобок не обойтись.

Глава 15.
Управление выполнением программы

Управление в Java почти идентично средствам, используемым в С и C++.

Условные операторы

if-else

В обобщенной форме этот оператор записывается следующим образом:

if (логическое выражение) оператор1; [ else оператор2;]

Раздел else необязателен. На месте любого из операторов может стоять составной оператор, заключенный в фигурные скобки. Логическое выражение — это любое выражение, возвращающее значение типа boolean.

int bytesAvailable;

// ...

if (bytesAvailable > 0) {

ProcessData();

bytesAvailable -= n;

} else

waitForMoreData();

А вот полная программа, в которой для определения, к какому времени года относится тот или иной месяц, используются операторы if-else.

class IfElse {

public static void main(String args[]) { int month = 4;

String season;

if (month == 12 || month == 1 || month == 2) {

season = "Winter";

} else if (month ==3 || month == 4 || month == 5) {

season = "Spring";

} else if (month == 6 || month == 7 || month == 8) {

season = "Summer";

} else if (month == 9 || month == 10 || month == 11) {

season = "Autumn";

} else {

season = "Bogus Month";

}

System.out.println( "April is in the " + season + ".");

} }

После выполнения программы вы должны получить следующий результат:

С: \> java IfElse

April is in the Spring.

break

В языке Java отсутствует оператор goto. Для того, чтобы в некоторых случаях заменять goto, в Java предусмотрен оператор break. Этот оператор сообщает исполняющей среде, что следует прекратить выполнение именованного блока и передать управление оператору, следующему за данным блоком. Для именования блоков в языке Java используются метки. Оператор break при работе с циклами и в операторах switch может использоваться без метки. В таком случае подразумевается выход из текущего блока.

Например, в следующей программе имеется три вложенных блока, и у каждого своя уникальная метка. Оператор break, стоящий во внутреннем блоке, вызывает переход на оператор, следующий за блоком b. При этом пропускаются два оператора println.

class Break {

public static void main(String args[]) { boolean t = true;

a: { b: { c: {

System.out.println("Before the break"); // Перед break

if (t)

break b;

System.out.println("This won't execute"); // He будет выполнено }

System.out.println("This won't execute"); // He будет выполнено }

System.out.println("This is after b"); //После b

} } }

В результате исполнения программы вы получите следующий результат:

С:\> Java Break

Before the break

This is after b

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

switch

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

switch ( выражение ) { case значение1:

break;

case значение2:

break;

case значением:

break;

default:

}

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

Если же значению выражения не соответствует ни один из операторов case, управление передается коду, расположенному после ключевого слова default. Отметим, что оператор default необязателен. В случае, когда ни один из операторов case не соответствует значению выражения и в switch отсутствует оператор default выполнение программы продолжается с оператора, следующего за оператором switch.

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

class SwitchSeason { public static void main(String args[]) {

int month = 4;

String season;

switch (month) {

case 12: // FALLSTHROUGH

case 1: // FALLSTHROUGH

case 2:

season = "Winter";

break;

case 3: // FALLSTHROUGH

case 4: // FALLSTHROUGH

case 5:

season = "Spring";

break;

case 6: // FALLSTHROUGH

case 7: // FALLSTHROUGH

case 8:

season = "Summer";

break;

case 9: // FALLSTHROUGH

case 10: // FALLSTHROUGH

case 11:

season = "Autumn";

break;

default:

season = "Bogus Month";

}

System.out.println("April is in the " + season + ".");

} }

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

class WordCount {

static String text = "Now is the tifne\n" +

"for all good men\n" +

"to come to the aid\n" +

"of their country\n"+

"and pay their due taxes\n";

static int len = text.length();

public static void main(String args[]) {

boolean inWord = false;

int numChars = 0;

int numWords = 0;

int numLines = 0;

for (int i=0; i < len; i++) {

char с = text.charAt(i);

numChars++;

switch (с) {

case '\n': numLines++; // FALLSTHROUGH

case '\t': // FALLSTHROUGH

case ' ' : if (inWord) {

numWords++;

inWord = false;

}

break;

default: inWord = true;

}

}

System.out.println("\t" + numLines +"\t" + numWords + "\t" + numChars);

} }

В этой программе для подсчета слов использовано несколько концепций, относящихся к обработке строк.

return

В Java для реализации процедурного интерфейса к объектам классов используется разновидность подпрограмм, называемых методами. Подпрограмма main, которую мы использовали до сих пор — это статический метод соответствующего класса-примера. В любом месте программного кода метода можно поставить оператор return, который приведет к немедленному завершению работы и передаче управления коду, вызвавшему этот метод. Ниже приведен пример, иллюстрирующий использование оператора return для немедленного возврата управления, в данном случае — исполняющей среде Java.

class ReturnDemo {

public static void main(String args[]) {

boolean t = true;

System.out.println("Before the return"); //Перед оператором return

if (t) return;

System.out.println("This won't execute"); //Это не будет выполнено

} }

Зачем в этом примере использован оператор if (t)? Дело в том, не будь этого оператора, транслятор Java догадался бы, что последний оператор println никогда не будет выполнен. Такие случаи в Java считаются ошибками, поэтому без оператора if оттранслировать этот пример нам бы не удалось.

Глава 16.
Циклы

Любой цикл можно разделить на 4 части — инициализацию, тело, итерацию и условие завершения. В Java есть три циклические конструкции: while (с предусловием), do-while (с постусловием) и for (с параметром).

while

Этот цикл многократно выполняется до тех пор, пока значение логического выражения равно true. Ниже приведена общая форма оператора while:

[ инициализация; ]

while ( завершение ) {

тело;

[итерация;] }

Инициализация и итерация необязательны. Ниже приведен пример цикла while для печати десяти строк «tick».

class WhileDemo {

public static void main(String args[]) {

int n = 10;

while (n > 0) {

System.out.println("tick " + n);

n--;

}

} }

do-while

Иногда возникает потребность выполнить тело цикла по крайней мере один раз — даже в том случае, когда логическое выражение с самого начала принимает значение false. Для таких случаев в Java используется циклическая конструкция do-while. Ее общая форма записи такова:

[ инициализация; ] do { тело; [итерация;] } while ( завершение );

В следующем примере тело цикла выполняется до первой проверки условия завершения. Это позволяет совместить код итерации с условием завершения:

class DoWhile {

public static void main(String args[]) {

int n = 10;

do {

System.out.println("tick " + n);

} while (--n > 0);

} }

for

В этом операторе предусмотрены места для всех четырех частей цикла. Ниже приведена общая форма оператора записи for.

for ( инициализация; завершение; итерация ) тело;

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

class ForDemo {

public static void main(String args[]) {

for (int i = 1; i <= 10; i++)

System.out.println("i = " + i);

} }

Следующий пример — вариант программы, ведущей обратный отсчет.

class ForTick {

public static void main(String args[]) {

for (int n = 10; n > 0; n--)

System.out.println("tick " + n);

} }

Обратите внимание — переменные можно объявлять внутри раздела инициализации оператора for. Переменная, объявленная внутри оператора for, действует в пределах этого оператора.

А вот — новая версия примера с временами года, в которой используется оператор for.

class Months {

static String months[] = {

"January", "February", “March", "April", "May", "June", "July", "August", "September", "October", "November", "December" };

static int monthdays[] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };

static String spring = "spring";

static String summer = "summer";

static String autumn = "autumn";

static String winter = "winter";

static String seasons[] = { winter, winter, spring, spring, spring, summer, summer, summer, autumn, autumn, autumn, winter };

public static void main(String args[]) {

for (int month = 0; month < 12; month++) {

System.out.println(months[month] + " is a " +

seasons[month] + " month with " + monthdays[month] + " days.");

} } }

При выполнении эта программа выводит следующие строки:

С:\> Java Months

January is a winter month with 31 days.

February is a winter month with 28 days.

March is a spring month with 31 days.

April is a spring month with 30 days.

May is a spring month with 31 days.

June is a summer month with 30 days.

July is a summer month with 31 days.

August is a summer month with 31 days.

September is a autumn month with 30 days.

October is a autumn month with 31 days.

November is a autumn month with 30 days.

December a winter month with 31 days.

Оператор запятая

Иногда возникают ситуации, когда разделы инициализации или итерации цикла for требуют нескольких операторов. Поскольку составной оператор в фигурных скобках в заголовок цикла for вставлять нельзя, Java предоставляет альтернативный путь. Применение запятой (,) для разделения нескольких операторов допускается только внутри круглых скобок оператора for. Ниже приведен тривиальный пример цикла for, в котором в разделах инициализации и итерации стоит несколько операторов.

class Comma {

public static void main(String args[]) {

int a, b;

for (a = 1, b = 4; a < b; a++, b--) {

System.out.println("a = " + a);

System.out.println("b = " + b);

}

} }

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

С: \> java Comma

а = 1

b = 4

а = 2

b = 3

continue

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

class ContinueDemo {

public static void main(String args[]) {

for (int i=0; i < 10; i++) {

System.out.print(i + " ");

if (i % 2 == 0) continue;

System.out.println("");

}

}}

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

С: \> java ContinueDemo

0 1

2 3

4 5

5 7

8 9

Как и в случае оператора break, в операторе continue можно задавать метку, указывающую, в каком из вложенных циклов вы хотите досрочно прекратить выполнение текущей итерации.

Для иллюстрации служит программа, использующая оператор continue с меткой для вывода треугольной таблицы умножения для чисел от 0 до 9:

class ContinueLabel {

public static void main(String args[]) {

outer: for (int i=0; i < 10; i++) {

for (int j = 0; j < 10; j++) {

if (j > i) {

System.out.println("");

continue outer;

}

System.out.print(" " + (i * j));

}

}

}}

Оператор continue в этой программе приводит к завершению внутреннего цикла со счетчиком j и переходу к очередной итерации внешнего цикла со счетчиком i. В процессе работы эта программа выводит следующие строки:

С:\> Java ContinueLabel

0

0 1

0 2 4

0 3 6 9

0 4 8 12 16

0 5 10 15 20 25

0 6 12 18 24 30 36

0 7 14 21 28 35 42 49

0 8 16 24 32 40 48 56 64

0 9 18 27 36 45 54 63 72 81

Глава 17.
Классы

Базовым элементом объектно-ориентированного программирования в языке Java является класс. Классы в Java не обязательно должны содержать метод main. Единственное назначение этого метода — указать интерпретатору Java, откуда надо начинать выполнение программы. Для того, чтобы создать класс, достаточно иметь исходный файл, в котором будет присутствовать ключевое слово class, и вслед за ним — допустимый идентификатор и пара фигурных скобок для его тела.

class Point {

}

Имя исходного файла Java должно соответствовать имени хранящегося в нем класса. Регистр букв важен и в имени класса, и в имени файла.

Класс определяет структуру объекта и его методы, образующие функциональный интерфейс. В процессе выполнения Java-программы система использует определения классов для создания представителей классов. Представители являются реальными объектами. Термины «представитель», «экземпляр» и «объект» взаимозаменяемы. Ниже приведена общая форма определения класса.

class имя_класса extends имя_суперкласса { type переменная1_объекта:

type переменная2_объекта:

type переменнаяN_объекта:

type имяметода1(список_параметров) { тело метода;

}

type имяметода2(список_параметров) { тело метода;

}

type имя методаМ(список_параметров) { тело метода;

}

}

Ключевое слово extends указывает на то, что «имя_класса» — это подкласс класса «имя_суперкласса». Во главе классовой иерархии Java стоит единственный ее встроенный класс — Object. Если вы хотите создать подкласс непосредственно этого класса, ключевое слово extends и следующее за ним имя суперкласса можно опустить — транслятор включит их в ваше определение автоматически. Примером может служить класс Point.

Переменные представителей (instance variables)

Данные инкапсулируются в класс путем объявления переменных между открывающей и закрывающей фигурными скобками, выделяющими в определении класса его тело. Эти переменные объявляются точно так же, как объявлялись локальные переменные в предыдущих примерах. Единственное отличие состоит в том, что их надо объявлять вне методов, в том числе вне метода main. Ниже приведен фрагмент кода, в котором объявлен класс Point с двумя переменными типа int.

class Point { int х, у;

}

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

Оператор new

Оператор new создает экземпляр указанного класса и возвращает ссылку на вновь созданный объект. Ниже приведен пример создания и присваивание переменной р экземпляра класса Point.

Point р = new Point();

Вы можете создать несколько ссылок на один и тот же объект. Приведенная ниже программа создает два различных объекта класса Point и в каждый из них заносит свои собственные значения. Оператор точка используется для доступа к переменным и методам объекта.

class TwoPoints {

public static void main(String args[]) {

Point p1 = new Point();

Point p2 = new Point();

p1.x = 10;

p1.y = 20;

р2.х = 42;

р2.у = 99;

System.out.println("x = " + p1.x + " у = " + p1.y);

System.out.println("x = " + р2.х + " у = " + р2.у);

} }

В этом примере снова использовался класс Point, было создано два объекта этого класса, и их переменным х и у присвоены различные значения.

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

С:\> Java TwoPoints

х = 10 у = 20

х = 42 у = 99

Поскольку при запуске интерпретатора мы указали в командной строке не класс Point, а класс TwoPoints, метод main класса Point был полностью проигнорирован. Добавим в класс Point метод main и, тем самым, получим законченную программу.

class Point { int х, у;

public static void main(String args[]) {

Point p = new Point();

р.х = 10;

p.у = 20;

System.out.println("x = " + р.х + " у = " + p.y);

} }

Объявление методов

Методы — это подпрограммы, присоединенные к конкретным определениям классов. Они описываются внутри определения класса на том же уровне, что и переменные объектов. При объявлении метода задаются тип возвращаемого им результата и список параметров. Общая форма объявления метода такова:

тип имя_метода (список формальных параметров) {

тело метода:

}

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

class Point { int х, у;

void init(int a, int b) {

х = а;

У = b;

} }

Вызов метода

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

Скрытие переменных представителей

В языке Java не допускается использование в одной или во вложенных областях видимости двух локальных переменных с одинаковыми именами. Интересно отметить, что при этом не запрещается объявлять формальные параметры методов, чьи имена совпадают с именами переменных представителей. Давайте рассмотрим в качестве примера иную версию метода init, в которой формальным параметрам даны имена х и у, а для доступа к одноименным переменным текущего объекта используется ссылка this.

class Point { int х, у;

void init(int х, int у) {

this.x = х;

this.у = у } }

class TwoPointsInit {

public static void main(String args[]) {

Point p1 = new Point();

Point p2 = new Point();

p1.init(10,20);

p2.init(42,99);

System.out.println("x = " + p1.x + " у = •• + p-l.y);

System.out.printlnC'x = " + p2.x + " у = •• + p2.y);

} }

Конструкторы

Инициализировать все переменные класса всякий раз, когда создается его очередной представитель — довольно утомительное дело даже в том случае, когда в классе имеются функции, подобные методу init. Для этого в Java предусмотрены специальные методы, называемые конструкторами. Конструктор — это метод класса, который инициализирует новый объект после его создания. Имя конструктора всегда совпадает с именем класса, в котором он расположен (также, как и в C++). У конструкторов нет типа возвращаемого результата — никакого, даже void. Заменим метод init из предыдущего примера конструктором.

class Point { int х, у;

Point(int х, int у) {

this.x = х;

this.у = у;

} }

class PointCreate {

public static void main(String args[]) {

Point p = new Point(10,20);

System.out.println("x = " + p.x + " у = " + p.у);

} }

Программисты на Pascal (Delphi) для обозначения конструктора используют ключевое слово constructor.

Совмещение методов

Язык Java позволяет создавать несколько методов с одинаковыми именами, но с разными списками параметров. Такая техника называется совмещением методов (method overloading). В качестве примера приведена версия класса Point, в которой совмещение методов использовано для определения альтернативного конструктора, который инициализирует координаты х и у значениями по умолчанию (-1).

class Point { int х, у;

Point(int х, int у) {

this.x = х;

this.у = у;

}

Point() {

х = -1;

у = -1;

} }

class PointCreateAlt {

public static void main(String args[]) {

Point p = new Point();

System.out.println("x = " + p.x + " у = " + p.y);

} }

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

С:\> java PointCreateAlt

х = -1 у = -1

Решение о том, какой конструктор нужно вызвать в том или ином случае, принимается в соответствии с количеством и типом параметров, указанных в операторе new. Недопустимо объявлять в классе методы с одинаковыми именами и сигнатурами. В сигнатуре метода не учитываются имена формальных параметров учитываются лишь их типы и количество.

this в конструкторах

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

class Point { int х, у;

Point(int х, int у) {

this.x = х;

this.у = у;

}

Point() {

this(-1, -1);

} }

В этом примере второй конструктор для завершения инициализации объекта обращается к первому конструктору.

Методы, использующие совмещение имен, не обязательно должны быть конструкторами. В следующем примере в класс Point добавлены два метода distance. Функция distance возвращает расстояние между двумя точками. Одному из совмещенных методов в качестве параметров передаются координаты точки х и у, другому же эта информация передается в виде параметра-объекта Point.

class Point { int х, у;

Point(int х, int у) {

this.x = х;

this. y = y;

}

double distance(int х, int у) {

int dx = this.x - х;

int dy = this.у - у;

return Math.sqrt(dx*dx + dy*dy);

}

double distance(Point p) {

return distance(p.x, p.y);

} }

class PointDist {

public static void main(String args[]) {

Point p1 = new Point(0, 0);

Point p2 = new Point(30, 40);

System.out.println("p1 = " + pi.x + ", " + p1.y);

System.out.println("p2 = " + p2.x + ", " + p2.y);

System.out.println("p1.distance(p2) = " + p1.distance(p2));

System.out.println("p1.distance(60, 80) = " + p1.distance(60, 80));

} }

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

С:\> java PointDist

р1 = 0, 0

р2 = 30, 40

р1.distance(p2) = 50.0

p1.distance(60, 80) = 100.0

Наследование

Вторым фундаментальным свойством объектно-ориентированного подхода является наследование (первый – инкапсуляция). Классы-потомки имеют возможность не только создавать свои собственные переменные и методы, но и наследовать переменные и методы классов-предков. Классы-потомки принято называть подклассами. Непосредственного предка данного класса называют его суперклассом. В очередном примере показано, как расширить класс Point таким образом, чтобы включить в него третью координату z.

class Point3D extends Point { int z;

Point3D(int x, int y, int z) {

this.x = x;

this.у = у;

this.z = z; }

Point3D() {

this(-1,-1,-1);

} }

В этом примере ключевое слово extends используется для того, чтобы сообщить транслятору о намерении создать подкласс класса Point. Как видите, в этом классе не понадобилось объявлять переменные х и у, поскольку Point3D унаследовал их от своего суперкласса Point.

super

В примере с классом Point3D частично повторялся код, уже имевшийся в суперклассе. Вспомните, как во втором конструкторе мы использовали this для вызова первого конструктора того же класса. Аналогичным образом ключевое слово super позволяет обратиться непосредственно к конструктору суперкласса (в Delphi / С++ для этого используется ключевое слово inherited).

class Point3D extends Point { int z;

Point3D(int x, int у, int z) {

super(x, y); // Здесь мы вызываем конструктор суперкласса this.z=z;

public static void main(String args[]) {

Point3D p = new Point3D(10, 20, 30);

System.out.println( " x = " + p.x + " y = " + p.y +

" z = " + p.z);

} }

Вот результат работы этой программы:

С:\> java Point3D

x = 10 у = 20 z = 30

Замещение методов

Новый подкласс Point3D класса Point наследует реализацию метода distance своего суперкласса (пример PointDist.java). Проблема заключается в том, что в классе Point уже определена версия метода distance(mt х, int у), которая возвращает обычное расстояние между точками на плоскости. Мы должны заместить (override) это определение метода новым, пригодным для случая трехмерного пространства. В следующем примере проиллюстрировано и совмещение (overloading), и замещение (overriding) метода distance.

class Point { int х, у;

Point(int х, int у) {

this.x = х;

this.у = у;

}

double distance(int х, int у) {

int dx = this.x - х;

int dy = this.у - у:

return Math,sqrt(dx*dx + dy*dy);

}

double distance(Point p) {

return distance(p.х, p.y);

}

}

class Point3D extends Point { int z;

Point3D(int х, int y, int z) {

super(x, y);

this.z = z;

(

double distance(int х, int y, int z) {

int dx = this.x - х;

int dy = this.y - y;

int dz = this.z - z;

return Math.sqrt(dx*dx + dy*dy + dz*dz);

}

double distance(Point3D other) {

return distance(other.х, other.y, other.z);

}

double distance(int х, int y) {

double dx = (this.x / z) - х;

double dy = (this.у / z) - y;

return Math.sqrt(dx*dx + dy*dy);

}

}

class Point3DDist {

public static void main(String args[]) {

Point3D p1 = new Point3D(30, 40, 10);

Point3D p2 = new Point3D(0, 0, 0);

Point p = new Point(4, 6);

System.out.println("p1 = " + p1.x + ", " + p1.y + ", " + p1.z);

System.out.println("p2 = " + p2.x + ", " + p2.y + ", " + p2.z);

System.out.println("p = " + p.x + ", " + p.y);

System.out.println("p1.distance(p2) = " + p1.distance(p2));

System.out.println("p1.distance(4, 6) = " + p1.distance(4, 6));

System.out.println("p1.distance(p) = " + p1.distance(p));

} }

Ниже приводится результат работы этой программы:

С:\> Java Point3DDist

p1 = 30, 40, 10

р2 = 0, 0, 0

р = 4, 6

p1.distance(p2) = 50.9902

p1.distance(4, 6) = 2.23607

p1.distance(p) = 2.23607

Обратите внимание — мы получили ожидаемое расстояние между трехмерными точками и между парой двумерных точек. В примере используется механизм, который называется динамическим назначением методов (dynamic method dispatch).

Динамическое назначение методов

Давайте в качестве примера рассмотрим два класса, у которых имеют простое родство подкласс/суперкласс, причем единственный метод суперкласса замещен в подклассе.

class A { void callme() {

System.out.println("Inside A's callrne method");

class В extends A { void callme() {

System.out.println("Inside B's callme method");

} }

class Dispatch {

public static void main(String args[]) {

A a = new B();

a.callme();

} }

Обратите внимание — внутри метода main мы объявили переменную а класса А, а проинициализировали ее ссылкой на объект класса В. В следующей строке мы вызвали метод callme. При этом транслятор проверил наличие метода callme у класса А, а исполняющая система, увидев, что на самом деле в переменной хранится представитель класса В, вызвала не метод класса А, а callme класса В.

Ниже приведен результат работы этой программы:

С:\> Java Dispatch

Inside B's calime method

final

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

final int FILE_NEW = 1;

По общепринятому соглашению при выборе имен переменных типа final — используются только символы верхнего регистра (т.е. используются как аналог препроцерных констант C++). Использование final-методов порой приводит к выигрышу в скорости выполнения кода — поскольку они не могут быть замещены, транслятору ничто не мешает заменять их вызовы встроенным (in-line) кодом (байт-код копируется непосредственно в код вызывающего метода).

finalize

В Java существует возможность объявлять методы с именем finalize. Методы finalize аналогичны деструкторам в C++ (ключевой знак ~) и Delphi (ключевое слово destructor). Исполняющая среда Java будет вызывать его каждый раз, когда сборщик мусора соберется уничтожить объект этого класса.

static

Иногда требуется создать метод, который можно было бы использовать вне контекста какого-либо объекта его класса. Так же, как в случае main, все, что требуется для создания такого метода — указать при его объявлении модификатор типа static. Статические методы могут непосредственно обращаться только к другим статическим методам, в них ни в каком виде не допускается использование ссылок this и super. Переменные также могут иметь тип static, они подобны глобальным переменным, то есть доступны из любого места кода. Внутри статических методов недопустимы ссылки на переменные представителей. Ниже приведен пример класса, у которого есть статические переменные, статический метод и статический блок инициализации.

class Static {

static int a = 3;

static int b;

static void method(int x) {

System.out.println("x = " + x);

System.out.println("a = " + a);

System.out.println("b = " + b);

}

static {

System.out.println("static block initialized");

b = a * 4;

}

public static void main(String args[]) {

method(42);

} }

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

С:\> java Static static block initialized

Х = 42

А = 3

B = 12

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

class StaticClass {

static int a = 42;

static int b = 99;

static void callme() {

System.out.println("a = " + a);

} }

class StaticByName {

public static void main(String args[]) {

staticClass.callme();

System.out.println("b = " + StaticClass.b);

} }

А вот и результат запуска этой программы:

С:\> Java StaticByName

а = 42 b = 99

abstract

Бывают ситуации, когда нужно определить класс, в котором задана структура какой-либо абстракции, но полная реализация всех методов отсутствует. В таких случаях вы можете с помощью модификатора типа abstract объявить, что некоторые из методов обязательно должны быть замещены в подклассах. Любой класс, содержащий методы abstract, также должен быть объявлен, как abstract. Поскольку у таких классов отсутствует полная реализация, их представителей нельзя создавать с помощью оператора new. Кроме того, нельзя объявлять абстрактными конструкторы и статические методы. Любой подкласс абстрактного класса либо обязан предоставить реализацию всех абстрактных методов своего суперкласса, либо сам должен быть объявлен абстрактным.

abstract class A {

abstract void callme();

void metoo() {

System.out.println("Inside A's metoo method");

} }

class B extends A {

void callme() {

System.out.println("Inside B's callme method");

} }

class Abstract {

public static void main(String args[]) {

A a = new B():

a.callme():

a.metoo():

} }

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

С:\> Java Abstract

Inside B's callrne method Inside A's metoo method

Глава 18.
Пакеты и интерфейсы

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

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

Пакеты

Все идентификаторы, которые мы до сих пор использовали в наших примерах, располагались в одном и том же пространстве имен (name space). Это означает, что нам во избежание конфликтных ситуаций приходилось заботиться о том, чтобы у каждого класса было свое уникальное имя. Пакеты — это механизм, который служит как для работы с пространством имен, так и для ограничения видимости. У каждого файла .java есть 4 одинаковых внутренних части, из которых мы до сих пор в наших примерах использовали только одну. Ниже приведена общая форма исходного файла Java.

u одиночный оператор package (необязателен)

u любое количество операторов import (необязательны)

u одиночное объявление открытого (public) класса

u любое количество закрытых (private) классов пакета (необязательны)

Оператор package

Первое, что может появиться в исходном файле Java — это оператор package, который сообщает транслятору, в каком пакете должны определяться содержащиеся в данном файле классы. Пакеты задают набор раздельных пространств имен, в которых хранятся имена классов. Если оператор package не указан, классы попадают в безымянное пространство имен, используемое по умолчанию. Если вы объявляете класс, как принадлежащий определенному пакету, например,

package java.awt.image;

то и исходный код этого класса должен храниться в каталоге java/awt/ image.

Трансляция классов в пакетах

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

Представьте себе, что вы написали класс с именем PackTest в пакете test. Вы создаете каталог test, помещаете в этот каталог файл PackTest.Java и транслируете. Пока — все в порядке. Однако при попытке запустить его вы получаете от интерпретатора сообщение «can't find class PackTest» («He могу найти класс PackTest»). Ваш новый класс теперь хранится в пакете с именем test, так что теперь надо указывать всю иерархию пакетов, разделяя их имена точками — test.PackTest. Кроме того, вам надо либо подняться на уровень выше в иерархии каталогов и снова набрать «java test.PackTest», либо внести в переменную CLASSPATH каталог, который является вершиной иерархии разрабатываемых вами классов.

Оператор import

После оператора package, но до любого определения классов в исходном Java-файле, может присутствовать список операторов import. Пакеты являются хорошим механизмом для отделения классов друг от друга, поэтому все встроенные в Java классы хранятся в пакетах. Общая форма оператора import такова:

import пакет1 [.пакет2].(имякласса|*);

Здесь пакет1 — имя пакета верхнего уровня, пакет2 — это необязательное имя пакета, вложенного в первый пакет и отделенное точкой. И, наконец, после указания пути в иерархии пакетов, указывается либо имя класса, либо метасимвол звездочка. Звездочка означает, что, если Java-транслятору потребуется какой-либо класс, для которого пакет не указан явно, он должен просмотреть все содержимое пакета со звездочкой вместо имени класса. В приведенном ниже фрагменте кода показаны обе формы использования оператора import:

import java.util.Date

import java.io.*;

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

import java.lang.*;

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

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

class MyDate extends Java.util.Date { }

Ограничение доступа

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

u Подклассы в том же пакете.

u Не подклассы в том же пакете.

u Подклассы в различных пакетах.

u Классы, которые не являются подклассами и не входят в тот же пакет.

В языке Java имеется три уровня доступа, определяемых ключевыми словами: private (закрытый), public (открытый) и protected (защищенный), которые употребляются в различных комбинациях.

Элемент, объявленный public, доступен из любого места. Все, что объявлено private, доступно только внутри класса, и нигде больше. Если у элемента вообще не указан модификатор уровня доступа, то такой элемент будет виден из подклассов и классов того же пакета. Именно такой уровень доступа используется в языке Java по умолчанию. Если же вы хотите, чтобы элемент был доступен извне пакета, но только подклассам того класса, которому он принадлежит, вам нужно объявить такой элемент protected. И наконец, если вы хотите, чтобы элемент был доступен только подклассам, причем независимо от того, находятся ли они в данном пакете или нет — используйте комбинацию private protected.

Ниже приведен довольно длинный пример, в котором представлены все допустимые комбинации модификаторов уровня доступа. В исходном коде первого пакета определяется три класса: Protection, Derived и SamePackage. В первом из этих классов определено пять целых переменных — по одной на каждую из возможных комбинаций уровня доступа. Переменной n приписан уровень доступа по умолчанию, n_pri — уровень private, n_pro — protected, n_pripro — private protected и n_pub — public. Во всех остальных классах мы пытаемся использовать переменные первого класса. Те строки кода, которые из-за ограничения доступа привели бы к ошибкам при трансляции, закомментированы с помощью однострочных комментариев (//) — перед каждой указано, откуда доступ при такой комбинации модификаторов был бы возможен. Второй класс — Derived — является подклассом класса Protection и расположен в том же пакете р1. Поэтому ему доступны все перечисленные переменные за исключением n_pri. Третий класс, SamePackage, расположен в том же пакете, но при этом не является подклассом Protection. По этой причине для него недоступна не только переменная n_pri, но и n_pripro, уровень доступа которой — private protected.

package р1;

public class Protection {

int n = 1;

private int n_pri = 2;

protected int n_pro = 3;

private protected int n_pripro = 4;

public int n_pub = 5;

public Protection() {

System.out.println("base constructor");

System.out.println("n = " + n);

System.out.println("n_pri = " + n_pri);

System.out.println("n_pro = " + n_pro);

System.out.println("n_pripro = " + n_pripro);

System.out.println("n_pub = " + n_pub);

} }

class Derived extends Protection {

Derived() {

System.out.println("derived constructor");

System.out.println("n = " + n);

// только в классе

// System.out.println("n_pri = " + n_pri);

System.out.println("n_pro = " + n_pro);

System.out.println("n_pripro = " + n_pripro);

System.out.println("n_pub = " + n_pub);

} }

class SamePackage {

SamePackage() {

Protection p = new Protection();

System.out.println("same package constructor");

System.out.println("n = " + p.n);

// только в классе

// System.out.println("n_pri = " + p.n_pri);

System.out.println("n_pro = " + p.n_pro);

// только в классе и подклассе

// System.out.println("n_pripro = " + p.n_pripro):

System.out.println("n_pub = " + p.n_pub):

} }

Интерфейсы

Интерфейсы Java созданы для поддержки динамического выбора (resolution) методов во время выполнения программы. Интерфейсы похожи на классы, но в отличие от последних у интерфейсов нет переменных представителей, а в объявлениях методов отсутствует реализация.

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

Интерфейсы обладают своей собственной иерархией, не пересекающейся с классовой иерархией наследования. Это дает возможность реализовать один и тот же интерфейс в различных классах, никак не связанных по линии иерархии классового наследования. Именно в этом и проявляется главная сила интерфейсов. Интерфейсы являются аналогом механизма множественного наследования в C++, но использовать их намного легче.

Оператор interface

Определение интерфейса сходно с определением класса, отличие состоит в том, что в интерфейсе отсутствуют объявления данных и конструкторов.

Общая форма интерфейса приведена ниже:

interface имя {

тип_результата имя_метода1(список параметров);

тип имя_final1-переменной = значение;

}

Обратите внимание — у объявляемых в интерфейсе методов отсутствуют операторы тела. Объявление методов завершается символом ; (точка с запятой). В интерфейсе можно объявлять и переменные, при этом они неявно объявляются final-переменными. Это означает, что класс реализации не может изменять их значения. Кроме того, при объявлении переменных в интерфейсе их обязательно нужно инициализировать константными значениями. Ниже приведен пример определения интерфейса, содержащего единственный метод с именем callback и одним параметром типа int.

interface Callback {

void callback(int param);

}

Оператор implements

Оператор implements — это дополнение к определению класса, реализующего некоторый интерфейс(ы).

class имя_класса [extends суперкласс]

[implements интерфейс0 [, интерфейс1...]] { тело класса }

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

class Client implements Callback {

void callback(int p) {

System.out.println("callback called with " + p);

} }

В очередном примере метод callback интерфейса, определенного ранее, вызывается через переменную — ссылку на интерфейс:

class TestIface {

public static void main(String args[]) { Callback с = new client();

c.callback(42);

} }

Ниже приведен результат работы программы:

С:\> Java TestIface

callback called with 42

Глава 19.
Переменные в интерфейсах

Интерфейсы можно использовать для импорта в различные классы совместно используемых констант. В том случае, когда вы реализуете в классе какой-либо интерфейс, все имена переменных этого интерфейса будут видимы в классе как константы. Это аналогично использованию файлов-заголовков для задания в С и C++ констант с помощью директив #define или ключевого слова const в Pascal/Delphi.

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

import java.util.Random;

interface SharedConstants { int NO = 0;

int YES = 1;

int MAYBE = 2;

int LATER = 3;

int SOON = 4;

int NEVER = 5; }

class Question implements SharedConstants {

Random rand = new Random();

int ask() {

int prob = (int) (100 * rand.nextDouble());

if (prob < 30)

return NO; // 30% else if (prob < 60)

return YES; // 30% else if (prob < 75)

return LATER; // 15% else if (prob < 98)

return SOON; // 13% else

return NEVER; // 2% } }

class AskMe implements SharedConstants {

static void answer(int result) {

switch(result) {

case NO:

System.out.println("No");

break;

case YES:

System.out.println("Yes");

break;

case MAYBE:

System.out.println("Maybe");

break;

case LATER:

System.out.println("Later");

break;

case SOON:

System.out.priniln("Soon");

break;

case NEVER:

System.out.println("Never");

break;

} }

public static void main(String args[]) {

Question q = new Question();

answer(q.ask());

answer(q.ask());

answer(q.askO);

answer(q.ask());

} }

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

С:\> Java AskMe

Later

Scon

No

Yes

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

Глава 20.
Работа со строками

В языках Си и Cи++ отсутствует встроенная поддержка такого объекта, как строка. В них при необходимости передается адрес последовательности байтов, содержимое которых трактуется как символы до тех пор, пока не будет встречен нулевой байт, отмечающий конец строки. В пакет java.lang встроен класс, инкапсулирующий структуру данных, соответствующую строке. Этот класс, называемый String, не что иное, как объектное представление неизменяемого символьного массива. В этом классе есть методы, которые позволяют сравнивать строки, осуществлять в них поиск и извлекать определенные символы и подстроки. Класс StringBuffer используется тогда, когда строку после создания требуется изменять.

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

Конструкторы

Как и в случае любого другого класса, вы можете создавать объекты типа String с помощью оператора new. Для создания пустой строки используется конструктор без параметров:

String s = new String():

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

char chars[] = { 'а', 'b', 'с' }:

String s = new String(chars);

System.out.println(s):

Этот фрагмент кода выводит строку «abc». Итак, у этого конструктора — 3 параметра:

String(char chars[], int начальныйИндекс, int числоСимволов);

Используем такой способ инициализации в нашем очередном примере:

char chars[] = { 'a', 'b', 'с', 'd', 'e', 'f' }:

String s = new String(chars,2,3);

System.out.println(s);

Этот фрагмент выведет «cde».

Глава 21.
Специальный синтаксис для работы со строками

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

Создание строк

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

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

String s = "abc";

System.out.println(s);

Один из общих методов, используемых с объектами String — метод length, возвращающий число символов в строке. Очередной фрагмент выводит число 3, поскольку в используемой в нем строке — 3 символа.

String s = "abc";

System.out.println(s.length);

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

Очередной пример также выводит число 3.

System.out.println("abc".Length());

Слияние строк

Строку

String s = «Не is » + age + " years old.";

в которой с помощью оператора + три строки объединяются в одну, прочесть и понять безусловно легче, чем ее эквивалент, записанный с явными вызовами тех самых методов, которые неявно были использованы в первом примере:

String s = new StringBuffer("He is ").append(age);

s.append(" years old.").toString();

По определению каждый объект класса String не может изменяться. Нельзя ни вставить новые символы в уже существующую строку, ни поменять в ней одни символы на другие. И добавить одну строку в конец другой тоже нельзя. Поэтому транслятор Java преобразует операции, выглядящие, как модификация объектов String, в операции с родственным классом StringBuffer.

Все это может показаться вам необоснованно сложным. А почему нельзя обойтись одним классом String, позволив ему вести себя примерно так же, как StringBuffer? Все дело в производительности. Тот факт, что объекты типа String в Java неизменны, позволяет транслятору применять к операциям с ними различные способы оптимизации.

Последовательность выполнения операторов

Давайте еще раз обратимся к нашему последнему примеру:

String s = "Не is " + age + " years old.";

В том случае, когда age — не String, а переменная, скажем, типа int, в этой строке кода заключено еще больше магии транслятора. Целое значение переменной int передается совмещенному методу append класса StringBuffer, который преобразует его в текстовый вид и добавляет в конец содержащейся в объекте строки. Вам нужно быть внимательным при совместном использовании целых выражений и слияния строк, в противном случае результат может получиться совсем не тот, который вы ждали. Взгляните на следующую строку:

String s = "four: " + 2 + 2;

Быть может, вы надеетесь, что в s будет записана строка «four: 4»? Не угадали — с вами сыграла злую шутку последовательность выполнения операторов. Так что в результате получается "four: 22".

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

String s = "four: " + (2 + 2);

Преобразование строк

В каждом классе String есть метод toString — либо своя собственная реализация, либо вариант по умолчанию, наследуемый от класса Object. Класс в нашем очередном примере замещает наследуемый метод toStrring своим собственным, что позволяет ему выводить значения переменных объекта.

class Point {

int х, у;

Point(int x, int у) {

this.x = х;

this.у = у;

}

public String toString() {

return "Point[" + x + ", " + у + "]";

} }

class toStringDemo {

public static void main(String args[]) {

Point p = new Point(10, 20);

System.out.println("p = " + p);

} }

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

С:\> Java toStringDemo

p = Point[10, 20]

Извлечение символов

Для того, чтобы извлечь одиночный символ из строки, вы можете сослаться непосредственно на индекс символа в строке с помощью метода charAt. Если вы хотите в один прием извлечь несколько символов, можете воспользоваться методом getChars. В приведенном ниже фрагменте показано, как следует извлекать массив символов из объекта типа String.

class getCharsDemo {

public static void main(String args[]) {

String s = "This is a demo of the getChars method.";

int start = 10;

int end = 14;

char buf[] = new char[end - start];

s.getChars(start, end, buf, 0);

System.out.println(buf);

} }

Обратите внимание — метод getChars не включает в выходной буфер символ с индексом end. Это хорошо видно из вывода нашего примера — выводимая строка состоит из 4 символов.

С:\> java getCharsDemo

demo

Для удобства работы в String есть еще одна функция — toCharArray, которая возвращает в выходном массиве типа char всю строку. Альтернативная форма того же самого механизма позволяет записать содержимое строки в массив типа byte, при этом значения старших байтов в 16-битных символах отбрасываются. Соответствующий метод называется getBytes, и его параметры имеют тот же смысл, что и параметры getChars, но с единственной разницей — в качестве третьего параметра надо использовать массив типа byte.

Сравнение

Если вы хотите узнать, одинаковы ли две строки, вам следует воспользоваться методом equals класса String. Альтернативная форма этого метода называется equalsIgnoreCase, при ее использовании различие регистров букв в сравнении не учитывается. Ниже приведен пример, иллюстрирующий использование обоих методов:

class equalDemo {

public static void main(String args[]) {

String s1 = "Hello";

String s2 = "Hello";

String s3 = "Good-bye";

String s4 = "HELLO";

System.out.println(s1 + " equals " + s2 + " -> " + s1.equals(s2));

System.out.println(s1 + " equals " + s3 + " -> " + s1.equals(s3));

System.out.println(s1 + " equals " + s4 + " -> " + s1.equals(s4));

System.out.println(s1 + " equalsIgnoreCase " + s4 + " -> " + s1.equalsIgnoreCase(s4));

} }

Результат запуска этого примера:

С:\> java equalsDemo

Hello equals Hello -> true

Hello equals Good-bye -> false

Hello equals HELLO -> false

Hello equalsIgnoreCase HELLO -> true

В классе String реализована группа сервисных методов, являющихся специализированными версиями метода equals. Метод regionMatches используется для сравнения подстроки в исходной строке с подстрокой в строке-параметре. Метод startsWith проверяет, начинается ли данная подстрока фрагментом, переданным методу в качестве параметра. Метод endsWith проверяет совпадает ли с параметром конец строки.

Равенство

Метод equals и оператор == выполняют две совершенно различных проверки. Если метод equal сравнивает символы внутри строк, то оператор == сравнивает две переменные-ссылки на объекты и проверяет, указывают ли они на разные объекты или на один и тот же. В очередном нашем примере это хорошо видно — содержимое двух строк одинаково, но, тем не менее, это — различные объекты, так что equals и == дают разные результаты.

class EqualsNotEqualTo {

public static void main(String args[]) {

String s1 = "Hello";

String s2 = new String(s1);

System.out.println(s1 + " equals " + s2 + " -> " + s1.equals(s2));

System.out.println(s1 + " == " + s2 + ", -> " + (s1 == s2));

} }

Вот результат запуска этого примера:

C:\> java EqualsNotEqualTo

Hello equals Hello -> true

Hello == Hello -> false

Упорядочение

Зачастую бывает недостаточно просто знать, являются ли две строки идентичными. Для приложений, в которых требуется сортировка, нужно знать, какая из двух строк меньше другой. Для ответа на этот вопрос нужно воспользоваться методом compareTo класса String. Если целое значение, возвращенное методом, отрицательно, то строка, с которой был вызван метод, меньше строки-параметра, если положительно — больше. Если же метод compareTo вернул значение 0, строки идентичны. Ниже приведена программа, в которой выполняется пузырьковая сортировка массива строк, а для сравнения строк используется метод compareTo. Эта программа выдает отсортированный в алфавитном порядке список строк.

class SortString {

static String arr[] = {"Now", "is", "the", "time", "for", "all", "good", "men", "to", "come", "to", "the","aid", "of", "their", "country" };

public static void main(String args[]) {

for (int j = 0; i < arr.length; j++) {

for (int i = j + 1; i < arr.length; i++) {

if (arr[i].compareTo(arr[j]) < 0) {

String t = arr[j];

arr[j] = arr[i];

arr[i] = t;

}

}

System.out.println(arr[j]);

}

} }

indexOf и lastIndexOf

В класс String включена поддержка поиска определенного символа или подстроки, для этого в нем имеются два метода — indexOf и lastIndexOf. Каждый из этих методов возвращает индекс того символа, который вы хотели найти, либо индекс начала искомой подстроки. В любом случае, если поиск оказался неудачным методы возвращают значение -1. В очередном примере показано, как пользоваться различными вариантами этих методов поиска.

class indexOfDemo {

public static void main(String args[]) {

String s = "Now is the time for all good men " +

"to come to the aid of their country " +

"and pay their due taxes.";

System.out.println(s);

System.out.println("indexOf(t) = " + s.indexOf('f’));

System.out.println("lastlndexOf(t) = " + s.lastlndexOf('f’));

System.out.println("indexOf(the) = " + s.indexOf("the"));

System.out.println("lastlndexOf(the) = " + s.lastlndexOf("the"));

System.out.println("indexOf(t, 10) = " + s.indexOf('f’ , 10));

System.out.println("lastlndexOf(t, 50) = " + s.lastlndexOf('f’ , 50));

System.out.println("indexOf(the, 10) = " + s.indexOf("the", 10));

System.out.println("lastlndexOf(the, 50) = " + s.lastlndexOf("the", 50));

} }

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

С:> java indexOfDemo

Now is the time for all good men to come to the aid of their country

and pay their due taxes.

indexOf(t) = 7

lastlndexOf(t) = 87

indexOf(the) = 7

lastlndexOf(the) = 77

index0f(t, 10) = 11

lastlndex0f(t, 50) = 44

index0f(the, 10) = 44

lastlndex0f(the, 50) = 44

Модификация строк при копировании

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

substring

Вы можете извлечь подстроку из объекта String, используя метод substring. Этот метод создает новую копию символов из того диапазона индексов оригинальной строки, который вы указали при вызове. Можно указать только индекс первого символа нужной подстроки — тогда будут скопированы все символы, начиная с указанного и до конца строки. Также можно указать и начальный, и конечный индексы — при этом в новую строку будут скопированы все символы, начиная с первого указанного, и до (но не включая его) символа, заданного конечным индексом.

"Hello World".substring(6) -> "World"

"Hello World".substring(3,8) -> "lo Wo"

concat

Слияние, или конкатенация строк выполняется с помощью метода concat. Этот метод создает новый объект String, копируя в него содержимое исходной строки и добавляя в ее конец строку, указанную в параметре метода.

"Hello".concat(" World") -> "Hello World"

replace

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

"Hello".replace('l' , 'w') -> "Hewwo"

toLowerCase и toUpperCase

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

"Hello".toLowerCase() -> "hello"

"Hello".toUpperCase() -> "HELLO"

trim

И, наконец, метод trim убирает из исходной строки все ведущие и замыкающие пробелы.

“Hello World “.trirn() -> "Hello World"

valueOf

Если вы имеете дело с каким-либо типом данных и хотите вывести значение этого типа в удобочитаемом виде, сначала придется преобразовать это значение в текстовую строку. Для этого существует метод valueOf. Такой статический метод определен для любого существующего в Java типа данных (все эти методы совмещены, то есть используют одно и то же имя). Благодаря этому не составляет труда преобразовать в строку значение любого типа.

StringBuffer

StringBuffer — близнец класса String, предоставляющий многое из того, что обычно требуется при работе со строками. Объекты класса String представляют собой строки фиксированной длины, которые нельзя изменять. Объекты типа StringBuffer представляют собой последовательности символов, которые могут расширяться и модифицироваться. Java активно использует оба класса, но многие программисты предпочитают работать только с объектами типа String, используя оператор +. При этом Java выполняет всю необходимую работу со StringBuffer за сценой.

Объект StringBuffer можно создать без параметров, при этом в нем будет зарезервировано место для размещения 16 символов без возможности изменения длины строки. Вы также можете передать конструктору целое число, для того чтобы явно задать требуемый размер буфера. И, наконец, вы можете передать конструктору строку, при этом она будет скопирована в объект и дополнительно к этому в нем будет зарезервировано место еще для 16 символов. Текущую длину StringBuffer можно определить, вызвав метод length, а для определения всего места, зарезервированного под строку в объекте StringBuffer нужно воспользоваться методом capacity. Ниже приведен пример, поясняющий это:

class StringBufferDemo {

public static void main(String args[]) {

StringBuffer sb = new StringBuffer("Hello");

System.out.println("buffer = " + sb);

System.out.println("length = " + sb.length());

System.out. println("capacity = " + sb.capacity());

} }

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

С:\> java StringBufferDemo

buffer = Hello

length = 5

capacity = 21

ensureCapacity

Если вы после создания объекта StringBuffer захотите зарезервировать в нем место для определенного количества символов, вы можете для установки размера буфера воспользоваться методом ensureCapacity. Это бывает полезно, когда вы заранее знаете, что вам придется добавлять к буферу много небольших строк.

setLength

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

charAt и setCharAt

Одиночный символ может быть извлечен из объекта StringBuffer с помощью метода charAt. Другой метод setCharAt позволяет записать в заданную позицию строки нужный символ. Использование обоих этих методов проиллюстрировано в примере:

class setCharAtDemo {

public static void main(String args[]) {

StringBuffer sb = new StringBuffer("Hello");

System.out.println("buffer before = " + sb);

System.out.println("charAt(1) before = " + sb.charAt(1));

sb.setCharAt(1, 'i');

sb.setLength(2);

System.out.println("buffer after = " + sb);

System.out.println("charAt(1) after = " + sb.charAt(1));

} }

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

C:\> java setCharAtDemo

buffer before = Hello

charAt(1) before = e

buffer after = Hi

charAt(1) after = i

append

Метод append класса StringBuffer обычно вызывается неявно при использовании оператора + в выражениях со строками. Для каждого параметра вызывается метод String.valueOf и его результат добавляется к текущему объекту StringBuffer. К тому же при каждом вызове метод append возвращает ссылку на объект StringBuffer, с которым он был вызван.

Это позволяет выстраивать в цепочку последовательные вызовы метода, как это показано в очередном примере.

class appendDemo {

public static void main(String args[]) {

String s;

int a = 42;

StringBuffer sb = new StringBuffer(40);

s = sb.append("a = ").append(a).append("!").toString();

System.out.println(s);

} }

Вот вывод этого примера:

С:\> Java appendDemo

а = 42!

insert

Метод insert идентичен методу append в том смысле, что для каждого возможного типа данных существует своя совмещенная версия этого метода. Правда, в отличие от append, он не добавляет символы, возвращаемые методом String.valueOf, в конец объекта StringBuffer, а вставляет их в определенное место в буфере, задаваемое первым его параметром. В очередном нашем примере строка «there» вставляется между «hello» и «world!».

class insertDemo {

public static void main(String args[]) {

StringBuffer sb = new StringBuffer("hello world !");

sb.insert(6,"there ");

System.out.println(sb);

} }

При запуске эта программа выводит следующую строку:

С:\> java insertDemo

hello there world!

Почти любой аспект программирования в Java на каком либо этапе подразумевает использование классов String и StringBuffer. Они понадобятся и при отладке, и при работе с текстом, и при указании имен файлов и адресов URL в качестве параметров методам. Каждый второй байт большинства строк в Java — нулевой (Unicode пока используется редко). То, что строки в Java требуют вдвое больше памяти, чем обычные ASCII, не очень пугает, пока вам для эффективной работы с текстом в редакторах и других подобных приложениях не придется напрямую работать с огромным массивом типа char.

Глава 22.
Обработка исключений

Исключение в Java — это объект, который описывает исключительное состояние, возникшее в каком-либо участке программного кода. Когда возникает исключительное состояние, создается объект класса Exception. Этот объект пересылается в метод, обрабатывающий данный тип исключительной ситуации. Исключения могут возбуждаться и «вручную» для того, чтобы сообщить о некоторых нештатных ситуациях.

К механизму обработки исключений в Java имеют отношение 5 ключевых слов: try, catch, throw, throws и finally. Схема работы этого механизма следующая. Вы пытаетесь (try) выполнить блок кода, и если при этом возникает ошибка, система возбуждает (throw) исключение, которое в зависимости от его типа вы можете перехватить (catch) или передать умалчиваемому (finally) обработчику.

Ниже приведена общая форма блока обработки исключений.

try {

// блок кода }

catch (ТипИсключения1 е) {

// обработчик исключений типа ТипИсключения1 }

catch (ТипИсключения2 е) {

// обработчик исключений типа ТипИсключения2

throw(e) // повторное возбуждение исключения }

finally {

}

Типы исключений

В вершине иерархии исключений стоит класс Throwable. Каждый из типов исключений является подклассом класса Throwable. Два непосредственных наследника класса Throwable делят иерархию подклассов исключений на две различные ветви. Один из них — класс Ехception — используется для описания исключительных ситуации, которые должны перехватываться программным кодом пользователя. Другая ветвь дерева подклассов Throwable — класс Error, который предназначен для описания исключительных ситуаций, которые при обычных условиях не должны перехватываться в пользовательской программе.

Неперехваченные исключения

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

class Exc0 {

public static void main(string args[]) {

int d = 0;

int a = 42 / d;

} }

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

С:\> java Exc0

java.lang.ArithmeticException: / by zero

at Exc0.main(Exc0.java:4)

Обратите внимание на тот факт что типом возбужденного исключения был не Exception и не Throwable. Это подкласс класса Exception, а именно: ArithmeticException, поясняющий, какая ошибка возникла при выполнении программы. Вот другая версия того же класса, в которой возникает та же исключительная ситуация, но на этот раз не в программном коде метода main.

class Exc1 {

static void subroutine() {

int d = 0;

int a = 10 / d;

}

public static void main(String args[]) {

Exc1.subroutine();

} }

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

С:\> java Exc1

java.lang.ArithmeticException: / by zero

at Exc1.subroutine(Exc1.java:4)

at Exc1.main(Exc1.java:7)

try и catch

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

class Exc2 {

public static void main(String args[]) {

try {

int d = 0;

int a = 42 / d;

}

catch (ArithmeticException e) {

System.out.println("division by zero");

}

} }

Целью большинства хорошо сконструированных catch-разделов должна быть обработка возникшей исключительной ситуации и приведение переменных программы в некоторое разумное состояние — такое, чтобы программу можно было продолжить так, будто никакой ошибки и не было (в нашем примере выводится предупреждение – division by zero).

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

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

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

class MultiCatch {

public static void main(String args[]) {

try {

int a = args.length;

System.out.println("a = " + a);

int b = 42 / a;

int c[] = { 1 };

c[42] = 99;

}

catch (ArithmeticException e) {

System.out.println("div by 0: " + e);

}

catch(ArrayIndexOutOfBoundsException e) {

System.out.println("array index oob: " + e);

}

} }

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

С:\> java MultiCatch

а = 0

div by 0: java.lang.ArithmeticException: / by zero

C:\> java MultiCatch 1

a = 1

array index oob: java.lang.ArrayIndexOutOfBoundsException: 42

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

class MultiNest {

static void procedure() {

try {

int c[] = { 1 };

c[42] = 99;

}

catch(ArrayIndexOutOfBoundsException e) {

System.out.println("array index oob: " + e);

} }

public static void main(String args[]) {

try {

int a = args.length();

System.out.println("a = " + a);

int b = 42 / a;

procedure();

}

catch (ArithmeticException e) {

System.out.println("div by 0: " + e);

}

} }

throw

Оператор throw используется для возбуждения исключения «вручную». Для того, чтобы сделать это, нужно иметь объект подкласса класса Throwable, который можно либо получить как параметр оператора catch, либо создать с помощью оператора new. Ниже приведена общая форма оператора throw.

throw ОбъектТипаThrowable;

При достижении этого оператора нормальное выполнение кода немедленно прекращается, так что следующий за ним оператор не выполняется. Ближайший окружающий блок try проверяется на наличие соответствующего возбужденному исключению обработчика catch. Если такой отыщется, управление передается ему. Если нет, проверяется следующий из вложенных операторов try, и так до тех пор пока либо не будет найден подходящий раздел catch, либо обработчик исключений исполняющей системы Java не остановит программу, выведя при этом состояние стека вызовов. Ниже приведен пример, в котором сначала создается объект-исключение, затем оператор throw возбуждает исключительную ситуацию, после чего то же исключение возбуждается повторно — на этот раз уже кодом перехватившего его в первый раз раздела catch.

class ThrowDemo {

static void demoproc() {

try {

throw new NullPointerException("demo");

}

catch (NullPointerException e) {

System.out.println("caught inside demoproc");

throw e;

} }

public static void main(String args[]) {

try {

demoproc();

}

catch(NulPointerException e) {

System.out.println("recaught: " + e);

}

} }

В этом примере обработка исключения проводится в два приема. Метод main создает контекст для исключения и вызывает demoproc. Метод demoproc также устанавливает контекст для обработки исключения, создает новый объект класса NullPointerException и с помощью оператора throw возбуждает это исключение. Исключение перехватывается в следующей строке внутри метода demoproc, причем объект-исключение доступен коду обработчика через параметр e. Код обработчика выводит сообщение о том, что возбуждено исключение, а затем снова возбуждает его с помощью оператора throw, в результате чего оно передается обработчику исключений в методе main. Ниже приведен результат, полученный при запуске этого примера.

С:\> java ThrowDemo

caught inside demoproc

recaught: java.lang.NullPointerException: demo

throws

Если метод способен возбуждать исключения, которые он сам не обрабатывает, он должен объявить о таком поведении, чтобы вызывающие методы могли защитить себя от этих исключений. Для задания списка исключений, которые могут возбуждаться методом, используется ключевое слово throws. Если метод в явном виде (т.е. с помощью оператора throw) возбуждает исключение соответствующего класса, тип класса исключений должен быть указан в операторе throws в объявлении этого метода. С учетом этого наш прежний синтаксис определения метода должен быть расширен следующим образом:

тип имя_метода(список аргументов) throws список_исключений {}

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

class ThrowsDemo1 {

static void procedure() {

System.out.println("inside procedure");

throw new IllegalAccessException("demo");

}

public static void main(String args[]) {

procedure();

} }

Для того, чтобы мы смогли оттранслировать этот пример, нам придется сообщить транслятору, что procedure может возбуждать исключения типа IllegalAccessException и в методе main добавить код для обработки этого типа исключений:

class ThrowsDemo {

static void procedure() throws IllegalAccessException {

System.out.println(" inside procedure");

throw new IllegalAccessException("demo");

}

public static void main(String args[]) {

try {

procedure();

}

catch (IllegalAccessException e) {

System.out.println("caught " + e);

}

} }

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

С:\> java ThrowsDemo

inside procedure

caught java.lang.IllegalAccessException: demo

finally

Иногда требуется гарантировать, что определенный участок кода будет выполняться независимо от того, какие исключения были возбуждены и перехвачены. Для создания такого участка кода используется ключевое слово finally. Даже в тех случаях, когда в методе нет соответствующего возбужденному исключению раздела catch, блок finally будет выполнен до того, как управление перейдет к операторам, следующим за разделом try. У каждого раздела try должен быть по крайней мере или один раздел catch или блок finally. Блок finally очень удобен для закрытия файлов и освобождения любых других ресурсов, захваченных для временного использования в начале выполнения метода. Ниже приведен пример класса с двумя методами, завершение которых происходит по разным причинам, но в обоих перед выходом выполняется код раздела finally.

class FinallyDemo {

static void procA() {

try {

System.out.println("inside procA");

throw new RuntimeException("demo");

}

finally {

System.out.println("procA's finally");

} }

static void procB() {

try {

System.out.println("inside procB");

return;

}

finally {

System.out.println("procB's finally");

} }

public static void main(String args[]) {

try {

procA();

}

catch (Exception e) {}

procB();

} }

В этом примере в методе procA из-за возбуждения исключения происходит преждевременный выход из блока try, но по пути «наружу» выполняется раздел finally. Другой метод procB завершает работу выполнением стоящего в try-блоке оператора return, но и при этом перед выходом из метода выполняется программный код блока finally. Ниже приведен результат, полученный при выполнении этой программы.

С:\> java FinallyDemo

inside procA

procA's finally

inside procB

procB's finally

Подклассы Exception

Только подклассы класса Throwable могут быть возбуждены или перехвачены. Простые типы — int, char, а также классы, не являющиеся подклассами Throwable, например, String и Object, использоваться в качестве исключений не могут. Наиболее общий путь для использования исключений — создание своих собственных подклассов класса Exception. Ниже приведена программа, в которой объявлен новый подкласс класса Exception.

class MyException extends Exception {

private int detail;

MyException(int a) {

detail = a:

}

public String toString() {

return "MyException[" + detail + "]";

}

}

class ExceptionDemo {

static void compute(int a) throws MyException {

System.out.println("called computer + a + ").");

if (a > 10)

throw new MyException(a);

System.out.println("normal exit.");

}

public static void main(String args[]) {

try {

compute(1);

compute(20);

}

catch (MyException e) {

System.out.println("caught" + e);

}

} }

Этот пример довольно сложен. В нем сделано объявление подкласса MyException класса Exception. У этого подкласса есть специальный конструктор, который записывает в переменную объекта целочисленное значение, и совмещенный метод toString, выводящий значение, хранящееся в объекте-исключении. Класс ExceptionDemo определяет метод compute, который возбуждает исключение типа MyExcepton. Простая логика метода compute возбуждает исключение в том случае, когда значение параметра метода больше 10. Метод main в защищенном блоке вызывает метод compute сначала с допустимым значением, а затем — с недопустимым (больше 10), что позволяет продемонстрировать работу при обоих путях выполнения кода. Ниже приведен результат выполнения программы.

С:\> java ExceptionDemo

called compute(1).

normal exit.

called compute(20).

caught MyException[20]

Обработка исключений предоставляет исключительно мощный механизм для управления сложными программами. Try, throw, catch дают вам простой и ясный путь для встраивания обработки ошибок и прочих нештатных ситуаций в программную логику. Если вы научитесь должным образом использовать рассмотренные механизмы, это придаст вашим классам профессиональный вид, и любые будущие пользователи вашего программного кода, несомненно, оценят это.

Глава 23.
Легковесные процессы и синхронизация

Параллельное программирование, связанное с использованием легковесных процессов, или подпроцессов (multithreading, light-weight processes) — концептуальная парадигма, в которой вы разделяете свою программу на два или несколько процессов, которые могут исполняться одновременно.

Во многих средах параллельное выполнение заданий представлено в том виде, который в операционных системах называется многозадачностью. Это совсем не то же самое, что параллельное выполнение подпроцессов. В многозадачных операционных системах вы имеете дело с полновесными процессами, в системах с параллельным выполнением подпроцессов отдельные задания называются легковесными процессами (light-weight processes, threads).

Цикл обработки событий в случае единственного подпроцесса

В системах без параллельных подпроцессов используется подход, называемый циклом обработки событий. В этой модели единственный подпроцесс выполняет бесконечный цикл, проверяя и обрабатывая возникающие события. Синхронизация между различными частями программы происходит в единственном цикле обработки событий. Такие среды называют синхронными управляемыми событиями системами. Apple Macintosh, Microsoft Windows, X11/Motif — все эти среды построены на модели с циклом обработки событий.

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

Модель легковесных процессов в Java

Исполняющая система Java в многом зависит от использования подпроцессов, и все ее классовые библиотеки написаны с учетом особенностей программирования в условиях параллельного выполнения подпроцессов. Java использует подпроцессы для того, чтобы сделать среду программирования асинхронной. После того, как подпроцесс запущен, его выполнение можно временно приостановить (suspend). Если подпроцесс остановлен (stop), возобновить его выполнение невозможно.

Приоритеты подпроцессов

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

Синхронизация

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

Сообщения

Коль скоро вы разделили свою программу на логические части — подпроцессы, вам нужно аккуратно определить, как эти части будут общаться друг с другом. Java предоставляет для этого удобное средство — два подпроцесса могут «общаться» друг с другом, используя методы wait и notify. Работать с параллельными подпроцессами в Java несложно. Язык предоставляет явный, тонко настраиваемый механизм управления созданием подпроцессов, переключения контекстов, приоритетов, синхронизации и обмена сообщениями между подпроцессами.

Подпроцесс

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

class CurrentThreadDemo {

public static void main(String args[]) {

Thread t = Thread.currentThread();

t.setName("My Thread");

System.out. println("current thread: " + t);

try {

for (int n = 5; n > 0; n--) {

System.out.println(" " + n);

Thread.sleep(1000);

} }

catch (InterruptedException e) {

System.out.println("interrupted");

}

} }

В этом примере текущий подпроцесс хранится в локальной переменной t. Затем мы используем эту переменную для вызова метода setName, который изменяет внутреннее имя подпроцесса на «My Thread», с тем, чтобы вывод программы был удобочитаемым. На следующем шаге мы входим в цикл, в котором ведется обратный отсчет от 5, причем на каждой итерации с помощью вызова метода Thread.sleep() делается пауза длительностью в 1 секунду. Аргументом для этого метода является значение временного интервала в миллисекундах, хотя системные часы на многих платформах не позволяют точно выдерживать интервалы короче 10 миллисекунд. Обратите внимание — цикл заключен в try/catch блок. Дело в том, что метод Thread.sleep() может возбуждать исключение InterruptedException. Это исключение возбуждается в том случае, если какому-либо другому подпроцессу понадобится прервать данный подпроцесс. В данном примере мы в такой ситуации просто выводим сообщение о перехвате исключения. Ниже приведен вывод этой программы:

С:\> java CurrentThreadDemo

current thread: Thread[My Thread,5,main]

5

4

3

2

1

Обратите внимание на то, что в текстовом представлении объекта Thread содержится заданное нами имя легковесного процесса — My Thread. Число 5 — это приоритет подпроцесса, оно соответствует приоритету по умолчанию, main — имя группы подпроцессов, к которой принадлежит данный подпроцесс.

Runnable

Не очень интересно работать только с одним подпроцессом, а как можно создать еще один? Для этого нам понадобится другой экземпляр класса Thread. При создании нового объекта Thread ему нужно указать, какой программный код он должен выполнять. Вы можете запустить подпроцесс с помощью любого объекта, реализующего интерфейс Runnable. Для того, чтобы реализовать этот интерфейс, класс должен предоставить определение метода run. Ниже приведен пример, в котором создается новый подпроцесс.

class ThreadDemo implements Runnable {

ThreadDemo() {

Thread ct = Thread.currentThread();

System.out.println("currentThread: " + ct);

Thread t = new Thread(this, "Demo Thread");

System.out.println("Thread created: " + t);

t.start();

try {

Thread.sleep(3000);

}

catch (InterruptedException e) {

System.out.println("interrupted");

}

System.out.println("exiting main thread");

}

public void run() {

try {

for (int i = 5; i > 0; i--) {

System.out.println("" + i);

Thread.sleep(1000);

} }

catch (InterruptedException e) {

System.out.println("child interrupted");

}

System.out.println("exiting child thread");

}

public static void main(String args[]) {

new ThreadDemo();

} }

Обратите внимание на то, что цикл внутри метода run выглядит точно так же, как и в предыдущем примере, только на этот раз он выполняется в другом подпроцессе. Подпроцесс main с помощью оператора new Thread создает новый объект класса Thread, причем первый параметр конструктора — this — указывает, что нам хочется вызвать метод run текущего объекта. Затем мы вызываем метод start, который запускает подпроцесс, выполняющий метод run. После этого основной подпроцесс (main) переводится в состояние ожидания на три секунды, затем выводит сообщение и завершает работу. Второй подпроцесс — «Demo Thread» — при этом по-прежнему выполняет итерации в цикле метода run до тех пор пока значение счетчика цикла не уменьшится до нуля. Ниже показано, как выглядит результат работы этой программы этой программы после того, как она отработает 5 секунд.

С:\> java ThreadDemo

Thread created: Thread[Demo Thread,5,main]

5

4

3

exiting main thread

2

1

exiting child thread

Если вы хотите добиться от Java предсказуемого независимого от платформы поведения, вам следует проектировать свои подпроцессы таким образом, чтобы они по своей воле освобождали процессор. Ниже приведен пример с двумя подпроцессами с различными приоритетами, которые не ведут себя одинаково на различных платформах. Приоритет одного из подпроцессов с помощью вызова setPriority устанавливается на два уровня выше Thread.NORM_PRIORITY, то есть, умалчиваемого приоритета. У другого подпроцесса приоритет, наоборот, на два уровня ниже. Оба этих подпроцесса запускаются и работают в течение 10 секунд. Каждый из них выполняет цикл, в котором увеличивается значение переменной-счетчика. Через десять секунд после их запуска основной подпроцесс останавливает их работу, присваивая условию завершения цикла while значение true и выводит значения счетчиков, показывающих, сколько итераций цикла успел выполнить каждый из подпроцессов.

class Clicker implements Runnable {

int click = 0;

private Thread t;

private boolean running = true;

public clicker(int p) {

t = new Thread(this);

t.setPriority(p);

}

public void run() {

while (running) {

click++;

} }

public void stop() {

running = false; }

public void start() {

t.start();

} }

class HiLoPri {

public static void main(String args[]) {

Thread.currentThread().setPriority(Thread.MAX_PRIORITY);

clicker hi = new clicker(Thread.NORM_PRIORITY + 2);

clicker lo = new clicker(Thread.NORM_PRIORITY - 2);

lo.start();

hi.start();

try Thread.sleep(-10000) {

}

catch (Exception e) {

}

lo.stop();

hi.stop();

System.out.println(lo.click + " vs. " + hi.click);

} }

По значениям, фигурирующим в распечатке, можно заключить, что подпроцессу с низким приоритетом достается меньше на 25 процентов времени процессора:

C:\>java HiLoPri

304300 vs. 4066666

Синхронизация

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

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

class Callme {

void call(String msg) {

System.out.println("[" + msg);

try Thread.sleep(-1000) {}

catch(Exception e) {}

System.out.println("]");

} }

class Caller implements Runnable {

String msg;

Callme target;

public Caller(Callme t, String s) {

target = t;

msg = s;

new Thread(this).start();

}

public void run() {

target.call(msg);

} }

class Synch {

public static void main(String args[]) {

Callme target = new Callme();

new Caller(target, "Hello.");

new Caller(target, "Synchronized");

new Caller(target, "World");

}

}

Вы можете видеть из приведенного ниже результата работы программы, что sleep в методе call приводит к переключению контекста между подпроцессами, так что вывод наших 3 строк-сообщений перемешивается:

[Hello.

[Synchronized

]

[World

]

]

Это происходит потому, что в нашем примере нет ничего, способного помешать разным подпроцессам вызывать одновременно один и тот же метод одного и того же объекта. Для такой ситуации есть даже специальный термин — race condition (состояние гонки), означающий, что различные подпроцессы пытаются опередить друг друга, чтобы завершить выполнение одного и того же метода. В этом примере для того, чтобы это состояние было очевидным и повторяемым, использован вызов sleep. В реальных же ситуациях это состояние, как правило, трудноуловимо, поскольку непонятно, где именно происходит переключение контекста, и этот эффект менее заметен и не всегда воспроизводятся от запуска к запуску программы. Так что если у вас есть метод (или целая группа методов), который манипулирует внутренним состоянием объекта, используемого в программе с параллельными подпроцессами, во избежание состояния гонки вам следует использовать в его заголовке ключевое слово synchronized.

Приоритеты подпроцессов

В Java имеется элегантный механизм общения между подпроцессами, основанный на методах wait, notify и notifyAll. Эти методы реализованы, как final-методы класса Object, так что они имеются в любом Java-классе. Все эти методы должны вызываться только из синхронизованных методов. Правила использования этих методов очень просты:

wait

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

notify

Выводит из состояния ожидания первый из подпроцессов, вызвавших wait с данным объектом.

notifyAll

Выводит из состояния ожидания все подпроцессы, вызвавшие wait с данным объектом.

Ниже приведен пример программы с наивной реализацией проблемы поставщик-потребитель. Эта программа состоит из четырех простых классов: класса Q, представляющего собой нашу реализацию очереди, доступ к которой мы пытаемся синхронизовать; поставщика (класс Producer), выполняющегося в отдельном подпроцессе и помещающего данные в очередь; потребителя (класс Consumer), тоже представляющего собой подпроцесс и извлекающего данные из очереди; и, наконец, крохотного класса PC, который создает по одному объекту каждого из перечисленных классов.

class Q {

int n;

synchronized int get() {

System.out.println("Got: " + n);

return n;

}

synchronized void put(int n) {

this.n = n;

System.out. println("Put: " + n);

} }

class Producer implements Runnable {

Q q;

Producer(Q q) {

this.q = q;

new Thread(this, "Producer").start();

}

public void run() {

int i = 0;

while (true) {

q.put(i++);

} } }

class Consumer implements Runnable {

Q q;

Consumer(Q q) {

this.q = q;

new Thread(this, "Consumer").start();

}

public void run() {

while (true) {

q.get();

}

} }

class PC {

public static void main(String args[]) {

Q q = new Q();

new Producer(q);

new Consumer(q);

} }

Хотя методы put и get класса Q синхронизованы, в нашем примере нет ничего, что бы могло помешать поставщику переписывать данные по того, как их получит потребитель, и наоборот, потребителю ничего не мешает многократно считывать одни и те же данные. Так что вывод программы содержит вовсе не ту последовательность сообщений, которую нам бы хотелось иметь:

С:\> java PC

Put: 1

Got: 1

Got: 1

Got: 1

Got: 1

Got: 1

Put: 2

Put: 3

Put: 4

Put: 5

Put: 6

Put: 7

Got: 7

Как видите, после того, как поставщик помещает в переменную n значение 1, потребитель начинает работать и извлекает это значение 5 раз подряд. Положение можно исправить, если поставщик будет при занесении нового значения устанавливать флаг, например, заносить в логическую переменную значение true, после чего будет в цикле проверять ее значение до тех пор пока поставщик не обработает данные и не сбросит флаг в false.

Правильным путем для получения того же результата в Java является использование вызовов wait и notify для передачи сигналов в обоих направлениях. Внутри метода get мы ждем (вызов wait), пока Producer не известит нас (notify), что для нас готова очередная порция данных. После того, как мы обработаем эти данные в методе get, мы извещаем объект класса Producer (снова вызов notify) о том, что он может передавать следующую порцию данных. Соответственно, внутри метода put, мы ждем (wait), пока Consumer не обработает данные, затем мы передаем новые данные и извещаем (notify) об этом объект-потребитель. Ниже приведен переписанный указанным образом класс Q.

class Q {

int n;

boolean valueSet = false;

synchronized int get() {

if (!valueSet)

try wait();

catch(InterruptedException e):

System.out.println("Got: " + n);

valueSet = false;

notify();

return n;

}

synchronized void put(int n) {

if (valueSet)

try wait(); catch(InterruptedException e);

this.n = n;

valueSet = true;

System.out.println("Put: " + n);

notify();

} }

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

С:\> java Pcsynch

Put: 1

Got: 1

Put: 2

Got: 2

Put: 3

Got: 3

Put: 4

Got: 4

Put: 5

Got: 5

Клинч (deadlock)

Клинч — редкая, но очень трудноуловимая ошибка, при которой между двумя легковесными процессами существует кольцевая зависимость от пары синхронизированных объектов. Например, если один подпроцесс получает управление объектом X, а другой — объектом Y, после чего Х пытается вызвать любой синхронизированный метод Y, этот вызов, естественно блокируется. Если при этом и Y попытается вызвать синхронизированный метод X, то программа с такой структурой подпроцессов окажется заблокированной навсегда. В самом деле, ведь для того, чтобы один из подпроцессов захватил нужный ему объект, ему нужно снять свою блокировку, чтобы второй подпроцесс мог завершить работу.

Глава 24.
Сводка функций программного интерфейса легковесных процессов

Методы класса

Методы класса — это статические методы, которые можно вызывать непосредственно с именем класса Thread.

currentThread

Статический метод currentThread возвращает объект Thread, выполняющийся в данный момент.

yield

Вызов метода yield приводит к тому, что исполняющая система переключает контекст с текущего на следующий доступный подпроцесс. Это один из способов гарантировать, что низкоприоритетные подпроцессы когда-нибудь получат управление.

sleep(int n)

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

Методы объекта

start

Метод start говорит исполняющей системе Java, что необходимо создать системный контекст подпроцесса и запустить этот подпроцесс. После вызова этого метода в новом контексте будет вызван метод run вновь созданного подпроцесса. Вам нужно помнить о том, что метод start с данным объектом можно вызвать только один раз.

run

Метод run — это тело выполняющегося подпроцесса. Это — единственный метод интерфейса Runnable. Он вызывается из метода start после того, как исполняющая среда выполнит необходимые операции по инициализации нового подпроцесса. Если происходит возврат из метода run, текущий подпроцесс останавливается.

stop

Вызов метода stop приводит к немедленной остановке подпроцесса. Это — способ мгновенно прекратить выполнение текущего подпроцесса, особенно если метод выполняется в текущем подпроцессе. В таком случае строка, следующая за вызовом метода stop, никогда не выполняется, поскольку контекст подпроцесса «умирает» до того, как метод stop возвратит управление. Более аккуратный способ остановить выполнение подпроцесса — установить значение какой-либо переменной-флага, предусмотрев в методе run код, который, проверив состояние флага, завершил бы выполнение подпроцесса.

suspend

Метод suspend отличается от метода stop тем, что метод приостанавливает выполнение подпроцесса, не разрушая при этом его системный контекст. Если выполнение подпроцесса приостановлено вызовом suspend, вы можете снова активизировать этот подпроцесс, вызвав метод resume.

resume

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

setPriority(int p)

Метод setPriority устанавливает приоритет подпроцесса, задаваемый целым значением передаваемого методу параметра. В классе Thread есть несколько предопределенных приоритетов-констант: MIN_PRIORITY, NORM_PRIORITY и MAX_PRIORITY, соответствующих соответственно значениям 1, 5 и 10. Большинство пользовательских приложений должно выполняться на уровне NORM_PRIORITY плюс-минус 1. Приоритет фоновых заданий, например, сетевого ввода-вывода или перерисовки экрана, следует устанавливать в MIN_PRIORITY. Запуск подпроцессов на уровне MAX_PRIORITY требует осторожности. Если в подпроцессах с таким уровнем приоритета отсутствуют вызовы sleep или yield, может оказаться, что вся исполняющая система Java перестанет реагировать на внешние раздражители.

SetPriority

Этот метод возвращает текущий приоритет подпроцесса — целое значение в диапазоне от 1 до 10.

setName(String name)

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

getName

Метод getName возвращает строку с именем подпроцесса, установленным с помощью вызова setName.

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

Глава 25.
Утилиты

Библиотека классов языка включает в себя набор вспомогательных классов, широко используемых в других встроенных пакетах Java. Эти классы расположены в пакетах java.lang и java.util. Они используются для работы с наборов объектов, взаимодействия с системными функциями низкого уровня, для работы с математическими функциями, генерации случайных чисел и манипуляций с датами и временем.

Простые оболочки для типов

Как вы уже знаете, Java использует встроенные примитивные типы данных, например, int и char ради обеспечения высокой производительности. Эти типы данных не принадлежат к классовой иерархии Java. Они передаются методам по значению, передать их по ссылке невозможно. По этой причине для каждого примитивного типа в Java реализован специальный класс.

Number

Абстрактный класс Number представляет собой интерфейс для работы со всеми стандартными скалярными типами: — long, int, float и double.

У этого класса есть методы доступа к содержимому объекта, которые возвращают (возможно округленное) значение объекта в виде значения каждого из примитивных типов:

u doubleValue() возвращает содержимое объекта в виде значения типа double.

u floatValue() возвращает значение типа float.

u intValue() возвращает значение типа int.

u longValue() возвращает значение типа long.

Double и Float

Double и Float — подклассы класса Number. В дополнение к четырем методам доступа, объявленным в суперклассе, эти классы содержат несколько сервисных функций, которые облегчают работу со значениями double и float. У каждого из классов есть конструкторы, позволяющие инициализировать объекты значениями типов double и float, кроме того, для удобства пользователя, эти объекты можно инициализировать и объектом String, содержащим текстовое представление вещественного числа. Приведенный ниже пример иллюстрирует создание представителей класса Double с помощью обоих конструкторов.

class DoubleDemo {

public static void main(String args[]) {

Double d1 = new Double(3.14159);

Double d2 = new Double("314159E-5");

System.out.println(d1 + " = " + d2 + " -> " + d1.equals(d2));

} }

Как вы можете видеть из результата работы этой программы, метод equals возвращает значение true, а это означает, что оба использованных в примере конструктора создают идентичные объекты класса Double.

С:\> java DoubleDemo

3.14159 = 3.14159 -> true

Бесконечность и NaN

В спецификации IEEE для чисел с вещественной точкой есть два значения типа double, которые трактуются специальным образом: бесконечность и NaN (Not a Number — неопределенность). В классе Double есть тесты для проверки обоих этих условий, причем в двух формах — в виде методов (статических), которым значение double передается в качестве параметра, и в виде методов, проверяющих число, хранящееся в объекте класса Double.

u islnfinite(d) возвращает true, если абсолютное значение указанного числа типа double бесконечно велико.

u islnfinite() возвращает true, если абсолютное значение числа, хранящегося в данном объекте Double, бесконечно велико.

u isNaN(d) возвращает true, если значение указанного числа типа double не определено.

u isNaN() возвращает true, если значение числа, хранящегося в данном объекте Double, не определено.

Очередной наш пример создает два объекта Double, один с бесконечным, другой с неопределенным значением.

class InfNaN {

public static void main(String args[]) {

Double d1 = new Double(1/0.);

Double d2 = new Double(0/0.);

System.out.println(d1 + ": " + d1.isInfinite() + ", " + d1.isNaN());

System.out.println(d2 + ": " + d2.isInfinite() + ", " + d2.isNaN());

} }

Ниже приведен результат работы этой программы:

С:\> java InfNaN

Infinity: true, false

NaN: false, true

Integer и Long

Класс Integer — класс-оболочка для чисел типов int, short и byte, a класс Long — соответственно для типа long. Помимо наследуемых методов своего суперкласса Number, классы Integer и Long содержат методы для разбора текстового представления чисел, и наоборот, для представления чисел в виде текстовых строк. Различные варианты этих методов позволяют указывать основание (систему счисления), используемую при преобразовании. Обычно используются двоичная, восьмеричная, десятичная и шестнадцатеричная системы счисления.

u parseInt(String) преобразует текстовое представление целого числа, содержащееся в переменной String, в значение типа int. Если строка не содержит представления целого числа, записанного в допустимом формате, вы получите исключение NumberFormatException.

u parseInt(String, radix) выполняет ту же работу, что и предыдущий метод, но в отличие от него с помощью второго параметра вы можете указывать основание, отличное от 10.

u toString(int) преобразует переданное в качестве параметра целое число в текстовое представление в десятичной системе.

u toString(int, radix) преобразует переданное в качестве первого параметра целое число в текстовое представление в задаваемой вторым параметром системе счисления.

Character

Character — простой класс-оболочка типа char. У него есть несколько полезных статических методов, с помощью которых можно выполнять над символом различные проверки и преобразования.

u isLowerCase(char ch) возвращает true, если символ-параметр принадлежит нижнему регистру (имеется в виду не просто диапазон a-z, но и символы нижнего регистра в кодировках, отличных от ISO-Latin-1).

u isUpperCase(char ch) делает то же самое в случае символов верхнего регистра.

u isDigit(char ch) и isSpace(char ch) возвращают true для цифр и пробелов, соответственно.

u toLowerCase(char ch) и toupperCase(char ch) выполняют преобразования символов из верхнего в нижний регистр и обратно.

Boolean

Класс Boolean — это очень тонкая оболочка вокруг логических значений, она бывает полезна лишь в тех случаях, когда тип boolean требуется передавать по ссылке, а не по значению.

Перечисления

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

Интерфейс Enumeration

Enumeration — простой интерфейс, позволяющий вам обрабатывать элементы любой коллекции объектов. В нем задается два метода. Первый из них — метод hasMoreElements, возвращающий значение типа boolean. Он возвращает значение true, если в перечислении еще остались элементы, и false, если у данного элемента нет следующего. Второй метод — nextElement — возвращает обобщенную ссылку на объект класса Object, которую, прежде чем использовать, нужно преобразовать к реальному типу содержащихся в коллекции объектов. Ниже приведен пример, в котором используется класс Enum, реализующий перечисление объектов класса Integer, и класс EnumerateDemo, создающий объект типа Enum, выводящий все значения перечисления. Обратите внимание на то, что в объекте Enum не содержится реальных данных, он просто возвращает последовательность создаваемых им объектов Integer.

import java.util.Enumeration;

class Enum implements Enumeration {

private int count = 0;

private boolean more = true;

public boolean hasMoreElements() {

return more;

}

public Object nextElement() {

count++;

if (count > 4) more = false;

return new Integer(count);

} }

class EnumerateDemo {

public static void main(String args[]) {

Enumeration enum = new Enum();

while (enum.hasMoreElements()) {

System.out.println(enum.nextElement());

}

} }

Вот результат работы этой программы:

С:\> java EnumerateDemo

1

2

3

4

5

Vector

Vector — это способный увеличивать число своих элементов массив ссылок на объекты. Внутри себя Vector реализует стратегию динамического расширения, позволяющую минимизировать неиспользуемую память и количество операций по выделению памяти. Объекты можно либо записывать в конец объекта Vector с помощью метода addElement, либо вставлять в указанную индексом позицию методом insertElementAt. Вы можете также записать в Vector массив объектов, для этого нужно воспользоваться методом copyInto. После того, как в Vector записана коллекция объектов, можно найти в ней индивидуальные элементы с помощью методов Contains, indexOf и lastIndexOf. Кроме того методы еlеmentAt, firstElement и lastElement позволяют извлекать объекты из нужного положения в объекте Vector.

Stack

Stack — подкласс класса Vector, который реализует простой механизм типа «первым вошел — первым вышел» (FIFO). В дополнение к стандартным методам своего родительского класса, Stack предлагает метод push для помещения элемента в вершину стека и pop для извлечения из него верхнего элемента. С помощью метода peek вы можете получить верхний элемент, не удаляя его из стека. Метод empty служит для проверки стека на наличие элементов — он возвращает true, если стек пуст. Метод search ищет заданный элемент в стеке, возвращая количество операция pop, которые требуются для того чтобы перевести искомый элемент в вершину стека. Если заданный элемент в стеке отсутствует, этот метод возвращает -1.

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

import java.util.Stack;

import java.util.EmptyStackException;

class StackDemo {

static void showpush(Stack st, int a) {

st.push(new Integer(a));

System.out.println("push(" + a + ")");

System.out.println("stack: " + st);

}

static void showpop(Stack st) {

System.out.print("pop -> ");

Integer a = (Integer) st.pop();

System.out.println(a);

System.out.println("stack: " + st);

}

public static void main(String args[]) {

Stack st = new Stack();

System.out.println("stack: " + st);

showpush(st, 42);

showpush(st, 66);

showpush(st, 99);

showpop(st);

showpop(st);

showpop(st);

try {

showpop(st);

}

catch (EmptyStackException e) {

System.out.println("empty stack");

} }

}

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

C:\> java StackDemo

stack: []

push(42)

stack: [42]

push(66)

stack: [42, 66]

push(99)

stack: [42, 66, 99]

pop -> 99

stack: [42, 66]

pop -> 66

stack: [42]

pop -> 42

stack: []

pop -> empty stack

Dictionary

Dictionary (словарь) — абстрактный класс, представляющий собой хранилище информации типа «ключ-значение». Ключ — это имя, по которому осуществляется доступ к значению. Имея ключ и значение, вы можете записать их в словарь методом put(key, value). Для получения значения по заданному ключу служит метод get(key). И ключи, и значения можно получить в форме перечисления (объект Enumeration) методами keys и elements. Метод size возвращает количество пар «ключ-значение», записанных в словаре, метод isEmpty возвращает true, если словарь пуст. Для удаления ключа и связанного с ним значения предусмотрен метод remove(key).

HashTable

HashTable — это подкласс Dictionary, являющийся конкретной реализацией словаря. Представителя класса HashTable можно использовать для хранения произвольных объектов, причем для индексации в этой коллекции также годятся любые объекты. Наиболее часто HashTable используется для хранения значений объектов, ключами которых служат строки (то есть объекты типа String).

import java.util.Dictionary;

import java.util.Hashtable;

class HTDemo {

public static void main(String args[]) {

Hashtable ht = new Hashtable();

ht.put("title", "The Java Handbook");

ht.put("author", "Patrick Naugnton");

ht.put("email", "naughton@starwave.com");

ht.put(“age", new Integer(30));

show(ht);

}

static void show(Dictionary d) {

System.out.println("Title: " + d.get("title"));

System.out.println("Author: " + d.get("author"));

System.out.println("Email: " + d.get("email"));

System.out.println("Age: " + d.get("age"));

} }

Результат работы этого примера иллюстрирует тот факт, что метод show, параметром которого является абстрактный тип Dictionary, может извлечь все значения, которые мы занесли в ht внутри метода main.

С:\> java HTDemo

Title: The Java Handbook

Author: Patrick Naughton

Email: naughton@starwave.com

Age: 30

Properties

Properties — подкласс HashTable, в который для удобства использования добавлено несколько методов, позволяющих получать значения, которые, возможно, не определены в таблице. В методе getProperty вместе с именем можно указывать значение по умолчанию:

getРгореrtу("имя","значение_по_умолчанию");

При этом, если в таблице свойство «имя» отсутствует, метод вернет «значение_по_умолчанию». Кроме того, при создании нового объекта этого класса конструктору в качестве параметра можно передать другой объект Properties, при этом его содержимое будет использоваться в качестве значений по умолчанию для свойств нового объекта. Объект Properties в любой момент можно записать либо считать из потока — объекта Stream. Ниже приведен пример, в котором создаются и впоследствии считываются некоторые свойства:

import java.util.Properties;

class PropDemo {

static Properties prop = new Properties();

public static void main(String args[]) {

prop.put("Title", "put title here");

prop.put("Author", "put name here");

prop.put("isbn", "isbn not set");

Properties book = new Properties(prop);

book.put("Title", "The Java Handbook");

book.put("Author", "Patrick Naughton");

System.out.println("Title: " +

book.getProperty("Title"));

System.out.println("Author: " +

book.getProperty("Author"));

System.out.println("isbn: " +

book.getProperty("isbn"));

System.out.println("ean: " +

book.getProperty("ean", "???"));

} }

Здесь мы создали объект prop класса Properties, содержащий три значения по умолчанию для полей Title, Author и isbn. После этого мы создали еще один объект Properties с именем book, в который мы поместили реальные значения для полей Title и Author. В следующих трех строках примера мы вывели результат, возвращенный методом getProperty для всех трех имеющихся ключей. В четвертом вызове getProperty стоял несуществующий ключ «еаn». Поскольку этот ключ отсутствовал в объекте book и в объекте по умолчанию prop, метод getProperty выдал нам указанное в его вызове значение по умолчанию, то есть «???»:

С:\> java PropDemo

Title: The Java Handbook

Author: Patrick Naughton

isbn: isbn not set

ean: ???

StrinsTokenizer

Обработка текста часто подразумевает разбиение текста на последовательность лексем — слов (tokens). Класс StringTokenizer предназначен для такого разбиения, часто называемого лексическим анализом или сканированием. Для работы StringTokenizer требует входную строку и строку символов-разделителей. По умолчанию в качестве набора разделителей используются обычные символы-разделители: пробел, табуляция, перевод строки и возврат каретки. После того, как объект StringTokenizer создан, для последовательного извлечения лексем из входной строки используется его метод nextToken. Другой метод — hasMoreTokens — возвращает true в том случае, если в строке еще остались неизвлеченные лексемы. StringTokenizer также реализует интерфейс Enumeration, а это значит, что вместо методов hasMoreTokens и nextToken вы можете использовать методы hasMoreElements и nextElement, соответственно.

Ниже приведен пример, в котором для разбора строки вида «ключ=значение» создается и используется объект StringTokenizer. Пары «ключ=значение» разделяются во входной строке двоеточиями.

import java.util.StringTokenizer;

class STDemo {

static String in = "title=The Java Handbook:" + "author=Patrick Naughton:" + "isbn=0-07-882199-1:" + "ean=9 780078 821998:" + "email=naughton@starwave. corn";

public static void main(String args[]) {

StringTokenizer st = new StringTokenizer(in, "=:");

while (st.hasMoreTokens()) {

String key = st.nextToken();

String val = st.nextToken();

System.out.println(key + "\t" + val);

}

} }

Runtime

Класс Runtime инкапсулирует интерпретатор Java. Вы не можете создать нового представителя этого класса, но можете, вызвав его статический метод, получить ссылку на работающий в данный момент объект Runtime. Обычно апплеты и другие непривелигированные программы не могут вызвать ни один из методов этого класса, не возбудив при этом исключения SecurityException. Одна из простых вещей, которую вы можете проделать с объектом Runtime — его останов, для этого достаточно вызвать метод exit(int code).

Глава 26.
Управление памятью

Хотя Java и представляет собой систему с автоматической сборкой мусора, вы для проверки эффективности своего кода можете захотеть узнать, каков размер «кучи» и как много в ней осталось свободной памяти. Для получения этой информации нужно воспользоваться методами totalMemory и freeMemory.

При необходимости вы можете «вручную» запустить сборщик мусора, вызвав метод gc. Если вы хотите оценить, сколько памяти требуется для работы вашему коду, лучше всего сначала вызвать gc, затем free-Memory, получив тем самым оценку свободной памяти, доступной в системе. Запустив после этого свою программу и вызвав freeMemory внутри нее, вы увидите, сколько памяти использует ваша программа.

Глава 27.
Выполнение других программ

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

В очередном примере используется специфичный для Windows вызов ехес, запускающий процесс notepad — простой текстовый редактор. В качестве параметра редактору передается имя одного из исходных файлов Java. Обратите внимание — ехес автоматически преобразует в строке-пути символы «/» в разделители пути в Windows — «\».

class ExecDemo {

public static void main(String args[]) {

Runtime r = Runtime. getRuntime();

Process p = null;

String cmd[] = { "notepad", "/java/src/java/lang/Runtime.java" };

try {

p = r.exec(cmd);

} catch (Exception e) {

System.out.println("error executing " + cmd[0]);

}

} }

System

Класс System содержит любопытную коллекцию глобальных функций и переменных. В большинстве примеров этой книге для операций вывода мы использовали метод System.out.println(). Метод currentTimeMillis возвращает текущее системное время в виде миллисекунд, прошедших с 1 января 1970 года. Метод arraycopy можно использовать для быстрого копирования массива любого типа из одного места в памяти в другое. Ниже приведен пример копирования двух массивов с помощью этого метода.

class ACDemo {

static byte a[] = { 65, 66, 67, 68, 69, 70, 71, 72, 73, 74 };

static byte b[] = { 77, 77, 77, 77, 77, 77, 77, 77, 77, 77 };

public static void main(

String args[]) {

System.out.println("a = " + new String(a, 0));

System.out.println("b = " + new String(b, 0));

System.arraycopy(a, 0, b, 0, a.length);

System.out.println("a = " + new String(a, 0));

System.out.println("b = " + new String(b, 0));

System.arraycopy(a, 0, a, 1, a.length - 1);

System.arraycopy(b, 1, b, 0, b.length - 1);

System.out.println("a = " + new String(a, 0));

System.out.println("b = " + new String(b, 0));

} }

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

С:\> java ACDemo

а = ABCDEFGHIJ

b = ММММММММММ

а = ABCDEFGHIJ

b = ABCDEFGHIJ

а = AABCDEFGHI

b = BCDEFGHIJJ

Глава 28.
Свойства окружения

Исполняющая среда Java предоставляет доступ к переменным окружения через представителя класса Properties, с которым можно работать с помощью метода System. getProperty. Для получения полного списка свойств можно вызвать метод System. getProperties().

Стандартные системные свойства

Имя Значение Доступ для апплета

java.versiou Версия интерпретатора Java да

java.vendor Строка идентификатора, заданная
разработчиком да

java.vendor.url URL разработчика да

java.class.versiou Версия Java API да

java.class.path Значение переменной CLASSPATH нет

java.home Каталог, в котором инсталлирована
среда Java нет

java.compiler Компилятор JIT нет

os.name Название операционной системы да

os.arch Архитектура компьютера, на котором
выполняется программа да

os.versiou Версия операционной системы
Web-узла да

file.separator Зависящие от платформы
разделители файлов (/ или \) да

path.separator Зависящие от платформы
разделители пути (: или ;) да

line.separator Зависящие от платформы разделители
строк (\n или \r\n) да

user.name Имя текущего пользователя нет

user.home Домашний каталог пользователя нет

user.dir Текущий рабочий каталог нет

user.language 2-символьный код языка для
местности по умолчанию нет

user.regiou 2-символьный код страны для
местности по умолчанию нет

user.timezone Временной пояс по умолчанию нет

user.encoding Кодировка символов для местности
по умолчанию нет

user.encoding.pkg Пакет, содержащий конверторы для
преобразования символов из местной
кодировки в Unicode нет

Date

Класс Date используется для операций с датой и временем. Через него вы можете получить доступ к дате, месяцу, году, дню недели, часам, минутам, секундам. У объектов этого класса — несколько конструкторов. Самый простой — Date() — инициализирует объект текущими датой и временем. Три остальных конструктора предлагают дополнительные возможности задавать начальные значения для нового объекта.

u Date(year, month, date) — устанавливает указанную дату, при этом время устанавливается в 00:00:00 (полночь).

u Date(year, month, date, hours, minutes) — устанавливает указанные дату и время, секунды устанавливаются в 0.

u Date(year, month, date, hours, minutes, seconds) — наиболее полное задание времени, в объекте устанавливаются указанные дата и время, в том числе и секунды.

get и set

Класс Date включает в себя набор методов для получения и установки отдельных атрибутов, хранящихся в объекте. Каждая из функций семейства get — getYear, getMonth, getDate, getDay, getHours, getMi-nutes и getSeconds — возвращает целое значение. Каждой из функций семейства set — setYear, setMonth, setDate, setHours, setMinutes и setSeconds — в качестве параметра передается целое значение. Вы также можете получить представление объекта Date в виде значения типа long с помощью метода getTime. Возвращаемое этим методом значение представляет собой число миллисекунд, прошедших после 1 января 1970 года.

Сравнение

Если у вас есть два объекта типа Date, и вы хотите их сравнить, то можете преобразовать хранящиеся в них даты в значения типа long, и сравнить полученные даты, выраженные в миллисекундах. Класс Date включает в себя три метода, которые можно использовать для прямого сравнения дат: — before, И и equals. Например, вызов

new Date(96, 2, 18).before(new Date(96, 2, 12)

возвращает значение true, поскольку 12-й день месяца предшествует 18-му.

Строки и часовые пояса

Объекты Date можно конвертировать в текстовые строки различных форматов. Прежде всего, обычный метод toString преобразует объект Date в строку, которая выглядит, как «Thu Feb 15 22:42:04 2003». Метод toLocaleString преобразует дату в более короткую строку, выглядящую примерно так: «02/15/03 22:42:04». И, наконец, метод toGMTString возвращает дату в формате среднего времени по Гринвичу: «16 Feb 2003 06:42:04 GMT».

Math

Класс Math содержит функции с плавающей точкой, которые используются в геометрии и тригонометрии. Кроме того, в нем есть две константы, используемые в такого рода вычислениях: — Е (приблизительно 2.72) и PI (приблизительно 3.14159).

Тригонометрические функции

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

u sin(double а) возвращает синус угла а, заданного в радианах.

u cos(double а) возвращает косинус угла а, заданного в радианах.

u tan(double а) возвращает тангенс угла а, заданного в радианах.

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

u asin(double r) возвращает угол, синус которого равен г.

u acos(double r) возвращает угол, косинус которого равен г.

u atan(double r) возвращает угол, тангенс которого равен г.

u atan2(double a, double b) возвращает угол, тангенс которого равен отношению а/b.

Степенные, показательные и логарифмические функции

u pow(double у, double x) возвращает у, возведенное в степень х. Так, например, pow(2.0, 3.0) равно 8.0.

u exp(double х) возвращает е в степени х.

u log(double х) возвращает натуральный логарифм х.

u sqrt(double х) возвращает квадратный корень х.

Округление

u ceil(double а) возвращает наименьшее целое число, значение которого больше или равно а.

u floor(double а) возвращает наибольшее целое число, значение которого меньше или равно а.

u rint(double а) возвращает в типе double значение а с отброшенной дробной частью.

u round(float а) возвращает округленное до ближайшего целого значение а.

u round(double а) возвращает округленное до ближайшего длинного целого значение а.

Кроме того, в классе Math имеются полиморфные версии методов для получения модуля, нахождения минимального и максимального значений, работающие с числами типов int, long, float и double:

u abs(a) возвращает модуль (абсолютное значение) а.

u max(a, b) возвращает наибольший из своих аргументов.

u min(a, b) возвращает наименьший из своих аргументов.

Random

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

Из объекта класса Random можно извлекать 5 типов случайных чисел. Метод nextInt возвращает целое число, равномерно распределенное по всему диапазону этого типа.

Аналогично, метод nextLong возвращает случайное число типа long. Методы nextFloat и nextDouble возвращают случайные числа соответственно типов float и double, равномерно распределенные на интервале 0.0..1.0. И, наконец, метод nextGaussian возвращает нормально распределенное случайное число со средним значением 0.0 и дисперсией 1.0.

Глава 29.
Ввод/Вывод

Обобщенное понятие источника ввода относится к различным способам получения информации: к чтению дискового файла, символов с клавиатуры, либо получению данных из сети. Аналогично, под обобщенным понятием вывода также могут пониматься дисковые файлы, сетевое соединение. Эти абстракции дают удобную возможность для работы с вводом-выводом (I/O), не требуя при этом, чтобы каждая часть вашего кода понимала разницу между, скажем, клавиатурой и сетью. В Java эта абстракция называется потоком (stream) и реализована в нескольких классах пакета java.io. Ввод инкапсулирован в классе InputStream, вывод — в OutputStream. В Java есть несколько специализаций этих абстрактных классов, учитывающих различия при работе с дисковыми файлами, сетевыми соединениями и даже с буферами в памяти.

File

File — единственный объект в java.io, который работает непосредственно с дисковыми файлами. Хотя на использование файлов в апплетах наложены жесткие ограничения, файлы по прежнему остаются основными ресурсами для постоянного хранения и совместного использования информации. Каталог в Java трактуется как обычный файл, но с дополнительным свойством — списком имен файлов, который можно просмотреть с помощью метода list.

Java правильно обрабатывает разделители имен каталогов в пути, используемые в UNIX и DOS. Если вы используете стиль UNIX — символы «/», то при работе в Windows Java автоматически преобразует их в «\». Не забудьте, если вы привыкли к разделителям, принятым в DOS, то есть, к «\», то для того, чтобы включить их в строку пути, необходимо их удвоить, аналогично тому, как это сделано в строке «\\java\\COPYRIGHT».

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

import java.io.File;

class FileTest {

static void p(String s) {

System.out.println(s);

}

public static void main(String args[]) {

File f1 = new File("/java/COPYRIGHT");

p("File Name:" + f1 .getName());

p("Path:" + f1.getPath());

p("Abs Path:" + f1.getAbsolutePath());

p("Parent:" + f1.getParent());

p(f1.exists() ? "exists" : "does not exist");

p(f1.canWrite() ? "is writeable" : "is not writeable");

p(f1.canRead() ? "is readable" : "is not readable");

p("is " + (f1.isDirectory() ? " " : "not") + " a directory");

p(f1.isFile() ? "is normal file" : "might be a named pipe");

p(f1.isAbsolute() ? "is absolute" : "is not absolute");

p("File last modified:" + f1. lastModified());

p("File size:" + f1.length() + " Bytes");

} }

При запуске этой программы вы получите что-то наподобие вроде:

File Name:COPYRIGHT (имя файла)

Path:/java/COPYRIGHT (путь)

Abs Path:/Java/COPYRIGHT (путь от корневого каталога)

Parent:/java (родительский каталог)

exists (файл существует)

is writeable (разрешена запись)

is readable (разрешено чтение)

is not a directory (не каталог)

is normal file (обычный файл)

is absolute

File last modified:812465204000 (последняя модификация файла)

File size:695 Bytes (размер файла)

Существует также несколько сервисных методов, использование которых ограничено обычными файлами (их нельзя применять к каталогам). Метод renameTo(File dest) переименовывает файл (нельзя переместить файл в другой каталог). Метод delete уничтожает дисковый файл. Этот метод может удалять только обычные файлы, каталог, даже пустой, с его помощью удалить не удастся.

Каталоги

Каталоги — это объекты класса File, в которых содержится список других файлов и каталогов. Если File ссылается на каталог, его метод isDirectory возвращает значение true. В этом случае вы можете вызвать метод list и извлечь содержащиеся в объекте имена файлов и каталогов. В очередном примере показано, как с помощью метода list можно просмотреть содержимое каталога.

import java.io.File;

class DirList {

public static void main(String args[]) {

String dirname = "/java"; // имя каталога

File f1 = new File(dirname);

if (f1.isDirectory()) { // является ли f1 каталогом

System.out.println("Directory of ' + dirname);

String s[] = f1.list();

for ( int i=0; i < s.length; i++) {

File f = new File(dirname + "/" + s[i]);

if (f.isDirectory()) { // является ли f каталогом System.out.println(s[i] + " is a directory"):

} else {

System.out.println(s[i] + " is a file");

} } } else {

System.out.println(dirname + " is not a directory");

} }

}

В процессе работы эта программа вывела содержимое каталога /java моего персонального компьютера в следующем виде:

С:\> java DirList

Directory of /java

bin is a directory

COPYRIGHT is a file

README is a file

FilenameFilter

Зачастую у вас будет возникать потребность ограничить количество имен файлов, возвращаемых методом list, чтобы получить от него только имена, соответствующие определенному шаблону. Для этого в пакет java.io включен интерфейс FilenameFilter. Объекту, чтобы реализовать этот интерфейс, требуется определить только один метод — accept(), который будет вызываться один раз с каждым новым именем файла. Метод accept должен возвращать true для тех имен, которые надо включать в список, и false для имен, которые следует исключить.

У класса File есть еще два сервисных метода, ориентированных на работу с каталогами. Метод mkdir создает подкаталог. Для создания каталога, путь к которому еще не создан, надо использовать метод mkdirs — он создаст не только указанный каталог, но и все отсутствующие родительские каталоги.

InputStream

InputStream — абстрактный класс, задающий используемую в Java модель входных потоков. Все методы этого класса при возникновении ошибки возбуждают исключение IOException. Ниже приведен краткий обзор методов класса InputStream.

u read() возвращает представление очередного доступного символа во входном потоке в виде целого.

u read(byte b[]) пытается прочесть максимум b.length байтов из входного потока в массив b. Возвращает количество байтов, в действительности прочитанных из потока.

u read(byte b[], int off, int len) пытается прочесть максимум len байтов, расположив их в массиве b, начиная с элемента off. Возвращает количество реально прочитанных байтов.

u skip(long n) пытается пропустить во входном потоке n байтов. Возвращает количество пропущенных байтов.

u available() возвращает количество байтов, доступных для чтения в настоящий момент.

u close() закрывает источник ввода. Последующие попытки чтения из этого потока приводят к возбуждению IOException.

u mark(int readlimit) ставит метку в текущей позиции входного потока, которую можно будет использовать до тех пор, пока из потока не будет прочитано readlimit байтов.

u reset() возвращает указатель потока на установленную ранее метку.

u markSupported() возвращает true, если данный поток поддерживает операции mark/reset.

OutputStream

Как и InputStream, OutputStream — абстрактный класс. Он задает модель выходных потоков Java. Все методы этого класса имеют тип void и возбуждают исключение IOException в случае ошибки. Ниже приведен список методов этого класса:

u write(int b) записывает один байт в выходной поток. Обратите внимание — аргумент этого метода имеет тип int, что позволяет вызывать write, передавая ему выражение, при этом не нужно выполнять приведение его типа к byte.

u write(byte b[]) записывает в выходной поток весь указанный массив байтов.

u write(byte b[], int off, int len) записывает в поток часть массива — len байтов, начиная с элемента b[off].

u flush() очищает любые выходные буферы, завершая операцию вывода.

u close() закрывает выходной поток. Последующие попытки записи в этот поток будут возбуждать IOException.

Файловые потоки

FilelnputStream

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

InputStream f0 = new FileInputStream("/autoexec.bat");

File f = new File("/autoexec.bat"):

InputStream f1 = new FileInputStream(f);

Когда создается объект класса FileInputStream, он одновременно с этим открывается для чтения. FileInputStream замещает шесть методов абстрактного класса InputStream. Попытки применить к объекту этого класса методы mark и reset приводят к возбуждению исключения IOException. В приведенном ниже примере показано, как можно читать одиночные байты, массив байтов и поддиапазон массива байтов. В этом примере также показано, как методом available можно узнать, сколько еще осталось непрочитанных байтов, и как с помощью метода skip можно пропустить те байты, которые вы не хотите читать.

import java.io.*;

import java.util.*;

class FileInputStreamS {

public static void main(String args[]) throws Exception {

int size;

InputStream f1 = new FileInputStream("/wwwroot/default.htm");

size = f1.available();

System.out.println("Total Available Bytes: " + size);

System.out.println("First 1/4 of the file: read()");

for (int i=0; i < size/4; i++) {

System.out.print((char) f1.read());

}

System.out.println("Total Still Available: " + f1.available());

System.out.println("Reading the next 1/8: read(b[])");

byte b[] = new byte[size/8];

if (f1.read(b) != b.length) {

System.err.println("Something bad happened");

}

String tmpstr = new String(b, 0, 0, b.length);

System.out.println(tmpstr);

System.out.println("Still Available: " + f1.available());

System.out.println("Skipping another 1/4: skip()");

f1.skip(size/4);

System.out.println( "Still Available: " + f1.available());

System.out.println("Reading 1/16 into the end of array");

if (f1.read(b, b.length-size/16, size/16) != size/16) {

System.err.println("Something bad happened");

}

System.out.println("Still Available: " + f1.available());

f1.close();

}

}

FileOutputStream

У класса FileOutputStream — два таких же конструктора, что и у FileInputStream. Однако, создавать объекты этого класса можно независимо от того, существует файл или нет. При создании нового объекта класс FileOutputStream перед тем, как открыть файл для вывода, сначала создает его.

В очередном нашем примере символы, введенные с клавиатуры, считываются из потока System.in — по одному символу за вызов, до тех пор, пока не заполнится 12-байтовый буфер. После этого создаются три файла. В первый из них, file1.txt, записываются символы из буфера, но не все, а через один — нулевой, второй и так далее. Во второй, file2.txt, записывается весь ввод, попавший в буфер. И наконец в третий файл записывается половина буфера, расположенная в середине, а первая и последняя четверти буфера не выводятся.

import java.io.*;

class FileOutputStreamS {

public static byte getlnput()[] throws Exception {

byte buffer[] = new byte[12];

for (int i=0; i<12; i++) {

buffer[i] = (byte) System.in.read();

}

return buffer;

}

public static void main(String args[]) throws Exception {

byte buf[] = getlnput();

OutputStream f0 = new FileOutputStream("file1.txt");

OutputStream f1 = new FileOutputStream("file2.txt");

OutputStream f2 = new FileOutputStream("file3.txt");

for (int i=0; i < 12; i += 2) {

f0.write(buf[i]);

}

f0.close();

f1.write(buf);

f1.close();

f2.write(buf, 12/4, 12/2);

f2.close();

} }

В настоящее время не существует способа открыть FileOutputStream для дозаписи в конец файла. Если вы открываете файл с помощью конструктора FileOutputStream, прежнее содержимое этого файла теряется. Это — явный недостаток реализации Java.

ByteArraylnputStream

ByteArrayInputStream — это реализация входного потока, в котором в качестве источника используется массив типа byte. У этого класса два конструктора, каждый из которых в качестве первого параметра требует байтовый массив. В приведенном ниже примере создаются два объекта этого типа. Эти объекты инициализируются символами латинского алфавита.

String tmp = "abcdefghijklmnopqrstuvwxyz";

byte b[] = new byte [tmp.length()];

tmp. getBytes(0, tmp.length(), b, 0);

ByteArrayInputStream input1 = new ByteArrayInputStream(b);

ByteArrayInputStream input2 = new ByteArreyInputStream(b,0,3);

ByteArrayOutputStream

У класса ByteArrayOutputStream — два конструктора. Первая форма конструктора создает буфер размером 32 байта. При использовании второй формы создается буфер с размером, заданным параметром конструктора (в приведенном ниже примере — 1024 байта):

OutputStream out0 = new ByteArrayOutputStream();

OutputStream out1 = new ByteArrayOutputStream(1024);

В очередном примере объект ByteArrayOutputStream заполняется символами, введенными с клавиатуры, после чего с ним выполняются различные манипуляции.

import java.io.*;

import java.util.*;

class ByteArrayOutputStreamS {

public static void main(String args[]) throws Exception {

int i;

ByteArrayOutputStream f0 = new ByteArrayOutputStream(12);

System.out.println("Enter 10 characters and a return");

while (f0.size() != 10) {

f0.write( System.in.read());

}

System.out.println("Buffer as a string");

System.out.println(f0.toString());

System.out.println ("Into array");

byte b[] = f0.toByteArray();

for (i=0; i < b.length; i++) {

System.out.print((char) b[i]);

}

System.out.println();

System.out. println("To an OutputStream()");

OutputStream f2 = new File0utput8tream("test.txt");

f0.writeTo(f2);

System.out.println("Doing a reset");

f0. reset();

System.out.println("Enter 10 characters and a return");

while (f0.size() != 10) {

f0.write (System.in.read());

}

System.out.println("Done.");

} }

Заглянув в созданный в этом примере файл test.txt, мы увидим там именно то, что ожидали:

С:\> type test.txt

0123456789

StringBufferlnputStream

StringBufferInputStream идентичен классу ByteArrayInputStream с тем исключением, что внутренним буфером объекта этого класса является экземпляр String, а не байтовый массив.

Кроме того, в Java нет соответствующего ему класса StringBufferedOutputStream. У этого класса есть единственный конструктор:

StringBufferInputStream( String s)

Фильтруемые потоки

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

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

Буферизованные потоки

Буферизованные потоки являются расширением классов фильтруемых потоков, в них к потокам ввода-вывода присоединяется буфер в памяти.

Этот буфер выполняет две основные функции:

u Он дает возможность исполняющей среде java проделывать за один раз операции ввода-вывода с более чем одним байтом, тем самым повышая производительность среды.

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

BufferedInputStream

Буферизация ввода-вывода — общепринятый способ оптимизации таких операций. Класс BufferedlnputStream в Java дает возможность «окружить» любой объект InputStream буферизованным потоком, и, тем самым, получить выигрыш в производительности. У этого класса два конструктора. Первый из них:

BufferedInputStream(InputStream in)

создает буферизованный поток, используя для него буфер длиной 32 байта. Во втором:

BufferedInputStream(InputStream in, int size)

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

BufferedOutputStream

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

Первая форма конструктора этого класса:

BufferedOutputStream(OutputStream out)

создает поток с буфером размером 32 байта. Вторая форма:

BufferedOutputStream(OutputStream out, int size)

позволяет задавать требуемый размер буфера.

PushbacklnputStream

Одно из необычных применений буферизации — реализация операции pushback (вернуть назад). Pushback применяется к InputStream для того, чтобы после прочтения символа вернуть его обратно во входной поток.

Однако возможности класса PushbacklnputStream весьма ограничены — любая попытка вернуть в поток более одного символа приведет к немедленному возбуждению исключения IOException. У этого класса — единственный конструктор:

PushbackInputStream(InputStream in)

Помимо уже хорошо нам знакомых методов класса InputStream, PushbacklnputStream содержит метод unread(int ch), который возвращает заданный аргументом символ ch во входной поток.

SequencelnputStream

Класс SequencelnputStream поддерживает новую возможность слияния нескольких входных потоков в один.

В конструкторе класса SequenceInputStream в качестве параметра используется либо два объекта InputStream, либо перечисление, содержащее коллекцию объектов InputStream:

SequenceInputStream(Enumeration e) SequenceInputStream(InputStream s0, InputStream s1)

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

PrintStream

Класс PrintStream предоставляет все те утилиты форматирования, которые мы использовали в примерах для вывода через файловые дескрипторы пакета System. У класса PrintStream два конструктора: PrintStream(OutputStream out) и PrintStream(OutputStream out, boolean autoflush). Параметр autoflush второго из них указывает, должна ли исполняющая среда Java автоматически выполнять операцию очистки буфера над выходным потоком.

В Java-объектах PrintStream есть методы print и println, «умеющие» работать с любыми типами данных, включая Object. Если в качестве аргумента этих методов используется не один из примитивных типов, то они вызывают метод toString класса Object, после чего выводят полученный результат.

В настоящее время в Java отсутствуют средства для форматирования выводимых данных простых типов, например, типов int и float. В C++ предусмотрены функции для форматирования чисел с плавающей точкой, позволяющие, например, задать вид вывода, при котором в напечатанном числе будет четыре цифры до десятичной точки и три — после.

Глава 30.
Сетевые средства

Java поддерживает протокол TCP/IP, во-первых, расширяя свой интерфейс потоков ввода-вывода, и во вторых, добавляя возможности, необходимые для построения объектов ввода-вывода при работе в сети.

InetAddress

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

Фабричные методы

В классе InetAddress нет доступных пользователю конструкторов. Для создания объектов этого класса нужно воспользоваться одним из его фабричных методов. Фабричные методы — это обычные статические методы, которые возвращают ссылку на объект класса, которому они принадлежат. В данном случае, у класса InetAddress есть три метода, которые можно использовать для создания представителей. Это методы getLocalHost, getByName и getAllByName.

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

InetAddress Address = InetAddress.getLocalHost();

System.out.println(Address);

Address = InetAddress.getByName("mailhost");

System.out.println(Address);

InetAddress SW[] = InetAddress.getAllByNarne(“www.starwave.com");

System.out.println(SW);

У класса InetAddress также есть несколько нестатических методов, которые можно использовать с объектами, возвращаемыми только что названными фабричными методами:

u getHostName() возвращает строку, содержащую символическое имя узла, соответствующее хранящемуся в данном объекте адресу Internet.

u getAddress() возвращает байтовый массив из четырех элементов, в котором в порядке, используемом в сети, записан адрес Internet, хранящийся в данном объекте.

u toString() возвращает строку, в которой записано имя узла и его адрес.

Дейтаграммы

Дейтаграммы, или пакеты протокола UDP (User Datagram Protocol) — это пакеты информации, пересылаемые в сети по принципу «fire-and-forget» (выстрелил и забыл). Если вам надо добиться оптимальной производительности, и вы в состоянии минимизировать затраты на проверку целостности информации, пакеты UDP могут оказаться весьма полезными.

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

Java реализует дейтаграммы на базе протокола UDP, используя для этого два класса. Объекты класса DatagramPacket представляют собой контейнеры с данными, a DatagramSocket — это механизм, используемый при передаче и получении объектов DatagramPacket.

Сокеты «для клиентов»

TCP/IP-сокеты используются для реализации надежных двунаправленных, ориентированных на работу с потоками соединений точка-точка между узлами Internet. Сокеты можно использовать для соединения системы ввода-вывода Java с программами, которые могут выполняться либо на локальной машине, либо на любом другом узле Internet. В отличие от класса DatagramSocket, объекты класса Socket реализуют высоконадежные устойчивые соединения между клиентом и сервером.

В пакете java.net классы Socket и ServerSocket сильно отличаются друг от друга. Первое отличие в том, что ServerSocket ждет, пока клиент не установит с ним соединение, в то время, как обычный Socket трактует недоступность чего-либо, с чем он хочет соединиться, как ошибку. Одновременно с созданием объекта Socket устанавливается соединение между узлами Internet. Для создания сокетов вы можете использовать два конструктора:

u Socket(String host, int port) устанавливает соединение между локальной машиной и указанным портом узла Internet, имя которого было передано конструктору. Этот конструктор может возбуждать исключения UnknownHostException и IOException.

u Socket(InetAddress address, int port) выполняет ту же работу, что и первый конструктор, но узел, с которым требуется установить соединение, задается не строкой, а объектом InetAddress. Этот конструктор может возбуждать только IOException.

Из объекта Socket в любое время можно извлечь информацию об адресе Internet и номере порта, с которым он соединен. Для этого служат следующие методы:

u getInetAddressQ возвращает объект InetAddress, связанный с данным объектом Socket.

u getPort() возвращает номер порта на удаленном узле, с которым установлено соединение.

u getLocalPort() возвращает номер локального порта, к которому присоединен данный объект.

После того, как объект Socket создан, им можно воспользоваться для того, чтобы получить доступ к связанным с ним входному и выходному потокам. Эти потоки используются для приема и передачи данных точно так же, как и обычные потоки ввода-вывода:

u getInputStream() возвращает InputStream, связанный с данным объектом.

u getOutputStream() возвращает OutputStream, связанный с данным объектом.

u close() закрывает входной и выходной потоки объекта Socket.

Приведенный ниже очень простой пример открывает соединение с портом 880 сервера «timehost» и выводит полученные от него данные.

import java.net.*;

import java.io.*;

class TimeHost {

public static void main(String args[]) throws Exception {

int c;

Socket s = new Socket("timehost.starwave.com",880);

InputStream in = s.getInputStream();

while ((c = in.read()) != -1) {

System.out.print( (char) c);

}

s.close();

} }

Сокеты «для серверов»

Как уже упоминалось ранее, Java поддерживает сокеты серверов. Для создания серверов Internet надо использовать объекты класса ServerSocket. Когда вы создаете объект ServerSocket, он регистрирует себя в системе, говоря о том, что он готов обслуживать соединения клиентов. У этого класса есть один дополнительный метод accept(), вызов которого блокирует подпроцесс до тех пор, пока какой-нибудь клиент не установит соединение по соответствующему порту. После того, как соединение установлено, метод accept() возвращает вызвавшему его подпроцессу обычный объект Socket.

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

u ServerSocket(int port) создает сокет сервера для заданного порта.

u ServerSocket(int port, int count) создает сокет сервера для заданного порта. Если этот порт занят, метод будет ждать его освобождения максимум count миллисекунд.

URL

URL (Uniform Resource Locators — однородные указатели ресурсов) — являются наиболее фундаментальным компонентом «Всемирной паутины». Класс URL предоставляет простой и лаконичный программный интерфейс для доступа к информации в Internet с помощью URL.

У класса URL из библиотеки Java — четыре конструктора. В наиболее часто используемой форме конструктора URL адрес ресурса задается в строке, идентичной той, которую вы используете при работе с браузером:

URL(String spec)

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

URL(String protocol, String host, int port, String file)

URL(String protocol, String host, String file)

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

URL(URL context, String spec)

В приведенном ниже примере создается URL, адресующий www-страницу (поставьте туда свой адрес), после чего программа печатает свойства этого объекта.

import java.net.URL;

class myURL {

public static void main(String args[]) throws Exception {

URL hp = new URL("http://coop.chuvashia.edu");

System.out. println("Protocol: " + hp.getProtocol());

System.out.printin("Port: " + hp.getPort());

System.out.println("Host: " + hp.getHost());

System.out.println("File: " + hp.getFile());

System.out.println("Ext: " + hp.toExternaLForm());

} }

Для того, чтобы извлечь реальную информацию, адресуемую данным URL, необходимо на основе URL создать объект URLConnection, воспользовавшись для этого методом openConnection().

URLConnection

URLConnection — объект, который мы используем либо для проверки свойств удаленного ресурса, адресуемого URL, либо для получения его содержимого. В приведенном ниже примере мы создаем URLConnection с помощью метода openConnection, вызванного с объектом URL. После этого мы используем созданный объект для получения содержимого и свойств документа.

import java.net.*;

import java.io.*;

class localURL {

public static void main(String args[]) throws Exception {

int c;

URL hp = new URL("http", "127.0.0.1", 80, "/");

URLConnection hpCon = hp.openConnection();

System.out.println("Date: " + hpCon.getDate());

System.out.println("Type: " + hpCon.getContentType());

System.out.println("Exp: " + hpCon.getExpiration());

System.out.println( "Last M: " + hpCon.getLastModified());

System.out.println("Length: " + hpCon.getContentLength());

if (hpCon.getContentLength() > 0) {

System.out.println("=== Content ===");

InputStream input = hpCon.getInputStream();

int i=hpCon.getContentLength();

while (((c = input. read()) != -1) && (--i > 0)) {

System.out.print((char) c);

}

input.close();

}

else {

System.out.println("No Content Available");

}

} }

Эта программа устанавливает HTTP-соединение с локальным узлом по порту 80 (у вас на машине должен быть установлен Web-сервер) и запрашивает документ по умолчанию, обычно это — index.html. После этого программа выводит значения заголовка, запрашивает и выводит содержимое документа.

Сетевые классы Java предоставляют ясный и простой в использовании интерфейс для работы в Internet. Фундамент, заложенный в пакете java.net — хорошая база для дальнейшего развития, которая позволит Java эволюционировать вместе с Internet.

Глава 31.
Апплеты

Апплеты — это маленькие приложения, которые размещаются на серверах Internet, транспортируются клиенту по сети, автоматически устанавливаются и запускаются на месте, как часть документа HTML. Когда апплет прибывает к клиенту, его доступ к ресурсам ограничен.

Ниже приведен исходный код канонической программы HelloWorld, оформленной в виде апплета:

import java.awt.*;

import java.applet.*;

public class HelloWorldApplet extends Applet {

public void paint(Graphics g) {

g.drawString("Hello World!", 20, 20);

} }

Этот апплет начинается двумя строками, которые импортируют все пакеты иерархий java.applet и java.awt. Дальше в нашем примере присутствует метод paint, замещающий одноименный метод класса Applet. При вызове этого метода ему передается аргумент, содержащий ссылку на объект класса Graphics. Последний используется для прорисовки нашего апплета. С помощью метода drawString, вызываемого с этим объектом типа Graphics, в позиции экрана (20,20) выводится строка «Hello World».

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

<applet code="HelloWorldApplet" width=200 height=40>

</applet>

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

Тег HTML <Applet>

Тег <applet> используется для запуска апплета как из HTML-документа, так и из программы appletviewer. Программа appletviewer выполняет каждый найденный ей тег <applet> в отдельном окне, в то время как браузеры позволяют разместить на одной странице несколько апплетов. Синтаксис тэга <APPLET> таков:

<APPLET

CODE = appletFile

OBJECT = appletSerialFile

WIDTH = pixels

HEIGHT = pixels

[ARCHIVE = jarFiles]

[CODEBASE = codebaseURL]

[ALT = alternateText]

[NAME = appletInstanceName]

[ALIGN = alignment]

[VSPACE = pixels]

[HSPACE = pixels]

>

[< PARAM NAME = AttributeNamel VALUE = AttributeValuel >]

[< PARAM NAME = AttributeName2 VALUE = AttributeValue2 >]

[HTML-текст, отображаемый при отсутствии поддержки Java]

</APPLET>

CODE = appletClassFile

CODE — обязательный атрибут, задающий имя файла, в котором содержится оттранслированный код апплета. Имя файла задается относительно codebase, то есть либо от текущего каталога, либо от каталога, указанного в атрибуте CODEBASE. В Java 1.1 вместо этого атрибута может использоваться атрибут OBJECT.

OBJECT = appletClassSerialFile

Указывает имя файла, содержащего сериализованный апплет, из которого последний будет восстановлен. При запуске определяемого таким образом апплета должен вызываться не метод init(), а метод start(). Для апплета необходимо задать либо атрибут CODE, либо атрибут OBJECT, но задавать эти атрибуты одновременно нельзя.

WIDTH = pixels
HEIGHT = pixels

WIDTH и HEIGHT — обязательные атрибуты, задающие начальный размер видимой области апплета.

ARCHIVE = jarFiles

Задает список jar-файлов (разделяется запятыми), которые предварительно загружаются в Web-броузер. В этих архивных файлах могут содержаться файлы классов, изображения, звуки и любые другие ресурсы, необходимые апплету. Для создания архивов используется утилита JAR, синтаксис вызова которой напоминает вызов команды TAR Unix:

c:\> jar cf soundmap.jar *.class image.gif sound.wav

Очевидно, что передача сжатых jar-файлов повышает эффективность работы. Поэтому многие средства разработки (Lotus JavaBeans, Borland JBuilder) уже имеют средства для публикации апплетов в виде jar-файлов.

CODEBASE = codebaseURL

CODEBASE — необязательный атрибут, задающий базовый URL кода апплета, являющийся каталогом, в котором будет выполняться поиск исполняемого файла апплета (задаваемого в признаке CODE). Если этот атрибут не задан, по умолчанию используется каталог данного HTML-документа. CODEBASE не обязательно должен указывать на тот же узел, с которого был загружен HTML-документ.

ALT = alternateAppletText

Признак ALT — необязательный атрибут, задающий короткое текстовое сообщение, которое должно быть выведено в том случае, если используемый браузер распознает синтаксис тега <applet>, но выполнять апплеты не умеет. Это не то же самое, что HTML-текст, который можно вставлять между <applet> и </applet> для браузеров, вообще не поддерживающих апплетов.

NAME = appletInstanceName

NAME — необязательный атрибут, используемый для задания имени для данного экземпляра апплета. Присвоение апплетам имен необходимо для того, чтобы другие апплеты на этой же странице могли находить их и общаться с ними. Для того, чтобы получить доступ к подклассу MyApplet класса Applet с именем «Duke», нужно написать:

MyApplet a = getAppletContext().getApplet("Duke");

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

ALIGN = alignment

ALIGN — необязательный атрибут, задающий стиль выравнивания апплета. Этот атрибут трактуется так же, как в теге IMG, возможные его значения — LEFT, RIGHT, TOP, TEXTTOP, MIDDLE, ABSMIDDLE, BASELINE, BOTTOM, ABSBOTTOM.

VSPACE = pixels
HSPACE = PIXELS

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

PARAM NAME = appletAttribute1 VALUE = value1

Этот тег дает возможность передавать из HTML-страницы апплету необходимые ему аргументы. Апплеты получают эти атрибуты, вызывая метод getParameter().

Передача параметров

getParameter(String)

Метод getParameter возвращает значение типа String, соответствующее указанному имени параметра. Если вам в качестве параметра требуется значение какого-либо другого типа, вы должны преобразовать строку-параметр самостоятельно. Вы сейчас увидите некоторые примеры использования метода getParameter для извлечения параметров из приведенного ниже примера:

<applet code=Testing width=40 height=40>

<param name=fontName value=Univers>

<param name=fontSize value=14>

<param name=leading value=2>

<param name=accountEnabled value=true>

Ниже показано, как извлекается каждый из этих параметров:

String FontName = getParameter("fontName");

String FontSize = Integer.parseInt(getParameter("fontSize"));

String Leading = Float.valueOf(getParameter("leading"));

String PaidUp = Boolean.valueOf(getParameter("accountEnabled"));

Контекст апплета

getDocumentBase и getCodeBase

Возможно, вы будете писать апплеты, которым понадобится явно загружать данные и текст. Java позволяет апплету загружать данные из каталога, в котором располагается HTML-документ, запустивший апплет (база документа — getDocumentBase), и из каталога, из которого был загружен class-файл с кодом апплета (база кода — getCodeBase).

AppletContext и showDocument

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

Отладочная печать

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

System.out.println(“Hello there, welcome to Java”);

Сообщения на консоли очень удобны, поскольку консоль обычно не видна пользователям апплета, и в ней достаточно места для нескольких сообщений. В браузере Netscape консоль Java доступна из меню Options, пункт «Show Java Console».

Метод showStatus выводит текст в статусной области программы арpletviewer или браузера с поддержкой Java. В статусной области можно вывести только одну строку сообщения.

Порядок инициализации апплета

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

init

Метод init вызывается первым. В нем вы должны инициализировать свои переменные.

start

Метод start вызывается сразу же после метода init. Он также используется в качестве стартовой точки для возобновления работы после того, как апплет был остановлен. В то время, как метод init вызывается только однажды — при загрузке апплета, start вызывается каждый раз при выводе HTML-документа, содержащего апплет, на экран. Так, например, если пользователь перейдет к новой WWW-странице, а затем вернется назад к странице с апплетом, апплет продолжит работу с метода start.

paint

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

update

Используемый по умолчанию метод update класса Applet сначала закрашивает апплет цветом фона по умолчанию, после чего вызывает метод paint. Если вы в методе paint заполняете фон другим цветом, пользователь будет видеть вспышку цвета по умолчанию при каждом вызове метода update — то есть, всякий раз, когда вы перерисовываете апплет. Чтобы избежать этого, нужно заместить метод update. В общем случае нужно выполнять операции рисования в методе update, а в методе paint, к которому будет обращаться AWT, просто вызвать update.

stop

Метод stop вызывается в тот момент, когда браузер покидает HTML-документ, содержащий апплет. При вызове метода stop апплет еще работает. Вы должны использовать этот метод для приостановки тех подпроцессов, работа которых необязательна при невидимом апплете. После того, как пользователь снова обратится к этой странице, вы должны будете возобновить их работу в методе start.

destroy

Метод destroy вызывается тогда, когда среда (например, браузер Netscape) решает, что апплет нужно полностью удалить из памяти. В этом методе нужно освободить все ресурсы, которые использовал апплет.

Перерисовка

Возвратимся к апплету HelloWorldApplet. В нем мы заместили метод paint, что позволило апплету выполнить отрисовку. В классе Applet предусмотрены дополнительные методы рисования, позволяющие эффективно закрашивать части экрана. При разработке первых апплетов порой непросто понять, почему метод update никогда не вызывается. Для инициации update предусмотрены три варианта метода repaint.

repaint

Метод repaint используется для принудительного перерисовывания апплета. Этот метод, в свою очередь, вызывает метод update. Однако, если ваша система медленная или сильно загружена, метод update может и не вызваться. Близкие по времени запросы на перерисовку могут объединяться AWT, так что метод update может вызываться спорадически. Если вы хотите добиться ритмичной смены кадров изображения, воспользуйтесь методом repaint(time) — это позволит уменьшить количество кадров, нарисованных не вовремя.

repaint(time)

Вы можете вызывать метод repaint, устанавливая крайний срок для перерисовки (этот период задается в миллисекундах относительно времени вызова repaint).

repaint(x, y, w, h)

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

repaint(time, x, у, w, h)

Этот метод — комбинация двух предыдущих.

Задание размеров графических изображений

Графические изображения вычерчиваются в стандартной для компьютерной графики системе координат, в которой координаты могут принимать только целые значения, а оси направлены слева направо и сверху вниз. У апплетов и изображений есть метод size, который возвращает объект Dimension. Получив объект Dimension, вы можете получить и значения его переменных width и height:

Dimension d = size();

System.out.println(d. width + "," + d.height);

Простые методы класса Graphics

У объектов класса Graphics есть несколько простых функций рисования. Каждую из фигур можно нарисовать заполненной, либо прорисовать только ее границы. Каждый из методов drawRect, drawOval, fillRect и fillOval вызывается с четырьмя параметрами: int x, int y, int width и int height. Координаты х и у задают положение верхнего левого угла фигуры, параметры width и height определяют ее границы.

drawLine
drawline(int x1, int у1, int х2, int у2)

Этот метод вычерчивает отрезок прямой между точками с координатами (х1,у1) и (х2,у2). Эти линии представляют собой простые прямые толщиной в 1 пиксель. Поддержка разных перьев и разных толщин линий не предусмотрена.

drawArc и fillArc

Форма методов drawArc и fillArc следующая:

drawArc(int x, int у, int width, int height, int startAngle, int sweepAngle)

Эти методы вычерчивают (fillArc заполняет) дугу, ограниченную прямоугольником (x, y, width, height), начинающуюся с угла startAngle и имеющую угловой размер sweepAngle. Ноль градусов соответствует положению часовой стрелки на 3 часа, угол отсчитывается против часовой стрелки (например, 90 градусов соответствуют 12 часам, 180 — 9 часам, и так далее).

drawPolyson и fillPolyson

Прототипы для этих методов:

drawPolygon(int[], int[], int)

fillPolygon(int[], int[], int)

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

Цвет

Цветовая система AWT разрабатывалась так, чтобы была возможность работы со всеми цветами. После того, как цвет задан, Java отыскивает в диапазоне цветов дисплея тот, который ему больше всего соответствует. Вы можете запрашивать цвета в той семантике, к которой привыкли — как смесь красного, зеленого и голубого, либо как комбинацию оттенка, насыщенности и яркости. Вы можете использовать статические переменные класса Color.black для задания какого-либо из общеупотребительных цветов — black, white, red, green, blue, cyan, yellow, magenta, orange, pink, gray, darkGray и lightGray.

Для создания нового цвета используется один из трех описанных ниже конструкторов.

Color(int, int, int)

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

Color(int)

У этого конструктора — один целочисленный аргумент, в котором в упакованном виде заданы красный, зеленый и голубой компоненты цвета. Красный занимает биты 16-23, зеленый — 8-15, голубой — 0-7.

Color(float, float, float)

Последний из конструкторов цвета, Color(float, float, float), принимает в качестве параметров три значения типа float (в диапазоне от 0.0 до 1.0) для красного, зеленого и голубого базовых цветов.

Методы класса Color

HSBtoRGB(float, float, float)
RGBtoHSB(int, int, int, float[1)

HSBtoRGB преобразует цвет, заданный оттенком, насыщенностью и яркостью (HSB), в целое число в формате RGB, готовое для использования в качестве параметра конструктора Color(int). RGBtoHSB преобразует цвет, заданный тремя базовыми компонентами, в массив типа float со значениями HSB, соответствующими данному цвету.

Цветовая модель HSB (Hue-Saturation-Brightness, оттенок-насыщенность-яркость) является альтернативой модели Red-Green-Blue для задания цветов. В этой модели оттенки можно представить как круг с различными цветами (оттенок может принимать значения от 0.0 до 1.0, цвета на этом круге идут в том же порядке, что и в радуге — красный, оранжевый, желтый, зеленый, голубой, синий, фиолетовый). Насыщенность (значение в диапазоне от 0.0 до 1.0) — это шкала глубины цвета, от легкой пастели до сочных цветов. Яркость — это также число в диапазоне от 0.0 до 1.0, причем меньшие значения соответствуют более темным цветам, а большие — более ярким.

getRedQ, getGreenO, setBlue()

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

getRGB()

Этот метод возвращает целое число, в котором упакованы значения базовых компонентов цвета, причем

red = Oxff & (getRGB() >> 16);

green = Oxff & (getRGB() >> 8);

blue = Oxff & getRGB();

setPaintMode() и setXORMode(Color)

Режим отрисовки paint — используемый по умолчанию метод заполнения графических изображений, при котором цвет пикселей изменяется на заданный. XOR устанавливает режим рисования, когда результирующий цвет получается выполнением операции XOR (исключающее или) для текущего и указанного цветов (особенно полезно для анимации).

Шрифты

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

/*

* <applet code="WhatFontsAreHere" width=100 height=40>

* </applet>

*

*/

import java.applet.*;

import java.awt.*;

public class WhatFontsAreHere extends Applet {

public void init() {

String FontList[];

FontList = getToolkit().getFontList();

for (int i=0; i < FontList.length; i++) {

System.out.println(i + ": " + FontList[i]);

}

} }

drawString

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

Использование шрифтов

Конструктор класса Font создает новый шрифт с указанным именем, стилем и размером в пунктах:

Font StrongFont = new Font("Helvetica", Font.BOLD|Font.ITALIC, 24);

В настоящее время доступны следующие имена шрифтов: Dialog, Helvetica, TimesRoman, Courier и Symbol. Для указания стиля шрифта внутри данного семейства предусмотрены три статические переменные: Font.PLAIN, Font.BOLD и Font.ITALIC, что соответствует обычному стилю, курсиву и полужирному.

Теперь давайте посмотрим на несколько дополнительных методов.

getFamily и getName

Метод getFamily возвращает строку с именем семейства шрифтов. С помощью метода getName можно получить логическое имя шрифта.

getSize

Этот метод возвращает целое число, представляющее собой размер шрифта в пунктах.

getStyle

Этот метод возвращает целое число, соответствующее стилю шрифта. Полученный результат можно побитово сравнить со статическими переменными класса Font: PLAIN, BOLD и ITALIC.

isBold, isItalic, isPlain

Эти методы возвращают true в том случае, если стиль шрифта — полужирный (bold), курсив (italic) или обычный (plain), соответственно.

Позиционирование и шрифты

В Java используются различные шрифты, а класс FontMetrics позволяет программисту точно задавать положение выводимого в апплете текста. Прежде всего нам нужно понять кое-что из обычной терминологии, употребляемой при работе со шрифтами:

u Высота (height) — размер от верхней до нижней точки самого высокого символа в шрифте.

u Базовая линия (baseline) — линия, по которой выравниваются нижние границы символов (не считая снижения (descent)).

u Подъем (ascent) — расстояние от базовой линии до верхней точки символа.

u Снижение (descent) — расстояние от базовой линии до нижней точки символа.

Ниже приведены некоторые методы класса FontMetrics:

stringWidth

Этот метод возвращает длину заданной строки для данного шрифта.

bytesWidth, charsWidth

Эти методы возвращают ширину указанного массива байтов для текущего шрифта.

getAscent, getDescent, getHeight

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

getMaxAscent и getMaxDescent

Эти методы служат для получения максимальных подъема и снижения всех символов в шрифте.

Центрирование текста

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

/*

* <applet code="HelloWorld" width=200 height=100>

* </applet>

*

*/

import java.applet.*;

import java.awt.*;

public class HelloWorld extends Applet {

final Font f = new Font("Helvetica", Font.BOLD, 18);

public void paint(Graphics g) {

Dimension d = this.size();

g.setColor(Color.white);

g.fillRect(0,0,d.width,d.height);

g.setColor(Color.black);

g.setFont(f);

drawCenteredString("Hello World!", d.width, d.height, g);

g.drawRect(0,0,d.width-1,d.height-1);

}

public void drawCenteredString(String s, int w, int h, Graphics g) {

FontMetrics fm = g.getFontMetrics();

int x = (w - fm.stringWidth(s)) / 2;

int y = (fm.getAscent() + (h - (fm.getAscent() + fm.getDescent()))/2);

g.drawString(s, x, y);

} }

Глава 32.
Набор абстракций для работы с окнами

Трудность при создании независимой от платформы библиотеки заключается в том, что ее разработчикам либо приходится требовать, чтобы все приложения на всех платформах вели себя и выглядели одинаково, либо для поддержки, скажем, трех различных разновидностей интерфейса приходится писать в три раза больше кода. Существуют два взгляда на эту проблему.Один подход заключается в том, что упор делается на графику низкого уровня — рисование пикселей, при этом разработчики библиотеки сами заботятся о внешнем виде каждого компонента. При другом подходе создаются абстракции, подходящие для библиотек каждой из операционных систем, и именно «родные» пакеты данной операционной системы служат подъемной силой для архитектурно-нейтральной библиотеки на каждой из платформ. В Java при создании библиотеки Abstraction Window Toolkit (AWT) выбран второй подход.

Компоненты

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

Container

Container — это абстрактный подкласс класса Component, определяющий дополнительные методы, которые дают возможность помещать в него другие компоненты, что дает возможность построения иерархической системы визуальных объектов. Container отвечает за расположение содержащихся в нем компонентов с помощью интерфейса LayoutManager.

Panel

Класс Panel — это очень простая специализация класса Container. В отличие от последнего, он не является абстрактным классом. Поэтому о Panel можно думать, как о допускающем рекурсивную вложенность экранном компоненте. С помощью метода add в объекты Panel можно добавлять другие компоненты. После того, как в него добавлены какие-либо компоненты, можно вручную задавать их положение и изменять размер с помощью методов move, resize и reshape класса Component.

В предыдущей главе мы уже использовали один из подклассов Panel — Applet. Каждый раз, когда мы создавали Applet, методы paint и update рисовали его изображение на поверхности объекта Panel. Прежде, чем мы углубимся в методы Panel, давайте познакомимся с компонентом Canvas, который можно вставлять в пустую Panel при работе с объектом Applet.

Canvas

Основная идея использования объектов Canvas в том, что они являются семантически свободными компонентами. Вы можете придать объекту Canvas любое поведение и любой желаемый внешний вид. Его имя подразумевает, что этот класс является пустым холстом, на котором вы можете «нарисовать» любой компонент — такой, каким вы его себе представляете.

Произведем от Canvas подкласс GrayCanvas, который будет просто закрашивать себя серым цветом определенной насыщенности. Наш апплет будет создавать несколько таких объектов, каждый со своей интенсивностью серого цвета.

/* <applet code = “PanelDemo”

width=300

height=300>

</applet>

*/

import java.awt.*;

import java.applet.*;

class GrayCanvas extends Canvas {

Color gray;

public GrayCanvas(float g) {

gray = new Color(g, g, g);

}

public void paint(Graphics g) {

Dimension size = size();

g.setColor(gray);

g.fillRect(0, 0, size.width, size.height);

g.setColor(Color.black);

g.drawRect(0, 0, size.width-1, size.height-1);

} }

public class PanelDemo extends Applet {

static final int n = 4;

public void init() {

setLayout(null);

int width = Integer.parseInt(getParameter("width"));

int height = Integer.parseInt(getParameter("height"));

for (int i = 0; i < n; i++) {

for (int j = 0; j < n; j++) {

float g = (i * n + j) / (float) (n * n);

Canvas с = new GrayCanvas(g);

add(c);

c.resize(width / n, height / n);

c.move(i * width / n, j * height / n);

}

}

} }

Мы устанавливаем размер каждого из объектов Canvas на основе значения, полученного с помощью метода size, который возвращает объект класса Dimension. Для размещения объектов Canvas в нужные места используются методы resize и move. Такой способ станет очень утомительным, когда мы перейдем к более сложным компонентам и более интересным вариантам расположения. А пока в нашем апплете для выключения упомянутого механизма использован вызов метода setLayout(null).

Label

Функциональность класса Label сводится к тому, что он знает, как нарисовать объект String — текстовую строку, выровняв ее нужным образом. Шрифт и цвет, которыми отрисовывается строка метки, являются частью базового определения класса Component. Для работы с этими атрибутами предусмотрены пары методов getFont/setFont и getForeground/setForeground. Задать или изменить текст строки после создания объекта с помощью метода setText. Для задания режимов выравнивания в классе Label определены три константы — LEFT, RIGHT и CENTER. Ниже приведен пример, в котором создаются три метки, каждая — со своим режимом выравнивания.

/* <applet code = “LabelDemo” width=100 height=100>

</applet>

*/

import java.awt.*;

import java.applet.*;

public class LabelDemo extends Applet {

public void init() {

setLayout(null);

int width = Integer.parseInt(getParameter("width"));

int height = Integer.parseInt(getParameter("height"));

Label left = new Label("Left", Label.LEFT);

Label right = new Label("Right", Label.RIGHT);

Label center = new Label("Center", Label.CENTER);

add(left);

add(right);

add(center);

left.reshape(0, 0, width, height / 3);

right.reshape(0, height / 3, width, height / 3);

center.reshape(0, 2 * height / 3, width, height / 3);

} }

На этот раз, чтобы одновременно переместить и изменить размер объектов Label, мы использовали метод reshape. Ширина каждой из меток равна полной ширине апплета, высота — 1/3 высоты апплета.

Button

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

/* <applet code = “ButtonDemo” width=100 height=100>

</applet>

*/

import java.awt.*;

import java.applet.*;

public class ButtonDemo extends Applet {

public void init() {

setLayout(null);

int width = Integer.parseInt(getParameter("width"));

int height = Integer.parseInt(getParameter("height"));

Button yes = new Button("Yes");

Button no = new Button("No");

Button maybe = new Button("Undecided");

add(yes);

add(no);

add(maybe);

yes.reshape(0, 0, width, height / 3);

no.reshape(0, height / 3, width, height / 3);

maybe.reshape(0, 2 * height / 3, width, height / 3);

} }

Checkbox

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

/* <applet code = “CheckBoxDemo” width=120 height=100>

</applet>

*/

import java.awt.*;

import java.applet.*;

public class CheckboxDemo extends Applet {

public void init() {

setLayout(null);

int width = Integer.parseInt(getParameter("width"));

int height = Integer.parseInt(getParameter("height"));

Checkbox win95 = new Checkbox("Windows 95/98", null, true);

Checkbox Solaris = new Checkbox("Solaris 2.5");

Checkbox mac = new Checkbox("MacOS 7.5");

add(win95);

add(solaris);

add(mac);

win95.reshape(0, 0, width, height / 3);

Solaris.reshape(0, height / 3, width, height / 3);

mac.reshape(0, 2 * height / 3, width, height / 3);

} }

CheckboxGroup

Второй параметр конструктора Checkbox (в предыдущем примере мы ставили там null) используется для группирования нескольких объектов Checkbox. Для этого сначала создается объект CheckboxGroup, затем он передается в качестве параметра любому количеству конструкторов Checkbox, при этом предоставляемые этой группой варианты выбора становятся взаимоисключающими (только один может быть задействован). Предусмотрены и методы, которые позволяют получить и установить группу, к которой принадлежит конкретный объект Checkbox — getCheckboxGroup и setCheckboxGroup. Вы можете пользоваться методами getCurrent и setCurrent для получения и установки состояния выбранного в данный момент объекта Checkbox. Ниже приведен пример, отличающийся от предыдущего тем, что теперь различные варианты выбора в нем взаимно исключают друг друга.

/* <applet code = “CheckboxGroupDemo” width=120 height=100>

</applet>

*/

import java.awt.*;

import java.applet.*;

public class CheckboxGroupDemo extends Applet {

public void init() {

setLayout(null);

int width = Integer.parseInt(getParameter("width"));

int height = Integer.parseInt(getParameter("height"));

CheckboxGroup g = new CheckboxGroup();

Checkbox win95 = new Checkbox("Windows 95/98", g, true);

Checkbox solaris = new Checkbox("Solaris 2.5", g, false);

Checkbox mac = new Checkbox("MacOS 7.5", g, false);

add(win95);

add(solaris);

add(mac);

win95.reshape(0, 0, width, height / 3);

solaris. reshape(0, height / 3, width, height / 3);

mac.reshape(0, 2 * height / 3, width, height / 3);

} }

Choice

Класс Choice (выбор) используется при создании раскрывающихся списочных меню (выпадающих списков типа ComboBox в Windows). Компонент Choice занимает ровно столько места, сколько требуется для отображения выбранного в данный момент элемента, когда пользователь щелкает мышью на нем, раскрывается меню со всеми элементами, в котором можно сделать выбор. Каждый элемент меню — это строка, которая выводится, выровненная по левой границе. Элементы меню выводятся в том порядке, в котором они были добавлены в объект Choice. Метод countItems возвращает количество пунктов в меню выбора. Вы можете задать пункт, который выбран в данный момент, с помощью метода select, передав ему либо целый индекс (пункты меню перечисляются с нуля), либо строку, которая совпадает с меткой нужного пункта меню. Аналогично, с помощью методов getSelectedItem и getSelectedIndex можно получить, соответственно, строку-метку и индекс выбранного в данный момент пункта меню. Вот очередной простой пример, в котором создается два объекта Choice.

/* <applet code = “ChoiceDemo” width=200 height=100>

</applet>

*/

import java.awt.*;

import java.applet.*;

public class ChoiceDemo extends Applet {

public void init() {

setLayout(null);

int width = Integer.parseInt(getParameter("width"));

int height = Integer.parseInt(getParameter("height"));

Choice os = new Choice();

Choice browser = new Choice();

os.addItem("Windows 95/98");

os.addItem("Solaris 2.5");

os.addItem(“MacOS 7.5");

browser.addItem("Netscape Navigator 3.0");

browser.addItem("Netscape Communicator 4.5");

browser.addItem(“Internet Explorer 3.0");

browser.addItem("Mosaic 3.0");

browser.addItem("Lynx 2.4");

browser.select("Netscape Communicator 4.5");

add(os);

add(browser);

os.reshape(0, 0, width, height / 2);

browser.reshape(0, height / 2, width, height / 2);

} }

List

Класс List представляет собой компактный список с возможностью выбора нескольких вариантов и с прокруткой (аналог ListBox в Windows). Ниже приведен пример с двумя списками выбора, один из которых допускает выбор нескольких элементов, а второй — выбор единственного элемента.

/* <applet code = “ListDemo” width=200 height=100>

</applet>

*/

import java.awt.*;

import java.applet.*;

public class ListDemo extends Applet {

public void init() { setLayout(null);

int width = Integer.parseInt(getParameter("width"));

int height = Integer.parseInt(getParameter("height"));

List os = new List(0, true);

List browser = new List(0, false);

os.addItem("Windows 95/98");

os.addItem("Solaris 2.5");

os.addItem("MacOS 7.5");

browser.addItem("Netscape Navigator 3.0");

browser.addItem("Netscape Communicator 4.5");

browser.addItem("lnternet Explorer 4.0");

browser.addItem("Mosaic 3.0");

browser.addItem("Lynx 2.4");

browser.select(1);

add(os);

add(browser);

os.reshape(0, 0, width, height / 2);

browser.reshape(0, height / 2, width, height / 2);

} }

Scrollbar

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

Конструктор класса Scrollbar позволяет задавать ориентацию линейки прокрутки — для этого предусмотрены константы VERTICAL и HORIZONTAL. Кроме того с помощью конструктора можно задать начальное положение и размер движка, а так же минимальное и максимальное значения, в пределах которых линейка прокрутки может изменять параметр. Для получения и установки текущего состояния линейки прокрутки используются методы getValue и setValue. Кроме того воспользовавшись методами getMinimum и getMaximum, вы можете получить рабочий диапазон объекта. Ниже приведен пример, в котором создается и вертикальная, и горизонтальная линейки прокрутки.

/* <applet code = “ScrollbarDemo” width=200 height=100>

</applet>

*/

import java.awt.*;

import java.applet.*;

public class ScrollbarDemo extends Applet {

public void init() {

setLayout(null);

int width = Integer.parseInt(getParameter("width"));

int height = Integer. parseInt(getParameter("height"));

Scrollbar hs = new Scrollbar(Scrollbar.HORIZONTAL, 50, width / 10, 0, 100);

Scrollbar vs = new Scrollbar(Scrollbar.VERTICAL, 50, height / 2, 0, 100);

add(hs);

add(vs);

int thickness = 16;

hs.reshape(0, height - thickness, width - thickness, thickness);

vs.reshape(width - thickness, 0, thickness, height - thickness);

} }

TextField

Класс TextField представляет собой реализацию однострочной области для ввода текста. Такие области часто используются в формах для пользовательского ввода. Вы можете «заморозить» содержимое объекта TextField с помощью метода setEditable, а метод isEditable сообщит вам, можно ли редактировать текст в данном объекте. Текущее значение объекта можно получить методом getText и установить методом setText. С помощью метода select можно выбрать фрагмент строки, задавая его начало и конец, отсчитываемые с нуля. Для выбора всей строки используется метод selectAll.

Метод setEchoChar задает символ, который будет выводиться вместо любых вводимых символов. Вы можете проверить, находится ли объект TextField в этом режиме, с помощью метода echoCharIsSet, и узнать, какой именно символ задан для эхо-печати, с помощью метода getEchoChar. Вот пример, в котором создаются классические поля для имени пользователя и пароля.

/* <applet code = “TextFieldDemo” width=200 height=100>

</applet>

*/

import java.awt.*;

import java.applet.*;

public class TextFieldDemo extends Applet {

public void init() {

setLayout(null);

int width = Integer.parseInt(getParameter("width"));

int height = Integer.parseInt(getParameter("height"));

Label namep = new Label("Name : ", Label.RIGHT);

Label passp = new Label("Password : ", Label.RIGHT);

TextField name = new Text Field(8);

TextField pass = new TextField(8);

pass.setEchoChar('*');

add(namep);

add(name);

add(passp);

add(pass);

int space = 25;

int w1 = width / 3;

namep.setBounds(0, (height - space) / 2, w1, space);

name.setBounds(w1, (height - space) / 2, w1, space);

passp.setBounds(0, (height + space) / 2, w1, space);

pass.setBounds(w1, (height + space) / 2, w1, space);

} }

TextArea

Порой одной строки текста оказывается недостаточно для конкретной задачи. AWT включает в себя очень простой многострочный редактор обычного текста, называемый TextArea. Конструктор класса TextArea воспринимает значение типа String в качестве начального текста объекта. Кроме того, в конструкторе указывается число колонок и строк текста, которые нужно выводить. Есть три метода, которые позволяют программе модифицировать содержимое объекта TextArea: appendText добавляет параметр типа String в конец буфера; insertText вставляет строку в заданное отсчитываемым от нуля индексом место в буфере; rеplaceText копирует строку-параметр в буфер, замещая ею текст, хранящийся в буфере между первым и вторым параметрами-смещениями. Ниже приведена программа, создающая объект TextArea и вставляющая в него строку.

/* <applet code = “TextAreaDemo” width=200 height=100>

</applet>

*/

import java.awt.*;

import java.applet.*;

public class TextAreaDemo extends Applet {

public void init() {

setLayout(null);

int width = Integer.parseInt(getParameter("width"));

int height = Integer.parseInt(getParameter("height"));

String val = "There are two ways of constructing " +

"a software design.\n" +

"One way is to make it so simple\n" +

"that there are obviously no deficiencies.\n" +

"And the other way is to make it so complicated\n" +

"that there are no obvious deficiencies.\n\n" +

“C.A.R. Hoare\n\n" +

"There's an old story about the person who wished\n" +

"his computer were as easy to use as his telephone. \n" +

"That wish has come true,\n" +

"since I no longer know how to use my telephone. \n\n" +

“Bjarne Stroustrup, AT&T (inventor of C++)";

TextArea text = new TextArea(val, 80, 40);

add(text);

text.setBounds(0, 0, width, height);

}}

Layout

Все компоненты, с которыми мы работали до сих пор, размещались «вручную». И в каждом примере мы вызывали загадочный метод setLayout(null). Этот вызов запрещал использование предусмотренного по умолчанию механизма управления размещением компонентов. Для решения подобных задач в AWT предусмотрены диспетчеры размещения (layout managers).

LayoutManager

Каждый класс, реализующий интерфейс LayoutManager, следит за списком компонентов, которые хранятся с именами типа String. Всякий раз, когда вы добавляете компонент в Panel, диспетчер размещения уведомляется об этом. Если требуется изменить размер объекта Panel, то идет обращение к диспетчеру посредством методов minimumLayoutSize и preferredLayoutSize. В каждом компоненте, который приходится обрабатывать диспетчеру, должны присутствовать реализации методов preferredSize и minimumSize. Эти методы должны возвращать предпочтительный и минимальный размеры для прорисовки компонента, соответственно. Диспетчер размещения по возможности будет пытаться удовлетворить эти запросы, в то же время заботясь о целостности всей картины взаимного расположения компонентов.

В Java есть несколько предопределенных классов — диспетчеров размещения, описываемых ниже.

FlowLayout

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

Ширину этого промежутка можно задать в конструкторе FlowLayout. Каждая строка с компонентами выравнивается по левому или правому краю, либо центрируется в зависимости от того, какая из констант LEFT, RIGHT или CENTER была передана конструктору. Режим выравнивания по умолчанию — CENTER, используемая по умолчанию ширина промежутка — 5 пикселей.

Ниже приведен пример, в котором в Panel включается несколько компонентов Label. Объект Panel использует FlowLayout с выравниванием RIGHT.

/* <applet code = “FlowLayoutDemo” width=200 height=100>

</applet>

*/

import java.awt.*;

import java.applet.*;

import java.util.*;

public class FlowLayoutDemo extends Applet {

public void init() {

setLayout(new FlowLayout(FlowLayout.RIGHT, 10, 3));

int width = Integer.parseInt(getParameter("width"));

int height = Integer.parseInt(getParameter("height"));

String val = "Data is not information " +

"is not knowledge is not wisdom.";

StringTokenizer st = new StringTokenizer(val);

while (st.hasMoreTokens()) {

add(new Button(st.nextToken()));

}

} }

BorderLayout

Класс BorderLayout реализует обычный стиль размещения для окон верхнего уровня, в котором предусмотрено четыре узких компонента фиксированной ширины по краям, и одна большая область в центре, которая может расширяться и сужаться в двух направлениях, занимая все свободное пространство окна. У каждой из этих областей есть строки-имена: String.North, String.South, String.East и String.West соответствуют четырем краям, a Center — центральной области. Ниже приведен пример BorderLayout с компонентом в каждой из названных областей.

/* <applet code = “BorderLayoutDemo” width=300 height=200>

</applet>

*/

import java.awt.*;

import java.applet.*;

import java.util.*;

public class BorderLayoutDemo extends Applet {

public void init() {

setLayout(new BorderLayout());

int width = Integer.parseInt(getParameter("width"));

int height = Integer.parseInt(getParameter("height"));

add("North", new Button("This is across the top"));

add("South", new Label("The footer message might go here"));

add("East", new Button("Left"));

add("West", new Button("Right"));

String msg = "The reasonable man adapts " +

"himself to the world;\n" +

"the unreasonable one persists in " +

"trying to adapt the world to himself.\n" +

"Therefore all progress depends " +

"on the unreasonable rnan.\n\n" +

“George Bernard Shaw\n\n";

add("Center", new TextArea(msg));

} }

GridLayout

Класс GridLayout размещает компоненты в простой равномерной сетке. Конструктор этого класса позволяет задавать количество строк и столбцов. Ниже приведен пример, в котором GridLayout используется для создания сетки 4х4, 15 квадратов из 16 заполняются кнопками, помеченными соответствующими индексами. Как вы уже, наверное, поняли, это — панель для игры в «пятнашки».

/* <applet code = “GridLayoutDemo” width=200 height=200>

</applet>

*/

import java.awt.*;

import java.applet.*;

public class GridLayoutDemo extends Applet {

static final int n = 4;

public void init() {

setLayout(new GridLayout(n, n));

setFont(new Font("Helvetica", Font.BOLD, 24));

int width = Integer.parseInt(getParameter("width"));

int height = Integer.parseInt(getParameter("height”));

for (int i = 0; i < n; i++) {

for (int j = 0; j < n; j++) {

int k = i * n + j;

if (k > 0)

add(new Button("" + k));

}

}

} }

Insets

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

public Insets insets() {

return new Insets(10, 10, 10, 10);

}

CardLayout

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

Window

Класс Window во многом напоминает Panel за тем исключением, что он создает свое собственное окно верхнего уровня. Большая часть программистов скорее всего будет использовать не непосредственно класс Window, а его подкласс Frame.

Frame

Frame — это как раз то, что обычно и считают окном на рабочей поверхности экрана. У объекта Frame есть строка с заголовком, управляющие элементы для изменения размера и линейка меню. Для того, чтобы вывести/спрятать изображение объекта Frame, нужно использовать методы show и hide. Ниже приведен пример апплета, который показывает объект Frame с содержащимся в нем компонентом TextArea.

/* <applet code = “FrameDemo” width=200 height=200>

</applet>

*/

import java.awt.*;

import java.applet.*;

public class FrameDemo extends Applet {

public void init() {

int width = Integer.parseInt(getParameter("width"));

int height = Integer.parseInt(getParameter("height"));

String val = "There are two ways of constructing " +

"a software design.\n" +

"One way is to make it so simple\n" +

"that there are obviously no deficiencies.\n" +

"And the other way is to make it so complicated" +

"that there are no obvious deficiencies.\n\n" +

“C.A.R. Hoare\n\n";

TextArea text = new TextArea(val, 80, 40);

Frame f = new Frame("Demo Frame");

f.setSize(width, height);

f.add("Center", text);

f.show();

} }

Меню

С каждым окном верхнего уровня может быть связана линейка меню. Объект MenuBar может включать в себя несколько объектов Menu. Последние, в свою очередь, содержат в себе список вариантов выбора — объектов MenuItem. Menu — подкласс MenuItem, так что объекты Menu также могут включаться в этот список, что позволяет создавать иерархически вложенные подменю. Вот пример, в котором к окну добавлены несколько вложенных меню.

/* <applet code = “MenuDemo” width=200 height=200>

</applet>

*/

import java.awt.*;

import java.applet. *;

public class MenuDemo extends Applet {

public void init() {

int width = Integer.parseInt(getParameter("width"));

int height = Integer.parseInt(getParameter("height"));

Frame f = new Frame("Demo Frame");

f.setSize(width, height);

MenuBar mbar = new MenuBar();

f.setMenuBar(mbar);

Menu file = new Menu("File");

file.add(new MenuItem("New... "));

file.add(new MenuItem("0pen..."));

file.add(new MenuItem("Close"));

file.add(new MenuItem("-"));

file.add(new MenuItem("Quit..."));

mbar.add(file);

Menu edit = new Menu("Edit");

edit.add(new MenuItem("Cut"));

edit.add(new MenuItem("Copy"));

edit.add(new Menultem("Paste"));

edit.add(new MenuItem("-"));

Menu sub = new Menu("Special");

sub.add(new MenuItem("First"));

sub.add(new MenuItem("Second"));

sub.add(new MenuItem("Third"));

edit.add(sub);

edit.add(new CheckBoxMenuItem("Debug"));

edit.add(new CheckBoxMenuItem("Testing"));

mbar.add(edit);

f.show();

} }

Глава 33.
Модели обработки событий

Несмотря на существенные изменения механизма обработки событий в AWT, Java1.1 поддерживает обратную совместимость с моделью обработки событий, принятой в Java 1.0.

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

Модель обработки событий Java 1.0

Каждый компонент может обрабатывать события, заместив определенные методы, вызываемые используемой по умолчанию реализацией метода handleEvents класса Component. Этот метод вызывается с объектом класса Event, описывающего все возможные типы событий. Наиболее часто используемые события, например, те, что связаны с мышью и клавиатурой, диспетчеризируются другим методам класса Component.

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

u mouseEnter вызывается в том случае, когда мышь входит в компонент.

u mouseExit вызывается при выходе мыши из области компонента.

u mouseMove вызывается при перемещении мыши в области компонента.

u mouseDown вызывается при нажатии кнопки мыши.

u mouseDrag вызывается при перемещении мыши с нажатой кнопкой.

u mouseUp вызывается при отпускании кнопки мыши.

Аналогично, keyDown и keyUp вызываются при каждом нажатии и отпускании клавиши. Событие передается методу вместе с кодом нажатой клавиши. Событие можно проверить, чтобы посмотреть, нажаты ли в данный момент какие либо клавиши-модификаторы, для этой цели можно также пользоваться методами shiftDown, controlDown и metaDown. В классе Event определены десятки констант, позволяющих использовать символические имена, например, PGUP и HOME.

Наконец, для работы со специальными событиями, например, с обратными вызовами (callback) из компонентов Button, Scrollbar и Menu, вам придется замещать метод action. Этот метод вызывается с исходным событием и со вторым параметром, который представляет собой компонент пользовательского интерфейса, создавший это событие. Вы должны проверить этот объект, разобраться, какой из компонентов послал вам событие, после чего передать управление соответствующему данному компоненту обработчику. Для того, чтобы перед приведением типа проверить, принадлежит ли объект к определенному классу, например, к классу Button, вы можете использовать оператор instanceof.

А вот и пример на обработку событий. Мы добавили объект Label к примеру с игрой в «пятнашки», а также заместили метод action для того, чтобы обрабатывать события, возникающие при нажатии кнопок. Точно такой же механизм можно использовать для управления вводом через любой из подклассов Component.

/* <applet code = “EventDemo” width=200 height=200>

</applet>

*/

import java.awt.*;

import java.applet.*;

public class EventDemo extends Applet {

static final int n = 4;

Label lab = new Label("?", Label.CENTER);

public void init() {

setLayout(new GridLayout(n, n));

setFont(new Font("Helvetica", Font.BOLD, 24));

int width = Integer.parseInt(getParameter("width"));

int height = Integer.parseInt(getParameter("height"));

for (int i = 0; i < n; i++) {

for (int j = 0; j < n; j++) {

int k = i * n + j;

if (k > 0)

add(new Button("" + k));

}

}

lab.setFont(new Font("Helvetica", Font.ITALIC, 24));

add(lab);

}

public boolean action(Event e, Object o) {

if (o instanceof String) {

lab.setText((String) o);

}

return false;

} }

Рисование «каракулей» в Java 1.0

Классический апплет, в котором используется модель обработки событий Java 1.0. В нем методы mouseDown() и mouseDragO переопределены таким образом, чтобы пользователь имел возможность рисовать «каракули» с помощью мыши. Также переопределен метод keyDown(), чтобы при нажатии клавиши [С] экран очищался, и метод action(), чтобы экран очищался после щелчка на кнопке Clear.

/* <applet code = “Scribble1” width=200 height=200>

</applet>

*/

import java.applet.*;

import java.awt.*;

/** Простой апплет, в котором используется модель обработки событий 1.0 */

public class Scribble1 extends Applet {

private int lastx, lasty; // Хранят координаты курсора мыши.

Button clear_button; // Кнопка Clear.

Graphics g; // Объект Graphics, который необходимо нарисовать.

/** Инициализация кнопки и объекта Graphics */

public void init() {

clear_button = new Button("Clear");

this.add(clear_button);

g = this.getGraphics();

}

/** Реакция на нажатие кнопки мыши */

public boolean mouseDown(Event e, int x, int y) {

lastx = x; lasty = y;

return true;

}

/** Реакция на перетаскивание с помощью мыши */

public boolean mouseDrag(Event e, int x, int y) {

g.setColor(Color.black) ;

g.drawLine(lastx, lasty, x, y);

lastx = x; lasty = y;

return true;

}

/** Реакция на нажатие клавиши [С] */

public boolean keyDown(Event e, int key) {

if ((e.id == Event.KEY_PRESS) && (key == 'с' ) ) {

clear() ;

return true;

}

else return false;

}

/** Реакция на нажатие кнопки Clear */

public boolean action(Event e, Object arg) {

if (e.target == clear_button) {

clear();

return true;

}

else return false;

}

/** Метод для стирания каракулей */

public void clear() {

g.setColor(this.getBackground());

g.fillRect(0, 0, bounds().width, bounds().height);

} }

Экран вроде бы пустой – но на нем можно рисовать!

Модель обработки событий Java 1.1

Новая модель обработки событий представляет собой, по существу, модель обратных вызовов (callback). При создании GUI-элемента ему сообщается, какой метод или методы он должен вызывать при возникновении в нем определенного события (нажатия кнопки, мыши). Эту модель очень легко использовать в C++, поскольку этот язык позволяет оперировать указателями на методы (чтобы определить обратный вызов, необходимо всего лишь передать указатель на функцию). Однако в Java это недопустимо (методы не являются объектами). Поэтому для реализации новой модели необходимо определить класс, реализующий некоторый специальный интерфейс. Затем можно передать экземпляр такого класса GUI-элементу, обеспечивая таким образом обратный вызов. Когда наступит ожидаемое событие, GUI-элемент вызовет соответствующий метод объекта, определенного ранее.

Модель обработки событий Java 1.1 используется как в пакете AWT, так и в JavaBeans API. В этой модели разным типам событий соответствуют различные классы Java. Каждое событие является подклассом класса java.util.EventObject. События пакета AWT являются подклассом java.awt.AWTEvent. Для удобства события различных типов пакета AWT (например, MouseEvent или АсtionEvent) помещены в новый пакет java.awt. event.

Для каждого события существует порождающий его объект, который можно получить с помощью метода getSource(), и каждому событию пакета AWT соответствует определенный идентификатор, который позволяет получить метод getid(). Это значение используется для того, чтобы отличать события различных типов, которые могут описываться одним и тем же классом событий. Например, для класса FocusEvent возможны два типа событий: FocusEvent.FOCUS_GAINED и FocusEvent.FOCUS_LOST. Подклассы событий содержат информацию, связанную с данным типом события. Например, в классе MouseEvent существуют методы getX(), getY() и getClickCount(). Этот класс наследует, в числе прочих, и методы getModifiers() и getWhen().

Модель обработки событий Java 1.1 базируется на концепции слушателя событий. Слушателем события является объект, заинтересованный в получении данного события. В объекте, который порождает событие (в источнике событий), содержится список слушателей, заинтересованных в получении уведомления о том, что данное событие произошло, а также методы, которые позволяют слушателям добавлять или удалять себя из этого списка. Когда источник порождает событие (или когда объект источника зарегистрирует событие, связанное с вводом информации пользователем), он оповещает все объекты слушателей событий о том, что данное событие произошло.

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

Например, объекты слушателей событий ActionEvent должны реализовывать интерфейс ActionListener. В пакете Java.awt.event определены интерфейсы слушателей для каждого из определенных в нем типов событий (например, для событий MouseEvent здесь определено два интерфейса слушателей: MouseListener и MouseMotionListener). Все интерфейсы слушателей событий являются расширениями интерфейса java.util.EventListener. В этом интерфейсе не определяется ни один из методов, но он играет роль интерфейса-метки, в котором однозначно определены все слушатели событий как таковые.

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

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

Типы событий, слушатели и методы слушателей в Java 1.1 приведены ниже.

Класс события Интерфейс слушателя Методы слушателя

ActionEvent ActionListener actionPerformed()

AdjustmentEvent AdjustmentListener adjustmentValueChanged()

ComponentEvent ComponentListener componentHidden()
componentMoved()
componentResized()
componentShown()

ContainerEvent ContainerListener componentAdded()
componentRemoved()

FocusEvent FocusListener focusGained()
focusLost()

ItemEvent ItemListener itemStateChanged()

KeyEvent KeyListener keyPressed()
keyReleased()
keyTyped()

MouseEvent MouseListener mouseClicked()
mouseEntered()
mouseExited()
mousePressed()
mouseReleased()
MouseMotionListener mouseDragged()
mouseMoved()

TextEvent TextListener textValueChanged()

WindowEvent WindowListener windowActivated()
windowClosed()
windowClosing()
windowDeactivated()
windowDeiconified()
windowlconified()
windowOpened()

Для каждого интерфейса слушателей событий, содержащего несколько методов, в пакете java.awt.event определен простой класс-адаптер, который обеспечивает пустое тело для каждого из методов соответствующего интерфейса. Когда нужен только один или два таких метода, иногда проще получить подкласс класса-адаптера, чем реализовать интерфейс самостоятельно. При получении подкласса адаптера требуется лишь переопределить те методы, которые нужны, а при прямой реализации интерфейса необходимо определить все методы, в том числе и ненужные в данной программе. Заранее определенные классы-адаптеры называются так же, как и интерфейсы, которые они реализуют, но в этих названиях Listener заменяется на Adapter: MouseAdapter, WindowAdapter.

Как только реализован интерфейс слушателя или получены подклассы класса-адаптера, необходимо создать экземпляр нового класса, чтобы определить конкретный объект слушателя событий. Затем этот слушатель должен быть зарегистрирован соответствующим источником событий. В программах пакета AWT источником событий всегда является какой-нибудь элемент пакета. В методах регистрации слушателей событий используются стандартные соглашения об именах: если источник событий порождает события типа X, в нем существует метод addXListener() для добавления слушателя и метод removeXListener() для его удаления. Одной из приятных особенностей модели обработки событий Java 1.1 является возможность легко определять типы событий, которые могут порождаться данным элементом. Для этого следует просто просмотреть, какие методы зарегистрированы для его слушателя событий. Например, из описания API для объекта класса Button следует, что он порождает события ActionEvent. Ниже приведен список элементов пакета AWT и событий, которые они порождают.

Button

ActionEvent

Пользователь нажал кнопку

CheckBox

ItemEvent

Пользователь установил или сбросил флажок

CheckBoxMenuItem

ItemEvent

Пользователь установил или сбросил флажок рядом с пунктом меню

Choice

ItemEvent

Пользователь выбрал элемент списка или отменил его выбор

Component

ComponentEvent

Элемент либо перемещен, либо он стал скрытым, либо видимым

FocusEvent

Элемент получил или потерял фокус ввода

KeyEvent

Пользователь нажал или отпустил клавишу

MouseEvent

Пользователь нажал или отпустил кнопку мыши, либо курсор мыши вошел или покинул область, занимаемую элементом, либо пользователь просто переместил мышь или переместил мышь при нажатой кнопке мыши

Container

ContainerEvent

Элемент добавлен в контейнер или удален из него

List

ActionEvent

Пользователь выполнил двойной щелчок мыши на элементе списка

ItemEvent

Пользователь выбрал элемент списка или отменил выбор

MenuItem

ActionEvent

Пользователь выбрал пункт меню

Scrollbar

AdjustmentEvent

Пользователь осуществил прокрутку

TextComponent

TextEvent

Пользователь внес изменения в текст элемента

TextField

ActionEvent

Пользователь закончил редактирование текста элемента

Window

WindowEvent

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

Рисование «каракулей» в Java 1.1

Модель обработки событий Java 1.1 является достаточно гибкой и предоставляет пользователю ряд возможностей для структуризации программы обработки событий. Первый из этих способов продемонстрирован в примере. В апплете данной версии реализованы интерфейсы MouseListener и MouseMotionListener, регистрирующие себя с помощью своих же методов addMouseListener() и addMouseMotionListener().

/* <applet code = “Scribble2” width=200 height=200>

</applet>

*/

import java.applet.*;

import java.awt.*;

import java.awt.event.*;

public class Scribble2 extends Applet implements

MouseListener, MouseMotionListener {

private int last_x, last_y;

public void init() {

// Сообщает данному апплету о том, какие объекты

// классов MouseListener и MouseMotionListener он должен оповещать

// о событиях, связанных с мышью и ее перемещением.

// Поскольку интерфейс реализуется в самом апплете,

// при этом будут вызываться методы апплета.

this.addMouseListener(this) ;

this.addMouseMotionListener(this);

}

// Метод интерфейса MouseListener. Вызывается при нажатии

// пользователем кнопки мыши.

public void mousePressed(MouseEvent e) {

last_x = e.getX();

last_y = e.getY();

}

// Метод интерфейса MouseMotionListener. Вызывается при

// перемещении мыши с нажатой кнопкой.

public void mouseDragged(MouseEvent e) {

Graphics g = this.getGraphics();

int x = e.getX(), y = e.getY();

g.drawLine(last_x, last_y, x, y);

last_x = x; last_y = y;

}

// Другие, не используемые методы интерфейса MouseListener.

public void mouseReleased(MouseEvent e) {;}

public void mouseClicked(MouseEvent e) {;}

public void mouseEntered(MouseEvent e) {;}

public void mouseExited(MouseEvent e) {;}

// Другой метод интерфейса MouseMotionListener.

public void mouseMoved(MouseEvent e) {;}

}

Рисование «каракулей» с использованием встроенных классов

Модель обработки событий Java 1.1 разработана с учетом того, чтобы хорошо сочетаться с другой новой особенностью Java 1.1: встроенными классами. В следующем примере показано, как изменится данный апплет, если слушатели событий будут реализованы в виде анонимных встроенных классов. Обратите внимание на компактность данного варианта программы. Новая особенность, добавленная в апплет — кнопка Clear. Для этой кнопки зарегистрирован объект ActionListener, а сама она выполняет очистку экрана при наступлении соответствующего события.

/* <applet code = “Scribble3” width=200 height=200>

</applet>

*/

import java.applet.*;

import java.awt.*;

import java.awt.event.*;

public class Scribble3 extends Applet {

int last_x, last_y;

public void init() {

// Определяет, создает и регистрирует объект MouseListener.

this.addMouseListener(new MouseAdapter() {

public void mousePressed(MouseEvent e) {

last_x = e.getX(); last_y = e.getY();

}

});

// Определяет, создает и регистрирует объект MouseMotionListener.

this.addMouseMotionListener(new MouseMotionAdapter() {

public void mouseDragged(MouseEvent e) {

Graphics g = getGraphics();

int x = e.getX(), y= e.getY();

g.setColor(Color.black);

g.drawLine(last_x, last_y, x, y);

last_x = x; last_y = y;

}

});

// Создает кнопку Clear.

Button b = new Button("Clear");

// Определяет, создает и регистрирует объект слушателя

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

b.addActionListener(new ActionListener() {

public void actionPerformed(ActionEvent e) {

// стирание каракулей

Graphics g = getGraphics();

g.setColor(getBackground());

g.fillRect(0, 0, getSize().width, getSize().height);

}

});

// Добавляет кнопку в апплет.

this.add(b);

} }

Глава 34.
Работа с изображениями

Java работает с наиболее популярными во Всемирной паутине форматами изображений — JPEG и GIF. JPEG лучше подходит для естественных цветных изображений, таких, как фотографии, а формат GIF является наилучшими для графических эмблем, изображений кнопок.

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

Простой загрузчик изображений

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

/* <title>SimpleImageLoad</title>

*<applet code="SimpleImageLoad" width=300 height=150>

* <param name="img" value="mupk.gif">

* </applet>

*/

import java.applet.*;

import java.awt.*;

public class SimpleImageLoad extends Applet {

Image art;

public void init() {

art = getImage(getDocumentBase(), getParameter("img"));

}

public void paint(Graphics g) {

g.drawImage(art, 0, 0, this);

} }

Метод paint использует drawlmage с четырьмя аргументами: это ссылка на изображение art, координаты левого верхнего угла рисунка х, у и объект типа ImageObserver. Здесь мы использовали this в качестве имени ImageObserver, поскольку он встроен в апплет. Когда этот апплет запускается, он в методе init начинает загрузку art. Процесс загрузки изображения по сети хорошо заметен, поскольку встроенный интерфейс ImageObserver вызывает процедуру paint при каждом поступлении новой порции данных из сети. Вы можете использовать ImageObserver для отслеживания загрузки изображения, а в это время выводить на экран другую информацию.

ImageObserver

ImageObserver — это абстрактный интерфейс, используемый для получения сообщения о создании изображения, Метод imageUpdate из ImageObserver — это все, что вы должны реализовать в своем апплете для его использования. В то время, как вы получаете информацию о загрузке, вы можете показывать любую понравившуюся вам мультипликацию, индикатор степени завершения загрузки или любую другую заставку. Для использования ImageObserver в своем подклассе Applet вы должны добавить в него строку implement ImageObserver, как показано в этом фрагменте программы:

public class MyApplet extends Applet implement ImageObserver {

Затем вам придется вставить в свой класс метод imageUpdate для интерфейса ImageObserver, как показано в следующем фрагменте:

public boolean imageUpdate(Image img, int status,

int x, int у int width, int height) {

if((status & ALLBITS) != 1) {

System.out.println("Still processing the image");

return true;

}

else {

System.out.println("Done processing the image");

return false;

} }

Метод imageUpdate вызывается с изображением Image, которое находится в процессе изменения, целым параметром status, отражающим состояние изменения, и с координатами прямоугольника (x, у, width, height), которые соответствуют различным величинам в зависимости от информационных флагов, перечисленных ниже. ImageUpdate должен возвращать false по окончании загрузки изображения и true — если изображение еще обрабатывается.

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

WIDTH

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

HEIGHT

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

PROPERTIES

Свойства изображения теперь доступны. Вы можете получить их посредством art.properties.

SOMEBITS

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

FRAMEBITS

Еще один кадр ранее нарисованного изображения с несколькими кадрами, готов для перерисовки. Параметры x, у, width, height не содержат информации.

ALLBITS

Обработка перерисовываемого изображения окончена, и оно может быть отрисовано в конечном виде. Значения аргументов x, у, width и height не содержат значимой информации.

ERROR

При пересылке изображения возникла ошибка. Поступление дальнейшей информации стало невозможным и рисование прервано. Для удобства выставляется и флаг ABORT для индикации прерывания загрузки изображения.

ABORT

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

Теперь давайте рассмотрим программный пример, который использует ImageObserver для показа количества обработанных строк изображения и выводит эту информацию (переменная progress) на консоль:

/* <title>ObservedImageLoad</title>

* <applet code="ObservedImageLoad" width=290 height=140>

* <param name="img" value="mupk.gif">

* </applet>

*/

import java.applet.*;

import java.awt.*;

import java.awt.image.*;

public class ObservedImageLoad extends Applet

implements Runnable, ImageObserver {

Image art;

Dimension d;

int progress;

Thread motor;

boolean loaded;

public void init() {

art = getImage(getDocumentBase(), getParameter("img"));

loaded = false;

progress = 0;

}

public void paint(Graphics g) {

d = this.getSize();

loaded = g.drawImage(art, 0, 0, this);

}

public boolean imageUpdate(Image img, int info,

int x, int y, int width, int height) {

if((info & ALLBITS) != 1) {

if(progress<d.height) {

progress = progress + height;

}

System.out.println(progress + "/" + d.height);

return true;

}

else {

return false;

} }

public void start() {

motor = new Thread(this);

motor. start();

}

public void stop() {

motor.stop();

}

public void run() {

motor.setPriority(Thread.MIN_PRIORITY);

while(!loaded) { // update progress indicator (5 fps)

repaint();

try {

motor.sleep(200);

}

catch(InterruptedException e) {}

}

} }

Метод imageUpdate обрабатывает статус загрузки изображения. Информация о статусе передается через переменную info, с которой сравнивается статическая переменная ALLBITS. Если еще не получено все изображение, то мы добавляем величину heihgt к общему числу обработанных строк изображения. Для проверки этой концепции мы выводим количество обработанных строк изображения на консоль. Метод run перерисовывает апплет пять раз в секунду (каждые 200 миллисекунд) до тех пор, пока изображение art не загрузится. То, как долго монитор статуса загрузки будет работать, зависит от скорости передачи данных изображения по сети.

MediaTracker

MediaTracker — это класс, предоставляющий удобный интерфейс для контроля статуса нескольких изображений. В следующих версиях этот класс будет контролировать другие мультимедийные форматы, такие, как звуковые файлы. Для использования MediaTracker нужно создать новый объект этого класса и использовать метод addImage для контроля статуса загрузки. Используйте MediaTracker при загрузке группы изображений. Пока все изображения, которые вас интересуют, не загружены, пользователя будет развлекать демонстрационный экран.

ImageProducer

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

MemorylmageSource

MemoryImageSource — класс, используемый для создания нового изображения из массива пикселей. Вот конструктор, используемый для создания объекта MemoryImageSource:

MemoryImageSource(int width, int height, int pixel[], int offset, int scanLineWidth)

Объект MemoryImageSource собирается из массива целых величин pixel[] в используемой по умолчанию модели цветов RGB для генерации данных объекта Image. В используемой по умолчанию цветовой модели пиксель — это целая величина состоящая из Alpha, Red, Green и Blue (OxAARRGGBB). Величина Alpha обозначает степень прозрачности элемента изображения. MemoryImageSource возвращает объект ImageProducer, который используется с createImage для получения изображения, пригодного к использованию. Приведенный ниже короткий пример создает MemoryImageSource, используя вариант простого алгоритма (побитовое исключающее ИЛИ значений х и у координат каждого элемента изображения).

/* <title>Memory Image Generator</title>

* <applet code="MemoryImager" width=256 height=256>

* </applet>

*/

import java.applet.*;

import java.awt.*;

import java.awt.image.*;

public class MemoryImager extends Applet {

Image art;

Dimension d;

public void init() {

generateImage();

}

public void generateImage() {

int pixels[] = new int[d.width * d.height];

int i = 0;

int r, g, b;

for(int y=0; y<h; y++) {

for(int x=0; x<h; x++) {

r = (x^y)&0xff; // red is x XOR у

g = (x*2^y*2)&0xff; //green is 2x XOR 2y

b = (x*4^y*4)&0xff; // blue is 4x XOR 4y

pixels[i++] = (255 << 24) | (r << 16) | (g << 8) | b;

} }

art = createImage(new MemoryImageSource(d.width, d.height, pixels, 0, d.width));

}

public void paint(Graphics g) {

g.drawlmage(art, 0, 0, this);

} }

ImageFilter и ImageFilterSource

Подклассы классов ImageFilter и ImageFilterSource используются совместно для создания новых изображений фильтрованием уже существующих. С двумя такими подклассами из пакета java.awt.image вы сейчас познакомитесь.

CropImageFilter

CropImageFilter создает новое изображение из фрагмента существующего. Использование этого фильтра полезно тогда, когда вы хотите использовать несколько маленьких изображений в одном апплете. Загрузка по сети двадцати изображений по 2 Кбайта происходит намного медленнее, чем загрузка одного файла размером 40 Кбайт. Если ваши изображения — одинакового размера, вы можете собрать их в единый блок и использовать CropImageFilter для разделения блока на отдельные изображения в Java-клиенте.

RGBImageFilter

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

Часть 6.
Microsoft FoxPro

Глава 1.
Система управления базами данных для Windows

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

Глава 2.
Открытие и просмотр таблицы

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

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

Открытие и просмотр таблицы

В меню «Файл» выберите команду «Открыть». Появится диалог «Открыть». Сделайте активным каталог, содержащий вашу таблицу. В списке «Тип» выберите «Таблица/DBF». И, выбрав файл, нажмите кнопку «Открыть».

Теперь эта таблица является активной и для ее просмотра в меню «База» выберите команду «Просмотр». Появится окно просмотра. Информация в окне просмотра представлена в строках и столбцах. Столбцы называются полями, строки — записями. Окно просмотра обычно недостаточно велико, чтобы дать возможность увидеть всю таблицу сразу. Для того чтобы увидеть различные части таблицы, нужно прокрутить окно просмотра по горизонтали и по вертикали.

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

Изменение ширины поля

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

Перемещение поля

Укажите с помощью мыши на заголовок поля CNO. И, нажав кнопку мыши, перемещайте заголовок, пока он не окажется между COMPANY и CONTACT.

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

Просмотр данных в режиме просмотра по записям

В меню «Просмотр» выберите команду «По записям». Режим просмотра изменится.

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

Разбиение окна просмотра

Установите мышь на маркере разбиения. Указатель мыши изменится. Переместите маркер разбиения окна за поле CONTACT.

Разбиение окна просмотра позволяет:

u прокручивать поля в одном разделе, не перемещая поля в другом;

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

Выбор раздела и изменение его режима

Выберите правый раздел, установив на нем указатель мыши и щелкнув. В меню «Просмотр» выберите команду «По записям».

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

Устранение связи между разделами

В меню «Просмотр» выберите команду «Связать разделы». Маркер, расположенный рядом с командой «Связать разделы», исчезнет. Прокрутите несколько записей.

Закрытие окна просмотра

Закрытие окна производится стандартно для системы WINDOWS, дважды щелкните на кнопке управляющего меню окна просмотра, или нажмите клавиши CTRL+F4.

Глава 3.
Работа с окном Memo

Теперь откроем еще одну таблицу, содержащую информацию о продавцах, участвовавших в некоторых коммерческих сделках, это файл: SALESMAN.DBF

В этой таблице определено поле NOTES, находящееся между полями ONO и NAME и содержащее слова Memo или memo. Это поле специального типа; оно может содержать неограниченное количество любой информации. Так как объем информации может быть очень большим, ее трудно отобразить в маленькой области. Для просмотра и редактирования этой информации служит окно редактирования memo-поля.

Глядя на memo-поле, по первой букве слова memo сразу можно определить, содержит данное поле информацию или нет. Прописная буква «M» указывает на то, что memo-поле содержит информацию, а строчная буква «m» — на то, что поле пусто.

Просмотр данных memo-полей

Откройте окно редактирования memo-поля. Для этого дважды щелкните на слове Memo.

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

Глава 4.
Управление данными

Итак, вы узнали, как просмотреть информацию в таблице. Но что делать, если окажется, что нужно просмотреть информацию заданного типа? Например, может потребоваться узнать все о покупателях, израсходовавших свыше $5000, или узнать лишь имя и номер каждого покупателя. FoxPro позволяет быстро извлекать и просматривать информацию, отвечающую заданным условиям. Процесс извлечения указанной информации называется «запросом».

Открытие окна запроса

В меню «Файл» выберите команду «Открыть» и в каталоге FOXPROW\TUTORIAL\ выберите файл CUSTOMER.DBF и нажмите кнопку «Открыть». Затем, в меню «Файл» выберите команду «Создать». В диалоге «Создать файл» установите опцию «Запрос», а затем нажмите кнопку «Создать». Появится окно запроса.

В окне запроса указывается информация, которую требуется извлечь из таблиц. Извлеченная информация помещается в окно просмотра.

В списке «Поля результата» окна запроса указывается, какие поля требуется отобразить в окне просмотра. По умолчанию во вновь открываемой таблице отображаются все поля.

В области «Критерий отбора» указывается, какие записи требуется извлечь из таблицы. Если область пуста — запрос извлекает все записи из таблицы.

Допустим, нет необходимости просматривать поля CNO, ADDRESS, ZIP и ONO. Тогда нам нужно указать только те поля, которые нужны.

Задание полей для результата запроса

В окне запроса установите флажок «Поля». Появится диалог «Выбор полей запроса».

Нажмите кнопку «Удалить все», чтобы очистить список «Выбранные поля». И поместите в список «Выбранные поля» COMPANY, CNO, CONTACT, PHONE, CITY, STATE и YTDPURCH, для этого выделите нужное поле в списке «Поля таблицы» и нажмите кнопку «Добавить». Каждое из перечисленных выше полей появится на экране с префиксом CUSTOMER, это означает принадлежность поля к таблице CUSTOMER. По окончании нажмите кнопку «Ok». Выбранные поля будут отображены в списке «Поля результата» окна запроса. При выполнении запроса поля в окне просмотра появляются в порядке, указанном в окне запроса.

Выполнение запроса

В окне запроса нажмите кнопку «Выполнить». И у вас появится окно просмотра с выбранными полями.

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

Удаление полей результата из запроса

Закройте окно просмотра, в котором отображен результат запроса. Установите флажок «Поля». Удалите поле CONTACT из списка «Выбранные поля», выделив его в списке и нажав кнопку «Удалить». Нажмите кнопку «Ok». Обратите внимание, что поля CONTACT больше нет в списке «Поля результата». И выполните запрос.

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

Упорядочивание полей в запросе

Закройте окно просмотра с отображенным результатом запроса. В окне запроса установите флажок «Порядок». В диалоге «Порядок полей запроса» поместите CUSTOMER.SITY в список «Критерий порядка», для этого выделите его и нажмите кнопку «Добавить». И установите флажок «По возрастанию». Для возвращения в окно запроса нажмите кнопку «ОK».

Обратите внимание, что в списке «Поля результата» рядом с CITY появились символы 1 и стрелка вверх. Цифра 1 означает, что CITY имеет первый приоритет при упорядочении данных, а стрелка вверх означает, что данные отображаются в порядке возрастания. Нажмите кнопку «Выполнить». Данные можно упорядочить любым способом. Например, можно упорядочить данные по штатам и по городам внутри штата.

Упорядочивание по нескольким полям

Закройте окно просмотра. Установите флажок «Порядок». Поместите CUSTOMER.STATE в список «Критерий порядка».

Установите порядок полей, для этого мышью переместите кнопку слева от STATE так, чтобы поле STATE было выше поля CITY. Нажмите кнопку «Оk». Теперь перед STATE стоит цифра 1, а перед CITY — 2, что означает приоритет по упорядочению данных. И нажмите кнопку «Выполнить».

До сих пор в таблице CUSTOMER просматривались все записи. Но предположим, что нужно просмотреть только те компании, которые расположены в штате Огайо. Используя область «Критерий отбора» окна запроса, можно выполнить такой запрос почти сразу же.

Глава 5.
Задание условий отбора

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

Как задать условия отбора

Закройте окно просмотра. В окне запроса щелкните на прямоугольнике ниже «Имя поля», чтобы вывести список «Имя поля». Выделите CUSTOMER.STATE

В списке вариантов сравнения по умолчанию устанавливается «похоже на». Наберите OH (Ohio) в текстовом поле ниже надписи «Образец». Так создано условие отбора для просмотра записей только о компаниях в штате Огайо. Таким же образом можно задать несколько условий отбора. Введенное условие отбора требует, чтобы поле STATE было похоже на OH. STATE — это поле, «похоже на» — это вариант сравнения, а OH — значение, с которым сравнивается содержимое поля. Вариант сравнения «похоже на» указывает, что поле STATE должно иметь значение OH для того, чтобы запись удовлетворяла требованиям запроса. Результат выполнения запроса показан ниже.

Другие варианты условия отбора

Попробуем вывести компании, расположенные в штате Огайо или Мичиган. Закройте окно просмотра. Щелкните на поле ввода вариантов сравнения и выберите значение «в списке». В поле «Образец» наберите MI (Michigan) после OH. Не забудьте поставить запятую.

Нажмите кнопку «Выполнить». Появится окно просмотра, в котором будут выведены все компании, расположенные в штатах Огайо и Мичиган, потому что «в списке» означает, что для включения в результат STATE должно содержать один из элементов, указанных в текстовом поле «Образец».

Другие условия отбора: равно, больше, меньше, в пределах; работают аналогично.

Глава 6.
Работа с таблицей

Создание структуры таблицы

Во-первых, решите, какую информацию следует помещать в таблицу:

u Фамилия, имя, отчество сотрудника;

u Дата рождения;

u Профессия;

u Заработная плата;

u Комментарий.

Каждая из перечисленных величин будет храниться в соответствующем поле таблицы.

В меню «Файл» выберите команду «Создать». Появится диалог «Создать файл». Выберите опцию «Таблица/DBF», а затем нажмите кнопку «Создать». Появится диалог «Структура таблицы».

Курсор находится в текстовом поле «Имя». Теперь можно добавить имя, тип и ширину каждого поля.

Описание полей таблицы

Наберите «фио» в текстовом поле «Имя». Нажмите клавишу Tab или щелкните мышью в списке «Тип». Установите тип «Character». Нажмите клавишу Tab или щелкните мышью на поле «Ширина», после чего наберите 25 или с помощью стрелок увеличьте ширину до 25. Далее опишите остальные поля.

Поле «Дес» указывает число десятичных разрядов в числовом поле. Так как в поле «зарплата» хранится значение денежной суммы, то следует задать число 2.

После ввода всех значений нажмите кнопку «Ok». Появится диалог «Сохранить как». Введите имя таблицы: TEST, если необходимо укажите в какой каталог ее записать и нажмите кнопку «Сохранить». Затем FoxPro спросит: «Вводить записи данных сейчас?». Нажмите кнопку «Да».

Ввод данных в таблицу

Появится окно просмотра в режиме изменения по записям. Добавляем в таблицу следующие данные:

Иванов Сергей Николаевич

01/01/64

бухгалтер

350000

Для ввода информации в поле memo нажмите клавиши CTRL+PGDN или дважды щелкните на этом поле. Появится memo-окно, в которое введите комментарии.

Закройте memo-окно. Заметьте, что буква «M» в слове Memo прописная, это означает, что данные в поле уже введены. Добавьте еще нескольких сотрудников и закройте окно просмотра.

Изменение структуры таблицы

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

В меню «База» выберите команду «Настройка». В левом верхнем углу диалога «Настройка базы» нажмите кнопку «Изменить». Появится диалог «Структура таблицы». Подведите курсор к полю «комментарий» и нажмите кнопку «Вставить». Над полем «комментарий» появится «новое_поле». Замените «новое_поле» на «фото». В списке «Тип» выберите значение «General», а затем нажмите кнопку «Ok». На запрос FoxPro о том, необходимо ли сохранить изменения нажмите кнопку «Да». В диалоге «Настройка базы» нажмите кнопку «Ok».

Ввод в таблицу графических данных

Прежде всего, вам необходимо внести в буфер обмена фотографию сотрудника.

В меню «Запись» выберите команду «Править». В окне просмотра устанавливается режим изменения по записям. Дважды щелкните по полю «фото». Появится окно редактирования поля типа General. В меню «Правка» выберите команду «Вставить». Картинка, содержащаяся в буфере обмена, появится в окне.

Добавление новых записей

В меню «Запись» выберите команду «Добавить». Появится окно просмотра в режиме добавления, куда вы можете добавить новых сотрудников.

Удаление записи

В режиме просмотра таблицы следует пометить поля для удаления. Для этого слева от поля нажать маркер удаления.

Как упаковать таблицу

В меню «База» выберите команду «Упаковать». Появится сообщение с запросом о том, хотите ли вы упаковать таблицу. Нажмите кнопку «Да». Теперь в меню «База» выберите команду «Просмотр» и убедитесь в том, что все выделенные записи были удалены.

Глава 7.
Формирование отчетов

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

Для создания отчета необходим запрос. Мы воспользуемся запросом созданным ранее.

Как открыть запрос

В меню «Файл» выберите команду «Открыть». В появившемся диалоге «Открыть» выберите каталог TUTORIAL. В списке «Тип» выберите «Запрос» Выберите нужный файл (GSCHAP5.QPR) и нажмите кнопку «Открыть».

В качестве первого отчета составим список телефонных номеров всех клиентов из Калифорнии.

Сформируйте отчет с телефонным списком, как показано ниже.

Включите следующие поля: COMPANY, PHONE, CONTACT, CITY и STATE. Критерий отбора: STATE похоже на CA. И главное, в списке «Вывод» выберите значение «Отчет/Этикетка». Нажмите кнопку «Выполнить».

Отчет начинает прокручиваться на экране.

После просмотра отчета нажмите клавишу ESC.

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

Как сформировать бланк отчета

Установите флажок «Опции». В диалоге «Параметры ввода запроса» выберите опцию «Отчет», установите флажок «Стандартный отчет», и в диалоге «Стандартный отчет запроса» в поле «Сохранить как» наберите имя gschap6.frx.

Нажмите кнопку «Ok» и кнопку «Выполнить».

Полное изображение целой страницы отчета можно увидеть в окне «Просмотр страницы».

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

В окне «Просмотр страницы» вы можете увеличить изображение (кнопка Увеличить), можно уменьшить (кнопка Уменьшить), просмотреть следующую или предыдущую страницу (кнопки Вперед/Назад). Для выхода нажмите кнопку «OK».

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

Как открыть бланк отчета

В меню «Файл» выберите команду «Открыть». В списке «Тип» выберите значение «Отчет», и откройте файл: GSCHAP6.FRX.

В окне разметки отчета появится бланк отчета GSCHAP6.

Бланк отчета разделен на три полосы:

u Полоса «Верхний колонтитул» содержит информацию, отображаемую вверху каждой страницы отчета.

u Полоса «Детали» печатается один раз для каждой записи, удовлетворяющей критерию отбора.

u Полоса «Нижний колонтитул» содержит информацию, отображаемую вниз каждой страницы отчета.

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

Как выполнить запрос

Сверните окно отчета. Нажмите кнопку «Выполнить». Обратите внимание, что отчет содержит внесенные изменения.

И этот отчет вы можете распечатать на принтере.

Глава 8.
Другие возможности работы с базами данных

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

Рассмотрим кратко другие основные возможности работы с базами данных:

Создание экрана

Система FoxPro используется для просмотра данных различными способами. Можно просмотреть таблицу данных в окне просмотра, можно просмотреть поля каждой записи, а также с помощью «создания экрана» вы можете создать свой метод просмотра/изменения данных, помещая данные в нужные места экрана или страницы. Возможности FoxPro в этом плане просто уникальны.

Создание меню

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

Создание проекта

Все элементы: экраны, отчеты, запросы, меню... можно объединить в единое целое, называемое проектом.

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

Часть 7.
Реляционные базы данных и
язык SQL

Глава 1. Реляционная база данных

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

Увеличение объема и структурной сложности хранимых данных, расширение круга пользователей информационных систем привели к широкому распространению наиболее удобных и сравнительно простых для понимания реляционных (табличных) СУБД. Для обеспечения одновременного доступа к данным множества пользователей, нередко расположенных достаточно далеко друг от друга и от места хранения баз данных, созданы сетевые мультипользовательские версии СУБД. В них тем или иным путем решаются специфические проблемы параллельных процессов, целостности (правильности) и безопасности данных, а также санкционирования доступа.

Ясно, что совместная работа пользователей в сетях с помощью унифицированных средств общения с базами данных возможна только при наличии стандартного языка манипулирования данными, обладающего средствами для реализации перечисленных выше возможностей. Таким языком стал SQL, разработанный фирмой IBM для экспериментальной реляционной СУБД System R.

Реляционная база данных представляется пользователю как совокупность таблиц и ничего кроме таблиц. Ниже приведен пример реляционной базы данных ПАНСИОН. Этот простой пример используется для иллюстрации большинства вопросов, рассматриваемых в нашей книге. Поэтому советуем потратить немного времени, чтобы хорошо с ним разобраться.

Кладовая пансионата периодически пополняется продуктами из списка, часть которого показана в таблице Продукты. Каждый продукт имеет кроме названия (столбец Продукт) уникальный номер этого продукта (столбец ПР). Химический состав продуктов приведен для 1 кг их съедобной части: основные пищевые вещества (белки, жиры и углеводы) даны в граммах, а минеральные вещества (калий, кальций, натрий) и витамины (B2, PP, C) — в миллиграммах.

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

Таблица Состав связывает между собой таблицы Блюда и Продукты, оговаривая, какая масса (в граммах) того или иного продукта (столбец Вес) должна входить в состав одной порции блюда. Так, порция блюда с номером 12 (Суп молочный) должна состоять из 350 г. продукта с номером 7 (Молоко), 35 г. продукта с номером 13 (Рис), 5 г. продукта с номером 3 (Масло) и 5 г. продукта с номером 16 (Сахар).

Шеф-повар ежедневно получает от завхоза сведения о количестве в килограммах имеющихся продуктов и их текущей стоимости (столбцы К_во и Стоимость таблицы Наличие). Используя эти сведения он определяет по таблице Состав перечень тех блюд, которые можно приготовить из этих продуктов, а также калорийность и стоимость таких блюд. При этом стоимость блюда складывается из стоимости и массы продуктов, необходимых для приготовления одной его порции, а также из трудозатрат на ее приготовление (из таблицы Блюда). Калорийность же определяется по массе и калорийности каждого из продуктов блюда. (Для получения значения калорийности продукта исходят из того, что при окислении 1 г. углеводов или белков в организме освобождается в среднем 4.1 ккал, а при окислении 1 г. жиров — 9.3 ккал.)

Основные таблицы базы данных ПАНСИОН

Учитывая примерную стоимость и необходимую калорийность дневного рациона отдыхающих, шеф-повар составляет меню на следующий день. В этом меню (таблица Меню) предлагается по несколько альтернативных блюд каждого вида (таблица Вид_ блюд) и для каждой трапезы (таблица Трапезы). Перед завтраком каждый отдыхающий вводит в ЭВМ номер закрепленного за ним места в столовой пансионата (столбец СМ в таблице Выбор) и желаемый набор блюд для каждой из трапез следующего дня (в примере таблица заполнялась отдыхающим, сидящим на месте с номером 2). Таблицы Выбор объединяются по мере их создания в общую таблицу Выбрано, по которой определяют, сколько порций того или иного блюда надо приготовить для каждой трапезы.

Завхоз связан с поставщиками продуктов, сведения о которых хранятся в таблице Поставщики. Эта таблица содержит уникальный номер поставщика (столбец ПС), его название, статус, месторасположение и телефон. Таблица Поставки связывает между собой таблицы Продукты и Поставщики, оговаривая, какое количество продукта (столбец К_во) и по какой цене поставил тот или иной поставщик. Отсутствие в строке цены и количества говорит о том, что поставщик ПС может поставлять продукт ПР, но в данный момент не осуществил такой поставки.

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

u всякому столбцу таблицы присвоено имя, которое должно быть уникальным для этой таблицы;

u столбцы таблицы упорядочиваются слева направо, то есть, столбец 1, столбец 2, ..., столбец n. С математической точки зрения это утверждение некорректно, потому что в реляционной системе столбцы не упорядочены. Однако с точки зрения пользователя, порядок, в котором определены имена столбцов, становится порядком, в котором должны вводиться в них данные, если не предварять при вводе каждое значение именем соответствующего столбца;

u строки таблицы не упорядочены (их последовательность определяется лишь последовательностью ввода в таблицу);

u в поле на пересечении строки и столбца любой таблицы всегда имеется только одно значение данных и никогда не должно быть множества значений (правда, это «атомарное» значение может быть достаточно объемным, например, таким, как рецепт блюда);

u всем строкам таблицы соответствует одно и то же множество столбцов, хотя в определенных столбцах любая строка может содержать пустые значения (NULL-значения), то есть, может не иметь значений для этих столбцов;

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

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

Почему же база данных, составленная из таких таблиц, называется реляционной? А потому, что отношение — relation — просто математический термин для обозначения неупорядоченной совокупности однотипных записей или таблиц определенного специфического вида, описанного выше. Таким образом, можно, например, сказать, что база данных ПАНСИОН состоит из одиннадцати отношений. Реляционные системы берут свое начало в математической теории множеств. Они были предложены в конце 1968 года доктором Э.Ф.Коддом из фирмы IBM, который первым осознал, что можно использовать математику для придания надежной основы и строгости области управления базами данных.

Нечеткость многих терминов, используемых в сфере обработки данных, заставила Кодда отказаться от них и придумать новые или дать более точные определения существующим. Так, он не мог использовать широко распространенный термин «запись», который в различных ситуациях может означать экземпляр записи, либо тип записей, запись в стиле Кобола (которая допускает повторяющиеся группы) или плоскую запись (которая их не допускает), логическую запись или физическую запись, хранимую запись или виртуальную запись. Вместо этого он использовал термин «кортеж длины n» или просто «кортеж», которому дал точное определение. В литературе можно подробно познакомиться с терминологией реляционных баз данных, а мы будем использовать неформальные их эквиваленты:

u таблица для отношения,

u строка или запись для кортежа,

u столбец или поле для атрибута.

Мы также принимаем, по определению, что «запись» означает «экземпляр записи», а «поле» означает «имя и тип поля».

Глава 2.
Почему SQL?

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

Рассматриваемый же ниже непроцедурный язык SQL (Structured Query Language — структуризованный язык запросов) ориентирован на операции с данными, представленными в виде логически взаимосвязанных совокупностей таблиц. Особенность предложений этого языка состоит в том, что они ориентированы в большей степени на конечный результат обработки данных, чем на процедуру этой обработки. SQL сам определяет, где находятся данные, какие индексы и даже наиболее эффективные последовательности операций следует использовать для их получения: не надо указывать эти детали в запросе к базе данных.

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

Для другого шофера такси вам, возможно, потребуется самому узнать, где демонстрируется нужный фильм и назвать кинотеатр. Тогда водитель должен найти адрес этого кинотеатра. Может случиться и так, что вам придется самому узнать адрес кинотеатра и предложить водителю проехать к нему по таким-то и таким-то улицам. В самом худшем случае вам, может быть, даже придется по дороге давать указания: «Повернуть налево... проехать пять кварталов... повернуть направо...». (Аналогично больший или меньший уровень детализации запроса приходится создавать пользователю в разных СУБД, не имеющих языка SQL.)

Появление теории реляционных баз данных и предложенного Коддом языка запросов «alpha», основанного на реляционном исчислении инициировало разработку ряда языков запросов, которые можно отнести к двум классам:

u Алгебраические языки, позволяющие выражать запросы средствами специализированных операторов, применяемых к отношениям (JOIN — соединить, INTERSECT — пересечь, SUBTRACT — вычесть).

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

Разработка, в основном, шла в отделениях фирмы IBM (языки ISBL, SQL, QBE) и университетах США (PIQUE, QUEL). Последний создавался для СУБД INGRES (Interactive Graphics and Retrieval System), которая была разработана в начале 70-х годов в Университете штата Калифорния и сегодня входит в пятерку лучших профессиональных СУБД. Сегодня из всех этих языков полностью сохранились и развиваются QBE (Query-By-Example — запрос по образцу) и SQL, а из остальных взяты в расширение внутренних языков СУБД только наиболее интересные конструкции.

В начале 80-х годов SQL «победил» другие языки запросов и стал фактическим стандартом таких языков для профессиональных реляционных СУБД. В 1987 году он стал международным стандартом языка баз данных и начал внедряться во все распространенные СУБД персональных компьютеров. Почему же это произошло?

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

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

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

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

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

Для исключения указанных и некоторых других недостатков была предложена технология «Клиент-Сервер», по которой запросы пользовательских ЭВМ (Клиент) обрабатываются на специальных серверах баз данных (Сервер), а на ЭВМ возвращаются лишь результаты обработки запроса. При этом, естественно, нужен единый язык общения с Сервером и в качестве такого языка выбран SQL. Поэтому все современные версии профессиональных реляционных СУБД (DB2, Oracle, Ingres, Informix, Sybase, Progress, Rdb) и даже нереляционных СУБД (например, Adabas) используют технологию «Клиент-Сервер» и язык SQL. К тому же приходят разработчики СУБД персональных ЭВМ, многие из которых уже сегодня снабжены языком SQL.

Бытует мнение, что поскольку большая часть запросов формулируется на SQL, практически безразлично, что это за СУБД — был бы SQL.

Реализация в SQL концепции операций, ориентированных на табличное представление данных, позволило создать компактный язык с небольшим (менее 30) набором предложений. SQL может использоваться как интерактивный (для выполнения запросов) и как встроенный (для построения прикладных программ). В нем существуют:

u предложения определения данных (определение баз данных, а также определение и уничтожение таблиц и индексов);

u запросы на выбор данных (предложение SELECT);

u предложения модификации данных (добавление, удаление и изменение данных);

u предложения управления данными (предоставление и отмена привилегий на доступ к данным, управление транзакциями и другие).

Кроме того, он предоставляет возможность выполнять в этих предложениях:

u арифметические вычисления (включая разнообразные функциональные преобразования), обработку текстовых строк и выполнение операций сравнения значений арифметических выражений и текстов;

u упорядочение строк и (или) столбцов при выводе содержимого таблиц на печать или экран дисплея;

u cоздание представлений (виртуальных таблиц), позволяющих пользователям иметь свой взгляд на данные без увеличения их объема в базе данных;

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

u агрегатирование данных: группирование данных и применение к этим группам таких операций, как среднее, сумма, максимум, минимум, число элементов.

В SQL используются следующие основные типы данных, форматы которых могут несколько различаться для разных СУБД:

INTEGER

Целое число (обычно до 10 значащих цифр и знак);

SMALLINT

«Короткое целое» (обычно до 5 значащих цифр и знак);

DECIMAL(p,q)

Десятичное число, имеющее p цифр (0<p<16) и знак; с помощью q задается число цифр справа от десятичной точки (q<p, если q=0, оно может быть опущено);

FLOAT

Вещественное число с 15 значащими цифрами и целочисленным порядком, определяемым типом СУБД;

CHAR(n)

Символьная строка фиксированной длины из n символов (0<n<256);

VARCHAR(n)

Символьная строка переменной длины, не превышающей n символов (n>0 и разное в разных СУБД, но не меньше 4096);

DATE

Дата в формате, определяемом специальной командой (по умолчанию mm/dd/yy); поля даты могут содержать только реальные даты, начинающиеся за несколько тысячелетий до н.э. и ограниченные пятым-десятым тысячелетием н.э.;

TIME

Время в формате, определяемом специальной командой, (по умолчанию hh.mm.ss);

DATETIME

Комбинация даты и времени;

MONEY

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

В некоторых СУБД еще существует тип данных LOGICAL, DOUBLE и ряд других. СУБД INGRES предоставляет пользователю возможность самостоятельного определения новых типов данных, например, плоскостные или пространственные координаты, единицы различных метрик, пяти- или шестидневные недели (рабочая неделя, где сразу после пятницы или субботы следует понедельник), дроби, графика, большие целые числа (что стало очень актуальным для российских банков).

Ориентированный на работу с таблицами SQL не имеет достаточных средств для создания сложных прикладных программ. Поэтому в разных СУБД он либо используется вместе с языками программирования высокого уровня (например, такими как Си или Паскаль), либо включен в состав команд специально разработанного языка СУБД (язык систем dBASE, R:BASE).

Унификация полных языков современных профессиональных СУБД достигается за счет внедрения объектно-ориентированного языка четвертого поколения 4GL. Последний позволяет организовывать циклы, условные предложения, меню, экранные формы, сложные запросы к базам данных с интерфейсом, ориентированным как на алфавитно-цифровые терминалы, так и на оконный графический интерфейс (X Windows, MS Windows).

Глава 3.
Таблицы SQL

До сих пор понятие «таблица», как правило, связывалось с реальной или базовой таблицей, то есть, c таблицей, для каждой строки которой в действительности имеется некоторый двойник, хранящийся в физической памяти машины. Однако SQL использует и создает ряд виртуальных (как будто существующих) таблиц: представлений, курсоров и неименованных рабочих таблиц, в которых формируются результаты запросов на получение данных из базовых таблиц и, возможно, представлений. Это таблицы, которые не существуют в базе данных, но как бы существуют с точки зрения пользователя.

Базовые таблицы создаются с помощью предложения CREATE TABLE (создать таблицу), подробное описание которого будет дано позднее. Здесь же приведем пример предложения для создания описания таблицы Блюда:

CREATE TABLE Блюда

(БЛ SMALLINT,

Блюдо CHAR (70),

В CHAR (1),

Основа CHAR (10),

Выход FLOAT,

Труд SMALLINT);

Предложение CREAT TABLE специфицирует имя базовой таблицы, которая должна быть создана, имена ее столбцов и типы данных для этих столбцов (а также, возможно, некоторую дополнительную информацию, не иллюстрируемую данным примером). CREAT TABLE — выполняемое предложение. Если его ввести с терминала, система тотчас построит таблицу Блюда, которая сначала будет пустой: она будет содержать только строку заголовков столбцов, но не будет еще содержать никаких строк с данными. Однако можно немедленно приступить к вставке таких строк данных, возможно, с помощью предложения INSERT и создать таблицу, аналогичную таблице Блюда.

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

SELECT БЛ,Блюдо

FROM Блюда

WHERE Основа = 'Овощи';

и мгновенно получить на экране следующий результат его реализации:

БЛ Блюдо

1 Салат летний

3 Салат витаминный

17 Морковь с рисом

23 Помидоры с луком

Для выполнения этого предложения SELECT (выбрать), подробное описание которого будет дано ниже, СУБД должна сначала сформировать пустую рабочую таблицу, состоящую из столбцов БЛ и Блюдо, тип данных которых должен совпадать с типом данных аналогичных столбцов базовой таблицы Блюда. Затем она должна выбрать из таблицы Блюда все строки, у которых в столбце Основа хранится слово Овощи, выделить из этих строк столбцы БЛ и Блюдо и загрузить укороченные строки в рабочую таблицу. Наконец, СУБД должна выполнить процедуры по организации вывода содержимого рабочей таблицы на экран терминала (при этом если в рабочей таблице содержится более 20-24 строк, она должна использовать процедуры постраничного вывода). После выполнения запроса СУБД должна уничтожить рабочую таблицу.

Если, например, надо получить значение калорийности всех овощей, включенных в таблицу Продукты, то можно набрать запрос:

SELECT Продукт, Белки, Жиры, Углев,((Белки+Углев)*4.1+Жиры*9.3)

FROM Продукты

WHERE Продукт IN ('Морковь','Лук','Помидоры','Зелень');

и получить на экране следующий результат его реализации:

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

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

Например, в запросе на получение состава овощных блюд:

SELECT БЛ,ПР,Вес

FROM Состав

WHERE БЛ IN (1,3,17,23);

пришлось перечислять номера этих блюд, так как в таблице Состав нет данных об основных продуктах блюда (они есть в таблице Блюда). Полученный состав овощных блюд оказался «слепым»: в нем и блюда и продукты представлены номерами, а не именами — вариант а). Удобнее и нагляднее вариант б).

Запрос сформированный по трем таблицам:

SELECT Блюдо, Продукт, Вес

FROM Состав,Блюда, Продукты

WHERE Состав.БЛ = Блюда.БЛ

AND Состав.ПР = Продукты.ПР

AND Основа = 'Овощи';

В нем для получения рабочей таблицы выполняется естественное соединение таблиц Блюда, Продукты и Состав (условие соединения — равенство значений номеров блюд и значений номеров продуктов). Затем выделяются строки, у которых в столбце Основа хранится слово Овощи, и из этих строк — столбцы Блюдо, Продукт и Вес.

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

Представление — это пустая именованная таблица, определяемая перечнем тех столбцов таблиц и признаками тех их строк, которые хотелось бы в ней увидеть. Представление является как бы «окном» в одну или несколько базовых таблиц. Оно создается с помощью предложения CREATE VIEW (создать представление). Приведем пример предложения для создания представления Состав_блюд:

CREATE VIEW Состав_блюд

AS SELECT Блюдо, Продукт, Вес

FROM Состав,Блюда,Продукты

WHERE Состав.БЛ = Блюда.БЛ

AND Состав.ПР = Продукты.ПР;

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

SELECT Блюдо,Продукт,Вес

FROM Состав_блюд

WHERE Основа = 'Овощи';

и получить на экране терминала данные, которые представлены на рисунке выше (вариант б). А для получения состава супа Харчо можно дать запрос:

SELECT Блюдо, Продукт, Вес

FROM Состав_блюд

WHERE Блюдо = 'Суп харчо';

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

Наконец, еще об одних виртуальных таблицах — курсорах.

Курсор — это пустая именованная таблица, определяемая перечнем тех столбцов базовых таблиц и признаками тех их строк, которые хотелось бы в ней увидеть. В чем же различие между курсором и представлением?

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

DECLARE Блюд_состав CURSOR FOR

SELECT Блюдо,Продукт,Вес

FROM Состав,Блюда,Продукты

WHERE Состав.БЛ = Блюда.БЛ

AND Состав.ПР = Продукты.ПР

AND Блюдо = 'Суп харчо';

и его активизации (OPEN Блюд_состав) будет создана временная таблица с составом блюда «Суп харчо» и специальным указателем, определяющим в качестве текущей первую строку этой таблицы. С помощью предложения FETCH (выбрать), которое обычно исполняется в программном цикле, можно присвоить определенным переменным значения указанных столбцов этой строки. Одновременно курсор будет передвинут к следующей строке таблицы. После обработки в программе полученных значений переменных выполняется следующее предложение FETCH до окончания перебора всех продуктов Харчо.

Глава 4.
Использование единственной таблицы

О предложении SELECT

Все запросы на получение практически любого количества данных из одной или нескольких таблиц выполняются с помощью единственного предложения SELECT. В общем случае результатом реализации предложения SELECT является другая таблица. К этой новой (рабочей) таблице может быть снова применена операция SELECT, то есть, такие операции могут быть вложены друг в друга. Представляет исторический интерес тот факт, что именно возможность включения одного предложения SELECT внутрь другого послужила мотивировкой использования прилагательного «структуризированный» в названии языка SQL.

Предложение SELECT может использоваться как:

u самостоятельная команда на получение и вывод строк таблицы, сформированной из столбцов и строк одной или нескольких таблиц (представлений);

u элемент WHERE- или HAVING-условия (сокращенный вариант предложения, называемый «вложенный запрос»);

u фраза выбора в командах CREAT VIEW, DECLARE CURSOR или INSERT;

u средство присвоения глобальным переменным значений из строк сформированной таблицы (INTO-фраза).

В синтаксических конструкциях используются следующие обозначения:

u звездочка (*) для обозначения «все» — употребляется в обычном для программирования смысле, то есть, «все случаи, удовлетворяющие определению»;

u квадратные скобки ([]) – означают, что конструкции, заключенные в эти скобки, являются необязательными (т.е. могут быть опущены);

u фигурные скобки ({}) – означают, что конструкции, заключенные в эти скобки, должны рассматриваться как целые синтаксические единицы, то есть, они позволяют уточнить порядок разбора синтаксических конструкций, заменяя обычные скобки, используемые в синтаксисе SQL;

u многоточие (...) – указывает на то, что непосредственно предшествующая ему синтаксическая единица факультативно может повторяться один или более раз;

u прямая черта (|) – означает наличие выбора из двух или более возможностей. Например, обозначение ASC|DESC указывает, можно выбрать один из терминов ASC или DESC; когда же один из элементов выбора заключен в квадратные скобки, то это означает, что он выбирается по умолчанию (так, [ASC]|DESC означает, что отсутствие всей этой конструкции будет восприниматься как выбор ASC);

u точка с запятой (;) – завершающий элемент предложений SQL;

u запятая (,) – используется для разделения элементов списков;

u пробелы ( ) – могут вводиться для повышения наглядности между любыми синтаксическими конструкциями предложений SQL;

u прописные жирные латинские буквы и символы – используются для написания конструкций языка SQL и должны (если это специально не оговорено) записываться в точности так, как показано;

u строчные буквы – используются для написания конструкций, которые должны заменяться конкретными значениями, выбранными пользователем, причем для определенности отдельные слова этих конструкций связываются между собой символом подчеркивания (_);

u термины таблица, столбец, ... – заменяют (с целью сокращения текста синтаксических конструкций) термины имя_таблицы, имя_столбца, ..., соответственно;

u термин таблица – используется для обобщения таких видов таблиц, как базовая_таблица, представление или псевдоним; здесь псевдоним служит для временного (на момент выполнения запроса) переименования и (или) создания рабочей копии базовой_таблицы (представления).

Предложение SELECT (выбрать) имеет следующий формат:

подзапрос [UNION [ALL] подзапрос] ...

[ORDER BY {[таблица.]столбец | номер_элемента_SELECT}

[[ASC] | DESC]

[,{[таблица.]столбец | номер_элемента_SELECT} [[ASC] | DESC]] ...;

Оно позволяет объединить (UNION) а затем упорядочить (ORDER BY) результаты выбора данных, полученных с помощью нескольких «подзапросов». При этом упорядочение можно производить в порядке возрастания — ASC (ASCending) или убывания DESC (DESCending), а по умолчанию принимается ASC.

В предложении, показанном ниже, подзапрос позволяет указать условия для выбора нужных данных и (если требуется) их обработки:

SELECT

(выбрать) данные из указанных столбцов и (если необходимо) выполнить перед выводом их преобразование в соответствии с указанными выражениями и (или) функциями

FROM

(из) перечисленных таблиц, в которых расположены эти столбцы

WHERE

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

GROUP BY

(группируя по) указанному перечню столбцов с тем, чтобы получить для каждой группы единственное агрегированное значение, используя во фразе SELECT SQL-функции SUM (сумма), COUNT (количество), MIN (минимальное значение), MAX (максимальное значение) или AVG (среднее значение)

HAVING

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

SELECT [[ALL] | DISTINCT]{ * | элемент_SELECT [,элемент_SELECT] ...}

FROM {базовая_таблица | представление} [псевдоним]

[,{базовая_таблица | представление} [псевдоним]] ...

[WHERE фраза]

[GROUP BY фраза [HAVING фраза]];

Элемент_SELECT — это одна из следующих конструкций:

[таблица.]* | значение | SQL_функция | системная_переменная

где значение – это:

[таблица.]столбец | (выражение) | константа | переменная

Синтаксис выражений имеет вид

( {[ [+] | — ] {значение | функция_СУБД} [ + | — | * | ** ]}...)

а синтаксис SQL_функций – одна из следующих конструкций:

{SUM|AVG|MIN|MAX|COUNT} ( [[ALL]|DISTINCT][таблица.]столбец )

{SUM|AVG|MIN|MAX|COUNT} ( [ALL] выражение )

COUNT(*)

Фраза WHERE включает набор условий для отбора строк:

WHERE [NOT]

WHERE_условие [[AND|OR][NOT] WHERE_условие]...

где WHERE_условие – одна из следующих конструкций:

значение { = | <> | < | <= | > | >= } { значение | ( подзапрос ) }

значение_1 [NOT] BETWEEN значение_2 AND значение_3

значение [NOT] IN { ( константа [,константа]... ) | ( подзапрос ) }

значение IS [NOT] NULL

[таблица.]столбец [NOT] LIKE 'строка_символов' [ESCAPE 'символ']

EXISTS ( подзапрос )

Кроме традиционных операторов сравнения (= | <> | < | <= | > | >=) в WHERE фразе используются условия BETWEEN (между), LIKE (похоже на), IN (принадлежит), IS NULL (не определено) и EXISTS (существует), которые могут предваряться оператором NOT (не). Критерий отбора строк формируется из одного или нескольких условий, соединенных логическими операторами:

AND

— когда должны удовлетворяться оба разделяемых с помощью AND условия;

OR

— когда должно удовлетворяться одно из разделяемых с помощью OR условий;

AND NOT

— когда должно удовлетворяться первое условие и не должно второе;

OR NOT

— когда или должно удовлетворяться первое условие или не должно удовлетворяться второе.

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

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

Наконец, синтаксис фразы GROUP BY имеет вид:

GROUP BY [таблица.]столбец [,[таблица.]столбец] ... [HAVING фраза]

GROUP BY инициирует перекомпоновку формируемой таблицы по группам, каждая из которых имеет одинаковое значение в столбцах, включенных в перечень GROUP BY. Далее к этим группам применяются агрегирующие функции, указанные во фразе SELECT, что приводит к замене всех значений группы на единственное значение (сумма, количество).

С помощью фразы HAVING (синтаксис которой почти не отличается от синтаксиса фразы WHERE), а именно:

HAVING [NOT] HAVING_условие [[AND|OR][NOT] HAVING_условие]...

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

значение { = | <> | < | <= | > | >= } {значение | ( подзапрос ) | SQL_функция}

{значение_1 | SQL_функция_1} [NOT] BETWEEN

{значение_2 | SQL_функция_2} AND {значение_3 | SQL_функция_3}

{значение | SQL_функция} [NOT] IN { (константа [,константа]...)| (подзапрос) }

{значение | SQL_функция} IS [NOT] NULL

[таблица.]столбец [NOT] LIKE 'строка_символов' [ESCAPE 'символ']

EXISTS ( подзапрос )

Глава 5.
Выборка без использования фразы WHERE

Простая выборка

Запрос на выдачу названия, статуса и адреса поставщиков:

SELECT Название, Статус, Адрес

FROM Поставщики;

дает результат, приведенный на рисунке ниже.

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

SELECT ПС, Название, Статус, Город, Адрес, Телефон

FROM Поставщики;

или использовать его более короткую нотацию:

SELECT *

FROM Поставщики;

Здесь «звездочка» (*) служит кратким обозначением всех имен полей в таблице, указанной во фразе FROM. При этом порядок вывода полей соответствует порядку, в котором эти поля определялись при создании таблицы.

Еще один пример. Выдать основу всех блюд:

SELECT Основа

FROM Блюда;

дает результат, показанный ниже.

Исключение дубликатов

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

SELECT DISTINCT Основа

FROM Блюда;

Результат приведен ниже.

Выборка вычисляемых значений

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

Например, если нужно получить значение калорийности всех продуктов, то можно учесть, что при окислении 1 г. углеводов или белков в организме освобождается в среднем 4.1 ккал, а при окислении 1 г. жиров — 9.3 ккал, и выдать запрос:

SELECT Продукт, ((Белки+Углев)*4.1+Жиры*9.3)

FROM Продукты;

результат которого приведен ниже.

Фраза SELECT может включать не только выражения, но и отдельные числовые или текстовые константы.

Следует отметить, что текстовые константы должны заключаться в апострофы ('). Ниже на рисунке приведен результат запроса:

SELECT Продукт, 'Калорий =', ((Белки+Углев)*4.1+Жиры *9.3)

FROM Продукты;

А что произойдет, если какой-либо член выражения не определен, то есть, имеет значение NULL и каким образом появилось такое значение?

Если при загрузке строк таблицы в какой-либо из вводимых строк отсутствует значение для какого-либо столбца, то СУБД введет в такое поле NULL-значение. NULL-значение «придумано» для того, чтобы представить единым образом «неизвестные значения» для любых типов данных. Действительно, так как при вводе данных в столбец или их изменении СУБД запрещает ввод значений, не соответствующих описанию данных этого столбца, то, например, нельзя использовать пробел для отсутствующего значения числа. Нельзя для этих целей использовать и ноль: нет месяца или дня недели равного нулю, да и для чисел ноль не может рассматриваться как неизвестное значение в одном месте и как известное — в другом. При выводе же NULL-значения на экран или печатающее устройство его код воспроизводится каким-либо специально заданным символом или набором символов: например, пробелом (если его нельзя перепутать с текстовым значением пробела) или сочетанием -0-.

С помощью специальной команды можно установить в СУБД один из режимов представления NULL-значений при выполнении числовых расчетов: запрет или разрешение замены NULL-значения нулем.

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

Например, при выполнении запроса

SELECT ПР, Цена, К_во, (Цена * К_во)

FROM Поставки;

и разных «настройках» СУБД могут быть получены разные результаты:

Глава 6.
Выборка c использованием фразы WHERE

Использование операторов сравнения

В синтаксисе фразы WHERE показано, что для отбора нужных строк таблицы можно использовать операторы сравнения = (равно), <> (не равно), < (меньше), <= (меньше или равно), > (больше), >= (больше или равно), которые могут предваряться оператором NOT, создавая, например, отношения «не меньше» и «не больше».

Так, для получения перечня продуктов, практически не содержащих углеводов, можно сформировать запрос:

SELECT Продукт, Белки, Жиры, Углев, K, Ca, Na, B2, PP, C

FROM Продукты

WHERE Углев = 0;

и получить:

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

Так, для получения перечня продуктов, практически не содержащих углеводов и натрия, можно сформировать запрос:

SELECT Продукт, Белки, Жиры, Углев, K, Ca, Na, B2, PP, C

FROM Продукты

WHERE Углев = 0 AND Na = 0;

Результат запроса имеет вид:

Добавим к этому запросу еще одно условие:

SELECT Продукт, Белки, Жиры, Углев, K, Ca, Na, B2, PP, C

FROM Продукты

WHERE Углев = 0 AND Na = 0 AND Продукт <> 'Судак';

и получим на экране сообщение «No rows exist or satisfy the specified clause» или аналогичное (в зависимости от вкусов разработчиков разных СУБД), информирующее об отсутствии строк, удовлетворяющих заданному(ым) условию(ям).

Использование BETWEEN

С помощью BETWEEN ... AND ... (находится в интервале от ... до ...) можно отобрать строки, в которых значение какого-либо столбца находятся в заданном диапазоне.

Например, выдать перечень продуктов, в которых значение содержания белка находится в диапазоне от 10 до 50:

SELECT Продукт, Белки

FROM Продукты

WHERE Белки BETWEEN 10 AND 50;

Результат:

Продукт Белки

Майонез 31

Сметана 26

Молоко 28

Морковь 13

Лук 17

Можно задать и NOT BETWEEN (не принадлежит диапазону между), например:

SELECT Продукт, Белки, Жиры

FROM Продукты

WHERE Белки NOT BETWEEN 10 AND 50

AND Жиры > 100;

Результат:

Продукт Белки Жиры

Говядина 189 124

Масло 60 825

Яйца 127 115

BETWEEN особенно удобен при работе с данными, задаваемыми интервалами, начало и конец которых расположен в разных столбцах.

Для примера воспользуемся таблицей «минимальных окладов», величина которых непосредственно связана со студенческой стипендией. В этой таблице для текущего значения минимального оклада установлена запредельная дата окончания 9 сентября 9999 года.

Минимальные оклады

Миноклад Начало Конец

2250 01-01-1993 31-03-1993

4275 01-04-1993 30-06-1993

7740 01-07-1993 30-11-1993

14620 01-12-1993 30-06-1994

20500 01-07-1994 09-09-9999

Если, например, потребовалось узнать, какие изменения минимальных окладов производились в 1993/94 учебном году, то можно выдать запрос:

SELECT Начало, Миноклад

FROM Миноклады

WHERE Начало BETWEEN '1-9-1993' AND '31-8-1994'

и получить результат:

Начало Миноклад

01-12-1993 14620

01-07-1994 20500

Отметим, что при формировании запросов значения дат следует заключать в апострофы, чтобы СУБД не путала их с выражениями и не пыталась вычитать из 31 значение 8, а затем 1994.

Для выявления всех значений минимальных окладов, которые существовали в 1993/94 учебном году, можно сформировать запрос:

SELECT *

FROM Миноклады

WHERE Начало BETWEEN '1-9-1993' AND '31-8-1994'

OR Конец BETWEEN '1-9-1993' AND '31-8-1994'

и получить результат:

Миноклад Начало Конец

7740 01/07/1993 30/11/1993

14620 01/12/1993 30/06/1994

20500 01/07/1994 09/09/9999

Наконец, для получения минимального оклада на 15-5-1994:

SELECT Миноклад

FROM Миноклады

WHERE '15-05-1994' BETWEEN Начало AND Конец

Результат:

Миноклад

14620

Использование IN

Выдать сведения о блюдах на основе яиц, крупы и овощей:

SELECT *

FROM Блюда

WHERE Основа IN (Яйца Крупа Овощи);

Результат:

Рассмотренная форма IN является в действительности просто краткой записью последовательности отдельных сравнений, соединенных операторами OR. Предыдущее предложение эквивалентно такому:

SELECT *

FROM Блюда

WHERE Основа=Яйца OR Основа=Крупа OR Основа=Овощи;

Можно задать и NOT IN (не принадлежит), а также возможность использования IN (NOT IN) с подзапросом.

Использование LIKE

Выдать перечень салатов

SELECT Блюдо

FROM Блюда

WHERE Блюдо LIKE 'Салат%';

Результат:

Блюдо

Салат летний

Салат мясной

Салат витаминный

Салат рыбный

Обычная форма «имя_столбца LIKE текстовая_константа» для столбца текстового типа позволяет отыскать все значения указанного столбца, соответствующие образцу, заданному «текстовой_константой».

Символы этой константы интерпретируются следующим образом:

u символ _ (подчеркивание) заменяет любой одиночный символ

u символ % (процент) – заменяет любую последовательность из N символов (где N может быть нулем)

u все другие символы означают просто сами себя.

Следовательно, в приведенном примере SELECT будет осуществлять выборку записей из таблицы Блюда, для которых значение в столбце Блюдо начинается сочетанием 'Салат' и содержит любую последовательность из нуля или более символов, следующих за сочетанием 'Салат'. Если бы среди блюд были «Луковый салат», «Фруктовый салат», то они не были бы найдены. Для их отыскания надо изменить фразу WHERE:

WHERE Блюдо LIKE '%салат%'

или при отсутствии различий между малыми и большими буквами (такую настройку допускают некоторые СУБД):

WHERE Блюдо LIKE '%Салат%'

Это позволит отыскать все салаты.

Вовлечение неопределенного значения (NULL-значения)

Как было рассказано ранее, если при загрузке данных не введено значение в какое-либо поле таблицы, то СУБД поместит в него NULL-значение. Аналогичное значение можно ввести в поле таблицы, выполняя операцию изменения данных. Так, при отсутствии сведений о наличии у поставщиков судака и моркови в столбцы Цена и К_во соответствующих строк таблицы Поставки вводится NULL и там будет храниться код NULL-значения, а не 0, 0. или пробел.

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

SELECT DISTINCT ПР

FROM Наличие

WHERE К_во IS NULL;

Результат:

ПР

2

9

Естественно, что для выявления продуктов, существующих в кладовой, следует дать запрос:

SELECT DISTINCT ПР

FROM Наличие

WHERE К_во IS NOT NULL;

Использование условий:

столбец IS NULL и столбец IS NOT NULL

вместо, например,

столбец = NULL и столбец <> NULL

связано с тем, что ничто — и даже само NULL-значение — не считается равным другому NULL-значению. (Несмотря на это, два неопределенных значения рассматриваются, однако, как дубликаты друг друга при исключении дубликатов, и предложение SELECT DISTINCT даст в результате не более одного NULL-значения.)

Глава 7.
Выборка с упорядочением

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

SELECT Продукт, Белки, Жиры, Углев

FROM Продукты

ORDER BY Белки DESC;

Результат:

Продукт Белки Жиры Углев

Судак 190 80 0

Говядина 189 124 0

Творог 167 90 13

Яйца 127 115 7

Кофе 127 36 9

Мука 106 13 732

. . .

При включении в список ORDER BY нескольких столбцов СУБД сортирует строки результата по значениям первого столбца списка пока не появится несколько строк с одинаковыми значениями данных в этом столбце. Такие строки сортируются по значениям следующего столбца из списка ORDER BY. Например, выдать содержимое таблицы Блюда, отсортировав ее строки по видам блюд и основе:

SELECT *

FROM Блюда

ORDER BY В Основа;

Результат:

Кроме того, в список ORDER BY можно включать не только имя столбца, а его порядковую позицию в перечне SELECT. Благодаря этому возможно упорядочение результатов на основе вычисляемых столбцов, не имеющих имен.

Например, запрос

SELECT Продукт, ((Белки+Углев)*4.1+Жиры*9.3)

FROM Продукты

ORDER BY 2;

позволит получить список продуктов – переупорядоченный по возрастанию значений калорийности.

Глава 8.
Агрегирование данных

SQL-функции

В SQL существует ряд специальных стандартных функций (SQL-функций). Кроме специального случая COUNT(*) каждая из этих функций оперирует совокупностью значений столбца некоторой таблицы и создает единственное значение, определяемое так:

COUNT

Число значений в столбце.

SUM

Сумма значений в столбце.

AVG

Среднее значение в столбце.

MAX

Самое большое значение в столбце.

MIN

Самое малое значение в столбце.

Для функций SUM и AVG рассматриваемый столбец должен содержать числовые значения.

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

Аргументу всех функций, кроме COUNT(*), может предшествовать ключевое слово DISTINCT (различный), указывающее, что избыточные дублирующие значения должны быть исключены перед тем, как будет применяться функция. Специальная же функция COUNT(*) служит для подсчета всех без исключения строк в таблице (включая дубликаты).

Функции без использования фразы GROUP BY

Если не используется фраза GROUP BY, то в перечень элементов_SELECT можно включать лишь SQL-функции или выражения, содержащие такие функции. Другими словами, нельзя иметь в списке столбцы, не являющихся аргументами SQL-функций.

Например, выдать данные о массе лука (ПР=10), проданного поставщиками, и указать количество этих поставщиков:

SELECT SUM(К_во),COUNT(К_во)

FROM Поставки

WHERE ПР = 10;

Результат:

SUM(К_во) COUNT(К_во)

220 2

Если бы для вывода в результат еще и номера продукта был сформирован запрос:

SELECT ПР,SUM(К_во),COUNT(К_во)

FROM Поставки

WHERE ПР = 10;

то было бы получено сообщение об ошибке. Это связано с тем, что SQL-функция создает единственное значение из множества значений столбца-аргумента, а для «свободного» столбца должно быть выдано все множество его значений. Без специального указания (оно задается фразой GROUP BY) SQL не будет выяснять, одинаковы значения этого множества (как в данном примере, где ПР=10) или различны (как было бы при отсутствии WHERE фразы). Поэтому подобный запрос отвергается системой.

Правда, никто не запрещает дать запрос:

SELECT 'Кол-во лука =',SUM(К_во),COUNT(К_во)

FROM Поставки

WHERE ПР = 10;

Результат:

'Кол-во лука =' SUM(К_во) COUNT(К_во)

Кол-во лука = 220 2

Отметим также, что в столбце-аргументе перед применением любой функции, кроме COUNT(*), исключаются все неопределенные значения. Если оказывается, что аргумент — пустое множество, функция COUNT принимает значение 0, а остальные — NULL.

Например, для получения суммы цен, средней цены, количества поставляемых продуктов и количества разных цен продуктов, проданных коопторгом УРОЖАЙ (ПС= 5), а также для получения количества продуктов, которые могут поставляться этим коопторгом, можно дать запрос:

SELECT SUM(Цена),AVG(Цена),COUNT(Цена),

COUNT(DISTINCT Цена),COUNT(*)

FROM Поставки

WHERE ПС = 5;

и получить:

В другом примере, где надо узнать «Сколько поставлено моркови и сколько поставщиков ее поставляют?»:

SELECT SUM(К_во),COUNT(К_во)

FROM Поставки

WHER ПР = 2;

будет получен ответ:

SUM(К_во) COUNT (К_во)

-0 - 0

Наконец, попробуем получить сумму массы поставленного лука с его средней ценой («Сапоги с яичницей»):

SELECT (SUM(К_во) +AVG(Цена))

FROM Поставки

WHERE ПР = 10;

Результат:

SUM(К_во)+AVG(Цена)

220.6

Фраза GROUP BY

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

SELECT ПР, SUM(К_во)

FROM Поставки

GROUP BY ПР;

Результат показан ниже.

Фраза GROUP BY (группировать по) инициирует перекомпоновку указанной во FROM таблицы по группам, каждая из которых имеет одинаковые значения в столбце, указанном в GROUP BY. В рассматриваемом примере строки таблицы Поставки группируются так, что в одной группе содержатся все строки для продукта с ПР = 1, в другой – для продукта с ПР = 2. Далее к каждой группе применяется фраза SELECT. Каждое выражение в этой фразе должно принимать единственное значение для группы, то есть, оно может быть либо значением столбца, указанного в GROUP BY, либо арифметическим выражением, включающим это значение, либо константой, либо одной из SQL-функций, которая оперирует всеми значениями столбца в группе и сводит эти значения к единственному значению (например, к сумме).

Отметим, что фраза GROUP BY не предполагает ORDER BY. Чтобы гарантировать упорядочение по ПР результата рассматриваемого примера следует дать запрос:

SELECT ПР, SUM(К_во)

FROM Поставки

GROUP BY ПР

ORDER BY ПР;

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

SELECT Т, БЛ, COUNT(БЛ)

FROM Заказ

GROUP BY Т, БЛ;

можно узнать коды и количество порций блюд, заказанных отдыхающими пансионата (32 человека) на каждую из трапез следующего дня:

Т БЛ COUNT(БЛ)

1 3 18

1 6 14

1 19 17

1 21 15

...

Если в запросе используются фразы WHERE и GROUP BY, то строки, не удовлетворяющие фразе WHERE, исключаются до выполнения группирования.

Например, выдать для каждого продукта его код и общий объем возможных поставок, учитывая временную недееспособность поставщика с ПС=2:

SELECT ПР, SUM(К_во)

FROM Поставки

WHERE ПС <> 2

GROUP BY ПР;

Использование фразы HAVING

Фраза HAVING играет такую же роль для групп, что и фраза WHERE для строк: она используется для исключения групп, точно так же, как WHERE используется для исключения строк. Эта фраза включается в предложение лишь при наличии фразы GROUP BY, а выражение в HAVING должно принимать единственное значение для группы.

Например, выдать коды продуктов, поставляемых более чем двумя поставщиками:

SELECT

FROM Поставки

GROUP BY ПС

HAVING COUNT(*) > 2;

Результат:

ПР

9

11

12

Глава 9.
Использование нескольких таблиц

Затрагивая вопросы проектирования баз данных, мы выяснили, что базы данных — это множество взаимосвязанных сущностей или отношений (таблиц) в терминологии реляционных СУБД. При проектировании стремятся создавать таблицы, в каждой из которых содержалась бы информация об одном и только об одном типе сущностей. Это облегчает модификацию базы данных и поддержание ее целостности.

Но такой подход тяжело усваивается начинающими проектантами, которые пытаются привязать проект к будущим приложениям и так организовать таблицы, чтобы в каждой из них хранилось все необходимое для реализации возможных запросов. Типичен вопрос: как же получить сведения о том, где купить продукты для приготовления того или иного блюда и определить его калорийность и стоимость, если нужные данные «рассыпаны» по семи различным таблицам? Не лучше ли иметь одну большую таблицу, содержащую все сведения базы данных ПАНСИОН?

Даже при отсутствии средств одновременного доступа ко многим таблицам нежелателен проект, в котором информация о многих типах сущностей перемешана в одной таблице. SQL же обладает великолепным механизмом для одновременной или последовательной обработки данных из нескольких взаимосвязанных таблиц. В нем реализованы возможности «соединять» или «объединять» несколько таблиц и так называемые «вложенные подзапросы». Например, чтобы получить перечень поставщиков продуктов, необходимых для приготовления Сырников, возможен запрос:

SELECT Продукт, Цена, Название, Статус

FROM Продукты, Состав, Блюда, Поставки, Поставщики

WHERE Продукты.ПР = Состав.ПР

AND Состав.БЛ = Блюда.БЛ

AND Поставки.ПР = Состав.ПР

AND Поставки.ПС = Поставщики.ПС

AND Блюдо = 'Сырники'

AND Цена IS NOT NULL;

Он получен следующим образом: СУБД последовательно формирует строки декартова произведения таблиц, перечисленных во фразе FROM, проверяет, удовлетворяют ли данные сформированной строки условиям фразы WHERE, и если удовлетворяют, то включает в ответ на запрос те ее поля, которые перечислены во фразе SELECT.

Следует подчеркнуть, что в SELECT и WHERE (во избежание двусмысленности) ссылки на все (*) или отдельные столбцы могут (а иногда и должны) уточняться именем соответствующей таблицы, например, Поставки.ПС, Поставщики.ПС, Меню.*, Состав.БЛ, Блюда.*.

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

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

Кроме механизма соединений в SQL есть механизм вложенных подзапросов, позволяющий объединить несколько простых запросов в едином предложении SELECT. Иными словами, вложенный подзапрос — это уже знакомый нам подзапрос (с небольшими ограничениями), кото рый вложен в WHERE фразу другого вложенного подзапроса или WHERE фразу основного запроса.

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

SELECT Продукт, Цена, Название, Статус

FROM Продукты, Состав, Блюда, Поставки, Поставщики

WHERE Продукты.ПР = Состав.ПР

AND Состав.БЛ = Блюда.БЛ

AND Поставки.ПР = Состав.ПР

AND Поставки.ПС = Поставщики.ПС

AND Блюдо = 'Сырники'

AND Цена = ( SELECT MIN(Цена)

FROM Поставки X

WHERE X.ПР = Поставки.ПР );

Результат запроса имеет вид:

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

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

Глава 10.
Запросы, использующие соединения

Декартово произведение таблиц

Соединения — это подмножества декартова произведения. Так как декартово произведение n таблиц — это таблица, содержащая все возможные строки r, такие, что r является сцеплением какой-либо строки из первой таблицы, строки из второй таблицы, ... и строки из n-й таблицы (а мы уже научились выделять с помощью SELECT любое подмножество реляционной таблицы), то осталось лишь выяснить, можно ли с помощью SELECT получить декартово произведение.

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

Так, для получения декартова произведения Вид_блюд и Трапезы надо выдать запрос:

SELECT Вид_блюд.*, Трапезы.*

FROM Вид_блюд, Трапезы;

Получим таблицу, содержащую 5 х 3 = 15 строк:

В другом примере, где перемножаются таблицы Меню, Трапезы, Вид_блюд, Блюда:

SELECT Меню.*, Трапезы.*, Вид_блюд.*, Блюда.*

FROM Меню, Трапезы, Вид_блюд, Блюда;

образуется таблица, содержащая 21 х 3 х 5 х 33 = 10395 строк.

Из первых 39 строк этой таблицы лишь две актуальных (отмечены «*»): в них совпадают номера блюд таблиц Меню и Блюда. В остальных – полная чепуха: к закускам отнесены супы и напитки, на завтрак предлагается незапланированный суп.

Эквисоединение таблиц

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

Очевидно, что отбор актуальных строк обеспечивается вводом в запрос WHERE фразы, в которой устанавливается соответствие между:

u кодами трапез (Т) в таблицах Меню и Трапезы (Меню.Т = Трапезы.Т)

u кодами видов блюд (В) в таблицах Меню и Вид_блюд (Меню.В = Вид_блюд.В)

u номерами блюд (БЛ) в таблицах Меню и Блюда (Меню.БЛ = Блюда.БЛ).

Такой скорректированный запрос:

SELECT Меню.*, Трапезы.*, Вид_блюд.*, Блюда.*

FROM Меню, Трапезы, Вид_блюд, Блюда

WHERE Меню.Т = Трапезы.Т

AND Меню.В = Вид_блюд.В

AND Меню.БЛ = Блюда.БЛ;

позволит получить эквисоединение таблиц Меню, Трапезы, Вид_блюд и Блюда:

Естественное соединение таблиц

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

SELECT Т, В, БЛ, Трапеза, Вид, Блюдо, Основа, Выход, Труд

FROM Меню, Трапезы, Вид_блюд, Блюда

WHERE Меню.Т = Трапезы.Т

AND Меню.В = Вид_блюд.В

AND Меню.БЛ = Блюда.БЛ;

Реализация естественного соединения таблиц имеет вид

Композиция таблиц

Для исключения всех столбцов, по которым проводится соединение таблиц, надо создать композицию

SELECT Трапеза, Вид, Блюдо, Основа, Выход, Труд

FROM Меню, Трапезы, Вид_блюд, Блюда

WHERE Меню.Т = Трапезы.Т

AND Меню.В = Вид_блюд.В

AND Меню.БЛ = Блюда.БЛ;

имеющую вид:

Тета-соединение таблиц

В базе данных ПАНСИОН трудно подобрать несложный пример, иллюстрирующий тета-соединение таблиц. Поэтому сконструируем такой надуманный запрос:

SELECT Вид_блюд.*, Трапезы.*

FROM Вид_блюд, Трапезы

WHERE Вид > Трапеза;

позволяющий выбрать из полученного ранее декартова произведения таблиц Вид_блюд и Трапезы лишь те строки, в которых значение трапезы «меньше» (по алфавиту) значения вида блюда:

В Вид Т Трапеза

З Закуска 1 Завтрак

С Суп 1 Завтрак

С Суп 2 Обед

Н Напиток 1 Завтрак

Соединение таблиц с дополнительным условием

При формировании соединения создается рабочая таблица, к которой применимы все операции, рассмотренные ранее: отбор нужных строк соединения (WHERE фраза), упорядочение получаемого результата (ORDER BY фраза) и агрегатирование данных (SQL-функции и GROUP BY фраза).

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

SELECT Вид, Блюдо, Основа, Выход, 'Номер -', БЛ

FROM Меню, Трапезы, Вид_блюд, Блюда

WHERE Меню.Т = Трапезы.Т

AND Меню.В = Вид_блюд.В

AND Меню.БЛ = Блюда.БЛ

AND Трапеза = ’Завтрак’;

Получим:

Соединение таблицы со своей копией

В ряде приложений возникает необходимость одновременной обработки данных какой-либо таблицы и одной или нескольких ее копий, создаваемых на время выполнения запроса.

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

Временную копию таблицы можно сформировать, указав имя псевдонима за именем таблицы во фразе FROM. Так, с помощью фразы:

FROM Блюда X, Блюда Y, Блюда Z

будут сформированы три копии таблицы Блюда с именами X, Y и Z.

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

SELECT Блюдо, Копия.Блюдо, Основа

FROM Блюда, Блюда Копия

WHERE Основа = Копия.Основа

AND Блюдо < Копия.Блюдо;

или двумя ее копиями (Первая и Вторая):

SELECT Первая.Блюдо, Вторая.Блюдо, Основа

FROM Блюда Первая, Блюда Вторая

WHERE Первая.Основа = Вторая.Основа

AND Первая.Блюдо < Вторая.Блюдо;

Получим результат вида:

Глава 11.
Вложенные подзапросы

Виды вложенных подзапросов

Вложенный подзапрос — это подзапрос, заключенный в круглые скобки и вложенный в WHERE (HAVING) фразу предложения SELECT или других предложений, использующих WHERE фразу. Вложенный подзапрос может содержать в своей WHERE (HAVING) фразе другой вложенный подзапрос. Нетрудно догадаться, что вложенный подзапрос создан для того, чтобы при отборе строк таблицы, сформированной основным запросом, можно было использовать данные из других таблиц (например, при отборе блюд для меню использовать данные о наличии продуктов в кладовой пансионата).

Существуют простые и коррелированные вложенные подзапросы. Они включаются в WHERE (HAVING) фразу с помощью условий IN, EXISTS или одного из условий сравнения (= | <> | < | <= | > | >=). Простые вложенные подзапросы обрабатываются системой «снизу вверх». Первым обрабатывается вложенный подзапрос самого нижнего уровня. Множество значений, полученное в результате его выполнения, используется при реализации подзапроса более высокого уровня.

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

Следует отметить, что SQL обладает большой избыточностью в том смысле, что он часто предоставляет несколько различных способов формулировки одного и того же запроса. Поэтому во многих примерах данной главы будут использованы уже знакомые нам по предыдущей главе концептуальные формулировки запросов. И несмотря на то, что часть из них успешнее реализуется с помощью соединений, здесь все же будут приведены их варианты с использованием вложенных подзапросов. Это связано с необходимостью детального знакомства с созданием и принципом выполнения вложенных подзапросов, так как существует немало задач (особенно на удаление и изменение данных), которые не могут быть реализованы другим способом. Кроме того, разные формулировки одного и того же запроса требуют для своего выполнения различных ресурсов памяти и могут значительно отличаться по времени реализации в разных СУБД.

Простые вложенные подзапросы

Простые вложенные подзапросы используются для представления множества значений, исследование которых должно осуществляться в каком-либо предикате IN, что иллюстрируется в следующем примере: вы дать название и статус поставщиков продукта с номером 11, то есть, помидоров.

SELECT Название, Статус

FROM Поставщики

WHERE ПС IN

( SELECT ПС

FROM Поставки

WHERE ПР = 11 );

Результат:

Название Статус

СЫТНЫЙ рынок

УРОЖАЙ коопторг

ЛЕТО агрофирма

КОРЮШКА кооператив

Как уже отмечалось ранее, при обработке полного запроса система выполняет прежде всего вложенный подзапрос. Этот подзапрос выдает множество номеров поставщиков, которые поставляют продукт с кодом ПР = 11, а именно множество (1, 5, 6, 8). Поэтому первоначальный запрос эквивалентен такому простому запросу:

SELECT Название, Статус

FROM Поставщики

WHERE ПС IN (1, 5, 6, 8);

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

SELECT Название, Статус

FROM Поставщики

WHERE ПС IN

( SELECT ПС

FROM Поставки

WHERE ПР IN

( SELECT ПР

FROM Продукты

WHERE Продукт = 'Помидоры' ));

В данном случае результатом самого внутреннего подзапроса является только одно значение (11). Как уже было показано выше, подзапрос следующего уровня в свою очередь дает в результате множество (1, 5, 6, 8). Последний, самый внешний SELECT, вычисляет приведенный выше окончательный результат. Вообще допускается любая глубина вложенности подзапросов.

Тот же результат можно получить с помощью соединения:

SELECT Название, Статус

FROM Поставщики, Поставки, Продукты

WHERE Поставщики.ПС = Поставки.ПС

AND Поставки.ПР = Продукты.ПР

AND Продукт = 'Помидоры';

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

Использование одной и той же таблицы во внешнем и вложенном подзапросе

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

SELECT DISTINCT ПС

FROM Поставки

WHERE ПР IN

( SELECT ПР

FROM Поставки

WHERE ПС = 6);

Результат:

ПС

1

3

5

6

8

Отметим, что ссылка на Поставки во вложенном подзапросе означает не то же самое, что ссылка на Поставки во внешнем запросе. В действительности, два имени Поставки обозначают различные значения. Чтобы этот факт стал явным, полезно использовать псевдонимы, например, X и Y:

SELECT DISTINCT X.ПС

FROM Поставки X

WHERE X.ПР IN

( SELECT Y.ПР

FROM Поставки Y

WHERE Y.ПС = 6 );

Здесь X и Y – произвольные псевдонимы таблицы Поставки, определяемые во фразе FROM и используемые как явные уточнители во фразах SELECT и WHERE. Напомним, что псевдонимы определены лишь в пределах одного запроса.

Вложенный подзапрос с оператором сравнения, отличным от IN

Выдать номера поставщиков, находящихся в том же городе, что и поставщик с номером 6.

SELECT ПС

FROM Поставщики

WHERE Город =

( SELECT Город

FROM Поставщики

WHERE ПС = 6 );

Результат:

ПС

1

4

6

В подобных запросах можно использовать и другие операторы сравнения (<>, <=, <, >= или >), однако, если вложенный подзапрос возвращает более одного значения и не используется оператор IN, будет возникать ошибка.

Коррелированные вложенные подзапросы

Выдать название и статус поставщиков продукта с номером 11.

SELECT Название, Статус

FROM Поставщики

WHERE 11 IN

( SELECT ПР

FROM Поставки

WHERE ПС = Поставщики.ПС );

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

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

( SELECT ПР

FROM Поставки

WHERE ПС = 1 );

получая в результате множество (9, 11, 12, 15). Теперь система может завершить обработку для поставщика с номером 1. Выборка значений Название и Статус для ПС=1 (СЫТНЫЙ и рынок) будет проведена тогда и только тогда, когда ПР=11 будет принадлежать этому множеству, что, очевидно, справедливо.

2. Далее система будет повторять обработку такого рода для следующего поставщика до тех пор, пока не будут рассмотрены все строки таблицы Поставщики.

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

Рассмотрим пример использования одной и той же таблицы во внешнем подзапросе и коррелированном вложенном подзапросе.

Выдать номера всех продуктов, поставляемых только одним поставщиком.

SELECT DISTINCT X.ПР

FROM Поставки X

WHERE X.ПР NOT IN

( SELECT Y.ПР

FROM Поставки Y

WHERE Y.ПС <> X.ПС );

Результат:

X.ПР

17

Действие этого запроса можно пояснить следующим образом: «Поочередно для каждой строки таблицы Поставки, скажем X, выделить значение номера продукта (ПР), если и только если это значение не входит в некоторую строку, скажем, Y, той же таблицы, а значение столбца номер поставщика (ПС) в строке Y не равно его значению в строке X».

Отметим, что в этой формулировке должен быть использован по крайней мере один псевдоним — либо X, либо Y.

Запросы, использующие EXISTS

Квантор EXISTS (существует) — понятие, заимствованное из формальной логики. В языке SQL предикат с квантором существования представляется выражением EXISTS (SELECT * FROM ...).

Такое выражение считается истинным только тогда, когда результат вычисления «SELECT * FROM ...» является непустым множеством, то есть, когда существует какая-либо запись в таблице, указанной во фразе FROM подзапроса, которая удовлетворяет условию WHERE подзапроса. (Практически этот подзапрос всегда будет коррелированным множеством.)

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

SELECT Название

FROM Поставщики

WHERE EXISTS

( SELECT *

FROM Поставки

WHERE ПС = Поставщики.ПС

AND ПР = 11 );

Результат:

Название

СЫТНЫЙ

УРОЖАЙ

КОРЮШКА

ЛЕТО

Система последовательно выбирает строки таблицы Поставщики, выделяет из них значения столбцов Название и ПС, а затем проверяет, является ли истинным условие существования, то есть, существует ли в таблице Поставки хотя бы одна строка со значением ПР=11 и значением ПС, равным значению ПС, выбранному из таблицы Поставщики. Если условие выполняется, то полученное значение столбца Название включается в результат.

Предположим, что первые значения полей Название и ПС равны, соответственно, 'СЫТНЫЙ' и 1. Так как в таблице Поставки есть строка с ПР=11 и ПС=1, то значение 'СЫТНЫЙ' должно быть включено в результат.

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

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

SELECT Название, Статус

FROM Поставщики

WHERE NOT EXISTS

( SELECT *

FROM Поставки

WHERE ПС = Поставщики.ПС

AND ПР = 11 );

Результат:

Название Статус

ПОРТОС кооператив

ШУШАРЫ совхоз

ТУЛЬСКИЙ универсам

ОГУРЕЧИК ферма

Функции в подзапросе

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

SELECT Продукт, Цена, Название, Статус

FROM Продукты, Состав, Блюда, Поставки, Поставщики

WHERE Продукты.ПР = Состав.ПР

AND Состав.БЛ = Блюда.БЛ

AND Поставки.ПР = Состав.ПР

AND Поставки.ПС = Поставщики.ПС

AND Блюдо = 'Сырники'

AND Цена = ( SELECT MIN(Цена)

FROM Поставки X

WHERE X.ПР = Поставки.ПР );

Естественно, что это коррелированный подзапрос: здесь сначала определяется минимальная цена продукта, входящего в состав Сырников, и только затем выясняется его поставщик.

На этом примере мы закончим знакомство с вложенными подзапросами, предложив попробовать свои силы в составлении ряда запросов, с помощью механизма таких подзапросов:

u Выдать названия всех мясных блюд.

u Выдать количество всех блюд, в состав которых входят помидоры.

u Выдать блюда, продукты для которых поставляются агрофирмой ЛЕТО.

Глава 12.
Объединение (UNION)

Реляционная операция «Объединение», позволяет получить отношение, состоящее из всех строк, входящих в одно или оба объединяемых отношения. Но при этом исходные отношения или их объединяемые проекции должны быть совместимыми по объединению. Для SQL это означает, что две таблицы можно объединять тогда и только тогда, когда:

u они имеют одинаковое число столбцов, например, m;

u для всех i (i = 1, 2, ..., m) i-й столбец первой таблицы и i-й столбец второй таблицы имеют в точности одинаковый тип данных.

Например, выдать названия продуктов, в которых нет жиров, либо входящих в состав блюда с кодом БЛ = 1:

SELECT Продукт

FROM Продукты

WHERE Жиры = 0

UNION

SELECT Продукт

FROM Состав

WHERE БЛ = 1

Результат:

Продукт

Майонез

Лук

Помидоры

Зелень

Яблоки

Сахар

Из этого простого примера видно, что избыточные дубликаты всегда исключаются из результата UNION. Поэтому, хотя в рассматриваемом примере Помидоры, Зелень и Яблоки выбираются обеими из двух составляющих предложения SELECT, в окончательном результате они появляются только один раз.

Предложением с UNION можно объединить любое число таблиц (проекций таблиц). Так, к предыдущему запросу можно добавить (перед точкой с запятой) конструкцию:

UNION

SELECT Продукт

FROM Продукты

WHERE Ca < 250

позволяющую добавить к списку продуктов Масло, Рис, Мука и Кофе. Однако тот же результат можно получить простым изменением фразы WHERE первой части исходного запроса:

WHERE Жиры = 0 OR Ca < 250

Глава 13.
Операции реляционной алгебры

С помощью предложения SELECT можно реализовать любую операцию реляционной алгебры.

Селекция (горизонтальное подмножество) таблицы создается из тех ее строк, которые удовлетворяют заданным условиям. Пример:

SELECT *

FROM Блюда

WHER Основа = 'Молоко'

AND Выход > 200;

Проекция (вертикальное подмножество) таблицы создается из указанных ее столбцов (в заданном порядке) с последующим исключением избыточных дубликатов строк. Пример:

SELECT DISTINCT Блюдо, Выход, Основа

FROM Блюда;

Объединение двух таблиц содержит те строки, которые есть либо в первой, либо во второй, либо в обеих таблицах. Пример:

SELECT Блюдо, Основа, Выход

FROM Блюда

WHER Основа = 'Овощи'

UNION

SELECT Блюдо, Основа, Выход

FROM Блюда

WHER В = 'Г';

Пересечение двух таблиц содержит только те строки, которые есть и в первой, и во второй. Пример:

SELECT БЛ

FROM Состав

WHERE БЛ IN

( SELECT БЛ

FROM Меню);

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

Пример:

SELECT БЛ

FROM Состав

WHERE БЛ NOT IN

( SELECT БЛ

FROM Меню);

Декартово произведение таблиц и различные виды соединений были подробно рассмотрены ранее.

Здесь опущено лишь достаточно нудное описание редко встречаемой операция деления, которая также может быть реализована предложением SELECT с коррелированными вложенными подзапросами.

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

Например, пусть требуется получить калорийность и стоимость тех блюд, для которых:

u есть все составляющие их продукты;

u калорийность не превышает 400 ккал;

u стоимость не превышает 1.5 рубля, а результат надо упорядочить по возрастанию калорийности блюд в рамках их видов.

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

Итак, пример сложного запроса:

SELECT Вид, Блюдо, 'калорий -',

(SUM(INT((Белки+Углев)*4.1+Жиры*9.3)* Вес/1000)),

(SUM(Стоимость/К_во*Вес/1000)+MIN(Труд/100)), ’руб’

FROM Блюда, Вид_блюд, Состав, Продукты, Наличие

WHERE Блюда.БЛ = Состав.БЛ

AND Состав.ПР = Продукты.ПР

AND Состав.ПР = Наличие.ПР

AND Блюда.В = Вид_блюд.В

AND БЛ NOT IN

( SELECT БЛ

FROM Состав

WHERE ПР IN

( SELECT ПР

FROM Наличие

WHERE К_во = 0))

GROUP BY Вид, Блюдо

HAVING SUM(Стоимость/К_во*Вес/1000+MIN(Труд/100)) < 1.5

AND SUM(((Белки+Углев)*4.1+Жиры*9.3)*Вес/1000) < 400

ORDER BY Вид, 4;

Такой результат строился пошагово следующим образом.

FROM

Эта фраза инициирует создание в рабочей памяти таблицы, являющейся декартовым произведением таблиц Блюда, Вид_блюд, Состав, Продукты и Наличие.

WHERE

Эта фраза нужна для преобразования полученного декартова произведения в естественное соединение и удаления из последнего строк с кодами блюд, не обеспеченных продуктами. Естественное соединение образуется путем вычеркивания строк, где не совпадают: код блюда из таблицы Блюда с кодом блюда из таблицы Состав, код продукта из таблицы Состав с кодом продукта из таблицы Продукты. Обеспеченность блюда всеми продуктами проверяется с помощью последовательности подзапросов. Внутренний подзапрос выдает перечень кодов продуктов, которых нет в кладовой пансионата. Следующий подзапрос выдает коды тех блюд, в состав которых должны входить «отсутствующие» продукты. И, наконец, из естественного соединения вычеркиваются строки с кодами полученных блюд (точнее оставляются строки «Где код блюда не принадлежит перечню кодов блюд, полученному в подзапросе».

SELECT

Из полученного соединения удаляются столбцы, не используемые в выражениях SELECT или других фразах. Если в списке SELECT есть выражения (константы), то для хранения их значений формируются дополнительные столбцы и инициируются операции по их заполнению. В рассматриваемом примере будут сохранены столбцы Вид, Блюдо, Белки, Углев, Жиры, Вес, Стоимость, К_во и созданы дополнительные столбцы для формирования и хранения значений стоимости и калорийности составляющих каждого блюда, а также для хранения текстовых констант 'калорий -' и 'руб'. Обратите внимание на прием, использованный при суммировании стоимостей продуктов, входящих в состав блюда, и стоимости его приготовления (Труд): можно ли заменить MIN на MAX или AVG?

GROUP BY

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

SELECT

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

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

HAVING

Сформированные строки, не удовлетворяющие условиям фразы HAVING:

SUM(Стоимость/К_во*Вес/1000+MIN(Труд/100)) < 1.5

и

SUM(((Белки+Углев)*4.1+Жиры*9.3)*Вес/1000) < 400

исключаются из результата предыдущего шага.

ORDER BY

Результат предыдущего шага упорядочивается в соответствии со списком фразы ORDER BY для получения окончательного результата. Сначала строки группируются по видам блюд (в алфавитном порядке), а затем – по значению элемента данных, указанного на четвертом месте фразы SELECT, то есть, по калорийности.

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

Глава 14.
Модификация данных

Особенности и синтаксис предложений модификации

Модификация данных может выполняться с помощью предложений DELETE (удалить), INSERT (вставить) и UPDATE (обновить). Подобно предложению SELECT они могут оперировать как базовыми таблицами, так и представлениями. Однако по ряду причин не все представления являются обновляемыми. Пока зафиксируем этот факт, отложив описание представлений и особенностей их обновления до следующей главы, но будем помнить, что термин «представление» относится только к обновляемым представлениям.

Предложение DELETE имеет формат:

DELETE

FROM базовая таблица | представление

[WHERE фраза];

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

Предложение INSERT имеет один из следующих форматов:

INSERT

INTO {базовая таблица | представление} [(столбец [,столбец] ...)]

VALUES ({константа | переменная} [,{константа | переменная}] ...);

или

INSERT

INTO {базовая таблица | представление} [(столбец [,столбец] ...)]

подзапрос;

В первом формате в таблицу вставляется строка со значениями полей, указанными в перечне фразы VALUES (значения), причем i-е значение соответствует i-му столбцу в списке столбцов (столбцы, не указанные в списке, заполняются NULL-значениями). Если в списке VALUES фразы указаны все столбцы модифицируемой таблицы и порядок их перечисления соответствует порядку столбцов в описании таблицы, то список столбцов в фразе INTO можно опустить. Однако не советуем этого делать, так как при изменении описания таблицы (перестановка столбцов или изменение их числа) придется переписывать и INSERT предложение.

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

Предложение UPDATE также имеет два формата. Первый из них:

UPDATE (базовая таблица | представление}

SET столбец = значение [, столбец = значение] ...

[WHERE фраза]

где значение — это

столбец | выражение | константа | переменная

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

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

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

UPDATE {базовая таблица | представление}

SET столбец = значение [, столбец = значение] ...

FROM {базовая таблица | представление} [псевдоним],

{базовая таблица | представление} [псевдоним]

[,{базовая таблица | представление} [псевдоним]] ...

[WHERE фраза]

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

В значениях, находящихся в правых частях равенств фразы SET, следует уточнять имена используемых столбцов, предваряя их именем таблицы (псевдонима).

Глава 15.
Предложение DELETE

Удаление единственной записи

Удалить поставщика с ПС = 7:

DELETE

FROM Поставщики

WHERE ПС = 7;

Если таблица Поставки содержит в момент выполнения этого предложения какие-либо поставки для поставщика с ПС = 7, то такое удаление нарушит непротиворечивость базы данных. К сожалению нет операции удаления, одновременно воздействующей на несколько таблиц. Однако в некоторых СУБД реализованы механизмы поддержания целостности, позволяющие отменить некорректное удаление или каскадировать удаление на несколько таблиц.

Удаление множества записей

Удалить все поставки:

DELETE

FROM Поставки;

Поставки — все еще известная таблица, но в ней теперь нет строк. Для уничтожения таблицы надо выполнить операцию DROP TABLE Поставки.

Удалить все мясные блюда:

DELETE FROM Блюда

WHERE Основа = 'Мясо';

Удаление с вложенным подзапросом

Удалить все поставки для поставщика из Паневежиса:

DELETE

FROM Поставки

WHERE ПС IN

(SELECT ПС

FROM Поставщики

WHERE Город = 'Паневежис');

Глава 16.
Предложение INSERT

Вставка единственной записи в таблицу

Добавить в таблицу Блюда блюдо:

Шашлык

u БЛ — 34

u Блюдо — Шашлык

u В — Г

u Основа — Мясо

u Выход — 150

при неизвестной пока трудоемкости приготовления этого блюда.

INSERT

INTO Блюда (БЛ, Блюдо, В, Основа, Выход)

VALUES (34, 'Шашлык', 'Г', 'Мясо', 150);

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

Порядок полей в INSERT не обязательно должен совпадать с порядком полей, в котором они определялись при создании таблицы. Вполне допустима и такая версия предыдущего предложения:

INSERT

INTO Блюда (Основа, В, Блюдо, БЛ, Выход)

VALUES ('Мясо', 'Г', 'Шашлык', 34, 150);

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

INSERT

INTO Блюда

VALUES (34, 'Шашлык', 'Г', 'Мясо', 150, 5);

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

В предыдущих примерах проводилась модификация стержневой сущности, то есть, таблицы с первичным ключом БЛ. Почти все СУБД имеют механизмы для предотвращения ввода не уникального первичного ключа, например, ввода «Шашлыка» под номером, меньшим 34. А как быть с ассоциациями или другими таблицами, содержащими внешние ключи?

Пусть, например, потребовалось добавить в рецепт блюда Салат летний (БЛ = 1) немного (15 г.) лука (ПР = 10), и мы воспользовались предложением:

INSERT

INTO Состав (БЛ, ПР, Вес)

VALUES (1, 10, 15);

Подобно операции DELETE операция INSERT может нарушить непротиворечивость базы данных. Если не принять специальных мер, то СУБД не проверяет, имеется ли в таблице Блюда блюдо с первичным ключом БЛ = 1 и в таблице Продукты — продукт с первичным ключом ПР = 10.

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

Вставка множества записей

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

Для создания описания временной таблицы можно, например, воспользоваться предложением CREATE TABLE:

CREATE TABLE К_меню

( Вид CHAR (10),

Блюдо CHAR (60),

Калор_блюда INTEGER,

Стоим_блюда REAL);

а для ее загрузки данными – предложение INSERT с вложенным подзапросами:

INSERT

INTO К_меню

SELECT Вид, Блюдо,

INT(SUM(((Белки+Углев)*4.1+Жиры*9.3) * Вес/1000)),

(SUM(Стоимость/К_во*Вес/1000) + MIN(Труд/100))

FROM Блюда, Вид_блюд, Состав, Продукты, Наличие

WHERE Блюда.БЛ = Состав.БЛ

AND Состав.ПР = Продукты.ПР

AND Состав.ПР = Наличие.ПР

AND Блюда.В = Вид_блюд.В

AND БЛ NOT IN

( SELECT БЛ

FROM Состав

WHERE ПР IN

( SELECT ПР

FROM Наличие

WHERE К_во = 0))

GROUP BY Вид, Блюдо

ORDER BY Вид, 3;

В этом запросе предложение SELECT выполняется так же, как обычно, но результат не выводится на экран, а копируется в таблицу К_меню. Теперь с этой копией можно работать как с обычной базовой таблицей (Блюда, Продукты, ...), то есть, выбирать из нее данные на экран или принтер, обновлять в ней данные.

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

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

Использование INSERT...SELECT для построения внешнего соединения

Рассмотренное ранее естественное соединение двух таблиц не включает тех строк какой-либо из них, для которых нет соответствующих строк в другой таблице. Например, если в таблицу Блюда были занесены под номером 34 сведения о Шашлыке, а рецепт его приготовления не был занесен в таблицу Рецепты, то при загрузке их естественного соединения в таблицу Временная:

CREATE TABLE Временная

( Вид CHAR (8),

Блюдо CHAR (60),

Рецепт CHAR (560));

INSERT

INTO Временная

SELECT Вид, Блюдо, Рецепт

FROM Блюда, Рецепты, Вид_блюд

WHERE Блюда.БЛ = Рецепты.БЛ

AND Блюда.В = Вид_блюд.В;

в ней не окажется строки с Шашлыком (в таблице Рецепты не обнаружен код 34, и строка с этим кодом исключена из результата).

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

Однако иногда (как и в приведенном примере) может потребоваться способность сохранить эту информацию.

В этом случае можно воспользоваться так называемым внешним соединением:

INSERT

INTO Временная

SELECT Вид, Блюдо, Рецепт

FROM Блюда, Рецепты, Вид_блюд

WHERE Блюда.БЛ = Рецепты.БЛ

AND Блюда.В = Вид_блюд.В;

INSERT

INTO Временная

SELECT Вид, Блюдо, “???”

FROM Блюда, Вид_блюд

WHERE Блюда.В = Вид_блюд.В

AND БЛ NOT IN

( SELECT БЛ

FROM Рецепты);

В результате будет создана базовая таблица:

где первые 33 строки соответствуют первому INSERT и представляют собой проекцию естественного соединения таблиц Блюда и Рецепты по кодам блюд (БЛ), включающую три столбца. Последняя строка результата соответствует второму INSERT и сохраняет информацию о блюде Шашлык, рецепт которого пока не введен в таблицу Рецепты.

Заметим, что для внешнего соединения нужны два отдельных INSERT...SELECT. Однако тот же результат можно получить и одним INSERT...SELECT, используя фразу UNION, объединяющую предложения SELECT из двух INSERT:

INSERT

INTO Временная

SELECT Вид, Блюдо, Рецепт

FROM Блюда, Рецепты, Вид_блюд

WHERE Блюда.БЛ = Рецепты.БЛ

AND Блюда.В = Вид_блюд.В

UNION

SELECT Вид, Блюдо, “???”

FROM Блюда, Вид_блюд

WHERE Блюда.В = Вид_блюд.В

AND БЛ NOT IN

( SELECT БЛ

FROM Рецепты);

Глава 17.
Предложение UPDATE

Обновление единственной записи

Изменить название блюда с кодом БЛ=5 на Форшмак, увеличить его выход на 30 г. и установить NULL-значение в столбец Труд.

UPDATE Блюда

SET Блюдо = 'Форшмак', Выход = (Выход+30), Труд = NULL

WHERE БЛ = 5;

Обновление множества записей

Утроить цену всех продуктов таблицы поставки (кроме цены кофе — ПР = 17).

UPDATE Поставки

SET Цена = Цена * 3

WHERE ПР <> 17;

Обновление с подзапросом

Установить равной нулю цену и К_во продуктов для поставщиков из Паневежиса и Резекне.

UPDATE Поставки

SET Цена = 0, К_во = 0

WHERE ПС IN

(SELECT ПС

FROM Поставщики

WHERE Город IN ('Паневежис', 'Резекне'));

Обновление нескольких таблиц

Изменить номер продукта ПР = 13 на ПР = 20.

UPDATE Продукты UPDATE Состав

SET ПР = 20 SET ПР = 20

WHERE ПР = 13; WHERE ПР = 13;

UPDATE Поставки UPDATE Наличие

SET ПР = 20 SET ПР = 20

WHERE ПР = 13; WHERE ПР = 13;

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

Глава 18.
О конструировании предложений модификации

Для тех, кто достаточно хорошо понял предложение SELECT, несложно овладеть конструированием предложений DELETE, INSERT и UPDATE. Но в процессе такого конструирования следует учитывать, что:

1. Если в WHERE фразе предложений DELETE и UPDATE используется вложенный подзапрос, то во фразе FROM этого подзапроса не должна упоминаться таблица, из которой удаляются (в которой обновляются) строки. Аналогично, в подзапросе предложения INSERT не должна упоминаться таблица, в которую загружаются данные.

Так, SQL отвергнет предложение:

INSERT

INTO Выбрано

SELECT (33), Т, БЛ

FROM Выбрано

WHERE СМ = 17;

позволяющее ввести информацию о том, что отдыхающий, сидящий на 33-м месте, выбирает тот же набор блюд, что и отдыхающий, сидящий на 17-м месте.

Ввод придется осуществить через какую-либо промежуточную таблицу, например, таблицу Выбор:

DELETE

FROM Выбор;

INSERT

INTO Выбор (СМ, Т, БЛ)

SELECT (33), Т, БЛ

FROM Выбрано

WHERE СМ = 17;

INSERT

INTO Выбрано

SELECT СМ, Т, БЛ

FROM Выбор;

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

Глава 19.
Определение данных и оптимизация запросов

Системный каталог

Системный каталог — это набор таблиц, в которых содержится информация, необходимая для правильного функционирования СУБД: о поддерживаемых базах данных и их базовых таблицах, представлениях, курсорах, индексах, пользователях и их правах доступа к информации, правилах модификации данных.

В разных СУБД, поддерживающих SQL, существует от десятка до нескольких десятков системных таблиц, структура которых ничем не отличается от уже знакомой нам структуры пользовательских таблиц.

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

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

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

SELRCT Tab_name,N_col,N_row,Tab_owner,Comments

FROM SYSTABLES;

и получить результат, показанный ниже.

Для получения же некоторых данных о столбцах таблицы Блюда можно дать запрос:

SELECT Col_name, Type, Length, Comments

FROM SYSCOLUMNS

WHERE Tab_name = 'Блюда';

и получить результат, показанный ниже.

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

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

Глава 20.
Создание и уничтожение базовых таблиц

Базовые таблицы описываются в SQL с помощью предложения CREATE TABLE (создать таблицу), синтаксис которого имеет небольшие различия в различных СУБД. Однако все они поддерживают следующую минимальную форму:

CREATE TABLE базовая_таблица (столбец тип_данных [NOT NULL]

[,столбец тип_данных [NOT NULL]] ...);

где тип_данных должен принадлежать к одному из типов данных, поддерживаемых СУБД.

Так, описание таблицы Блюда может быть записано в виде:

CREATE TABLE Блюда

( БЛ SMALLINT NOT NULL,

Блюда CHAR (70) NOT NULL,

В CHAR (1),

Основа CHAR (10),

Выход FLOAT,

Труд SMALLINT );

В результате создается пустая базовая таблица Блюда, а в системный каталог помещается строка, описывающая эту таблицу. Отметим, что в профессиональных СУБД имя таблицы дополняется именем пользователя, который издал предложение CREATE TABLE. Если этот пользователь зарегистрирован в системе под именем Kirillov, то в каталоге будет зарегистрирована таблица Kirillov.Блюда и указанный пользователь может обращаться к ней по имени Kirillov.Блюда или по сокращенному имени Блюда, которое использовалось во всех предшествующих примерах и будет использоваться далее.

Конструкция NOT NULL запрещает использование неопределенного значения, то есть, специального значения, которое вводится для представления «неизвестного значения» или «неприменимого значения». Например, строка поставки таблицы Поставки может содержать неопределенное значение в столбце Цена и (или) К_во (известно, что поставщик поставляет указанный продукт, но на данный момент неизвестна цена этого продукта и (или) объем поставки).

Существующую базовую таблицу можно в любой момент уничтожить с помощью предложения DROP TABLE (уничтожить таблицу):

DROP TABLE базовая_таблица;

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

В SQL существует также предложение ALTER TABLE (изменить таблицу), которое позволяет добавить справа к таблице новый столбец, то есть, модифицировать описание таблицы.

Глава 21.
О индексах и производительности

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

Например, индекс, построенный для столбца Основа таблицы Блюда, будет содержать следующие сведения:

Значения столбца Строки, в которых встречается такое значение

Кофе 32 33

Крупа 20 21

Молоко 7 8 12 18 22 24 28 31

Мясо 2 6 9 13 14

Овощи 1 3 17 23 15

Рыба 4 5 10 11

Фрукты 25 26 27 29 30

Яйца 16 19

Отметим, что такой индекс уже существовал (в несколько иной форме) в базе данных, хотя это обстоятельство никак не повлияло на текст иллюстрационных предложений SELECT, DELETE, INSERT и UPDATE. SQL намеренно не включает в свои конструкции ссылки на индексы. Решение о том, использовать или не использовать какой-либо индекс при обработке некоторого конкретного запроса принимается не пользователем, а оптимизатором СУБД, который учитывает множество факторов — размер таблиц, тип используемых структур хранения данных, статистическое распределение данных в таблицах и индексах. Однако чтобы оптимизатор смог использовать индексы, их нужно построить (чтобы выиграть в лотерею нужно, по крайней мере, иметь лотерейный билет).

Естественно, что поиск какого-либо значения путем последовательного перебора неупорядоченных данных будет во много раз медленнее, чем поиск с использованием упорядоченного списка (индекса). Ясно также, что таблицу можно упорядочить лишь по данным одного столбца, тогда как поиск часто приходится осуществлять по данным нескольких столбцов. По нескольким столбцам производится и соединение таблиц. Поэтому, несмотря на то, что индексы увеличивают объем базы данных, их следует использовать как для отдельных столбцов таблицы, так и для комбинации нескольких ее столбцов (например, для комбинации: Фамилия, Имя, Отчество).

Для построения индекса в SQL существует предложение CREATE INDEX (создать индекс), имеющее формат:

CREATE [UNIQUE] INDEX имя_индекса ON базовая_таблица

(столбец [[ASC] | DESC] [, столбец [[ASC] | DESC]] ...);

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

Например, индексы для столбцов БЛ и Основа таблицы Блюда создаются с помощью предложений:

CREATE UNIQUE INDEX Блюда_БЛ ON Блюда (БЛ);

CREATE INDEX Блюда_Основа ON Блюда (Основа);

а индекс для первичного ключа (столбцы БЛ и ПР) таблицы Состав — с помощью предложения:

CREATE UNIQUE INDEX Состав_БЛ_ПР ON Состав (БЛ, ПР);

В больших (более 1000 строк) таблицах поиск индексированных значений выполняется на порядок быстрее, чем поиск неиндексированных, а в очень больших таблицах — на два-три порядка.

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

Если база данных не должна модифицироваться, то на этот вопрос можно дать положительный ответ. Однако при удалении или добавлении строки таблицы должны быть перестроены все индексы, построенные для ее столбцов, а при изменении значения индексированного столбца — индекс этого столбца. Когда модифицируется много — несколько сотен (тысяч) строк — и после модификации каждой строки перестраиваются все ее индексы, время модификации может быть на порядок (несколько порядков) больше времени модификации строк с неиндексированными столбцами. Поэтому перед модификацией множества строк таблицы целесообразно уничтожить индексы ее столбцов, что можно сделать с помощью предложения DROP INDEX (уничтожить индекс), имеющего следующий формат:

DROP INDEX имя_индекса;

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

Глава 22.
Представления

Создание и уничтожение представлений

Напомним, что представление — это виртуальная таблица, которая сама по себе не существует, но для пользователя выглядит таким образом, как будто она существует. Представление не поддерживаются его собственными физическими хранимыми данными. Вместо этого в каталоге таблиц хранится определение, оговаривающее, из каких столбцов и строк других таблиц оно должно быть сформировано при реализации SQL-предложения на получение данных из представления или на модификацию таких данных.

Синтаксис предложения CREATE VIEW имеет вид:

CREATE VIEW имя_представления

[(столбец[,столбец] ...)]

AS подзапрос

[WITH CHECK OPTION];

где

u подзапрос, следующий за AS и являющийся определением данного представления, не исполняется, а просто сохраняется в каталоге;

u необязательная фраза «WITH CHECK OPTION» (с проверкой) указывает, что для операций INSERT и UPDATE над этим представлением должна осуществляться проверка, обеспечивающая удовлетворение WHERE фразы подзапроса;

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

u хотя бы один из столбцов подзапроса не имеет имени (создается с помощью выражения, SQL-функции или константы);

u два или более столбцов подзапроса имеют одно и то же имя;

u если же список отсутствует, то представление наследует имена столбцов из подзапроса.

Например, создадим представление Мясные_блюда:

CREATE VIEW Мясные_блюда

AS SELECT БЛ, Блюдо, В, Выход

FROM Блюда

WHERE Основа = 'Мясо';

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

Уничтожение ненужных представлений выполняется с помощью предложения DROP VIEW (уничтожить представление), имеющего следующий формат:

DROP VIEW представление;

Операции выборки из представлений

Создав представление Мясные_блюда пользователь может считать, что в базе данных реально существует такая таблица и дать, например, запрос на получение из нее всех данных:

SELECT *

FROM Мясные_блюда;

результат которого имеет вид:

БЛ Блюдо В Выход

2 Салат мясной З 200

6 Мясо с гарниром З 250

9 Суп харчо С 500

13 Бастурма Г 300

14 Бефстроганов Г 210

Поскольку при определении представления может быть использован любой допустимый подзапрос, то выборка данных может осуществляться как из базовых таблиц, так и из представлений:

CREATE VIEW Горячие_мясные_блюда

AS SELECT Блюдо, Продукт, Вес

FROM Мясные_блюда, Состав, Продукты

WHERE Мясные_блюда.БЛ = Состав.БЛ

AND Продукты.ПР = Состав.ПР

AND В = 'Г';

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

SELECT Блюдо, Продукт, Вес

FROM Горячие_мясные_блюда

WHERE Блюдо IN

( SELECT Блюдо

FROM Горячие_мясные_блюда

WHERE Продукт = 'Помидоры')

и получить:

Блюдо Продукт Вес

Бастурма Говядина 180

Бастурма Помидоры 100

Бастурма Лук 40

Бастурма Зелень 20

Бастурма Масло 5

Легко заметить, что данный запрос, осуществляющий выбор данных через два представления, выглядит для пользователя точно так же, как обычный SELECT, оперирующий обычной базовой таблицей. Однако СУБД преобразует его при выполнении в эквивалентную операцию над лежащими в основе базовыми таблицами (перед выполнением проводит слияние выданного пользователем SELECT с предложениями SELECT из описаний представлений, хранящихся в каталоге).

Обновление представлений

Рассмотренные ранее операции DELETE, INSERT и UPDATE могут оперировать не только базовыми таблицами, но и представлениями. Однако, если из базовых таблиц можно удалять любые строки, обновлять значения любых их столбцов и вводить в такие таблицы новые строки, то этого нельзя сказать о представлениях, не все из которых являются обновляемыми.

Безусловно обновляемыми являются представления, полученные из единственной базовой таблицы простым исключением некоторых ее строк и (или) столбцов, обычно называемые «представление-подмножество строк и столбцов». Таким является представление Мясные_блюда, полученное из базовой таблицы Блюда исключением из нее столбца Труд и строк, не содержащих значение 'Мясо' в столбце Основа. Работая с ним, можно:

u вставить (операция INSERT) новую строку, например, строку (34, 'Шашлык', 'Г', 150), фактически вставляя соответствующую строку (34, 'Шашлык', 'Г', 150, NULL) в лежащую в основе базовую таблицу Блюда;

u удалить (операция DELETE) существующую строку из представления, например строку (13, 'Бастурма', 'Г', 300), фактически удаляя соответствующую строку (13, 'Бастурма', 'Г', 300, 5) из таблицы Блюда;

u обновить (операция UPDATE) какое-либо поле в существующей строке, например увеличить массу порции Бефстроганова с 210 до 250 граммов, фактически осуществляя то же самое изменение в соответствующем поле таблицы Блюда.

Однако если бы представление Мясные_блюда имело вместо столбца Выход столбец Вых_труд, полученный путем суммирования значений столбцов Выход и Труд таблицы Блюда, то указанные выше операции INSERT и UPDATE были бы отвергнуты системой.

Действительно, как распределить вводимое значение столбца Вых_труд (153 или 254) между значениями столбцов Выход и Труд базовой таблицы Блюда? Была бы отвергнута и операция удаления масла из состава бастурмы, если бы ее попытались выполнить путем удаления строки ('Бастурма', 'Масло', 5) в представлении Горячие_мясные_блюда.

Встает множество вопросов:

u надо ли удалять из базовой таблицы Блюда строку, содержащую значение 'Бастурма' в столбце Блюдо?

u надо ли удалять из базовой таблицы Продукты строку, содержащую значение 'Масло' в столбце Продукт?

u надо ли удалять из базовой таблицы Состав все строки, содержащие значение 5 в столбце Вес?

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

Таким образом, некоторые представления по своей природе обновляемы, в то время как другие таковыми не являются. Здесь следует обратить внимание на слова «по своей природе». Дело заключается не просто в том, что некоторая СУБД не способна поддерживать определенные обновления, в то время как другие СУБД могут это делать. Никакая СУБД не может непротиворечивым образом поддерживать без дополнительной помощи обновление такого представления как Горячие_мясные_блюда. «Без дополнительной помощи» означает здесь «без помощи какого-либо человека — пользователя».

Как было указано выше, к теоретически обновляемым представлениям относятся представления-подмножества строк и столбцов. Однако существуют некоторые представления, которые не являются представлениями-подмножествами строк и столбцов, но также теоретически обновляемы. Хотя известно, что такие есть и можно привести их примеры, но невозможно дать их формального определения. Неверным является такое формальное определение некоторых авторов — «нельзя обновлять соединение». Во-первых, в некоторых соединениях с успехом выполняется операция UPDATE, а, во-вторых, как было показано выше, не обновляемы и некоторые представления, не являющиеся соединениями. Кроме того, не все СУБД поддерживают обновление любых теоретически обновляемых представлений. Поэтому пользователь должен сам оценивать возможность использования операций DELETE, INSERT или UPDATE в созданном им представлении.

Для чего нужны представления

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

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

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

Глава 23.
Другие предложения и конструкции SQL

Безопасность и санкционирование доступа

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

u механизм представлений, рассмотренный в предыдущей главе и используемый для сокрытия засекреченных данных от пользователей, не обладающих правом доступа;

u подсистема санкционирования доступа, позволяющая предоставить указанным пользователям определенные привилегии на доступ к данным и дать им возможность избирательно и динамически передавать часть выделенных привилегий другим пользователям, отменяя впоследствии эти привилегии, если потребуется.

Обычно при установке СУБД в нее вводится какой-то идентификатор, который должен далее рассматриваться как идентификатор наиболее привилегированного пользователя — системного администратора. Каждый, кто может войти в систему с этим идентификатором (и может выдержать тесты на достоверность), будет считаться системным администратором до выхода из системы. Системный администратор может создавать базы данных и имеет все привилегии на их использование. Эти привилегии или их часть могут предоставляться другим пользователям (пользователям с другими идентификаторами). В свою очередь, пользователи, получившие привилегии от системного администратора, могут передать их (или их часть) другим пользователям, которые могут их передать следующим.

Привилегии предоставляются с помощью предложения GRANT (предоставить), общий формат которого имеет вид:

GRANT привилегии ON объект TO пользователи;

В нем «привилегии» — список, состоящий из одной или нескольких привилегий, разделенных запятыми, либо фраза ALL PRIVILEGES (все привилегии); «объект» — имя и, если надо, тип объекта (база данных, таблица, представление, индекс); «пользователи» — список, включающий один или более идентификаторов санкционирования, разделенных запятыми, либо специальное ключевое слово PUBLIC (общедоступный).

К таблицам (представлениям) относятся привилегии SELECT, DELETE, INSERT и UPDATE [(столбцы)], позволяющие соответственно считывать (выполнять любые операции, в которых используется SELECT), удалять, добавлять или изменять строки указанной таблицы (изменение можно ограничить конкретными столбцами). Например, предложение:

GRANT SELECT, UPDATE (Труд) ON Блюда TO cook;

позволяет пользователю, который представился системе идентификатором cook, использовать информацию из таблицы Блюда, но изменять в ней он может только значения столбца Труд.

Если пользователь USER_1 предоставил какие-либо привилегии другому пользователю USER_2, то он может впоследствии отменить все или некоторые из этих привилегий. Отмена осуществляется с помощью предложения REVOKE (отменить), общий формат которого очень похож на формат предложения GRANT:

REVOKE привилегии ON объект FROM пользователи;

Например, можно отобрать у пользователя cook право изменения значений столбца Труд:

REVOKE UPDATE (Труд) ON Блюда FROM cook;

Обработка транзакций

Ранее рассматривался пример, в котором требовалось изменить номер продукта ПР = 13 на ПР = 20 и для этого пришлось проводить последовательное изменение четырех таблиц:

UPDATE Продукты UPDATE Состав

SET ПР = 20 SET ПР = 20

WHERE ПР = 13; WHERE ПР = 13;

UPDATE Поставки UPDATE Наличие

SET ПР = 20 SET ПР = 20

WHERE ПР = 13; WHERE ПР = 13;

Этот пример приведен здесь для иллюстрации того, что единственная, с точки зрения пользователя, операция может потребовать нескольких операций над базой данных. Более того, между этими операциями может даже нарушаться непротиворечивость базы данных. Например, в ней могут временно содержаться некоторые записи поставок, для которых не имеется соответствующих записей поставляемых продуктов. Положение не спасает и перестановка последовательности обновляемых таблиц. Противоречивость исчезнет только после выполнения всех обновлений, то есть, выполнения логической единицы работы — полной замены номера продукта в базе данных (независимо от количества таблиц, в которых встречается номер продукта).

Теперь можно дать определение транзакции. Транзакция или логическая единица работы, — это в общем случае последовательность ряда таких операций, которые преобразуют некоторое непротиворечивое состояние базы данных в другое непротиворечивое состояние, но не гарантируют сохранения непротиворечивости во все промежуточные моменты времени. Никто кроме пользователя, генерирующего ту или иную последовательность SQL-предложений, не может знать о том, когда может возникнуть противоречивое состояние базы данных и после выполнения каких SQL-предложений оно исчезнет, то есть, база данных вновь станет актуальной. Поэтому в большинстве СУБД создается механизм обработки транзакций, при инициировании которого все изменения данных будут рассматриваются как предварительные до тех пор, пока пользователь (реже система) не выдаст предложения:

u COMMIT (фиксировать), превращающее все предварительные обновления в окончательные («зафиксированные»);

u ROLLBACK (откат), аннулирующее все предварительные обновления.

Таким образом, транзакцией можно назвать последовательность SQL-предложений, расположенных между «точками синхронизации», учреждаемых в начале выполнения программы и издании COMMIT или ROLLBACK и только в этих случаях. При этом следует иметь в виду, что возможен неявный COMMIT (существует режим AUTOCOMMIT, в котором система издает COMMIT после выполнения каждого SQL-предложения) и ROLLBACK (выполняемый при аварийном завершении программы).

Ясно теперь, что пользователь должен сам решать, включать ли механизм обработки транзакций и если включать, то где издавать COMMIT (ROLLEBACK), то есть, какие последовательности SQL-предложений являются транзакциями.

Теперь о проблемах, связанных с параллельным использованием базы данных множеством разнообразных пользователей.

Большинство СУБД позволяют любому числу транзакций одновременно осуществлять доступ к одной и той же базе данных и в них существуют те или иные механизмы управления параллельными процессами, предотвращающие нежелательные воздействия одних транзакций на другие. По сути это механизм блокирования, главная идея которого достаточно проста. Если транзакции нужны гарантии, что некоторый объект (база данных, таблица, строка или поле), в котором она заинтересована, не будет изменен каким-либо непредсказуемым образом в течение требуемого промежутка времени, она устанавливает блокировку этого объекта. Результат блокировки заключается в том, чтобы изолировать этот объект от других транзакций и, в частности, предотвратить его изменение средствами этих транзакций. Для первой транзакции, таким образом, имеется возможность выполнять предусмотренную в ней обработку, располагая определенными знаниями о том, что объект в запросе будет оставаться в стабильном состоянии до тех пор, пока данная транзакция этого пожелает.

Прикладное программирование

Ранее, рассматривая предложения SQL, мы практически не оговаривали, откуда попадают в СУБД такие запросы. Молчаливо предполагалось, что язык используется в интерактивном режиме и запросы вводятся с клавиатуры. Однако все предложения SQL, которые можно ввести с терминала, можно использовать также в прикладной программе.

Многие современные СУБД имеют собственные языки программирования, ряд которых включает в себя SQL. Другие работают с программами, написанными на одном из распространенных алгоритмических языков (Си, Паскаль или Фортран), в которые включаются предложения SQL. Для обмена информацией с частями программы, написанными на любых из этих языков, существуют специальные конструкции SQL, позволяющие работать с переменными и (или) отдельными строками таблиц.

Переменные включающего языка:

u могут появляться в предложениях манипулирования данными языка SQL только во фразах SELECT и INTO предложения SELECT, фразе WHERE предложений SELECT, UPDATE и DELETE, фразе SET предложения UPDATE, фразе VALUES предложения INSERT и, наконец, в качестве элемента выражения во фразах SELECT, WHERE или SET, но не VALUES;

u должны иметь типы данных, совместимые с типами данных тех столбцов базы данных, с которыми они должны сравниваться, значения которых им должны быть присвоены или которым должны быть присвоены значения переменных;

u могут иметь имена, совпадающие с именами столбцов базы данных (система различает их по месторасположению в предложениях SQL или по специальному символу, например, «.», устанавливаемому перед именем переменной, когда надо использовать ее значение).

После выполнения любого предложения SQL происходит обновление системной переменной SQLCODE (в нее заносится числовой индикатор состояния). Нулевое значение SQLCODE означает, что данное предложение выполнено успешно. Положительное значение означает, что предложение выполнено, но имела место некоторая исключительная ситуация. Например, значение +100 указывает, что не было найдено никаких данных, удовлетворяющих запросу. Наконец, отрицательное значение указывает, что имела место ошибка и предложение не выполнено. Поэтому за каждым предложением SQL в программе должна следовать проверка значения SQLCODE и должно предприниматься соответствующее действие, если это значение оказалось не таким, которое ожидалось. На практике же такую проверку осуществляют после тех предложений SQL, при выполнении которых возможна исключительная ситуация).

Основная проблема «встраивания» предложения SELECT в программу заключается в том, что SELECT, как правило, порождает таблицу с множеством строк и столбцов, а включающий язык не обладает хорошими средствами, позволяющими оперировать одновременно более чем одной записью (строкой). По этим причинам необходимо обеспечить своего рода мост между уровнем множеств языка SQL и уровнем записей включающего языка. Такой мост обеспечивают курсоры. Курсор состоит, по существу, из некоторого рода указателя, который может использоваться для просмотра множества записей. Поочередно указывая каждую запись в данном множестве, он обеспечивает возможность обращения к этим записям по одной одновременно.

Однако нередко программе требуются в каждый момент времени значения только из одной строки какой-либо таблицы, и для этого используется единичное SELECT, формат которого имеет вид:

SELECT [[ALL] | DISTINCT]{ * | элемент_select [,элемент_select] ...}

INTO переменная [[INDICATOR] индикаторная_переменная]

[,переменная [[INDICATOR] индикаторная_переменная]] ...

FROM базовая_таблица | представление [псевдоним]

[,базовая_таблица | представление [псевдоним]] ...

[WHERE фраза]

[GROUP BY фраза [HAVING фраза]];

где элемент_select — это одна из следующих конструкций:

[таблица.]* | [таблица.]столбец | SQL_функция | переменная

| (выражение) | системная_переменная

Очевидно, что это описание отличается от описания подзапроса наличием фразы INTO и включением в список элементов_select переменных. Переменные могут также включаться в выражения и фразы WHERE и HAVING. Приведем несколько примеров. Получить общий вес продуктов в кладовой пансионата и занести его в переменную Общий_вес:

SELECT SUM(К_во)

INTO Общий_вес

FROM Наличие;

Здесь определяется единственное значение (сумма данных в столбце) и поэтому системная переменная SQLCODE устанавливается в нуль.

Однако в следующем примере:

SELECT Продукт, К_во

INTO Продукт, К_продукта

FROM Наличие, Продукты

WHERE Наличие.ПР = Продукты.ПР

AND ПР IN

( SELECT ПР

FROM Продукты

WHERE Продукт = 'Икра черная');

где требовалось узнать количество черной икры в кладовой пансионата и занести название этого продукта и его количество в переменные Продукт и К_продукта, соответственно, переменная SQLCODE примет значение +100, так как в кладовой нет икры.

Наконец, в примере:

SELECT Продукт, К_во

INTO Продукт, К_продукта

FROM Наличие, Продукты

WHERE Наличие.ПР = Продукты.ПР;

где не указан конкретный продукт и, следовательно, SELECT спродуцирует вывод всей таблицы продуктов, значение SQLCODE будет отрицательным. При этом значения переменных Продукт и К_продукта останутся неизменными, то есть, такими, какими они были после последнего правильного выполнения команды.

В единичном SELECT можно ввести за каждой целевой переменной слово INDICATOR и имя индикаторной переменной. Значения индикаторных переменных не равны нулю только при нулевом значении SQLCODE и NULL-значениях элементов SELECT для соответствующих целевых переменных. Например, если в столбцах К_во и Стоимость продукта 9 хранится значение NULL, то после выполнения запроса:

SELECT ПР, К_во, Стоимость

INTO ПР INDICATOR Инд1, К_прод INDICATOR Инд2, Стоим INDICATOR Инд3

FROM Наличие

WHERE ПР = 9;

будут получены следующие значения переменных: SQLCODE = 0, ПР = 9, Инд1 = 0, Инд2 = Инд3 = -1, а К_прод и Стоим имеют значение NULL.

Переменные можно использовать и в предложениях модификации данных. Приведем несколько примеров.

Изменить цены продуктов калужских поставщиков на величину, заданную переменной Измен:

UPDATE Поставки

SET Цена = Цена + .Измен

WHERE ПС IN

(SELECT ПC

FROM Поставщики

WHERE Город = 'Калуга');

Удалить все блюда, основа которых указана в переменной Осн:

DELETE

FROM Блюда

WHERE Основа = .Осн;

Добавить в таблицу Поставщики нового поставщика, атрибуты которого заданы соответствующими переменными ПС, Имя, Статус, Город, а Адрес и Телефон неизвестны:

INSERT

INTO Поставщики

(ПС, Название, Статус, Город)

VALUES (.ПС, .Имя, .Статус, .Город);

В тех же приложениях, где надо отыскивать и обрабатывать множество подходящих записей из одной таблицы или совокупности таблиц базы данных следует использовать курсоры, позволяющие организовать по следовательный доступ к строкам какой-либо таблицы (соединению таблиц, представлению).

Предложение:

DECLARE имя_курсора CURSOR

FOR подзапрос

определяет имя курсора и связанный с ним подзапрос. С его помощью идентифицируется некоторое множество столбцов и строк указанной таблицы (совокупности таблиц), которое становится активным множеством для данного курсора. (Точнее говоря, определяется множество частей строк, в которые входят только значения из указанных столбцов.) Курсор идентифицирует также позицию в этом множестве (сначала это позиция его первой записи). Активные множества всегда рассматриваются как упорядоченные. При этом упорядочение определяется фразой ORDER BY, а при ее отсутствии – системой (в порядке загрузки строк в таблицу).

Описанные с помощью DECLARE CURSOR множества используются рядом предложений SQL для удаления отмеченных строк (DELETE), их модификации (UPDATE) или присвоения значений перечисленных в SELECT столбцов переменным, список которых указывается в предложении FETCH (вызвать). Однако перед выполнением этих команд необходимо активизировать курсор, который в этот момент не должен быть открыт. Для этого используется предложение OPEN (OPEN имя_курсора).

Предложение FETCH используется для выборки той записи активного множества, на которую указывает курсор, для присвоения значений столбцов этой записи переменным, перечисленным во фразе INTO, и для перемещения курсора на следующую строку активного множества.

При перемещении запоследнюю строку переменная SQLCODE примет значение +100.

FETCH имя_курсора

INTO переменная [[INDICATOR] индикаторная_переменная]

{,переменная [[INDICATOR] индикаторная_переменная]} ...

Команду FETCH обычно помещают в некоторый цикл, размещая непосредственно за ней команду анализа SQLCODE. Это позволяет обнаружить переход от значения 0 на +100 и организовать выход из цикла.

Наконец, следует упомянуть еще два предложения, связанные с курсорами. Это предложение для дезактивации курсора (CLOSE имя_курсора) и предложение для уничтожения курсора (DROP CURSOR имя_курсора).

Глава 24.
Справочник по SQL-операторам

В SQL используются следующие типы операторов:

DDL

Data Defenition Language (Язык Определения Данных): эти операторы действуют на структуру объектов базы данных и неявно используют словарь данных. До и после выполнения каждого DDL-оператора SQL неявно выполняет операцию COMMIT.

DML

Data Manipulation Language (Язык манипуляции данными): эти операторы выполняют различные манипуляции с данными базы.

EMB

Embedded SQL (Встроенный SQL): операторы, используемые для помещения DDL и DML-операторов внутрь программы на процедурном языке.

Ниже содержится детальное описание всех SQL-операторов, приведенных в алфавитном порядке.

ALTER CLUSTER

Назначение

Переопределение будущих характеристик памяти для кластера.

Предпосылки

Вы должны быть либо владельцем кластера, либо иметь привилегию DBA для изменения характеристик памяти чужого кластера.

Синтаксис

ALTER CLUSTER [user.]cluster

[PCTUSED integer] [PCTFREE integer]

[SIZE integer]

[INITRANS integer] [MAXTRANS integer] [STORAGE storage]

Ключевые слова и параметры

user

Имя пользователя — владельца кластера.

cluster

Имя кластера.

PCTUSED integer

(Обратитесь к ниже к параметру PCTUSED в описании оператора CREATE TABLE).

PCTFREE integer

(Обратитесь к ниже к параметру PCTFREE в описании оператора CREATE TABLE).

SIZE integer

Определяет, сколько ключей кластера будет запоминаться в одном блоке, распределенном кластеру. Полное описание этого параметра дается ниже в операторе CREATE CLUSTER.

INITRANS integer

(Обратитесь к ниже к параметру INITRANS в описании оператора CREATE TABLE).

MAXTRANS integer

(Обратитесь к ниже к параметру MAXTRANS в описании оператора CREATE TABLE).

STORAGE storage

(Смотрите описание фразы STORAGE).

Используя команду ALTER CLASTER, вы можете:

u изменить PCTUSED и PCTFREE для будущих блоков кластера

u изменить SIZE для будущих блоков кластера

u изменить для будущих блоков значения INITRANS и MAXTRANS

u изменить будущие распределения памяти, задаваемые с помощью параметров PCTINCREASE, MAXEXTENTS, NEXT.

Используя команду ALTER CLUSTER, вы не можете:

u изменять количество столбцов кластера и их имена

u изменять параметр MINEXTENTS

u изменять любой параметр относительно блоков, уже распределенных кластеру

u удалять таблицы из кластера (См. команды DROP CLUSTER и DROP TABLESPACE)

u менять табличное пространство, которому принадлежит кластер

ALTER DATABASE

Назначение

Изменение существующей базы данных в следующих направлениях:

u монтировать базу данных (в разделяемом или исключительном режимах)

u открыть или закрыть базу данных

u переименовать или отменить файл журнала повторного выполнения

u специфицировать, что файл журнала будет архивироваться и, следовательно, будет полезен при восстановлении носителя или не будет архивироваться и, значит, поможет только при восстановлении экземпляра.

Предпосылки

Требуется привилегия DBA.

Синтаксис

ALTER DATABASE [ database ]

ADD LOGFILE filespec [,filespec ...] |DROP LOGFILE 'filename'
[,'filename'] ...]

|RENAME FILE 'filename' [,'filename'] ... TO filename [,filename] ... ] |ARCHIVELOG

|NOARCHIVELOG

|MOUNT [SHARED | EXCLUSIVE]

| DISMOUNT

|OPEN

| CLOSE [NORMAL | IMMEDIATE]

Ключевые слова и параметры

database

Идентификатор базы данных, который не может содержать более 8 символов. Если отсутствует, имя базы будет браться из значения параметра файла INIT.ORA — DB_ NAME.

ADD LOGFILE

Спецификация базы данных в форме:

filename' [SIZE integer [K|M]] integer [REUSE]

указывает SIZE в байтах. Если integer опущено, предполагается размер файла журнала в 500К. Если далее идет «К», integer умножается на 1024, если «М» — на 1048576.

DROP LOGFILE filename

Имя текущего файла журнала, который должен быть отменен.

RENAME FILE filename TO

Специфицирует старое и новое имена для файлов базы данных.

filename

Новые имена файлов должны соответствовать соглашениям вашей операционной системы.

ARCHIVELOG

NOARCHIVELOG

Соответственно разрешает и запрещает архивирование.

MOUNT

DISMOUNT

Определяет, на что изменить статус базы данных: монтировать или размонтировать. Для большинства видов действий база данных должна быть монтирована и открыта. Некоторые операции по сопровождению базы требуют, чтобы она была монтированной, но не открытой.

OPEN

CLOSE

Указывает, что база данных должна быть открыта и доступна для нормального к ней обращения или закрыта и недоступна.

Приемлемость опций зависит от текущего состояния базы данных: если база данных не монтирована, вы можете использовать только MOUNT; если монтирована, но не открыта — все, кроме MOUNT и CLOSE; если открыта — только CLOSE.

SIZE

REUSE

Если специфицировано SIZE, а REUSE — опущено, будет создаваться файл указанного размера (его не должно быть на диске). Если указаны оба параметра, то в случае отсутствия файла — он создается, а при наличии — проверяется его размер. Если указано только REUSE или не указано ничего, файл должен существовать и оставаться такого же размера.

Некоторые аргументы требуют дополнительных действий со стороны DBA, например — распределение и подготовка файлов для опции REUSE или выполнение backup, если используется режим ARCHIVELOG.

В общем случае рекомендуется перед изменением базы закрыть ее и размонтировать.

ALTER INDEX

Назначение

Изменение будущих характеристик распределения памяти для блоков индекса.

Предпосылки

Вы должны быть либо владельцем индекса, либо иметь привилегию DBA.

Синтаксис

ALTER INDEX [user.]index

[INITRANS integer] [MAXTRANS integer]

[STORAGE storage]

Ключевые слова и параметры

user

Имя пользователя — владельца индекса

index

Имя индекса

INITRANS integer

(Обратитесь к ниже к параметру INITRANS в описании оператора CREATE TABLE).

MAXTRANS integer

(Обратитесь к ниже к параметру MAXTRANS в описании оператора CREATE TABLE).

STORAGE storage

(Смотрите описание фразы STORAGE).

Параметры INITRANS, MAXTRANS и STORAGE имеют то же значение, что и в операторе CREATE TABLE.

ALTER ROLLBACK SEGMENT

Назначение

Изменить параметры памяти существующего сегмента отката.

Предпосылки

Требуется привилегия DBA.

Синтаксис

ALTER [PUBLIC] ROLLBACK SEGMENT rollback_segment

STORAGE storage

Ключевые слова и параметры

PUBLIC

Говорит что существующий сегмент отката — общий.

rollback_segment

Специфицирует имя существующего сегмента отката.

STORAGE storage

Смотрите описание синтаксиса фразы STORAGE далее.

В операторе ALTER ROLLBACK SEGMENT фраза STORAGE влияет лишь на будущее распределение памяти в указанном сегменте отката. Следовательно аргументы INITIAL и MINEXTENTS для изменения существующего сегмента отката применять нельзя.

Имейте в виду, что оператор ALTER ROLLBACK SEGMENT нельзя использовать для смены статуса сегмента отката с PUBLIC на private и наоборот. Для произведения таких изменений необходимо отменить сегмент и создать заново с нужной характеристикой.

ALTER SEQUENCE

Назначение

Изменить опции последовательности в одном из следующих направлений:

u изменить на будущее шаг между соседними последовательными номерами.

u установить MINVALUE или MAXVALUE или выключить один или оба этих параметра.

u включить/выключить действие параметра CACHE

u включить/выключить действие параметра ORDER

Предпосылки

Вы должны быть владельцем последовательности, иметь предоставленные к ней привилегии или иметь привилегию DBA.

Синтаксис

ALTER SEQUENCE [user.]sequence

[INCREMRNT BY integer]

[MAXVALUE integer | NOMAXVALUE]

[MINVALUE integer | NOMINVALUE]

[CYCLE | NOCYCLE]

[CACHE integer | NOCACHE]

[ORDER | NOORDER]

Ключевые слова и параметры

user

Имя пользователя для владельца

sequence

Имя существующей последовательности.

INCREMENT BY

Специфицирует инкремент для integer будущей последовательности.

MAXVALUE | NOMAXVALUE

Специфицирует новый максимальный последовательный номер или делает его неограниченным.

MINVALUE | NOMINVALUE

Специфицирует новый минимальный последовательный номер или делает его неограниченным.

CYCLE | NOCYCLE

Определяет переключение на номер MINVALUE, когда в возрастающей последовательности достигается MAXVALUE; и переключение на MAXVALUE для убывающей последовательности.

CACHE integer | NOCHACE

Определяет, будут ли храниться в памяти будущие последовательные номера (для ускорения доступа). Предполагается 20. CACHE должен быть меньше разницы между MAXVALUE и MINVALUE.

ORDER | NOORDER

Специфицирует, будет ли БД или нет генерировать последовательные номера в порядке запроса. Обычно, такая генерация производится без указания ORDER.

Для того, чтобы начать последовательность с другого номера, ее надо отменить и создать заново. Оператор ALTER SEQUENCE влияет только на будущие последовательные номера.

Выполняются некоторые проверки правильности задания оператора. Например — невозможно назначить новое MAXVALUE, если оно меньше текущего последовательного номера.

ALTER SESSION

Назначение

Включает или отключает в текущем сеансе средство трассировки SQL.

Предпосылки

Нет.

Синтаксис

ALTER SESSION

SET SQL_TRACE {TRUE | FALSE}

Ключевые слова и параметры

TRUE

Включает средство трассировки SQL

FALSE

Отключает средство трассировки SQL

Средство трассировки SQL генерирует статистику производительности обработки SQL-операторов. Для включения средства трассировки SQL-операторов в вашем сеансе выполните команду ALTER SESSION с ключевым словом TRUE. Для выключения средства трассировки выполните команду ALTER SESSION с ключевым словом FALSE.

Можно также активизировать средство трассировки SQL для всех сеансов данного экземпляра БД, установив в TRUE параметр файла INIT.ORA: SQL_TRACE. В этом случае Вы можете выключить трассировку отдельного сеанса, выдав в нем команду ALTER SESSION с ключевым словом FALSE.

ALTER TABLE

Назначение

Меняет определение таблицы:

u добавляя столбцы или ограничения

u модифицируя определения существующих столбцов (например — тип данных или NOT NULL).

u отменяя ограничения

u модифицируя будущее распределение памяти

u отмечая, что для данной таблицы был выполнен backup

Предпосылки

Вы должны быть владельцем таблицы, получить на нее привилегию ALTER или иметь привилегию DBA.

Синтаксис

ALTER TABLE [user.]table

[ADD ({column_element|table_constraint}

[,{column_element|table_constraint}]...)]

[MODIFY (column_element [,column_element]...)]

[DROP CONSTRAINT constraint]...

[PCTFREE integer] [PCTUSED integer]

[INITRANS integer] [MAXTRANS integer]

[STORAGE storage]

[BACKUP]

Ключевые слова и параметры

[user.]table

Имя существующего пользователя для таблицы, которую вы хотите изменить. По умолчанию в качестве user предполагается пользователь, выдавший команду ALTER TABLE. Для изменения характеристик не своей таблицы, вы должны либо иметь для нее привилегию ALTER, либо иметь привилегию DBA.

ADD/MODIFY

Добавляет или модифицирует column_element определение столбца, ограничение столбца или его умолчание. Синтаксис column_element приводится ниже.

ADD

Добавляет ограничение для table_constraint таблицы.

DROP constraint

Отменяет указанное ограничение для столбца или таблицы.

PCTFREE integer

PCTUSED integer

(Смотрите описание этих параметров ниже в операторе INITRANS integer CREATE TABLE)

MAXTRANS integer

STORAGE storage

(Смотрите ниже фразу Storage).

BACKUP

Модифицирует словарь данных, отмечая что таблица была скопирована на момент выдачи команды ALTER TABLE. Если вы добавляете к таблице столбец с помощью фразы ADD, его начальное значение для каждой строки — NULL. Добавлять столбец NOT NULL можно только в пустую (не содержащую строк) таблицу. Фраза MODIFY используется для изменения следующих атрибутов столбца:

u размеров

u типа данных

u NOT NULL

Изменять тип данных и уменьшать размер столбца можно лишь для содержащих NULL во всех строках. Изменить тип на NОT NULL можно только если все строки содержат значение не null.

Если вы меняете размер столбца, имевшего атрибут NOT NULL и не указываете «NOT NULL», столбец все равно сохраняет этот атрибут.

Обзор, определенный запросом по всем столбцам таблицы (SELECT * FROM ...), не будет больше правильно работать, если вы добавили к соответствующей таблице новые столбцы. Это можно обойти удалением и повторным созданием обзора.

Примеры

Чтобы добавить к таблице EMP числовой столбец THRIFTPLAN с максимальным размером в семь цифр и двумя позициями после точки, а также однобайтового столбца LOANCODE, введите:

ALTER TABLE EMP

ADD (THRIFTPLAN NUMBER(7,2), LOANCODE CHAR(1))

Для увеличения размера THRIFTPLAN до 9 цифр и отмены ограничения ENAME_CNSTR введите:

ALTER TABLE EMP

MODIFY (THRIFTPLAN NUMBER(9,2))

DROP CONSTRAINT ENAME_CNSTR

ALTER TABLESPACE

Назначение

Изменить существующее табличное пространство путем:

u добавления или переименования файла(ов) базы данных

u изменения умалчиваемых параметров памяти для таблиц

u перевода его в online или offline

u старта или остановки backup.

Предпосылки

Требуется привилегия DBA

Синтаксис

ALTER TABLESPACE tablespace

{ADD DATAFILE filespec [,filespec [REUSE] ] ...

|RENAME DATAFILE 'filename' [,'filename'] ...

TO 'filename' [,'filename']...

|DEFAULT STORAGE storage]

|ONLINE

| OFFLINE [ NORMAL | IMMEDIATE ]

| {BEGIN BACKUP | END BACKUP}}

Ключевые слова и параметры

tablespace

Имя существующего табличного пространства

ADD DATAFILE filespec

Делает доступной табличному пространству дополнительную память. Добавить файл к табличному пространству можно когда оно находится в состоянии online или offline. Filespec спецификация базы данных в форме:

filename' [SIZE integer [K|M]][REUSE] integer

указывает SIZE в байтах. Если integer опущено, предполагается размер файла журнала в 500К. Если далее идет «К», integer умножается на 1024, если «М» — на 1048576. Если SIZE указан, а REUSE — опущен, будет создаваться файл специфицированного размера (до этого он не должен существовать). Если указаны параметра, файл создается, если его нет, а если есть — проверяется его размер. Если REUSE опущено или не указаны оба аргумента, файл должен существовать и оставаться такого же размера.

RENAME DATAFILE filename TO filename

Переименовывает один или несколько файлов, связанных с табличным пространством. Файл операционной системы не переименовывается. Во время этой операции табличное пространство должно находиться в offline.

DEFAULT STORAGE storage

Специфицирует новые умалчиваемые параметры памяти для будущих объектов, создаваемых в табличном пространстве.

ONLINE

Указывает, что табличное пространство должно быть возвращено в состояние online.

OFFLINE

Указывает, что табличное пространство должно быть выведено в offline либо немедленно (IMMEDIATE), либо после того, как текущие пользователи больше к нему не обращаются (NORMAL).

BEGIN BACKUP

Специфицирует, что файлы базы, составляющие это пространство, будут скопированы системной процедурой backup. Этот оператор не влияет на доступ к базе данных; пользователи могут продолжать к ней обращаться. Этот оператор для работы потребует также управляющий файл и файл журнала.

END BACKUP

Указывает на завершение процедуры системного копирования.

Для переименования файла(ов) необходимо выполнить следующие шаги:

1. Перевести табличное пространство в offline.

2. Переименовать файлы на уровне операционной системы.

3. Переименовать файлы с помощью оператора ALTER TABLESPACE.

4. Вернуть табличное пространство в online.

ALTER USER

Назначение

Изменять следующие характеристики существующих пользователей базы данных:

u пароль

u умалчиваемое табличное пространство для создания объектов

u умалчиваемое временное табличное пространство для временных сегментов, создаваемых в интересах пользователя.

Предпосылки

Требуется привилегия DBA

Синтаксис

ALTER USER username [IDENTIFIED BY password]

[DEFAULT TABLESPACE tablespace]

[TEMPORARY TABLESPACE tablespace]

Ключевые слова и параметры

username

Имя существующего в настоящий момент пользователя базы данных.

password

Новый пароль для данного пользователя.

DEFAULT TABLESPACE

Специфицирует умалчиваемое табличное пространство для создания объектов.

TEMPORARY TABLESPACE

Специфицирует умалчиваемое табличное пространство для создания временных сегментов.

Пароль пользователя может быть изменен также и с помощью оператора GRANT. Умалчиваемое временное табличное пространство для пользователя может быть установлено также при назначении пользователю привилегии RESOURCE для табличного пространства (и если это первое табличное пространство в котором предоставляется такая привилегия). Умалчиваемое временное табличное пространство устанавливается также при предоставлении пользователю первого табличного пространства.

AUDIT (Форма I)

Назначение

Выбор опций регистрации доступа к базе данных.

Предпосылки

Требуется привилегия DBA

Синтаксис

AUDIT {system_option [,system_option]... | ALL]

WHENEVER [NOT] SUCCSESSFUL]

Ключевые слова и параметры

system_option

Одна из опций: DBA, CONNECT, NOT EXISTS или RESOURCE.

ALL

Эквивалентен списку всех перечисленных опций. По умолчанию операторы регистрируются независимо от того, успешно или нет они завершились.

WHENEVER SUCCSESSFUL

Указывает, что SQL-операторы будут регистрироваться только при их успешном завершении.

WHENEVER NOT SUCCSESSFUL

Указывает, что SQL-операторы будут регистрироваться только при их аварийном завершении.

Эта форма регистрации доступа дает возможность DBA отслеживать следующие действия:

u подключение и отключение пользователей к базе данных (опция
CONNECT)

u операторы, требующие привилегии RESOURCE, например такие, которые создают объекты базы данных

u операторы, требующие привилегии DBA (опция SBA)

u операторы, завершившиеся с ошибкой БД, так как указанные объекты базы данных не существуют (опция NOT EXIST).

Регистрация доступа на уровне системы включается с помощью параметра AUDIT_TRAIL файла INIT.ORA. Независимо от того, включена регистрация или нет, SQL-оператор AUDIT будет выполняться правильно. Если регистрация задействована, в журнал будут заноситься записи, в противном случае — нет.

Все формы регистрации помещают результаты в таблицы словаря данных. В результате этого вы можете легко генерировать отчеты на основании этих данных. Если таблицы регистрации словаря данных разрастаются, вы можете архивировать старые записи регистрации доступа и удалить их из таблиц. Удалять таблицы словаря данных вы не должны.

Заметьте, что для операторов DDL и DML термин «завершение» означает завершение «фазы разбора» (анализ, просмотр словаря, проверка защиты) безотносительно их выполнения (execution). Обратите внимание, что операция rollback не удаляет строки из журнала регистрации доступа.

Если не определено ни WHENEVER SUCCSESSFUL, ни WHENEVER NOT SUCCSESSFUL, SQL-операторы регистрируются независимо от их завершения.

AUDIT (Форма II)

Назначение

Включать регистрацию доступа к таким объектам базы данных, как таблицы и обзоры.

Предпосылки

Для регистрации объектов вы должны быть либо их владельцем, либо иметь привилегии DBA. Опция DEFAULT требует привилегий DBA.

Синтаксис

AUDIT {table_option [,table_option]... | ALL}

ON { [user.]table | DEFAULT}

[BY {SESSION | ACCESS}]

-------

[WHENEVER [NOT] SUCCSESSFUL]

Ключевые слова и параметры

table_option

Для таблиц приемлемы следующие опции: AUDIT, COMMENT, DELETE, GRANT, INDEX, INSERT, LOCK, RENAME, SELECT, UPDATE.

Для обзоров приемлемы следующие опции: AUDIT, COMMENT, INSERT, DELETE, GRANT, LOCK, RENAME, SELECT, UPDATE.

Для последовательностей приемлемы опции: ALTER, AUDIT, GRANT, SELECT.

Для синонимов приемлемы те же опции, что и для таблиц (так как все действия реально производятся с таблицами).

ALL

Может применяться для обозначения всех приемлемых опций.

username

Имя владельца таблицы.

table

Имя таблицы, обзора, последовательности или синоним, означающий обзор или таблицу.

DEFAULT

Специфицирует, что созданные впоследствии таблицы будут регистрироваться, как указано в table_option (устанавливает общесистемные опции регистрации доступа).

BY SESSION

Указывает, что за сеанс будет создано только по одной записи для каждого типа регистрируемого доступа BY ACCESS запись будет создаваться для каждой попытки доступа каждого типа.

Фраза BY определяет степень детализации регистрационного контроля доступа, указывая как часто будут записываться новые элементы в AUDIT_TRAIL. BY SESSION говорит о том, что определенный тип доступа применялся к объекту базы данных. BY ACCESS определяет, что регистрация будет выполняться так часто, как часто осуществляется тот или иной вид доступа.

Некоторые события случаются гораздо более часто, чем другие (например — обычно DML-операторы выполняются более часто, нежели GRANT). Чем более детальная регистрация, тем большее количество записей генерируется, так что вы должны проанализировать, какие операторы надо регистрировать.

В режиме BY SESSION для каждой таблицы генерируется не более одной записи для всего сеанса пользователя.

Выбранные SQL-операторы записываются не добавлением, а изменением записей в таблице AUDIT_TRAIL. Количество таких обновлений записи зависит от числа успешных или неудачных попыток выполнения операторов определенного типа. Отсутствие фразы BY ... означает по умолчанию BY SESSION.

В режиме BY ACCESS регистрируемые SQL-операторы отмечаются в AUDIT_ TRAIL добавлением (вставкой строк). Для операций DML строки добавляются или обновляются после завершения «фазы разбора» оператора.

CLOSE cursor (Встроенный SQL)

Назначение

Освобождает ресурсы, распределенные оператором OPEN cursor.

Предпосылки

Должен именовать ранее открытый курсор.

Синтаксис

EXEC SQL CLOSE cursor_name

Ключевые слова и параметры

cursor_name

Ранее открытый курсор.

Попытка закрыть не открытый курсор приведет к ошибке. Нет необходимости закрывать курсор, чтобы вновь открыть его. Из закрытого курсора строки не могут быть выбраны.

Закрытие курсора освобождает ранее открытый неявный курсор; освобождение, однако, не выполняется, пока память, связанная с курсором, нужна другому неявному курсору. Такое освобождение автоматически выполняет БД.

Опции прекомпилятора HOLD_CURSOR и RELEASE_CURSOR влияют на действие оператора CLOSE cursor.

Элемент описания для столбца

Назначение

Определение столбца и связанных с ним ограничений.

Предпосылки

Нет.

Синтаксис

column datatype [DEFAULT expr] [column constraint]

Ключевые слова и параметры

column

Имя вводимого столбца, подчиняющееся соглашениям по именованию объектов базы данных.

datatype

Правильный тип данных.

DEFAULT expr

Необязательная фраза DEFAULT определяет выражение, назначаемое столбцу, если оно явно не указывается при добавлении строки в таблицу. Действительно любое выражение, исключая ссылающиеся на столбец, LEVEL, ROWNUM. Тип данных выражения должен соответствовать типу столбца.

Элементы описания столбца используются в операторах CREATE TABLE и ALTER TABLE для определения столбцов таблиц.

COMMENT

Назначение

Записывать в словарь данных комментарии, относящиеся к таблице.

Предпосылки

Для записи комментария о таблице или столбце вы должны быть владельцем таблицы или иметь привилегию DBA или иметь привилегию ALTER для данной таблицы.

Синтаксис

COMMENT ON { TABLE [user.]table

| COLUMN [user.]table.column }

IS 'text'

Ключевые слова и параметры

user

Владелец таблицы.

TABLE table

Специфицирует имя комментируемой таблицы или обзора.

COLUMN column

Специфицирует имя комментируемого столбца.

text

Заключенный в одиночные кавычки текст длиной до 255 символов.

Удалить комментарий из базы данных можно просто задав в качестве комментария пустую строку. Чтобы посмотреть комментарий, запросите его в одном из обзоров словаря данных: ALL_COL_COMMENTS, ALL_TAB_COMMENTS, USER_ COL_COMMENTS или USER_ TAB_COMMENTS.

COMMIT

Назначение

Оператор COMMIT применяется для того, чтобы:

u сделать «постоянными» все изменения, сделанные в текущей транзакции (реально данные могут быть изменены несколько позже)

u очистить все точки сохранения данной транзакции

u завершить транзакцию

u освободить все блокировки данной транзакции

Предпосылки

Нет.

Синтаксис

COMMIT [WORK]

Ключевые слова и параметры

WORK

Необязателен и обеспечивает ANSI-совместимость. Операторы COMMIT и COMMIT WORK — идентичны.

Рекомендуется явно завершать транзакции в прикладных программах (включая и последнюю транзакцию перед отсоединением от БД), используя команды COMMIT WORK (или ROLLBACK WORK). Если вы явно не записали транзакцию, а прикладная программа завершилась аварийно, последняя не записанная транзакция будет откачена.

Нормальный выход из большинства утилит и инструментальных средств БД будет записывать текущую транзакцию. Нормальный выход из программы прекомпилятора БД приводит к откату текущей транзакции.

Транзакция (или логическая единица работы) — это последовательность SQL-операторов, которую БД трактует как единый элемент. Транзакция начинается с первого выполнимого SQL-оператора после COMMIT, ROLLBACK или присоединения к базе данных. Транзакция завершается по командам COMMIT, ROLLBACK или при отсоединении от БД.

Заметьте, что система выдает неявный COMMIT до и после выполнения любого DDL-оператора.

Операторы COMMIT и ROLLBACK завершают также и транзакцию типа READ ONLY, начинающуюся оператором SET TRANSACTION.

COMMIT (Встроенный SQL)

Назначение

Сделать постоянными все изменения, выполненные в данной транзакции. Опция RELEASE используется для освобождения всех ресурсов и отсоединения от базы данных.

Предпосылки

Нет.

Синтаксис

EXEC SQL [AT dbname] COMMIT [WORK] [RELEASE]

Ключевые слова и параметры

AT dbname

Необязательная фраза, указывающая имя базы данных, для которой выдан COMMIT.

RELEASE

Необязательный параметр, отсоединяющий вас от базы данных.

Всегда завершайте явно последнюю единицу работы с помощью операторов COMMIT или ROLLBACK с опцией RELEASE. Если вы не выполните COMMIT и RELEASE, блокировки, запрошенные в процессе работы вашей программы, будут держать ресурсы, пока система не поймет, что процесс больше не активен.

Это может привести к тому, что остальные пользователи, нуждающиеся в тех же ресурсах, будут ожидать или получать ошибки при попытке обращения.

Если команда COMMIT выдана без опции RELEASE, ваша текущая транзакция будет записана без последующего отсоединения от базы данных. Система автоматически откатывает изменения, если программа завершается аварийно. Оператор COMMIT WORK не влияет на содержимое host-переменных и на процесс дальнейшей работы программы.

CONNECT (Встроенный SQL)

Назначение

Обеспечивает прямое подключение к ORACLE RDBMS.

Программа должна это выполнить до того, как попытается обратиться к данным. CONNECT используется для подсоединения пользователя к любой базе данных ORACLE.

Предпосылки

Для специфицируемой базы данных должна быть привилегия CONNECT.

Синтаксис

EXEC SQL CONNECT :user_password

[AT dbname]

[USING :dbstring]

или

EXEC SQL CONNECT :user IDENTIFIED BY :password

[AT dbname]

[USING :dbstring]

Ключевые слова и параметры

:user_password

Одна host-переменная, содержащая имя пользователя и пароль, разделенные слешем «/». Для автоматической регистрации установите значение этой переменной в «/». Это говорит системе, что для регистрации необходимо использовать имя пользователя из операционной системы.

:user IDENTIFIED By :password

Позволяет передать имя пользователя и пароль как два отдельных элемента.

AT dbname

Необязательная фраза, определяющая неумалчиваемую базу данных. Это имя должно быть предварительно определено в операторе DECLARE DATABASE.

USING connect_string

Определяет строку спецификации для удаленной базы данных. Если отсутствует фраза AT, в качестве подразумеваемой используется база данных, указанная во фразе USING. В программе может быть выполнено несколько операторов CONNECT, но только одно соединение может быть выполнено без использования фразы AT для специфицирования удаленной базы данных. Если вы опустили фразу USING, оператор CONNECT подключит вас к подразумеваемой базе данных.

Фраза CONSTRAINT

Назначение

Ограничивает диапазон подходящих значений для столбца или группы столбцов. При выполнении операторов INSERT, UPDATE и DELETE соответствующие ограничения проверяются. Чтобы оператор выполнился успешно, ограничение должно выполняться.

Ограничения используются для наложения на столбец или группу столбцов следующих условий:

u требование, чтобы столбец или группа столбцов содержал значения NOT NULL

u требование, чтобы столбец или группа столбцов были уникальны для данной таблицы

u идентификация столбца или группы как основного ключа (PRIMARY KEY)

u требования наличия значения столбца или группы в другой таблице (внешний ключ — FOREIGN KEY)

u требование удовлетворения столбца или группы некоторому выражению (CHECK)

Предпосылки

Должен использоваться в column_element или table_constraint в операторах CREATE TABLE и ALTER TABLE. Оператор CREATE TABLE требует привилегии RESOURCE.

Синтаксис ограничения для таблицы

[{UNIQUE | PRIMARY KEY} (column [,column]...)

[CONSTRAINT constraint] ]

[FOREIGN KEY (column [,column]...)

REFERENCES [user.]table [(column [,column]...)]

[CONSTRAINT constraint] ]

[CHECK (condition) [CONSTRAINT constraint] ]

Синтаксис ограничения для столбца

Column [NULL] | [NOT NULL [CONSTRAINT constraint] ]

[{UNIQUE | PRIMARY KEY} [CONSTRAINT constraint] ]

REFERENCES [user.]table [(column)]

[CONSTRAINT constraint] ]

[CHECK (condition) [CONSTRAINT constraint] ]

Ключевые слова и параметры

Column

Специфицируется имя столбца, на который накладывается ограничение.

CONSTRAINT

Специфицируется имя ограничения.

constraint

Эта необязательная фраза применима ко всем ограничениям.

NULL | NOT NULL

Указывает, что столбец может или не может содержать значения NULL.

UNIQUE

Требует, чтобы столбец был уникальным.

PRIMARY KEY

Определяет столбец как основной ключ.

FOREIGN KEY (column..)

Определяет столбец как внешний ключ для user.table(column(s)).

REFERENCES user.table(column)

По умолчанию columns(s) являются основным ключом table.

CHECK condition

Определяет условие, которому должна удовлетворять строка, чтобы находиться в таблице. Существует два типа ограничений: для таблиц и столбцов (table constraints, column constraints). Они идентичны, за исключением того, что ограничение для столбца ссылается на единственный столбец, в то время как ограничение таблицы может ссылаться на большее число столбцов. Ограничение может быть специфицировано двумя способами:

u Определение ограничений для таблицы как часть глобального определения таблицы:

CREATE TABLE Project_Empoloyee

(PROJECT NUMBER,

EMPLOYEE NUMBER,

PRIMARY KEY (PROJECT,EMPLOYEE) )

u Определение ограничений для столбца локально для определенного столбца:

CREATE TABLE DEPT

(DEPTNO NUMBER PRIMARY KEY, ...)

Как и другие объекты базы данных, ограничения сохраняются в словаре данных. У вас есть возможность именования ограничения. Если вы не сделали этого, имя ограничению будет назначено системой в виде SYS_Cn, где n — целое, делающее это имя уникальным для базы данных. Такие назначенные системой имена переназначаются в процессе импорта. Ограничения должны использоваться либо в операторе CREATE TABLE, либо — в ALTER TABLE. Они могут быть удалены или добавлены оператором ALTER TABLE. Для модификации ограничения его необходимо удалить и вновь добавить.

NULL

Ограничение NULL может применяться только для столбцов. Это ограничение может иметь значения NULL либо NOT NULL. Первое означает, что в таблице может быть сколько угодно строк, содержащих в этом столбце значение NULL и наоборот, NOT NULL означает отсутствие таких строк. При отсутствии предполагается ограничение NULL, что разрешает столбцам содержать такие значения.

CREATE CLUSTER

Назначение

Создать кластер, содержащий одну и более таблиц.

Строки в одной или нескольких таблицах, разделяющие один и тот же ключ кластера, запоминаются в базе вместе. Из этого следует, что время доступа к строкам с одинаковым ключом кластера улучшается.

Предпосылки

Необходимо иметь привилегию RESOURCE. Для создания кластера для другого пользователя вам необходимо иметь привилегию DBA.

Синтаксис

CREATE CLUSTER [user.]cluster

(column datatype [,column datatype] ...]

[PCTUSED integer] [PCTFREE integer]

[SIZE integer]

[INITRANS integer] [MAXTRANS integer]

[TABLESPACE tablespace]

[STORAGE storage]

Ключевые слова и параметры

user

Имя владельца кластера

cluster

Имя кластера

column

Имя одного или нескольких столбцов, составляющих ключ кластера.

PCTUSED integer

Специфицирует предел, который система будет использовать для определения, можно ли в блок добавлять еще строки. integer — это целое число, интерпретирующееся как процент.

PCTFREE integer

Специфицирует память, резервируемую в каждом блоке кластера для будущего расширения. integer — это целое число, интерпретирующееся как процент.

SIZE

Специфицирует среднее значение памяти, необходимой для хранения всех строк с одинаковым значением ключа кластера.

INITRANS integer

Специфицирует начальное количество параллельных транзакций, пытающихся изменить блоки, принадлежащих данному кластеру.

MAXTRANS integer

Специфицирует максимальное число параллельных транзакций, пытающихся изменить любой блок, принадлежащий кластеру.

TABLESPACE tablespace

Специфицирует табличное пространство, которому будет принадлежать кластер.

STORAGE storage

Определяет, как блоки будут распределяться для кластера.

Кластеризация позволяет собрать строки разных таблиц, имеющие одинаковый ключ кластера в один блок БД.

Кластеризация обеспечивает более высокий уровень управления физическим хранением строк данных в базе. Она позволяет как уменьшить время доступа к кластеризованным таблицам, так и снизить необходимую таблице память. С точки зрения пользователя кластеризованные таблицы трактуются идентично некластеризованным так как для их использования не требуется никаких модификаций SQL-операторов.

Общее правило — кластеризовать таблицы, часто объединяемые вместе. Хотя кластеризация может снизить время доступа при объединении, она может его увеличить при полном сканировании таблиц, входящих в мультитабличные кластеры. Перед кластеризацией вы должны рассмотреть, насколько часто выполняется объединение таблиц в запросах и насколько часто выполняется их полное сканирование (равно как и негативное влияние кластеризации на выполнение операций INSERT, UPDATE, DELETE).

Ключ кластера

Ключ кластера определяет, каким образом БД будет группировать строки в кластере. Строки с одинаковым значением ключа кластера будут физически храниться вместе. Столбцы, определяемые в операторе CREATE CLUSTER, становятся ключом кластера. Эти столбцы должны соответствовать и по типу данных и по размеру столбца каждой кластеризованной таблицы. Столбцы таблиц, составляющие ключ кластера, не обязательно должны совпадать по имени. Каждое отличающееся значение ключа кластера запоминается лишь однажды вне зависимости от числа строк, в которых оно присутствует. Это экономит дисковую память и увеличивает производительность многих операций.

Каждый значение ключа кластера включает в себя:

u ROWID (19 байтов)

u внутреннюю память для кластеризованных столбцов (длина в байтах каждого столбца плюс 1 байт заголовка)

Например, для следующего оператора:

CREATE CLUSTER XYZ (ABC CHAR(5), MNO CHAR(7))

создается ключевое значение из 33 байтов:

u 19 байтов на ROWID

u 5 и 7 байтов на столбцы ABC и MNO

u 2 байта на заголовки для обоих столбцов

Максимально допустимый размер ключа кластера зависит от операционной системы. Для кластеризованных столбцов не обязательно задание ограничений, их можно задавать для отдельных таблиц, входящих в кластер. Как и в случае с индексом, порядок столбцов в ключе кластера влияет на структуру индекса кластера.

Size

Система использует параметр SIZE для определения памяти, резервируемой для строк, соответствующих одному ключу кластера и, следовательно, максимального количества ключей кластера на один блок БД. Если SIZE не является делителем количества байтов в блоке БД, система будет использовать ближайший следующий делитель. Если SIZE больше размера физического блока БД, будет браться значение, равное блоку (другими словами — как минимум один ключ на блок). Если вы опустили фразу SIZE, система назначит один ключ на физический блок. Кроме того, при назначении реального значения SIZE для кластера, система принимает во внимание длину ключа кластера (больший ключ требует большего SIZE). Посмотреть действительный размер SIZE можно, запросив столбец KEY_SIZE таблицы словаря данных USER_CLUSTERS.

Хотя максимальное число ключей кластера и фиксировано для данного кластера, система не резервирует одинаковую память для каждого ключа. Например, если вы специфицировали SIZE, приведший к шести ключам на блок БД, 6 ключей кластера вместе со своими строками может быть любого размера, пока их общая длина не превысит размер блока. Ключи кластера переменной длины позволяют запоминать данные более эффективно, нежели — фиксированной длины, так как данные, запоминающиеся на один ключ кластера редко бывают фиксированными.

Значение SIZE, меньшее средней длины данных на ключ кластера, может привести к распространению данных одного ключа на несколько блоков. И наоборот, чересчур большое значение приведет к бесполезной трате памяти базы.

Добавление таблиц к кластеру

Таблицы добавляются к кластеру с помощью фразы CLUSTER оператора CREATE TABLE. Кластер может включать в себя до 16 таблиц, хотя затраты производительности растут после кластеризации 4–5 таблиц. Таблицы с данными типа LONG также могут кластеризоваться. Все таблицы кластера используют параметры распределения памяти кластера, включающие следующие: PCTUSED, PCTFREE, INITRANS, MAXTRANS, TABLESPACE, STORAGE. Индекс кластера обеспечивает быстрый доступ к строкам, базируясь на ключе кластера. Индекс кластера необходимо создать до выполнения каких-либо DML-операций над его таблицами. Его необходимо создать вручную, пользуясь следующим синтаксисом:

CREATE INDEX iname ON CLUSTER cname

Обратите внимание, что при создании индекса не задается имя столбца так как индекс кластера создается на ключе кластера, определенном оператором CREATE CLUSTER.

CREATE DATABASE

Назначение

Создать базу данных с возможностями:

u установки максимального количества экземпляров, файлов базы данных и файлов журнала повторного выполнения

u указания имен и размеров базы данных и файлов журнала повторного выполнения

u выбора режима использования журнала (ARCHIVELOG или NOARCHIVELOG)

Предпосылки

Требуется привилегия DBA

Важно: Эта команда готовит базу данных для начального использования и удаляет все данные из существующих файлов. Пользуйтесь этой командой только узнав все ее возможности.

Синтаксис

CREATE DATABASE [database]

[CONTROLFILE REUSE]

[LOGFILE filespec [,filespec] ...]

[MAXLOGFILES integer ]

[DATAFILE filespec [,filespec] ...]

[MAXDATAFILES integer ]

[MAXINSTANCES integer ]

[ARCHIVELOG | NOARCHIVELOG]

------------

[EXCLUCIVE]

---------

Ключевые слова и параметры

database

Назначает состоящее из не более чем восьми символов имя базы данных. Умалчиваемое имя базы специфицируется в параметре DB_NAME файла INIT.ORA. Имя базы данных проверяется по управляющему файлу в момент монтирования и открытия.

filespec

Спецификация файла базы данных в форме: 'filename' [SIZE integer [K|M] [REUSE]

SIZE integer

Указывает SIZE в байтах. Отсутствие этого параметра предполагает по умолчанию использование файлов базы размером 10 мегабайтов и журнала регистрации — по 500 килобайтов. Если указано «К», integer умножается на 1024, «М» — на 1048576.

REUSE

Указывает, что будут повторно использоваться существующие файлы, информация в них полностью разрушается.

CONTROLFILE REUSE

Указывает, что существующие управляющие файлы, специфицируемые параметром CONTROL_FILES файла INIT.ORA, будут использоваться повторно, игнорируя и переписывая любую содержавшуюся в них информацию.

Не используется, при первом создании базы данных.

LOGFILE filespec

Специфицирует один или более файлов, которые будут использоваться для журнала повторного выполнения. Если эта опция не указана, по умолчанию создаются два файла с именами, зависящими от конкретной операционной системы.

MAXLOGFILES integer

Специфицирует максимальное количество файлов журнала, которые когда-либо будут использоваться в этой базе данных. Это число устанавливает абсолютный лимит, перекрывая параметр LOG_FILES. Это число не может быть увеличено кроме как при пересоздании базы. Диапазон значений 2–256. Потери при указании большего числа незначительны и влияют лишь на размер управляющих файлов.

DATAFILE filespec

Специфицирует одно или более имен файлов, которые будут использоваться для хранения данных. Если не указан, создается один файл с именем, зависящим от операционной системы. Все файлы, указанные в этом аргументе будут принадлежать табличному пространству SYSTEM.

MAXDATAFILES integer

Специфицирует максимальное количество файлов базы данных, которые когда-либо будут использоваться. Это число устанавливает абсолютный лимит, перекрывая параметр DB_FILES. Это число не может быть увеличено кроме как при пересоздании базы. Диапазон значений 1–255 и зависит от операционной системы. Потери при указании большего числа файлов невелики и влияют незначительно только на размер управляющих файлов. Умалчиваемое значение 32.

MAXINSTANCES integer

Указывает максимальное количество экземпляров, которые одновременно могут монтировать и открывать базу. Это число устанавливает абсолютный лимит, перекрывая параметр INSTANCES. Диапазон значений 1–255.

ARCHIVELOG

NOARCHIVELOG

Специфицирует начальный режим использования файлов журнала повторного выполнения. Режим может быть изменен с помощью ALTER DATABASE. По умолчанию принимается NOARCHIVELOG, что означает повторное использование файлов без предварительного архивирования; такой журнал может использоваться только для восстановления экземпляра и бесполезен для восстановления носителя. ARCHIVELOG означает, что перед повторным использованием файлы журнала будут архивироваться, что дает возможность их применения для восстановления как экземпляра, так и носителя.

EXCLUSIVE

Специфицирует, как будет монтироваться база после завершения создания. EXLUSIVE указывает, что к базе данных может обратиться только один экземпляр. Так как база данных всегда создается в режиме EXCLUSIVE, это ключевое слово не обязательно. Для доступа к базе данных нескольких экземпляров ее необходимо размонтировать и монтировать в режиме SHARED, используя опции DISMOUNT и MOUNT SHARED оператора ALTER DATABASE.

Оператор CREATE DATABASE будет удалять все данные из указанных файлов базы данных для подготовки их к начальному использованию. Следовательно, если этот оператор применяется к существующей базе данных, ее содержание будет утеряно.

CREATE DATABASE LINK

Назначение

Создание связи из локальной базы данных с пользователем в удаленной базе. Межбазовая связь может быть как с базой данных, так и нет.

Предпосылки

Необходимо иметь доступ к имени пользователя в удаленной базе данных. Межбазовые связи типа PUBLIC может делать только пользователь с привилегией DBA. И на локальной, и на удаленной базе данных должно быть инсталлировано программное обеспечение SQLNet.

Синтаксис

CREATE [PUBLIC] DATABASE LINK link

[CONNECT TO user IDENTIFIED BY password]

USING 'connect_string'

Ключевые слова и параметры

PUBLIC

Если опция PUBLIC опущена, создается личная связь (доступная только пользователю, ее создавшего). Если же эта опция специфицирована, связь становится доступной всем пользователям за исключением тех, кто создал личную связь с тем же именем.

link

Имя, которое дается создаваемой связи.

user,password

Действительное имя пользователя и пароль в специфицированной удаленной базе данных.

connect_string

Спецификация удаленной базы данных, доступной через SQLNet.

Создав межбазовую связь, вы можете обращаться к таблицам удаленной базы в запросах и подзапросах, ссылаясь на нее во фразе FROM с тем же эффектом, что и при прямом подключении к определенному пользователю в удаленной базе. Если фраза, содержащая имя пользователя и пароль, отсутствует, будет использоваться имя и пароль текущего пользователя. Например, если SCOTT выдаст команду создания связи и не задаст пользователя и пароль, система будет использовать для доступа к удаленной базе данных имя пользователя SCOTT и его пароль. Удаленные таблицы специфицируются добавлением к таблице имени связи (@linkname) во фразе FROM оператора SELECT.

Запросы и подзапросы, выполняемые через межбазовую связь, подчиняются следующим ограничениям:

u Количество межбазовых связей, которые можно использовать в одном запросе, в большинстве систем ограничиваются четырьмя. Этот предел можно расширить, увеличив значение параметра OPEN_LINKS файла INIT.ORA.

u Через связь не могут быть выбраны столбцы типа LONG.

u В древовидных структурах (используя фразу CONNECT BY).

u оператор PRIOR не может применяться во фразах, отличных от CONNECT BY

u фраза START WITH не может содержать подзапросов

u ни фраза CONNECT BY, ни START WITH не может содержать функции USERENV('ENTRYID'), ROWNUM и определенные пользователем функции.

CREATE INDEX

Назначение

Создание индекса таблицы или индекса кластера.

Индекс обеспечивает прямой доступ к строкам таблицы, снижая тем самым время доступа. Индекс содержит элементы для каждого значения, существующего в индексируемых столбцах. В настоящее время индекс используется также для обеспечения уникальности.

Предпосылки

Необходимо быть владельцем таблицы или иметь к ней привилегию INDEX или иметь привилегию DBA.

Синтаксис

CREATE [UNIQUE] INDEX index ON

{table (column [ASC|DESC][,column [ASC|DESC]]...]

| CLUSTER cluster}

[INITRANS n] [MAXTRANS n]

[TABLESPACE tablespace]

[STORAGE storage]

[PCTFREE n]

[NOSORT]

Ключевые слова и параметры

UNIQUE

Гарантирует, что в таблице не будет двух строк с одинаковыми значениями в индексированных столбцах. Если уникальный индекс не создан, таблица может содержать дублирующиеся строки.

index

Имя создаваемого индекса. Как и для других объектов базы данных, имя индекса должно быть уникальным среди объектов данного пользователя.

table

Имя существующей таблицы, для которой создается индекс.

column

Имя столбца таблицы.

ASC DESC

Допустим для совместимости, хотя в любом случае индексы создаются в порядке возрастания.

CLUSTER cluster

Имя кластера, для которого будет создаваться индекс кластера.

INITRANS

(Смотрите описание MAXTRANS параметров INITRANS и MAXTRANS в операторе CREATE TABLE).

TABLESPACE tablespace

Имя табличного пространства, в котором будет содержаться индекс.

STORAGE storage

(Смотрите описание фразы STORAGE).

PCTFREE

Специфицирует процент памяти, остающейся свободной в каждом блоке индекса для последующих изменений и вставок.

NOSORT

Указывает системе, что данные в таблице расположены в возрастающем порядке и, следовательно, при сортировке индекса строки сортировать не надо. Система может воспользоваться индексами с целью увеличения производительности в следующих случаях:

u при доступе к таблицам по индексным столбцам

u при поиске строк со значениями, заданными для индексированных столбцов.

Однако, индекс замедляет операции по добавлению, удалению и изменению строк, так как данные индекса также нуждаются в сопровождении.

Индексируемые столбцы

В один индекс может быть включено максимально 16 столбцов. Элемент индекса в этом случае представляет собой конкатенацию данных изо всех столбцов.

Столбцы могут указываться в любом порядке и этот порядок важен лишь при использовании индекса.

При необходимости система может использовать весь индекс или его начальную часть. Допустим, индекс IDX1 создан на столбцах A,B,C таблицы TAB1 (в порядке A, B, C). Система может использовать этот индекс для ссылки на столбцы A,B,C (полный индекс), на столбцы A,B и на столбец C. Ссылка на столбцы B,C не может использовать индекс IDX1. Но вы конечно же можете создать индекс только для столбцов B и C.

Несколько индексов в таблице

Для одной таблицы можно создать индексы для различных столбцов (или различных комбинаций в таблице столбцов). Имейте в виду, что каждый индекс снижает производительность модификации данных. На количество индексов, которые можно создать для одной таблицы, ограничений не накладывается.

Внутренний формат индекса

Элемент индекса для заданного столбца состоит из:

u ROWID строк (6 байтов для уникального или 7 байтов — для неуникального индекса)

u внутренней памяти для индексируемых столбцов (длина в байтах каждого столбца плюс один байт дополнительно)

Например, неуникальный индекс для столбца ENAME CHAR(10) требует 18 байтов:

u 7 байтов на ROWID

u 10 байтов — длина столбца

u 1 дополнительный байт

Максимально допустимый размер элемента индекса зависит от вашей операционной системы.

Уникальные индексы

Создавая уникальный индекс для одного или группы столбцов, вы гарантируете, что в таблице не будет двух одинаковых по этим столбцам строк. Для таблицы можно создать любое число уникальных индексов; большинство таблиц имеют как минимум один уникальный индекс: индекс на основном ключе таблицы.

Соглашения по именованию

Каждый индекс должен следовать стандартам именования системы и отличаться от других имен объектов данного пользователя. Ниже приводятся дополнительные рекомендации по именованию индексов:

u включайте в имена индексов имена таблиц и столбцов, на которых он создается.

u предваряйте имя индекса буквой «I».

u разделяйте пунктуацией имена таблиц и столбцов.

Например, индекс, созданный на столбцах SAL и COMM таблицы EMP можно назвать: I_EMP$SAL_COMM.

Производительность

В момент создания индекса система будет оставлять незаполненным PCTFREE от блока индекса. Например, если PCTFREE равен 20, каждый блок индекса будет заполнен на 80% своей емкости. Большее значение PCTFREE позволяет выполнить большее количество вставок перед тем, как индексные блоки будут разделяться (что требует дополнительных временных затрат). Небольшое значение PCTFREE подходит для статичных таблиц, в которых маловероятно разделение индексных блоков. Маленький индекс влечет за собой ускорение его просмотра.

При начальном заполнении таблицы в общем случае будет быстрее, если вы создадите таблицу, заполните ее, а затем создадите индекс. Если же вы создали индекс до заполнения таблицы, он будет модифицироваться при вставке каждой строки.

NOSORT

Опция NOSORT может существенно снизить время, необходимое для создания индекса. При нормальном создании индекса сперва таблица сортируется по индексируемым столбцам, а уже затем строится индекс. На сортировку обычно тратится значительная часть времени построения индекса. Если же строки таблицы уже отсортированы в порядке возрастания по индексируемым столбцам, опция NOSORT позволяет системе пропустить фазу сортировки.

При создании индекса кластера опцию NOSORT использовать нельзя.

Опция NOSORT будет также снижать и общее количество памяти, необходимой для построения индекса. Для сортировки система использует временные сегменты и если сортировка выполняться не будет, индекс создастся с гораздо меньшей временной памятью.

Для того, чтобы воспользоваться опцией NOSORT, вы должны гарантировать, что строки таблицы расположены в возрастающем порядке. Так как физическая независимость данных присуща всем системам управления реляционными базами данных (особенно — системе ORACLE), не существует способа изменения внутреннего порядка расположения строк в таблице. Поэтому создание индекса с опцией NOSORT должно выполняться сразу же после загрузки в таблицу отсортированных данных.

Запуская построение индекса с опцией NOSORT, вы ничем не рискуете, так как если порядок строк в таблице окажется нарушенным, система выдаст ошибку и вы сможете повторить создание индекса уже без опции NOSORT.

Nulls

Значения NULL не индексируются, следовательно запрос:

SELECT ENAME FROM EMP WHERE COMM IS NULL

не будет использовать индекс, созданный на столбце COMM.

Уникальный индекс также не рассматривает значения NULL как дублирующиеся и добавление нескольких таких строк не повлечет за собой ошибки.

Создание индексов кластера

При начальном создании кластера система автоматически не создает для него индекса, однако пока он не создан, над кластером нельзя выполнять операции DML. Заметьте, что одним оператором CREATE INDEX вы можете создать либо индекс кластере, либо индекс таблицы, но не оба вместе. Следовательно, можно использовать фразу CLASTER только если вы не указали в том же операторе CREATE INDEX имени таблицы.

Для создания индекса в кластере EMPLOYEE введите:

CREATE INDEX IC_EMP ON CLUSTER EMPLOYEE

Заметьте, что при этом не указываются столбцы, на которых создается индекс, так как для кластера в качестве них берутся столбцы ключа кластера.

CREATE ROLLBACK SEGMENT

Назначение

Создать сегмент отката, указав по желанию:

u сможет ли создаваемый сегмент распределяться по запросу любого экземпляра (PUBLIC), либо только для экземпляров, поименовавших его в своем файле INIT.ORA (private).

u табличное пространство, содержащее сегмент

u параметры памяти

Предпосылки

Требуется привилегия DBA.

Синтаксис

CREATE [PUBLIC] ROLLBACK SEGMENT rollback_segment

[ TABLESPACE tablespace ]

[ STORAGE storage ]

Ключевые слова и параметры

PUBLIC

Если указан, говорит о том, что данный сегмент отката будет общим, то есть, доступным для использования любым экземпляром.Если опущен, сегмент будет личным.

rollback_segment

Правильный идентификатор длиной до 30 символов.

TABLESPASE tablespace

Именует табличное пространство, в котором будет создаваться сегмент. По умолчанию принимается SYSTEM.

STORAGE storage

Специфицирует параметры распределения памяти для сегмента отката. Правила задания смотрите в описании фразы STORAGE. При добавлении сегмента к табличному пространству последнее должно быть в состоянии online. В табличном пространстве может быть несколько сегментов отката. В общем случае, несколько сегментов отката увеличивают производительность.

CREATE SEQUENCE

Назначение

Создание объекта базы данных, с помощью которого многие пользователи могут генерировать уникальные целые числа. Последовательные номера можно использовать для автоматической генерации основных ключей.

Предпосылки

Требуется привилегия RESOURCE как минимум — в одном табличном пространстве. При создании последовательности, которой будет владеть другой пользователь, необходима привилегия DBA.

Синтаксис

CREATE SEQUENCE [user.]sequence

[INCREMENT BY n]

[START WITH n]

[MAXVALUE n | NOMAXVALUE]

[MINVALUE n | NOMINVALUE]

[CYCLE | NOCYCLE]

-------

[CACHE n | NOCACHE]

[ORDER | NOORDER]

Ключевые слова и параметры

user

Владелец последовательности. Подразумевается пользователь, выдавший команду CREATE SEQUENCE

sequence

Имя последовательности. Должно следовать соглашениям по именованию объектов базы данных.

INCREMENT BY

Определяет интервал между последовательными номерами. Если имеет отрицательное значение, то последовательность убывающая, если положительное — последовательность возрастающая. Допустимо любое целое число не равное нулю. Умолчание 1 (возрастающая).

START WITH

Первый генерируемый последовательный номер. По умолчанию для возрастающих последовательностей равен MINVALUE, а для убывающих — MAXVALUE. Используйте эту фразу, если не хотите, чтобы последовательность начиналась с умалчиваемых значений.

MINVALUE NOMINVALUE

Минимальное значение последовательного номера, которое будет генерироваться. Эта фраза определяет нижнюю границу последовательности. Для возрастающих последовательностей равно 1.

MAXVALUE NOMAXVALUE

Максимальное значение последовательного номера, которое будет генерироваться. Эта фраза определяет верхнюю границу последовательности. Для убывающих последовательностей равно 1.

CYCLE NOCYCLE

По умолчанию предполагается NOCYCLE, что означает завершение генерирования последовательных номеров по достижении конца последовательности. Любая попытка сгенерировать номер после этого приведет к ошибке.

Если специфицировано CYCLE, последовательность после достижения MAXVALUE (для возрастающих) выдаст MINVALUE. Для убывающих последовательностей после выдачи MINVALUE выдастся MAXVALUE.

CACHE NOCACHE

CACHE заранее готовит последовательные номера, так что при запросе их можно выбрать их памяти, в результате чего генерация последовательных номеров выполняется быстрее.

По умолчанию предполагается значение CACHE 20. Значение, указываемое в CACHE не должно превышать разницы между MAXVALUE и MINVALUE.

ORDER NOORDER

ORDER специфицирует, что гарантируется генерация последовательных номеров в порядке запроса. Это бывает важно для приложений, которые используют последовательные номера в качестве меток времени. Обычно это неважно, если таблицы используют последовательности для генерирования основных ключей. Даже если параметр ORDER не указан, последовательность скорее всего будет генерироваться так, как если бы он был указан.

Вы можете использовать последовательные номера для автоматической генерации основных ключей к данным, а также координировать ключи между нескольких столбцов или таблиц. Для создания последовательности вы должны иметь привилегию RESOURSE как минимум — в одном табличном пространстве, а для доступа к чужой последовательности необходимо иметь к ней привилегию SELECT. DBA может генерировать последовательности с владельцами, отличными от создающего пользователя. Последовательности могут быть изменены и отменены. Возможна регистрация доступа к ним. Кроме того, последовательность может иметь синоним.

Действительные последовательные номера генерируются системой автоматически и, следовательно, не являются узким местом, что случается, когда последовательные номера генерируются на уровне приложения. Например, при реализации на уровне приложения каждая транзакция должна блокировать таблицу последовательных номеров, увеличивать последовательный номер и затем освобождать таблицу. При такой реализации одновременно может быть сгенерирован только один последовательный номер. Напротив, генераторы последовательностей системы допускают параллельное генерирование нескольких последовательных номеров, гарантируя при этом их уникальность.

Так как последовательные номера генерируются независимо от таблиц, один и тот же генератор последовательностей может быть использован для одной или нескольких таблиц. Допустимо, чтобы последовательные номера иногда пропускались. Это связано с тем, что транзакция, их сгенерировавшая, была откачена (roll back). Более того, отдельные пользователи могут и не знать, что из той же последовательности берут номера другие пользователи. Чтобы посмотреть, к каким последовательностям вы имеете доступ, запросите обзоры словаря данных USER_SEQUENCES и ALL_SEQUENCES. Последовательности могут экспортироваться и импортироваться наравне с таблицами. Экспорт сохраняет последовательные номера, так что после импорта получается результат, как будто экспорт или импорт не выполнялись.

Умолчания для последовательностей

Умолчания для последовательностей спроектированы таким образом, что не указав никаких фраз вы создадите возрастающую последовательность, начинающуюся с 1, возрастающую на 1 и не имеющую верхнего предела. Если вы специфицировали только INCREMENT BY -1, создастся убывающая последовательность начинающаяся с -1 и не имеющая нижнего ограничения. Фразы, описанные в подзаголовке «Ключевые слова и параметры», допускают различные вариации этих стандартных последовательностей.

Рост последовательностей

Последовательность может:

u расти безгранично

u останавливаться на заранее заданном пределе

u начинаться снова после достижения заранее заданного лимита

Чтобы создать последовательность без границ не специфицируйте MAXVALUE для возрастающих последовательностей и MINVALUE — для убывающих, а также специфицируйте NOCYCLE. Чтобы создать последовательность, останавливающуюся в на определенном номере, специфицируйте MAXVALUE для возрастающих последовательностей и MINVALUE — для убывающих, а также специфицируйте NOCYCLE. Любая попытка сгенерировать последовательный номер после достижения ограничения приведет к ошибке. Чтобы создать последовательность, начинающуюся снова после достижения предела, специфицируйте и MAXVALUE, и MINVALUE. Специфицируйте также и CYCLE.

Фраза START WITH влияет только на первый последовательный номер. При возврате на начало последовательность будет начинаться с MINVALUE в случае возрастания и MAXVALUE — в случае убывания.

Использование кэша

Число последовательных номеров, кеширующихся в памяти, определяется аргументом фразы CACHE. Кешируемые последовательности обеспечивают более быструю генерацию последовательны номеров. Кеш для каждой данной последовательности заполняется после первого запроса номера из этой последовательности. В случае краха системы все кешируемые последовательные номера, не использованные в записанных транзакциях (commited), теряются. Потенциальное число потерянных номеров равно числу, указанному во фразе CACHE.

Параметры, влияющие на поведение всех последовательностей, задаются в файле INIT.ORA. По умолчанию предполагается, что в памяти будут кешироваться 20 последовательных номеров, которые генерируются системой заранее для ускорения доступа.

NEXTVAL

Псевдостолбец NEXTVAL используется для генерирования очередного номера из указанной последовательности. Ссылка на NEXTVAL приводит к генерированию очередного номера. Обращение имеет следующий синтаксис:

sequence.NEXTVAL

где sequence — это имя последовательности.

CURRVAL

Псевдостолбец CURRVAL — это текущее значение последовательного номера. Ссылка на NEXTVAL приводит к тому, что текущее значение номера помещается в CURRVAL. В текущем сеансе NEXTVAL должен быть использован хотя бы один раз до ссылки на CURRVAL.

Обращение к CURRVAL имеет следующий синтаксис:

sequense.CURRVAL

где sequence — имя последовательности, на которую ссылался NEXTVAL.

Последовательности в операторах UPDATE

Допустим, ваше предприятие производит телевизоры. Вы используете генератор последовательностей для создания серийных номеров выпускаемых телевизоров. Случайно выбранные телевизоры тестируются и по этому факту модифицируется запись о тестировании, при этом используется серийный номер аппарата. Если вы используете последовательность TV_SERIAL, вставка строк в таблицу TV и модификация таблицы TEST_RESULT будет выглядеть следующим образом:

INSERT INTO TV VALUES (TV_SERIAL.NEXTVAL, SYSDATE,

'19 INCH')

UPDATE TEST_RESULT

SET TV_NUMBER=TV_SERIAL.CURRVAL

WHERE TEST_NUMBER = 1015

CREATE SYNONYM

Назначение

Создание синонимов для таблиц, обзоров, последовательностей с возможностями:

u создание общего синонима (PUBLIC)

u создание синонима, ссылающегося на удаленную базу данных

Предпосылки

Вы должны быть владельцем или иметь привилегию SELECT таблицы или обзора, для которых создаете синоним. Чтобы создать синоним типа PUBLIC или синоним для объекта, владельцем которого является другой пользователь, необходимо иметь привилегию DBA.

Синтаксис

CREATE [PUBLIC] SYNONYM [user.]synonym

FOR [user.]table [@databaselink]

Ключевые слова и параметры

PUBLIC

Специфицирует, что синоним будет доступен всем пользователям. По умолчанию синоним доступен только его создателю.

synonym

Имя синонима, следующее соглашениям по именованию объектов.

user

Существующее в базе данных имя пользователя. Если оно опущено, оператор CREATE SYNONYM предполагает, что вы владелец таблицы или обзора.

table

Имя существующей таблицы, обзора или последовательности.

databaselink

Существующая связь к удаленной базе данных. Если user опущен, синоним ссылается к таблице, принадлежащей пользователю, специфицированному в межбазовой связи. Использование синонимов продиктовано соображениями безопасности и удобства, включая следующие:

u для обращения к таблице или обзору без указания их владельца

u для обращения к таблице или обзору без указания базы данных, которой они принадлежат

u для назначения другого имени таблице

Вышеуказанные свойства обеспечивают как независимость данных, так и прозрачность; синонимы позволяют функционировать приложениям не обращая внимание на то, какому пользователю принадлежит таблица или обзор, а также — в какой базе данных они хранятся.

Контекст

Личный синоним должен иметь имя, отличное от остальных объектов данного пользователя. Система пытается разрешить ссылки к объектам на уровне пользователя перед объектами на уровне PUBLIC-синонимов. Например, если пользователь SCOTT владеет таблицей с именем DEPT, а DBA создал PUBLIC-синоним с именем DEPT для объекта BLAKE.DEPT, то если SCOTT выполнит следующий оператор:

SELECT * FROM DEPT

то выберутся строки из SCOTT.DEPT. Для выборки строк из одноименной таблицы другого пользователя SCOTT должен выполнить следующий оператор:

SELECT * FROM BLAKE.DEPT

Система будет пытаться разрешить ссылки к объектам через синонимы, если верны следующие предположения:

u таблице или обзору не предшествует имя пользователя

u за именем таблицы или обзора не следует имя межбазовой связи

CREATE TABLE

Назначение

Создание базовой структуры для хранения данных в базе — таблицы со следующими возможностями:

u определить характеристики распределения памяти

u специфицировать табличное пространство для таблицы

u кластеризовать таблицу

u загрузить таблицу результатами произвольного запроса

Предпосылки

Требуется привилегия RESOURCE. Для создания таблицы, владельцем которой вы не являетесь, необходима привилегия DBA.

Синтаксис

CREATE TABLE [user.]table

( {column_element | table_constraint}

[, {column_element | table_constraint} ]...)

[PCTFREE n] [PCTUSED n]

[INITRANS n] [MAXTRANS n]

[TABLESPACE tablespace]

[STORAGE storage]

[CLUSTER cluster (column [,column]...)]

[AS query]

Ключевые слова и параметры

user

Владелец таблицы. Если не указан, то пользователь, выдавший команду CREATE TABLE, становится владельцем таблицы.

table

Имя таблицы, следующее стандартным соглашениям по именованию объектов базы (включая правило, что таблицы, обзоры, кластеры, индексы, межбазовые связи, синонимы и последовательности должны иметь у данного пользователя различные имена).

column_element

Определяет столбцы и необязательные ограничения для столбцов таблицы. Как минимум, таблица должна содержать один столбец.

table_constraint

Определяет ограничения для таблицы.

tablespace

Определяет табличное пространство, к которому будет принадлежать таблица.

storage

Определяет будущее распределение памяти для таблицы.

cluster

Именует существующий кластер, владельцем которого является пользователь.

query

Правильный запрос (как определяется в разделе SELECT). Если вы специфицируете query, то можете в операторе CREATE TABLE определять только имена столбцов. Тип данных и размер копируются из соответствующих столбцов, специфицированных в запросе. Если все столбцы в запросе имеют правильно определенные и уникальные имена, (column...) может быть опущено. В этом случае будут использоваться имена столбцов из запроса. Если же (column...) специфицировано, количество столбцов должно совпадать с количеством столбцов в запросе. После того, как таблица будет создана, результаты запроса будут занесены в эту таблицу. Запрос не может содержать фраз ORDER BY и UPDATE. Если используется запрос, фраза column_element может содержать только имена столбцов. Тип данных и размерность каждого будут производными от результатов запроса. Ограничения для таблиц и столбцов могут быть добавлены с помощью фразы ADD оператора ALTER TABLE. Для модификации ограничений для столбцов и таблицы необходимо их отменить и добавить заново (DROP и ADD).

Следующие ниже фразы могут быть указаны в любом порядке, но запрос должен быть последним.

PCTFREE

Резервирует часть общей памяти в каждом блоке таблицы под последующие модификации данных. PCTFREE специфицируется как положительное число от 0 до 100 и указывает, какой процент памяти блока будет резервироваться под модификации. PCTFREE равное 0 говорит о том, что блоки будут заполняться до конца. Значение 0 бесполезно, так как в этом случае в таблицу невозможно будет заносить данные. По умолчанию предполагается 10 процентов. Если берется умалчиваемое значение, вставляемые строки будут заполнять максимально 90% каждого блока.

PCTFREE имеет такой же смысл в операторах CREATE CLUSTER и CREATE INDEX. Комбинация PCTFREE и PCTUSED определяет, будет ли вставляемая строка помещаться в существующий блок или — в новый.

Свободное пространство, зарезервированное с помощью PCTFREE, разрешено использовать для расширения строк внутри блока. Вместо того, чтобы расширяться на дополнительные блоки, измененные строки расширяются в пределах свободной памяти блока. Когда же этой свободной памяти становится недостаточно, строки расширяются на другие блоки, образуя сцепленные строки. Сцепленные строки снижают производительность, так как для доступа к той же информации необходимо выполнить большую работу. PCTFREE следует выбирать после тщательной оценки, насколько сильно строки будут расширяться в результате модификации данных.

Слишком маленькое значение приведет к сцепленным строкам, слишком большое — к неэффективному использованию памяти.

PCTUSED

Специфицирует минимальный уровень использования памяти, который система будет поддерживать в каждом блоке таблицы. Как только процент используемой памяти станет меньше PCTUSED, блок становится кандидатом на вставку строк. PCTUSED специфицируется как положительное число от 0 до 100. По умолчанию предполагается 40.

Новые строки будут вставляться в блоки, в которых используемая память меньше, чем значение PCTUSED. Блок становится кандидатом на вставку строк и заносится в список свободных блоков, когда процент использования памяти в нем становится меньше указанного в PCTUSED. Блоки удаляются из списка свободных, если в результате вставки строки процент использования памяти становится выше PCTUSED. Таблицы, в которых видится мало удалений, могу работать с PCTUSED 40 и ниже. Большее значение PCTUSED приводит к более эффективному использованию памяти при потерях от снижения производительности из за необходимости обслуживания списка свободных блоков. В операторе CREATE CLUSTER фраза PCTUSE имеет такое же значение.

MAXTRANS

Специфицируется максимальное число транзакций, которые могут параллельно менять данные в блоке. Каждая транзакция, изменяющая данные, требует элемента транзакции (но не для SELECT). Память, необходимая элементу транзакции, зависит от операционной системы и обычно равна 23 байтам, распределяющимся в свободной памяти блока. MAXTRANS может меняться от 1 до 255 и по умолчанию предполагается 255. В этом случае при нормальной величине свободной памяти вы скорее превысите ее, ежели исчерпаете MAXTRANS. Для кластера MAXTRANS играет ту же роль, что и для таблицы.

INITRANS

Определяется начальное количество элементов транзакций, распределяемых внутри каждого блока. Для каждого элемента транзакций система резервирует 23 байта из свободной памяти блока (но это зависит от операционной системы) в момент, когда блок впервые используется. INITRANS может меняться в диапазоне от 1 до 255 и по умолчанию предполагается 1.

Когда число транзакций, обращающихся к этом блоку превысит INITRANS, система будет распределять дополнительные элементы транзакций пока не исчерпается память в блоке или не превысится значение MAXTRANS. INITRANS гарантирует, таким образом, что при начальном использовании именно такое количество транзакций могут модифицировать данные в блоке. Корме того, этот параметр предотвращает накладные расходы, связанные с динамическим распределением элементов транзакций.

INITRANS играет ту же роль для кластеров и индексов, за исключением того, что там умалчиваемое значение для INITRANS=2.

TABLESPACE

Задает имя табличного пространства, в котором будет создаваться таблица. Если оно не указано, то распределение будет выполняться в умалчиваемом табличном пространстве, задаваемом оператором ALTER USER.

STORAGE

Управляет распределением памяти для таблицы. Память должна распределяться так, чтобы минимизировать частоту распределения новых участков.

CLUSTER

Специфицирует, что таблица должна быть включена в кластер. В этом случае columns — столбцы таблицы, соответствующие кластеризуемым столбцам кластера. В общем случае это столбцы, составляющие основной ключ или являющиеся его частью. Специфицируйте по одному столбцу таблицы для каждого столбца ключа кластера. Столбцы должны соответствовать по позиции, а не по имени. Данная таблица будет пользоваться распределением памяти кластера и, следовательно, в сочетании с опцией cluster параметры PCTUSED, PCTFREE, TABLESPACE и STORAGE будут давать ошибку.

Таблица может содержать до 254 столбцов. Она должна соответствовать стандартным соглашениям по именованию объектов базы данных.

За исключением параметра query, таблицы создаются без данных. Обычно строки добавляются с помощью оператора INSERT.

Взаимное соответствие PCTUSED и PCTFREE

При использовании вместе PCTUSED и PCTFREE помогут вам использовать память в таблице более эффективно. Сумма этих величин должна быть меньше 100. При вставке строк система в первую очередь вставляет их в блоки, содержащиеся в списке свободных блоков. К этим блокам относятся и те, которые еще не содержат в себе строк. Система будет продолжать работать с блоками из списка свободных пока у всех их не превысится PCTFREE. Когда все блоки из списка свободных будут исчерпаны, для хранения строк будут распределяться новые.

Словарь данных

Оператор CREATE TABLE заставляет систему записывать информацию о таблицах, столбцах и ограничениях в Словарь Данных. Любая операция по изменению структуры таблицы автоматически приводит к отображению изменения в словаре данных. Следующие обзоры словаря данных содержат информацию, относящуюся к таблицам:

u USER_TABLES

u USER_OBJECTS

u USER_TAB_COLUMNS

u USER_INDEXES

u ALL_TABLES

Ограничения

СУБД поддерживает синтаксис ограничений для таблиц и столбцов.

CREATE TABLESPACE

Назначение

Создать табличное пространство, назначив:

u имена файлов, составляющих это пространство

u умалчиваемые параметры памяти

u будет ли табличное пространство после создания в online или offline.

Предпосылки

Требуется привилегия DBA

Синтаксис

CREATE TABLESPACE tablespace

DATAFILE {filespec [,filespec] ...}

[DEFAULT STORAGE storage]

[ONLINE | OFFLINE]

Ключевые слова и параметры

tablespace

Идентификатор БД длиной до 30 символов используется для задания имени табличному пространству.

filespec

Спецификация файла базы данных в форме: filename [SIZE integer [K|M][REUSE]

SIZE integer

Указывает количество байтов. Отсутствие этого параметра предполагает по умолчанию использование файлов базы размером 10 мегабайтов и журнала регистрации — по 500 килобайтов. Если указано «К», integer умножается на 1024, «М» — на 1048576.

REUSE

Указывает, что будут повторно использоваться существующие файлы, информация в них полностью разрушается.

DATAFILE

Определяет файл или файлы, составляющие табличное пространство. Точная спецификация файлов зависит от операционной системы.

DEFAULT

Умалчиваемые параметры памяти для STORAGE всех объектов, создаваемых в этом пространстве. Умалчиваемые значения памяти зависят от операционной системы.

ONLINE OFFLINE

Делает табличное пространство соответственно доступным или недоступным. Умолчание — ONLINE — означает, что табличное пространство становится доступным пользователям, имеющим право это делать, немедленно. Обзор словаря данных DBA_ TABLESPACES показывает статус каждого табличного пространства. Табличное пространство является единицей копирования/восстановления базы данных. Оно содержит сегменты для таблиц, индексов, сегменты отката и временные. Все базы данных имеют по крайней мере одно табличное пространство SYSTEM, которое создается в момент создания базы данных. Независимо от того, в каком состоянии находится табличное пространство, к нему можно добавлять файлы операционной системы. Аналогично, оно может быть удалено независимо от состояния (online или offline), но все же рекомендуется перед отменой выводить табличное пространство в offline.

Этим вы гарантируете, что не существует незавершенных транзакций в данном табличном пространстве. Создав табличное пространство, вы должны создать для него по крайней мере — один сегмент отката.

SIZE и REUSE

Если вы специфицировали и ключевое слово SIZE и REUSE, система будет использовать указанный файл базы данных, если он существует и имеет указанный размер. В противном случае система создаст файл указанного размера. Если вы специфицировали SIZE и опустили ключевое слово REUSE, файл базы данных существовать не должен. В этом случае система создаст файл указанного размера. Если опущен параметр SIZE, файл базы данных уже должен существовать.

CREATE VIEW

Назначение

Определение обзора (логической таблицы, базирующейся на одной или нескольких таблицах).

Предпосылки

Надо быть владельцем или иметь привилегию SELECT для таблиц(ы) базы данных. Чтобы создать обзор, владельцем которого вы не являетесь, необходимо иметь привилегию DBA.

Синтаксис

CREATE VIEW [user.]view [(alias [,alias]...)]

AS query

[WITH CHECK OPTION [CONSTRAINT constraint]]

Ключевые слова и параметры

user

Владелец создаваемого обзора.

view

Имя обзора.

query

Определяет столбцы и строки таблиц(ы), на которых будет базироваться обзор. В качестве query может выступать любой правильный оператор SELECT, не содержащий фраз ORDER BY и FOR UPDATE.

WITH CHECK OPTION

Специфицирует, что вставки и модификации, выполняемые через этот обзор, запрещены, если данные строки невозможно выбрать через этот обзор. WITH CHECK OPTION можно использовать и в обзоре, базирующемся на другом обзоре.

CONSTRAINT

Имя, назначаемое ограничению «WITH CHECK OPTION». Если это имя не указано, оно назначается системой в форме: SYS_Cn, где n — целое, делающее имя ограничения уникальным в пределах системы.

Обзор — это логическое «окно» в одну или более таблиц. Обзоры обладают следующими свойствами:

u Обзор не хранит данных, вместо этого он показывает данные из других таблиц (которые, в свою очередь, могут быть обзорами).

u Обзор можно использовать в SQL-операторах везде, где можно использовать таблицы (т.е. — в операторах INSERT, UPDATE, DELETE, SELECT) с несколькими ограничениями. Вы можете только выбирать (select) данные из обзоров, чьи запросы содержат:

u объединения

u фразы GROUP BY, CONNECT BY или START WITH

u фразу DISTINCT, псевдостолбцы (например, ROWNUM) или выражение в списке столбцов

Вы можете модифицировать обзоры, содержащие псевдостолбцы или выражения, если оператор UPDATE не ссылается к псевдостолбцу или выражению из обзора.

Обзоры можно использовать для:

u обеспечения дополнительного уровня защиты таблиц, ограничив доступ к заранее определенным строкам и/или столбцам базовой таблицы.

u скрывания действительной сложности данных. Например, обзор может использоваться для некоторых действий с таблицей, хотя на самом деле для формирования результата используются несколько таблиц.

u иного представления данных. Например, обзоры дают способ переименования столбцов без реального влияния на определение реальной таблицы.

u обеспечивают уровень относительной целостности.

Правила именования обзоров

Имена столбцов в обзорах должны согласовываться со следующими правилами:

u Имена столбцов должны следовать стандартным соглашениям для объектов базы данных

u Имена столбцов иметь алиасы, если они являются производными от выражений в запросе

u Имена столбцов должны быть различными (таблицы, составляющие обзор могут иметь одинаковые имена столбцов).

u Если алиасы не используются, имена столбцов обзора должны быть производными из столбцов запроса. Алиасы столбцов могут также использоваться в списке select запроса.

u Когда используются алиасы, их количество должно совпадать с числом выбираемых в запросе столбцов. Например, если запрос выбирает 4 столбца, алиасов должно быть также 4. Первому столбцу запроса будет назначен первый алиас, второму столбцу — второй алиас и так далее.

Когда вы обращаетесь к обзору, СУБД проделывает следующие шаги:

1. Выбирает определение обзора как указывалось в операторе CREATE VIEW и запоминает его в словаре данных.

2. Проверяет ваши привилегии доступа.

3. Использует запрос из обзора для выборки данных из таблиц(ы) базы данных. Таблица базы — любая таблица, специфицированная в запросе обзора. Имейте в виду, что если обзор определяется в виде SELECT * FROM table, а вы поменяли определение таблицы, обзор работать перестанет. Эту проблему можно решить удалением и повторным созданием обзора.

Регистрация доступа к обзорам

Умалчиваемыми опциями регистрации для обзора является объединение опций регистрации всех таблиц, составляющих обзор.

DECLARE CURSOR (Встроенный SQL)

Назначение

Определение курсора путем задания ему имени и связывания с ним определенного SQL-оператора.

Курсор не распределяется, пока не будет открыт (OPEN).

Предпосылки

Нет.

Синтаксис

EXEC SQL [AT dbname] DECLARE cursor_name CURSOR

FOR {SELECT subststment | ststment}

Ключевые слова и параметры

AT dbname

Указывает необязательную фразу слова и AT, которая использует имя базы параметры данных, ранее определенное оператором DECLARE DATABASE и используемое во встроенном операторе CONNECT.

cursor_name

Имя, назначаемое курсору.

substatment

Оператор SELECT без фразы INTO.

statement

Имя оператора, заранее определенного встроенным SQL-оператором DECLARE STATEMENT. Оператор DECLARE CURSOR относится к декларативным SQL-операторам и, следовательно, должен появляться в исходном тексте прекомпилятора до операторов, ссылающихся к данному курсору. Система сможет интерпретировать курсор, если он был ранее определен декларативным оператором. Контекстом (областью видимости) оператора определения курсора является весь исходный файл текущего модуля; это отличается от правил видимости системы или host-языка. Например, курсор не может быть декларирован (определен) в процедуре, компилирующейся отдельно от тела.

Каждый оператор DECLARE CURSOR должен быть уникальным; Вы не можете определить два курсора с одинаковым именем в одной компилируемой единице. Если в операторе SELECT специфицирована фраза FOR UPDATE OF, на данный курсор можно ссылаться в UPDATE, используя WHERE CURRENT OF CURSOR. Определенный курсор может быть использован в последующем WHERE CURRENT OF CURSOR, если курсор открыт и позиционирован на строку с помощью FETCH.

DECLARE DATABASE (Встроенный SQL)

Назначение

Определение имени удаленной базы данных, на которую ссылаются с помощью фразы AT последующие SQL-операторы.

Предпосылки

Необходимо иметь доступ к существующему в удаленной базе данных пользователю.

Синтаксис

EXEC SQL DECLARE dbname DATABASE

Ключевые слова и параметры

dbname

Идентификатор, назначаемый удаленной базе данных.

Когда база данных определена, к ней можно обращаться во встроенных SQL-операторах с помощью фразы AT. К таким операторам относятся следующие:

u COMMIT

u DECLARE CURSOR

u DELETE

u INSERT

u ROLLBACK

u SELECT

u UPDATE

Пример

user :string(1..10);

pass :string(1..10);

my_database :string(1..10);

user :="scott";

pass :="tiger";

my_database :="D:CHICAGO";

EXEC SQL DECLARE oracle1 DATABASE;

EXEXC SQL CONNECT :user

IDENTIFIED BY :pass

AT oracle1

USING :my_database;

EXEC SQL AT oracle1 SELET ...;

DECLARE STATEMENT (Встроенный SQL)

Назначение

Назначить SQL-оператору имя SQL-переменной.

Предпосылки

Нет.

Синтаксис

EXEC SQL DECLARE ststement_name STATEMENT

Ключевые слова и параметры

ststement_name

Идентификатор оператора.

Выполнять оператор DECLARE STATEMENT необходимо только в случае, когда DECLARE CURSOR, ссылающийся на определенный оператор, выполняется до оператора PRERARE. Если оператор PREPARE предшествует оператору OPEN cursor, DECLARE STATEMENT не требуется. Если DECLARE STATEMENT используется, он должен предшествовать операторам DECLARE CURSOR, OPEN cursor, DESCRIBE или PREPARE, выполняющим этот оператор. Контекст определения оператора — вся единица компиляции текущего модуля (идентично декларации курсора), что отличается от правил видимости системы и host-языка. Например, оператор не может быть декларирован (определен) в процедуре, компилирующейся отдельно от тела.

DELETE

Назначение

Удаление строк из таблицы.

Предпосылки

Вы должны быть владельцем таблицы, иметь привилегию DBA или иметь для данной таблицы привилегию DELETE.

Синтаксис

DELETE [FROM] [user.]table [alias] [WHERE condition]

Ключевые слова и параметры

user

Владелец таблицы.

table

Имя таблицы или обзора, из которых удаляются строки.

alias

Алиас, назначенный таблице, который используется в общем случае — при коррелированных запросах внутри оператора DELETE.

condition

Условие, следующее соглашениям по написанию условий. Condition должно приводить к результату TRUE или FALSE. Если отсутствует фраза WHERE, из таблицы удаляются все строки. Если же эта фраза используется, удаляются строки, для которых условие condition дает TRUE. Условие может ссылаться на адресуемую таблицу или включать в себя коррелированный запрос. Все удаляемые строки и соответствующие индексы освобождают занимаемую ими память.

DELETE (Встроенный SQL)

Назначение

Удаление строк из таблицы или обзора. Синтаксис встроенного DELETE отличается от синтаксиса стандартного SQL-оператора DELETE во фразах AT, FOR, CURRENT OF и host-переменными.

Предпосылки

Вы должны быть владельцем таблицы или иметь к ней привилегию DELETE, или иметь привилегию DBA.

Синтаксис

EXEC SQL [AT dbname] [FOR :host_integer]

DELETE [FROM] table [alias]

[WHERE {condition | CURRENT OF cursor}]

Ключевые слова и параметры

AT dbname

Необязательная фраза, вводящая имя неумалчиваемой межбазовой связи, ранее назначенной с помощью оператора DECLARE DATABASE.

FOR :host_integer

Специфицирует минимальное количество итераций оператора DELETE, когда во фразе WHERE используются массивы.

FROM table

Указывает имя таблицы или обзора, из которых вы намереваетесь удалять строки.

alias

Алиас, назначенный таблице, который используется в общем случае — при коррелированных запросах внутри оператора DELETE.

WHERE

Условие поиска строк, подлежащих удалению. Если отсутствует, удаляться будут все строки таблицы или обзора.

condition

Логическое выражение, которое может содержать ссылки на host-переменные или host-массивы.

cursor

Используется для именования курсора, определенного в операторе DECLARE CURSOR. До выполнения оператора DELETE с фразой CURRENT OF курсор должен быть открыт и позиционирован на строку.

Если в качестве host-переменных в операторе WHERE указаны массивы, оператор DELETE выполняется лишь однажды для всего набора компонентов массива. Каждое выполнение оператора DELETE может удалить ноль, одну или более строк. Если какая-либо host-переменная, используемая во фразе WHERE, является массивом, значит все host-переменные должны быть массивами. Массивы могут быть различного размера, но число обрабатываемых компонентов определяется размером минимального. Если ни одна строка не удовлетворяет условию, ни одной строки удалено не будет и SQLCODE вернет условие NOT_FOUND. Общее количество удаленных строк возвращается в SQLROWS. Когда во фразе WHERE используются массивы, этот счетчик отражает общее количество удалений для всех компонентов массива, обработанного в операторе DELETE. Если фраза WHERE опущена, это отмечается выставлением флажка в 5-й компоненте SQLWARN (SQLCA).

DESCRIBE (Динамический встроенный SQL: Метод 4)

Назначение

Распределение и инициализация дескриптора для хранения описаний host-переменной из SQL-оператора, предварительно подготовленного оператором PREPARE.

Предпосылки

Необходимо с помощью оператора PREPARE подготовить SQL-оператор, содержащий host-переменные, которые надо описать (DESCRIBE).

Синтаксис Для связанных переменных:

EXEC SQL DESCRIBE BIND VARIABLES FOR statement_name

INTO descriptor_name

Для элементов списка SELECT:

EXEC SQL SESCRIBE [SELECT LIST FOR] statement_name

INTO descriptor_name

Ключевые слова и параметры

BIND

Требуется для специфицирования VARIABLES того, что этот оператор будет описывать входные переменные.

SELECT LIST

Специфицирует, что будет описываться список выходных переменных. Явное использование этой фразы необязательно.

statement_name

Указывает на ранее определенный SQL-оператор. Может содержать правильные ссылки на массивы host-переменных.

descriptor_name

Указывает имя, по которому ссылаются на дескриптор. Дескриптор должен быть предварительно определен в программе. Входные и выходные переменные не должны включаться в один дескриптор. Вообще говоря, DESCRIBE выполняется после оператора PREPARE.

DROP CLUSTER

Назначение

Удаление из базы данных специфицированного кластера.

Предпосылки

Вы должны быть либо владельцем этого кластера, либо иметь привилегию DBA.

Синтаксис

DROP CLUSTER [user.]cluster

[INCLUDING TABLES]

Ключевые слова и параметры

user

Имя владельца кластера.

cluster

Имя существующего кластера.

INCLUDING TABLES

Удаляет все таблицы, принадлежащие кластеру. Если эта фраза не специфицирована, до удаления кластера необходимо удалить все входящие в него таблицы. Кластер, владельцем которого вы не являетесь, не может быть удален, если у вас нет привилегии DBA. Удаление кластера удаляет также индекс кластера и возвращает всю память (включая блоки индекса) в соответствующие табличные пространства. Удаление кластеризованной таблицы имеет побочным эффектом удаление ее из кластера. Отдельную таблицу раскластеризовать нельзя. Чтобы получить такой эффект, необходимо выполнить следующее:

1. Создать новую таблицу с такой же структурой и содержанием, но без опции CLUSTER:

CREATE TABLE new

AS SELECT * FROM old

2. Удалить старую таблицу:

DROP TABLE old

3. И наконец, воспользуйтесь оператором RENAME для переименования новой таблицы в старую:

RENAME new TO old

Привилегии, предоставленные к старой таблице не переносятся на новую и при необходимости их надо предоставить заново.

DROP DATABASE LINK

Назначение

Удаление специфицированной межбазовой связи.

Предпосылки

Необходимо быть владельцем межбазовой связи. Для отмены общей (PUBLIC) связи необходимо иметь привилегию DBA.

Синтаксис

DROP [PUBLIC] DATABASE LINK link

Ключевые слова и параметры

PUBLIC

Должен быть специфицирован для отмены PUBLIC-связи.

link

Имя отменяемой связи.

Если связь общая, должно быть специфицировано PUBLIC; эту фразу может применять только DBA. Если межбазовая связь личная, фраза PUBLIC должна быть опущена. DBA может отменить связь, владельцем которой является другой DBA.

DROP INDEX

Назначение

Удалить из базы данных специфицированный индекс.

Предпосылки

Необходимо быть владельцем индекса или иметь привилегию DBA.

Синтаксис

DROP INDEX [user.]index

Ключевые слова и параметры

user

Имя владельца индекса.

index

Название отменяемого индекса. Вы не сможете удалить индекс, созданный другим пользователем, если не имеете привилегии DBA. Если индекс отменяется, все связанные с ним блоки памяти возвращаются в соответствующее табличное пространство. В качестве user предполагается пользователь, выдавший команду DROP.

DROP ROLLBACK SEGMENT

Назначение

Удаление указанного сегмента отката.

Предпосылки

Требуется привилегия DBA.

Синтаксис

DROP [PUBLIC] ROLLBACK SEGMENT segment

Ключевые слова и параметры

PUBLIC

Должно специфицироваться для общих (PUBLIC) сегментов.

segment

Специфицируется имя существующего сегмента отката.

Отменяться могут только неиспользуемые сегменты отката. Для определения, используется или нет данный сегмент отката, запросите столбец STATUS обзора словаря данных DBA_ROLLBACK_SEGS. Если он используется, вы можете либо ждать, пока все невыполненные транзакции не завершатся, либо выполнить команду SHUTDOWN с параметром IMMEDIATE и затем стартовать (STARTUP) базу данных в режиме EXCLUSIVE. Вся распределенная сегменту отката память возвращается в соответствующее табличное пространство.

DROP SEQUENCE

Назначение

Удаление указанной последовательности.

Предпосылки

Необходимо быть владельцем последовательности либо иметь привилегию DBA.

Синтаксис

DROP SEQUENCE [user.]sequence

Ключевые слова и параметры

user

Владелец последовательности.

sequence

Имя отменяемой последовательности. В качестве user подразумевается пользователь, выдавший команду DROP. Один из методов повторного старта последовательности — ее отмена и повторное создание. Например, у вас есть последовательность, имеющая значение 150, а вы хотите начать с 27. Для этого необходимо:

1. Отменить последовательность.

2. Создать последовательность с тем же именем, указав в START WITH значение 27.

DROP SYNONYM

Назначение

Удалить из базы данных указанный синоним.

Предпосылки

Для удаления личного синонима вы должны быть его владельцем. Для удаления общего синонима или синонима, владельцем которого вы не являетесь, необходимо иметь привилегию DBA.

Синтаксис

DROP [PUBLIC] SYNONYM [user.]synonym

Ключевые слова и параметры

PUBLIC

Должно специфицироваться при отмене общего синонима.

synonym

Имя удаляемого синонима. Вы не сможете удалить личный синоним, владельцем которого не являетесь, если не имеете привилегии DBA. Изменить синоним можно отменив его и назначив заново.

DROP TABLE

Назначение

Удалить из базы таблицу вместе с ее содержимым.

Предпосылки

Необходимо быть либо владельцем таблицы, либо иметь привилегию DBA.

Синтаксис

DROP TABLE [user.]table

Ключевые слова и параметры

user

Владелец удаляемой таблицы.

table

Имя подлежащей удалению таблицы. Вы не сможете удалить таблицу, вам не принадлежащую, если не имеете привилегии DBA. Когда вы удаляете таблицу, автоматически удаляются ее индексы, даже если они были созданы другим пользователем, а также привилегии, предоставленные к этой таблице. Обзоры и синонимы, ссылающиеся на эту таблицу, не удаляются, но становятся ошибочными. Вы можете удалить их и создать заново или создать другую таблицу таким образом, чтобы обзоры и синонимы вновь стали действительными.

Естественно, вместе с таблицей удаляются все ее строки. Если таблица не входит в кластер, вся принадлежавшая ей память, включая индексы, возвращается в табличное пространство. Чтобы не удалять таблицы кластера по отдельности, можно удалить весь кластер, выполнив команду DROP CLUSTER с параметром INCLUDING TABLES. Перед удалением таблицы необходимо запросить обзор словаря данных USER_CROSS_REFS на предмет определения зависимости от данной других таблиц.

DROP TABLESPACE

Назначение

Удаление табличного пространства с возможностью удаления всех объектов базы данных, ему принадлежащих.

Предпосылки

Требуется привилегия DBA. Табличное пространство может быть как в online, так и в offline.

Синтаксис

DROP TABLESPACE tablespace

[INCLUDING CONTENTS]

Ключевые слова и параметры

tablespace

Специфицируется имя существующего табличного пространства. Табличное пространство SYSTEM не может быть отменено.

INCLUDING CONTENTS

Специфицирует, что табличное пространство должно быть удалено даже если содержит данные. Если этот аргумент опущен и пространство не содержит данных, оно отменяется. Если же при опущенном аргументе табличное пространство содержит данные, оно не отменяется.

Перед удалением табличное пространство необходимо перевести в offline, так как если пользователи обращаются в нем к сегментам данных, индекса, отката или временным, табличное пространство отменено не будет.

DROP VIEW

Назначение

Удалить специфицированный обзор из базы данных.

Предпосылки

Необходимо быть владельцем обзора или иметь привилегию DBA.

Синтаксис

DROP VIEW [user.]view

Ключевые слова и параметры

user

Владелец обзора.

view

Имя существующего обзора.

Если у вас есть привилегия DBA, можно отменить обзор другого пользователя.

Когда вы отменяете обзор, синонимы и обзоры, на него ссылающиеся, не отменяются, но становятся неверными. Их можно отменить или переопределить, а также можно переопределить другие обзоры и таблицы, с ним связанные, чтобы обзоры и синонимы стали вновь действительны. Однако привилегии на удаляемый обзор также отменяются.

Обзор можно «изменить» путем его отмены и пересоздания.

Перед отменой обзора можно воспользоваться обзором словаря данных USER_ CROSS_REFS для определения, связаны ли с данным обзором какие-либо другие таблицы или обзоры.

EXECUTE (Динамический встроенный SQL: Метод 2)

Назначение

Выполнить оператор INSERT, DELETE или UPDATE, ранее подготовленный в операторе PREPARE. Оператор может ссылаться на host- переменные, используя фразу USING.

Предпосылки

Перед выполнением необходимо выполнить оператор PREPARE.

Синтаксис

EXEC SQL [AT dbname] [FOR :host_integer]

EXECUTE statement_name

[USING values_list]

Ключевые слова и параметры

AT dbname

Определяет имя связи для неумалчиваемой базы данных.

FOR :host_integer

Специфицирует максимальное число итераций оператора EXECUTE, когда во фразе WHERE используются массивы.

statement_name

Имя подготовленного оператора, который вы хотите выполнить. Оно не должно быть именем оператора SELECT.

USING

Обеспечивает список host-переменных или выражений, которые подставляются как входные host-переменные в ранее подготовленный оператор. Заметьте себе, что если одна из этих переменных является массивом, остальные переменные фразы USING также должны быть массивами.

values_list values_element [,values_element...]

values_element host_variable [:indicator] |

literal | expression

EXECUTE IMMEDIATE (Динамический встроенный SQL: Метод 1)

Назначение

Подготовить и выполнить SQL-оператор, не содержащий ссылок на host-переменные.

Предпосылки

Нет.

Синтаксис

EXEC SQL [AT dbname]

EXECUTE IMMEDIATE [:host_string | string_literal]

Ключевые слова и параметры

AT dbname

Необязательная фраза, специфицирующая неумалчиваемую базу данных.

:host_string

host-переменная, содержащая подлежащий выполнению SQL-оператор.

string_literal

Строчный литерал, содержащий текст SQL-оператора, который должен быть выполнен.

Оператор EXECUTE IMMEDIATE воспринимает либо строчный символьный литерал, либо host-переменную, содержащую SQL-оператор и создает выполнимую форму этого оператора. Далее он выполняет этот оператор и уничтожает выполнимую форму. Когда выполняется оператор EXECUTE IMMEDIATE, заданный SQL-оператор анализируется и проверяется на предмет обнаружения ошибок. Если он окажется ошибочным, ничего не выполняется и в SQLCODE возвращается код ошибки. Если оператор синтаксически правильный, но ошибка возникает на стадии выполнения, код ее также помещается в SQLCODE. Оператор EXECUTE IMMEDIATE наиболее полезен для операторов, которые надо выполнить лишь однажды.

Для многократного выполнения операторов надо воспользоваться командой PREPARE — для подготовки SQL-оператора и затем — EXECUTE. При многократном выполнении это имеет преимущества, так как не требует для каждого выполнения нового синтаксического разбора.

EXPLAIN PLAN

Назначение

Определяет план выполнения, которому будет следовать СУБД для выполнения указанного SQL-оператора. Заносит в специфицированную таблицу строки, описывающий каждый шаг плана выполнения.

Предпосылки

Вы должны быть владельцем или иметь привилегию INSERT для таблицы, в которую будут записываться результаты плана выполнения.

Синтаксис

EXPLAIN PLAN

[SET STATEMENT ID = char]

[INTO [user.]table]

FOR sql_statement

Ключевые слова и параметры

SET STATEMENT_ID = char

Специфицируется содержимое столбца STATEMENT_ID для всех строк плана выполнения в выходной таблице. Если опущено, значение этого столбца будет NULL.

INTO [user.]table

Описывает выходную таблицу. user — владелец этой таблицы. По умолчанию user — пользователь, выполнивший оператор EXPLAIN PLAN. По умолчанию table — PLAN_TABLE.

FOR sql_statement

Текст оператора SELECT, INSERT, UPDATE или DELETE. Оператор EXPLAIN PLAN генерирует план выполнения для sql_statement. Выходная таблица должна быть создана до выполнения оператора EXPLAIN PLAN. Можно создать эту таблицу, воспользовавшись примером, данным на дистрибутивном носителе в файле XPLAINPL.SQL. Если вы захотите воспользоваться другой таблицей, она должна содержать столбцы с теми же именами и типом данных, что и приведенная в примере. После выполнения оператора EXPLAIN PLAN требуемые строки можно определить по содержанию столбца STATEMENT_ID. Для строк конкретного оператора это значение равно указанному в операторе значению char в SET STATEMENT_ID. Так как по умолчанию значение этого столбца (STATEMENT_ID) предполагается NULL, необходимо удостовериться, что вы указали соответствующее SET-значение, особенно если выходная таблица содержит информацию о плане выполнения нескольких операторов.

Оператор EXPLAIN PLAN можно также выполнить как составную часть средства трассировки SQL.

FETCH (Встроенный SQL)

Назначение

Выбирает из базы данных одну строку. Встроенный оператор SQL FETCH позиционирует курсор на следующую выбираемую строку и назначает элементы текущей строки соответствующим host-переменным.

Предпосылки

Предварительно вы должны открыть курсор, который содержит SQL-оператор SELECT. Для динамического SQL вы должны подготовить (PREPARE) оператор и, возможно, установить дескрипторы для оператора перед выполнением OPEN.

Синтаксис

EXEC SQL [FOR :host_integer]

FETCH cursor_name

INTO host_variable_list

Для динамического SQL, метод 4:

EXEC SQL [FOR :host_integer]

FETCH cursor_name

USING descriptor

Ключевые слова и параметры

FOR :host_integer

Максимальное число строк, выбираемых в выходные переменные.

cursor_name

Указывает имя курсора, определенного оператором DECLARE CURSOR, который должен предшествовать в программе оператору FRTCH.

INTO host_variable_list

Эта фраза определяет host- переменные, которые должны быть определены в программе. Если какая-либо из этих переменных — массив, то все host-переменные должны быть массивами.

host_variable_list

Множество, состоящее из одной или более host-переменных, разделенных запятыми (каждая может ассоциироваться с индикаторной переменной).

descriptor

Имя дескриптора, определенного ранее оператором DESCRIBE. Курсор должен быть определен (DECLARE) и открыт (OPEN). На момент выполнения оператора FETCH курсор должен быть открыт. Оператор SELECT, связанный с запросом, может и не содержать фразы INTO; вместо того эта фраза вместе со списком host-переменных может быть включена в оператор FETCH.

Оператор FETCH считывает строки в активные host-переменные, которые будут содержать результат. Индикаторная переменная устанавливается в «-1», если соответствующая host-переменная имеет значение null. Количество выбираемых строк определяется размерностью выходных переменных и значением необязательной переменной во фразе FOR. Например, если выходная переменная описана как массив размерностью 100, СУБД попытается выбрать 100 строк.

Фраза FOR может быть использована для ограничения выборки до 75 строк. Если в таблице еще остались строки, курсор позиционируется на следующую строку, а выбранные значения присваиваются host-переменным, специфицированным фразой INTO оператора FETCH и/или фразой USING оператора OPEN CURSOR.

Если курсор в настоящий момент позиционирован на последнюю строку в таблице или за ней, SQLCODE устанавливается в +100 (или +1403 в режиме совместимости). В этом случае если выбираются значения null, значения соответствующих host-переменных не изменяются. Обратите внимание, что с оператором FETCH не ассоциируется фраза AT. Соответствующая база данных специфицируется в операторе DECLARE CURSOR. FETCH может двигаться по выбираемым строкам только в одном направлении. Если вам надо выбрать одну из предыдущих строк, необходимо заново открыть курсор и выбирать строку за строкой.

Фраза FOR (Встроенный SQL)

Назначение

Ограничивает количество повторений при работе с массивами в операторах SELECT, DELETE, INSERT, FETCH и EXECUTE.

Предпосылки

Нет.

Синтаксис

FOR :host_integer

Ключевые слова и параметры

FOR :host_integer

При использовании фразы FOR количество выполнений оператора равно минимуму между размерностью массивов host- переменных (включая массивы индикаторных переменных) и host_integer. Фраза FOR не обязательна. Массивы host-переменных могут иметь различную размерность. Если фраза FOR опущена, для вычисления количества обработанных элементов выбирается минимальная размерность.

GRANT (Форма I)

Назначение

Предоставить доступ к базе данных

Предпосылки

За исключением случая, когда пользователь меняет свой собственный пароль, требуется привилегия DBA.

Синтаксис

GRANT database_priv [,database_priv] ...

TO user [,user] ...

[ IDENTIFIED BY password [,password] ...]

Ключевые слова и параметры

database_priv

Специфицируется одна или несколько привилегий базы данных: CONNECT, DBA, RESOURCE.

user

Задается новое или существующее имя пользователя базы данных. Если это имя уже существует, тогда оператор используется для предоставления привилегий. Если же такого пользователя нет, он добавляется вместе со специфицированными привилегиями.

password

Специфицируется пароль для каждого пользователя. Список пользователей и паролей должны содержать одинаковое количество элементов.

Этот аргумент используется либо для назначения пароля вновь заводимым пользователям, либо для изменения у существующих. Пароль обязательно должен быть задан если оператор GRANT используется для добавления привилегий существующим пользователям.

Привилегия CONNECT вводит (устанавливает) в базу данных нового пользователя. Имея эту привилегию, он может подключаться к базе данных и работать с объектами, к которым он имеет привилегии по доступу. Например — все пользователи могут выполнять запросы по таблице словаря данных DICTIONARY.

Имея привилегию CONNECT, пользователь может создавать обзоры, синонимы и межбазовые связи.

Привилегия RESOURCE разрешает пользователю создавать объекты базы данных, включая: таблицы, индексы, кластеры и последовательности. Эта форма оператора GRANT RESOURCE не ограничивает привилегии по ресурсам для всех табличных пространств.

Для работы с определенными пространствами воспользуйтесь Формой II команды GRANT.

Привилегия DBA позволяет пользователю многие стандартные привилегии, требуемые для работы с объектами базы данных. Кроме того, привилегия DBA разрешает пользователю выполнять определенные действия администратора базы данных, например: CREATE TABLESPACE или CREATE ROLLBACK SEGMENT.

Имея привилегию DBA, пользователь может:

u выполнять команду SELECT для любой таблицы и обзора

u создавать объекты для других пользователей

u отменять объекты других пользователей, включая таблицы, обзоры, синонимы и межбазовые связи

u предоставлять различные привилегии

u создавать общие (PUBLIC) синонимы и связи

u выполнять полный экспорт/импорт базы данных

GRANT (Форма II)

Назначение

Предоставлять доступ к памяти базы данных (табличному пространству) с возможностью наложения ограничения на объем памяти, получаемый пользователем для работы.

Предпосылки

Требуется привилегия DBA.

Синтаксис

GRANT RESOURCE [ (quota [K|M]) ]

ON tablespace

TO { PUBLIC | user [,user] ...}

Ключевые слова и параметры

RESOURCE

Указывает на то, что список пользователей может создавать объекты в данном табличном пространстве.

quota

Специфицируется как целое число, представляющее собой количество байтов памяти, которое может для себя распределить пользователь. Отсутствие параметра quota означает неограниченное использование ресурса табличного пространства. Назначение 0 отменяет у пользователя привилегию RESOURCE для данного табличного пространства. Если за числом следует «К», оно умножается на 1024, если — «М», умножается на 1048576.

tablespace

Имя существующего табличного пространства

user

Специфицирует существующего пользователя. Ключевое слово PUBLIC означает предоставление привилегий на ресурсы данного табличного пространства всем пользователям базы данных.

Форма II оператора GRANT предоставляет по сравнению с Формой I дополнительную гибкость. Использование второй формы предпочтительнее, так как DBA имеет дополнительную возможность управления распределением памяти в базе данных.

GRANT (Форма III)

Назначение

Предоставляет различные типы доступа к объектам базы данных, таким как таблицы, обзоры и последовательности.

Предпосылки

Вы должны быть владельцем объекта, иметь GRANT OPTION для этого объекта или иметь привилегию DBA.

Синтаксис

GRANT {object_priv [,object_priv] ...} | {ALL

[PRIVILEGES] } ON [user.]object

TO { user | PUBLIC } [,user] ...

[ WITH GRANT OPTION ]

Ключевые слова и параметры

object_priv

Для таблиц — одна из: ALTER, DELETE, INDEX, INSERT, REFERENCES, SELECT, UPDATE.

Для обзоров — одна из: DELETE, INSERT, SELECT, UPDATE.

Для последовательностей — ALTER или SELECT.

Привилегии UPDATE и REFERENCES могут ограничивать возможность изменений и ссылок для определенных столбцов. Синтаксис предоставления привилегии для столбцов следующий:

GRANT column_priv [,column_priv]

(column [,column]...)

ON [user.]table

TO {user | PUBLIC} [,user] ...

[WITH GRANT OPTION]

Где:

column

Специфицирует столбец таблицы.

ALL PRIVILEGES

Это ключевое слово аналогично заданию всех привилегий object_ priv.

ON [user.]object

Специфицирует таблицу, обзор или синоним, на которые дается привилегия.

TO user

Указывает пользователя(лей), которым предоставляется привилегия.

PUBLIC

Представляет всех пользователей базы данных, а также — будущих пользователей, которым предоставляется указанная привилегия.

WITH GRANT OPTION

Указывает, что пользователь, которому предоставляется привилегия, может в свою очередь предоставлять ее другим пользователям.

Владелец всегда имеет все привилегии по доступу к таблице.

ALTER — привилегия на выполнение оператора ALTER TABLE.

DELETE — привилегия на удаление строк из таблицы.

INDEX — привилегия на создание индекса на таблице.

INSERT — привилегия на вставку строк в таблицу.

REFERENCES — привилегия для ссылки на таблицу внутри ограничения для таблицы или столбца.

SELECT — привилегия для запроса по таблице. Чтобы ограничить возможности по запросу определенных столбцов таблицы, создайте обзор на основании этих столбцов и и предоставьте привилегию по запросу для этого обзора.

UPDATE — привилегия на модификацию строк в таблице. Могут быть изменены только те столбцы, которые указаны.

Привилегии, предоставленные определенным пользователям не зависят от данных при этом привилегий PUBLIC. Например, если Scott предоставил Blake привилегию SELECT для таблицы EMP, а также предоставил ту же привилегию всем (PUBLIC), то отменив привилегию PUBLIC он не отменит привилегию у Blake, хотя последний и является членом PUBLIC. Ограничивать доступ к таблице можно прежде всего созданием на ее базе обзоров и предоставления привилегий для обзоров. Команда GRANT для синонимов преобразует их в привилегии к соответствующим объектам.

Последовательности

Для использования последовательности кем-либо, кроме владельца необходимо предоставление ему привилегии SELECT. Соответственно — привилегия ALTER предназначена для выполнения над последовательностью команды ALTER SEQUENCE. Привилегии по использованию последовательности можно передать другому пользователю, указав фразу WITH GRANT OPTION. СУБД может определить, что вы предоставили право доступа к последовательности, а не к таблице, так как все объекты (последовательности, таблицы, обзоры, индексы и кластеры) пользователя должны иметь различные имена.

INSERT

Назначение

Добавление новых строк к таблице или к таблице, лежащей в основе обзора.

Предпосылки

Вы должны быть владельцем таблицы или иметь для нее привилегию INSERT или иметь привилегию DBA.

Синтаксис

INSERT INTO[user.]table [column [,column] ...)]

{VALUES (value [,value]...) | query}

Ключевые слова и параметры

user

Имя пользователя — владельца слова и таблицы.

table

Имя существующей таблицы, обзора или синонима. По умолчанию — пользователь, выдавший команду INSERT.

column

Столбец таблицы или обзора.

value

Значение, соответствующее по позиции столбцу из списка столбцов. value может быть выражением. Если value не NULL, оно должно соответствовать по столбцу по типу данных.

query

Оператор SELECT, возвращающий столько значений, сколько указано в списке столбцов. Не может содержать фраз ORDER BY и UPDATE.

Оператор INSERT, применяемый с фразой VALUES, всегда добавляет не более одной строки. Полям, определяемым columns, назначаются специфицированные значения. Столбцы, опущенные в списке columns, будут содержать значения NULL и, следовательно, не могут быть описаны NOT NULL.

Оператор INSERT, в котором используется запрос (query) вместо фразы VALUES, может добавлять более одной строки (столько, сколько возвращено запросом). После того, как запрос выполнится, его столбцы сопоставляются по позиции со столбцами, специфицированными после ключевого слова INSERT (или всем, если ничего не специфицировано). Запрос может ссылаться на ту же таблицу, в которую строки вставляются.

Значение обязательно должно быть сопоставлено всем столбцам, определенным как NOT NULL.

Если в скобках указывается список столбцов, им должны быть поставлены в соответствие значения; если нет, то значения должны быть обеспечены всем столбцам. Если список столбцов опущен, значения должны указываться в порядке внутреннего расположения столбцов таблицы. Эти позиции можно определить, запросив обзор словаря данных USER_TAB_COLUMNS.

Если подзапрос внутри оператора INSERT не возвращает строк, в таблицу не будет добавлено ни одной строки.

INSERT (Встроенный SQL)

Назначение

Добавление строк к таблице.

Предпосылки

Вы должны быть владельцем таблицы или иметь для нее привилегию INSERT или иметь привилегию DBA.

Синтаксис

EXEC SQL [AT dbname] [FOR :host_integer]

INSERT INTO [user.]table [(column [,column]...)]

{VALUES(values_element [,values_element]...) | query}

Ключевые слова и параметры

AT dbname

Необязательная фраза, определяющая неумалчиваемую базу данных.

FOR :host_integer

Ограничивает число элементов массива, связанных с выполнением операции INSERT.

[user.]table

Имя существующей таблицы, обзора или синонима. Каждый специфицируемый столбец должен принадлежать таблице, указанной во фразе INTO.

VALUES

Представляет одну строку значений, подлежащую добавлению. Значениями строки будут значения ключевых слов, констант, SQL-выражений или host-переменных, специфицированных во фразе. Каждая host-переменная, специфицированная как значение, должна быть описана в программе как переменная. Количество значений в этом списке должно быть равно количеству имен столбцов.

values_element

Может быть:

u host-переменной с необязательной индикаторной переменной, либо

u любым выражением

query

Оператор SELECT, возвращающий столько значений, сколько указано в списке столбцов. Не может содержать фраз ORDER BY и FOR UPDATE.

Если host-переменные в списке VALUES-массивы, оператор будет выполняться количество раз, равное минимуму среди размерностей массивов и значением в параметре FOR.

Если фраза WHERE не возвращает строк, ни одной строки в таблицу вставлено не будет и SQLCODE устанавливается в +100 (или +1403 — в режиме совместимости).

LOCK TABLE

Назначение

Ручное перекрытие автоматических блокировок с целью блокировки одной или нескольких таблиц в специфичном режиме, позволяющее вам разделять или индивидуализировать доступ на время вашей работы.

Предпосылки

Необходимо быть владельцем таблицы или иметь привилегию DBA, или иметь привилегии ALTER, DELETE, INSERT, SELECT или UPDATE.

Синтаксис

LOCK TABLE [user.]table [, [user.]table]...

IN lockmode MODE [NOWAIT]

Ключевые слова и параметры

user

Владелец таблицы.

table

Существующая таблица, к которой вы имеете доступ.

lockmode

Один из: ROW SHARE, ROW EXCLUSIVE, SHARE UPDATE, SHARE, SHARE ROW EXCLUSIVE или EXCLUSIVE.

NOWAIT

Специфицирует, что СУБД не должна ожидать таблицы, если она заблокирована другим пользователем. Вместо ожидания система возвращает управление программе пользователя.

Исключительные блокировки (exclusive) разрешают выполнение запросов по этим таблицам, но запрещают любую иную работу.

Разделяемые блокировки (share) разрешают параллельное выполнение запросов, но запрещают модификации таблиц.

Блокировки типа «Row share» допускают параллельный доступ к таблице. Они запрещают пользователям блокирование всей таблицы в исключительном режиме. ROW SHARE и SHARE UPDATE являются синонимами.

Блокировки типа «Row Exclusive» аналогичны ROW SHARE, но кроме того — запрещают блокировку в режиме SHARE. Такие блокировки выполняются автоматически при изменении, вставке и удалении.

Блокировки типа «Share Row Exclusive» используются для просмотра всей таблицы и позволяют другим пользователям просматривать строки, но запрещают блокировку таблицы в режиме SHARE и модификацию строк.

Блокировки типа «Share Update» являются синонимом ROW SHARE и присутствуют для совместимости с ранними версиями ORACLE RDBMS.

Некоторые типы блокировок можно применять к одной и той же таблице одновременно, другие — только индивидуально. Например, несколько пользователей могут установить SHARE-блокировки к одной и той же таблице одновременно, но блокировку типа EXCUSIVE — только один пользователь. При блокировании таблицы вы должны оценить, сколько еще пользователей может к ней обращаться. Однажды заблокированная, таблица остается таковой до окончания транзакции (до выполнения commit или rollback).

Если запрос LOCK TABLE не может быть выполнен, например — так как другой пользователь уже заблокировал таблицу, LOCK TABLE либо ожидает, когда запрос сможет быть обслужен, либо возвращает управление пользователю (если специфицировано NOWAIT). Если NOWAIT специфицирован, а ресурс зарезервирован другим пользователем, СУБД возвращает управление пользователю вместе с кодом ошибки.

Блокировка никогда не мешает другим пользователям делать запросы по таблице. Сам запрос также не блокирует таблицы, следовательно — читающие и записывающие пользователи друг другу не мешают.

Блокировка обзора преобразовывается в блокировку соответствующей таблицы или таблиц.

NOAUDIT (Форма I)

Назначение

Частично или полностью отменяет эффект предшествующего системного оператора AUDIT.

Предпосылки

Эта форма оператора NOAUDIT нуждается в привилегии DBA.

Синтаксис

NOAUDIT {system_option [,system_option]...| ALL}

WHENEVER [NOT] SUCCSESSFUL]

Ключевые слова и параметры

system_option

Одна из следующих системных опций: CONNECT, DBA, NOT EXISTS или параметры RESOURCE.

ALL

Представляет все опции сразу.

WHENEVER [NOT] SUCCSESSFUL

Специфицирует, что регистрация должна быть выключена для успешного или неудачного обращения. Если данная фраза опущена, регистрация отключается как для успешного, так и для неудачного обращения.

NOAUDIT (Форма II)

Назначение

Частично или полностью меняет на противоположный эффект предшествующего оператора AUDIT или умалчиваемых опций регистрации доступа к таблицам.

Предпосылки

Вы должны быть владельцем объекта, с которым работает NOAUDIT. Для изменения опций регистрации, устанавливаемых по умолчанию (DEFAULT), необходимо иметь привилегию DBA.

Синтаксис

NOAUDIT {option [,option]... | ALL}

ON {object | DEFAULT }

[WHENEVER [NOT] SUCCSESSFUL}

Ключевые слова и параметры

option

Для таблиц приемлемы следующие опции: AUDIT, COMMENT, DELETE, GRANT, INDEX, INSERT, LOCK, RENAME, SELECT, UPDATE. Для обзоров приемлемы следующие опции: AUDIT, COMMENT, INSERT, DELETE, GRANT, LOCK, RENAME, SELECT, UPDATE. Для последовательностей приемлемы опции: ALTER, AUDIT, GRANT, SELECT. Для синонимов приемлемы те же опции, что и для таблиц (так как все действия реально производятся с таблицами).

ALL

Обозначает все приемлемые опции.

object

Существующая таблица, обзор или синоним, владельцем которого вы являетесь.

DEFAULT

Ссылается на умалчиваемые опции регистрации доступа, устанавливаемые администратором базы данных для всех вновь создаваемых таблиц. DEFAULT не применим к обзорам и последовательностям.

WHENEVER [NOT] SUCCSESSFUL

Специфицирует, что регистрация должна быть выключена для успешного или неудачного обращения. Если данная фраза опущена, регистрация отключается как для успешного, так и для неудачного обращения. Команда NOAUDIT указывает СУБД прекратить регистрацию использования таблиц или обзоров. Будучи оператором DDL, NOAUDIT также записывает текущую транзакцию.

OPEN cursor (Встроенный SQL)

Назначение

Распределяется ранее явно объявленного курсор.Затем выполняется запрос из объявления курсора с подставлением имен host-переменных, назначенных фразой USING во фразу запроса WHERE.

Предпосылки

Перед открытием курсор должен быть объявлен.

Синтаксис

EXEC SQL OPEN cursor_name [USING host_variable_list]

Для динамического SQL, метод 4:

EXEC SQL OPEN cursor_name [USING DESCRIPTOR_name]

Ключевые слова и параметры

cursor_name

Указывает на ранее объявленный с помощью оператора DECLARE CURSOR курсор.

USING host_variable-list

Список host-переменных, которые будут подставляться в оператор из DECLARE CURSOR. Подстановка выполняется позиционно: имена переменных во фразе USING обычно отличаются от имен, задаваемых в операторе PREPARE.

descriptor_name

Ссылка на результат предыдущего оператора DESCRIBE.

Оператор OPEN cursor определяет активное множество строк и инициализирует курсор в начало этого множества. Значения host- переменных в момент открытия подставляются в оператор. Оператор OPEN cursor действителен только при включении в прикладную программу. Все курсоры при старте программы закрыты. Кроме того, их можно закрыть явно, выдав оператор CLOSE. Оператор OPEN cursor распределяет курсор и позиционирует его непосредственно перед первой строкой активного множества. В данный момент строки не выбираются, это делается в операторе FETCH.

После открытия курсора входные host-переменные не проверяются до повторного открытия. Чтобы изменить какую-либо входную host-переменную и, следовательно, активное множество, необходимо вновь открыть курсор. Курсор можно открывать заново без предварительного закрытия. Этим можно сэкономить на анализе оператора, если он не изменился. Если используется Динамический SQL, метод 4 (оператор DESCRIBE), host-переменные, на которые ссылаются через дескриптор, подставляются в SQL-оператор, определенный в объявлении курсора.

PREPARE (Динамический встроенный SQL)

Назначение

Выполняет грамматический разбор SQL-оператора, задаваемого в host-переменной.

Предпосылки

Нет.

Синтаксис

EXEC SQL PREPARE statement_name

FROM {:host_string | string_literal}

Ключевые слова и параметры

statement_name

Назначает имя разбираемому оператору. Если это имя уже назначено существующему оператору, предыдущее назначение будет перекрыто. Это имя идентификатора, используемое системой, оно не является host- переменной.

:host_string

host-переменная, содержащая любой SQL-оператор, подлежащий разбору (подготовке к выполнению). Эта переменная должна иметь тип varchar, string и им подобные.

string_literal

Строчный литерал, содержащий текст SQL-оператора.

Оператор PREPARE выполняет разбор SQL-оператора и дает ему имя. SQL-оператору не предшествует фраза EXEC SQL, в то время как оператору PREPARE — предшествует. Оператор может быть оператором SELECT с фразой FOR UPDATE. host-переменные, перечисленные в host_string, являются шаблонами. Действительные имена host-переменных назначаются фразой USING оператора OPEN cursor (входные) или фразой INTO оператора FETCH (выходные). Подстановка выполняется позиционно. SQL-оператор подготавливается только однажды, а выполняться может произвольное число раз.

RENAME

Назначение

Переименование таблицы, обзора или синонима.

Предпосылки

Необходимо быть владельцем переименовываемого объекта.

Синтаксис

RENAME old TO new

Ключевые слова и параметры

old

Старое имя таблицы, обзора или синонима.

new

Вновь назначаемое имя таблицы, обзора, синонима.

Этот оператор меняет имя таблицы, обзора или синонима с old на new. old — имя объекта, подлежащее замене, а new — новое его имя (предполагается, что new — правильное в системе имя, не используемое другими объектами). Привилегии и индексы, принадлежащие старому имени, переносятся на новое. С помощью этой команды невозможно переименование столбцов. Один из способов такого переименования — это выполнение оператора вида: CREATE TABLE ... AS SELECT... Например, таблица STATIC, имеющая всего один столбец — OLDNAME, должна быть пересоздана с именем столбца NEWNAME. Это делается следующим образом:

CREATE TABLE TEMPORARY (NEWNAME)

AS SELECT OLDNAME FROM STATIC

DROP TABLE STATIC

RENAME TEMPORARY TO STATIC

REVOKE (Форма I)

Назначение

Отмена привилегий базы данных для одного или нескольких пользователей.

Предпосылки

Требуется привилегия DBA.

Синтаксис

REVOKE { [CONNECT] [,RESOURCE] [,DBA] }

FROM user [,user] ...

Ключевые слова и параметры

user

Существующий пользователь.

CONNECT, RESOURCE, DBA

Привилегии, которые должны быть отменены. Значение этих аргументов в точности совпадает с командой GRANT.

Команда REVOKE завершается успешно даже если не существует привилегий, которые пытались отменить. REVOKE не влияет на какие-либо объекты, владельцем которых является данный пользователь. Например, отмена привилегии CONNECT у пользователя Blake никак не повлияет на его таблицу SALES. Остальные пользователи, когда-либо получившие право доступа к этой таблице, могут продолжать ее использовать. Если Blake вновь получит доступ к базе данных, за ним сохранится право собственности на старые объекты. Из этого следует рекомендация удалять объекты пользователя, прежде чем отменять привилегию CONNECT у этого пользователя.

REVOKE (Форма II)

Назначение

Отмена привилегии на использование памяти в определенном табличном пространстве.

Предпосылки

Необходима привилегия DBA.

Синтаксис

REVOKE space_privilege ON tablespace

FROM user [,user] ...

Ключевые слова и параметры

space_privilege

Это привилегия RESOURCE. Пока это только привилегия на использование памяти.

tablespace

Существующее табличное пространство, в котором пользователю была предоставлена привилегия RESOURCE.

user

Существующий пользователь, ранее получивший привилегию RESOURCE для данного табличного пространства.

Эта форма команды REVOKE отбирает у пользователя возможность создавать объекты в указанном табличном пространстве. Никакие другие объекты не могут создаваться пользователем в данном табличном пространстве, а существующие не могут распределять дополнительную память.

REVOKE (Форма III)

Назначение

Отмена привилегий пользователя по доступу к определенным таблицам, обзорам и последовательностям.

Предпосылки

Вы должны быть владельцем объекта базы данных или иметь предоставленную привилегию на его использование WITH GRANT OPTION.

Синтаксис

REVOKE { object_priv [,object_priv]... | ALL }

ON [user.]object

FROM { user | PUBLIC } [,user]...

Ключевые слова и параметры

object_priv

Для таблиц — одна из: ALTER, DELETE, INDEX, INSERT, REFERENCES, SELECT, UPDATE.

Для обзоров — одна из: DELETE, INSERT, SELECT, UPDATE.

Для последовательностей: ALTER или SELECT.

ALL

Отмена всех привилегий

ON object

Специфицируется имя таблицы, обзора или последовательности, для которых вы отменяете привилегии. Таблица должна быть либо вашей собственной, либо на нее вам должна быть предоставлена привилегия WITH GRANT OPTION.

user

Имя пользователя, у которого вы отбираете привилегии.

FROM PUBLIC

Отменяет привилегии, предоставленные соответствующим образом, но не отбирает привилегий, данных конкретному пользователю.

PUBLIC

Имеет в виду всех существующих и будущих пользователей.

Эта форма команды отменяет привилегии на таблицы или обзоры у одного или нескольких пользователей. Если пользователь имеет полномочия к определенной таблице, предоставленные более чем одним пользователем, он будет их сохранять, пока каждый из этих пользователей не отменит привилегий. Отмена привилегий у пользователя автоматически отменяет их и у пользователей, которым он передал привилегии.

ROLLBACK

Назначение

Отмена работы, выполненной в текущей транзакции.

Предпосылки

Нет.

Синтаксис

ROLLBACK [WORK]

[ TO [SAVEPOINT] savepoint ]

Ключевые слова и параметры

WORK

Необязательный параметр, обеспечивающий ANSI — совместимость.

SAVEPOINT

Необязательный параметр, использующийся для частичного отката транзакции к специфицированной точке сохранения.

savepoint

Имя точки сохранения, сделанной в текущей транзакции.

Транзакция (или логическая единица работы) — это последовательность SQL-операторов, которые СУБД трактует как единое целое. Транзакция начинается с первого выполнимого SQL-оператора после COMMIT, ROLLBACK или присоединения к базе данных. Транзакция завершается оператором COMMIT, ROLLBACK или при отсоединении от базы данных (преднамеренном или случайном). Заметьте, что СУБД неявно выдает команду COMMIT до и после любого DDL-оператора.

Использование оператора ROLLBACK без фразы SAVEPOINT:

u завершает транзакцию

u отменяет все изменения текущей транзакции

u очищает все точки сохранения данной транзакции

u освобождает блокировки данной транзакции

Использование оператора ROLLBACK с фразой SAVEPOINT:

u выполняет частичный откат транзакции (назад к поименованной точке сохранения)

u сохраняет информацию точки сохранения, к которой был выполнен откат, но теряет все последующие точки сохранения. Следовательно, вы можете многократно выполнять откат к одной и той же точке сохранения. Предшествующие точки сохранения не затрагиваются.

u освобождает все блокировки строк и таблиц, которые были выполнены после данной точки сохранения.

На практике рекомендуется в программах пользователя завершать транзакции явно с помощью операторов COMMIT WORK или ROLLBACK WORK. Если вы явно не завершите транзакцию и прикладная программа завершится аварийно, последняя не записанная транзакция будет откачена.

ROLLBACK (Встроенный SQL)

Назначение

Завершить текущую транзакцию, отбросить все изменения и освободить все блокировки. Опция RELEASE используется для одновременного освобождения ресурсов и отсоединения от базы данных.

Предпосылки

Нет.

Синтаксис

EXEC SQL [AT dbname] ROLLBACK [WORK]

[TO [SAVEPOINT] savepoint]

[RELEASE]

Ключевые слова и параметры

AT dbname

Необязательная фраза, определяющая имя базы данных, где будет выдана команда ROLLBACK.

WORK

Необязательна и не влияет на ROLLBACK.

SAVEPOINT

Необязательный параметр, использующийся для частичного отката транзакции к специфицированной точке сохранения.

savepoint

Имя точки сохранения, сделанной в текущей транзакции.

RELEASE

Необязательный параметр, освобождающий все ресурсы и отсоединяющий вас от базы данных. Последнюю транзакцию в программе необходимо явно завершать подтверждением или отменой изменений, а отсоединение от базы данных выполняется с помощью опции RELEASE. Если опция RELEASE была опущена, а программа завершилась, блокировки, выполненные в последней транзакции не будут освобождены, пока СУБД не распознает, что процесс больше не активен. Это может привести к ожиданию блокировки другими пользователями или отказе в их получении. СУБД автоматически откатывает изменения, если программа завершилась аварийно. Оператор ROLLBACK WORK не оказывает влияния на содержание host-переменных и последовательность выполнения команд программы.

SAVEPOINT

Назначение

Определение точки в транзакции, к которой вы можете впоследствии откатиться с помощью оператора ROLLBACK TO SAVEPOINT.

Предпосылки

Нет.

Синтаксис

SAVEPOINT savepoint

или во встроенном SQL:

EXEC SQL SAVEPOINT savepoint

Ключевые слова и параметры

savepoint

Имя точки сохранения, которую вы хотите создать. Имя точки сохранения — это идентификатор SQL и должно подчиняться стандартным соглашениям по именованию объектов (кроме отсутствия необходимости разниться от имен других объектов базы данных. Точки сохранения используются совместно с оператором ROLLBACK для отката части текущей транзакции. По умолчанию максимальное число точек сохранения на транзакцию равно 5. Активная точка сохранения — такая, которая была сделана после операции COMMIT или ROLLBACK. Это ограничение может быть изменено путем изменения значения параметра SAVEPOINTS файла INIT.ORA.

Точки сохранения полезны в интерактивных программах, так как вы можете создать и поименовать промежуточные стадии некоторой процедуры. Это дает вам большие возможности по управлению большими, комплексными процедурами. Например, вы можете использовать точки сохранения внутри многочисленных изменений, что позволит в случае ошибки не повторять заново все предыдущие шаги.

Точки сохранения полезны также и в прикладных программах следующего типа: если процедура включает в себя несколько функций, точки сохранения могут быть установлены перед началом каждой. Тогда, если функция завершится аварийно, информацию базы легко будет вернуть к состоянию, предшествующему функции и выполнить ее заново с моди фицированными параметрами или предпринять действия по восстановлению.

Имена точек сохранения внутри данной транзакции должны отличаться друг от друга. Если вы создаете вторую точку сохранения с идентификатором, который уже использовался в предыдущей точке сохранения, вторая перекрывает первую. После создания контрольной точки вы можете продолжить обработку, записать работу (commit), откатить всю транзакцию или откатиться к точке сохранения.

SELECT

Назначение

Показывает строки и столбцы из одной или нескольких таблиц. Может использоваться как самостоятельный оператор или (с определенными ограничениями во фразах) как запрос или подзапрос в другом операторе.

Предпосылки

Необходимо быть владельцем таблицы или иметь к ней привилегию SELECT или иметь привилегию DBA.

Синтаксис

SELECT [ALL|DISTINCT] {* | table.* | expr [c_alias]}

---

[, {table.* | expr [c_alias]}]...

FROM [user.]table [t_alias][,[user.]table [t_alias]]...

[WHERE condition]

[CONNECT BY condition [START WITH condition]]

[GROUP BY expr [, expr]...[HAVING condition]]

[{UNION|INTERSECT|MINUS} SELECT ...]

[ORDER BY {expr|position} [ASC|DESC]

[,{expr|position} [ASC|DESC]] ]...

---

[FOR UPDATE OF column [,column]...[NOWAIT] ]

Ключевые слова и параметры

ALL

Берется по умолчанию и означает, слова и что все строки, удовлетворяющие параметры условию оператора, будут показываться.

DISTINCT

Специфицирует, что дублирующие строки удаляются до показа. Строка рассматривается дублирующейся, если все соответствующие значения из списка SELECT одинаковы для обеих строк.

*

Специфицирует, что показываются все столбцы таблиц из фразы FROM.

table.*

Специфицирует, что показываются все столбцы указанной таблицы.

c_alias

Обеспечивает имя для выражения из столбцов, которое будет показано в заголовке при выдаче. Не влияет на действительные имена столбцов. На алиасы столбцов нельзя ссылаться где-либо еще в запросе.

[user.]table

Специфицирует имя таблицы или обзора, подлежащих выдаче. Имени таблицы может предшествовать имя владельца; если не указано, предполагается пользователь, выдавший запрос.

t_alias

Обеспечивает таблице другое имя для выполнения запроса и наиболее часто используется коррелированных запросах. Другие ссылки к таблице всюду в данном запросе должны выполняться через алиас.

position

Идентифицирует относительную позицию столбца в списке SELECT.

ASC|DESC

Указывает порядок сортировки (соответственно — возрастающий и убывающий), базирующийся на последовательности слияния операционной системы.

column

Столбец, принадлежащий таблице, указанной в списке фразы FROM.

NOWAIT

Указывает, что система должна немедленно вернуть управление пользователю, если он попытается заблокировать строку, уже заблокированную другим пользователем.

Фразы должны использоваться в приведенном выше порядке за исключением:

u CONNECT BY, START WITH, GROUP BY и HAVING могут указываться в любом порядке.

u фразы ORDER BY и FOR UPDATE OF могут меняться местами.

Хотя обычно и необязательно предварять имя столбца именем таблицы и имя таблицы — именем пользователя, иногда такое указание бывает необходимо. Обычно система выполняет меньшую работу, если имена таблиц и столбцов квалифицированы полностью. Полное квалифицирование столбцов необходимо также, если две или более таблицы имеют столбцы с одинаковыми именами. Каждое выражение expr приводит к выдаче одного столбца, а table.* — к выдаче нескольких столбцов — по одному на столбец таблицы (в порядке их определения в момент создания таблицы). Тип данных и размер выдачи для выражения определяется на основании элементов выражения.

Звездочка (*) означает «все» столбцы таблицы. Если во фразе FROM указано несколько таблиц, звездочка означает «все столбцы всех таблиц». Звездочка может быть предварена именем таблицы (table.*), что ограничивает выдачу только столбцами данной таблицы. Каждый алиас, если указан, используется для пометки предшествующего выражения и появляется в качестве имени столбца в выходном заголовке. Алиас не влияет на действительные имена столбцов таблицы и не может использоваться для ссылки в оставшейся части запроса.

FROM table

Специфицирует таблицу или обзор, из которой будут выбираться данные. Если алиас указан для таблицы, он должен использоваться для ссылки на данную таблицу во всей оставшейся части оператора SELECT.

CONNECT BY

Фраза CONNECT BY используется для запроса иерархических отношений. Она указывает, что строки должны выдаваться в иерархическом порядке и определяет отношение, используемое для определения иерархии связи строк в таблице. Фраза PRIOR должна указываться перед первой частью условия. PRIOR означает родителя в каждом отношении «родитель»–«потомок». Другая часть представляет дочернюю вершину. Например, CONNECT BY PRIOR EMPNO=MGR означает, что поле EMPNO в родительской вершине равно полю MGR в каждой дочерней (порожденной) вершине. Число уровней CONNECT BY лимитируется доступной пользователю памятью.

CONNECT BY нельзя использовать совместно с:

u подзапросами

u объединениями

START WITH

Фраза START WITH идентифицирует строки (или строку), которые будут использоваться в качестве корня дерева, назначая условие, которому они должны удовлетворять. Отсутствие этой фразы означает начало со всех столбцов, удовлетворяющих условию фразы WHERE.

Во фразе START WITH допустимы подзапросы.

Если в операторе SELECT используется фраза CONNECT BY, в нем же можно воспользоваться псевдостолбцом LEVEL, который возвращает 1 для корня, 2 — для вершины, порожденной корнем, 3 — для внука.

GROUP BY, HAVING

Фразы GROUP BY и HAVING используются для показа обобщающей информации по группам строк, имеющих одинаковые значения в одном или более полях.

Множественные операции

Операции UNION, INTERSECT и MINUS комбинируют в один результаты двух операторов SELECT. UNION показывает результаты обоих компонент, исключая дублирующиеся; INTERSECT показывает только строки, возвращенные обоими запросами; MINUS показывает строки, возвращенные первым запросом после удаления строк, возвращенных вторым запросом. Число и типы столбцов, возвращаемых обеими компонентами, должно быть одинаковым, идентичность размерности необязательна.

Если таким способом комбинируется более двух запросов, смежные пары элементов рассматриваются сверху вниз. Для изменения порядка можно воспользоваться скобками. Все три операции подразумевают во всех SELECT фразы DISTINCT.

ORDER BY

Фраза ORDER BY указывает порядок, в котором будут возвращаться строки.

FOR UPDATE OF

Фраза FOR UPDATE блокирует выбранные строки.

Обычно за оператором SELECT ... FOR UPDATE следует один или несколько операторов UPDATE ...WHERE.

Когда вы заблокировали строку, другие пользователи не смогут сделать то же, пока вы не выполните команды COMMIT или ROLLBACK. (Другие пользователи увидят выполненные вами изменения только после выполнения вами оператора COMMIT).

Фраза FOR UPDATE означает, что вы намереваетесь изменить строки, выбранные, но не требует обязательного изменения. (Термин «изменить» включает в себя операции UPDATE, DELETE и INSERT).

SELECT ... FOR UPDATE нельзя использовать совместно с:

u DISTINCT или GROUP BY

u UNION, INTERSECT или MINUS

u групповыми функциями вроде COUNT и MAX.

Фраза NOWAIT заставляет SELECT завершиться, а не ожидать, если строки, выбираемые с помощью SELECT FOR UPDATE, кем-либо уже используются.

Последовательности

Оператор SELECT может ссылаться также на псевдостолбцы последовательностей NEXTVAL и CURRVAL.

Примеры

Чтобы показать ENAME, SAL, JOB и DEPTNO всех строк таблицы EMP для Отдела номер 30 и меньше, введите:

SELECT ENAME, SAL, JOB, DEPTNO

FROM EMP

WHERE DEPTNO <= 30

SELECT (Встроенный SQL)

Назначение

Выбирает строки из указанной таблицы. Возвращаемые значения назначаются выходным host-переменным.

Предпосылки

Необходимо быть владельцем таблицы или иметь к ней привилегию SELECT, или иметь привилегию DBA.

Синтаксис

EXEC SQL [AT dbname] [FOR :host_integer]

SELECT select_list

INTO :host_variable [,:host_variable]...

FROM table_list

[WHERE condition ]

[CONNECT BY condition [START WITH condition]]

[GROUP BY expr[,expr]...[HAVING condition]]

[ {UNION|INTERSECT|MINUS} SELECT ...]

[ORDER BY {expr|position} [ASC|DESC]

[,{expr|position} [ASC|DESC] ] ]...

[FOR UPDATE OF column [,column]...[NOWAIT] ]

Ключевые слова и параметры

AT dbname

Необязательная фраза, ссылающаяся на предшествующий оператор CONNECT. Это имя базы данных должно быть предварительно определено оператором DECLARE DATABASE.

FOR :host_integer

Ограничивает количество вбираемых строк.

select_list

Идентично обычному оператору SELECT за исключением возможности использования host-переменной везде, где может использоваться константа.

INTO

Представляет список выходных host-переменных. Эти переменные определяются как обычные переменные процедурного языка и являются либо ранее определенными, либо явно определенными пользователем для использования в SQL-операторе. Если какая-либо из host-переменных в списке INTO является массивом, массивами должны быть и все остальные, не обязательно одинаковой размерности.

FROM

Определяется идентично невстроенному оператору SELECT. В качестве имен таблиц нельзя использовать host-переменные.

WHERE

Может содержать ссылки на host- переменные, но не на массивы.

condition

Логическое выражение, содержащее ссылки на host-переменные или массивы.

Все ключевые слова и параметры идентичны невстроенному оператору SELECT.

Наиболее часто встроенный оператор SELECT используется для выборки одной строки, однако при использовании массивов host-переменных в качестве мишеней возможна и выборка нескольких строк за одно выполнение оператора FETCH.

Если не возвращается ни одной строки, оператор SELECT записывает значение +100 в sqlcode и не меняет значений host-переменных. Для передачи управления по условию NOT FOUND (+100) можно воспользоваться оператором WHENEVER.

Оператор SELECT может использоваться операторах DECLARE CURSOR или PREPARE вместе с последующим оператором FETCH. В этих случаях он может включать фразу FOR UPDATE OF:

EXEC SQL DECLARE c1 CURSOR

FOR SELECT column [,colunmn,...]

FROM table|vie [,table|view ] [,...]

WHERE search_condition

FOR UPDATE OF column [,column] [,...]

На имена столбцов затем можно сослаться во фразе CURRENT OF оператора UPDATE, который следует за оператором FETCH.

Примеры

Следующий пример иллюстрирует использование встроенного оператора SELECT:

EXEC SQL SELECT ENAME, SAL+100, JOB

INTO :ENAME, :SAL, :JOB

FROM EMP

WHERE EMPNO = :EMPNO;

Следующий пример показывает использование оператора SELECT:

EXEC SQL INSERT INTO EMP (ENAME,DEPTNO)

SELECT * FROM NEWHIRES

SET TRANSACTION

Назначение

Устанавливает текущую транзакцию как READ ONLY.

Предпосылки

Нет.

Синтаксис

SET TRANSACTION {READ ONLY}

Ключевые слова и параметры

READ ONLY

Является необходимым параметром.

По умолчанию все транзакции устанавливаются для поддержания согласованного чтения на уровне операторов. Оператор SET TRANSACTION READ ONLY может явно устанавливать согласованность чтения на уровне транзакции. Как только READ ONLY установлено, все последующие операторы данной транзакции могут видеть изменения, записанные до начала данной транзакции. Функционально это очень полезно для отчетов, выдающих много запросов по одной или нескольким таблицам в то время как остальные пользователи могут эти таблицы модифицировать.

Оператор SET TRANSACTION должен быть первым оператором в транзакции; если это не так, будет выдана ошибка. Данный оператор может быть выдан в транзакции только однажды. Использование READ ONLY не оказывает никакого влияния на других пользователей или транзакции.

В транзакции READ ONLY разрешены только запросы, операции UPDATE, DELETE и INSERT — недопустимы.

Состояние согласованности чтения будет продолжаться до окончания транзакции, другими словами — до выдачи операторов COMMIT или ROLLBACK. Как указывалось выше, операторы DDL неявно выдают COMMIT, следовательно они также будут завершать транзакцию READ ONLY. Для всех DDL-операторов пользователь не оповещается о неявных операциях COMMIT, но тем не менее — начинается новая транзакция. Вы должны явно завершать свои транзакции, а не полагаться на их неявное завершение DDL-операторами.

Согласованность чтения для транзакций READ ONLY реализуется точно так же, как и для согласованности на уровне операторов. Каждый оператор по умолчанию видит согласованные данные на момент его начала. Транзакции типа READ ONLY обеспечивают согласованный взгляд на данные с момента выдачи оператора SET TRANSACTION.

Когда над данными таблицы производятся изменения, запись об этих изменениях производится в сегментах отката базы данных. Когда оператору встречается блок данных, измененный с момента его начала, СУБД реконструирует данные, существовавшие до выполнения изменений. Перед началом выполнения длинных транзакций типа READ ONLY, работающих с часто меняющимися таблицами, имеет смысл увеличить число сегментов отката для поддержания таких транзакций; эти действия может выполнить DBA.

Фраза STORAGE

Назначение

Специфицирование характеристик памяти для различных объектов базы данных, включая таблицы, индексы и кластеры. Некоторые SQL-операторы содержат фразы по определению памяти, обычно предваряемые ключевыми словами STORAGE или DEFAULT STORAGE. К этим операторам относятся следующие: CREATE TABLESPACE, CREATE TABLE, CREATE CLUSTER, CREATE INDEX, CREATE ROLLBACK SEGMENT, а также соответствующие операторы ALTER.

Предпосылки

Нет.

Синтаксис

STORAGE (

[ INITIAL integer ] [ NEXT integer ]

[ MINEXTENTS integer ] [ MAXEXTENTS integer ]

[ PCTINCREASE integer ] )

Ключевые слова и параметры

INITIAL

Размер в байтах первого экстента, параметры распределяемого при создании объекта — исходная память для объекта.

По умолчанию — начальный размер экстента 10240 байтов. Минимальное значение 4096 байтов, а максимальное 4095 мегабайтов. Все значения округляются к 1024 байтам (одному килобайту).

NEXT

Размер в байтах каждого последующего распределяемого экстента. Умолчание 10240 байтов, минимум — 2048 байтов, максимум — 4095 мегабайтов. Все значения округляются до 1 Килобайта.

Это базовое значение, которое может оставаться неизменным, а может меняться для каждого следующего экстента в зависимости от значения параметра PCTINCREASE. Если последний равен 0, каждый следующий экстент будет иметь одинаковый размер, равный NEXT. Если PCTINCREASE положителен, каждый последующий сегмент будет на такое количество процентов больше предыдущего.

MAXEXTENTS

Общее количество экстентов, включая первый, которые могут быть распределены. По умолчанию 99 экстентов.

MINEXTENTS

Общее количество экстентов, которое должно быть распределено при создании сегмента. Это позволяет распределять первоначально большее количество памяти даже если нет непрерывного участка соответствующего размера. Умолчание 1, что означает первоначальное распределение только одного экстента. Если MINEXTENTS больше 1, в момент выполнения оператора CREATE распределяется указанное количество экстентов, основываясь на значениях параметров INITIAL, NEXT, PCTINCREASE.

PCTINCREASE

Процент, на который будет возрастать каждый распределяемый экстент по сравнению с предыдущим. Если параметр PCTINCREASE равен нулю, все экстенты будут одинакового размера.

По умолчанию PCTINCREASE равен 50. Это значение позволяет увеличиваться каждому следующему экстенту, что в конечном счете приводит к уменьшению общего числа экстентов и, следовательно, снижению ввода/вывода. Каждый раз, когда вычисляется NEXT, он будет расти на PCTINCREASE. При умножении размера предыдущего блока на PCTINCREASE результат округляется до ближайшего числа, кратного размеру блока.

PCTINCREASE не может быть отрицательным.

Рассмотренные параметры влияют как на скорость доступа к данным в базе, так и на эффективность использования памяти.

Когда параметр STORAGE используется в предложении ALTER, задаваемые им значения влияют лишь на будущие экстенты.

UPDATE

Назначение

Изменение данных в указанной таблице.

Предпосылки

Вы должны быть владельцем таблицы или иметь для нее привилегию UPDATE, или иметь привилегию DBA.

Синтаксис

UPDATE [user.]table [alias]

SET column = expr [,column = expr]...

[WHERE condition]

или

UPDATE [user.]table [alias]

SET (column [,column]...) = (query)

[,(column [,column]...) = (query) ] ...

[WHERE condition]

Ключевые слова и параметры

user

Имя владельца таблицы.

table

Имя существующей таблицы, к которой у вас есть доступ.

alias

Используется для переименования таблицы для ссылок в последующих фразах оператора.

column

Столбец, принадлежащий таблице. Если он единственный, круглые скобки не обязательны.

query

Оператор SELECT, не содержащий фраз ORDER BY и FOR UPDATE, обычно коррелированный с подлежащей модификации таблицей.

condition

Правильное условие, имеющее значение TRUE или FALSE.

Фраза SET определяет столбцы, подлежащие изменению и заносимые в них значения. Фраза WHERE определяет строки, чьи значения должны изменяться; если она отсутствует, изменению подлежат все строки таблицы. Для каждой строки, удовлетворяющей условию WHERE столбцы слева от знака равенства получают значения выражений справа от равенства. Выражения вычисляются только для строк подлежащих модификации.

Запрос (query)

Если фраза SET содержит запрос, последний должен возвращать в точности одну строку для каждой изменяемой строки. Каждое значение из результата запроса назначается соответственно столбцу из заключенных в круглые скобки. Если запрос не возвращает строк, присеваемое (т.е. SET value=query) значение будет NULL. Подзапросы могут выбирать строки из таблицы, которая меняется. Во фразе SET можно смешивать значения, возвращаемые подзапросами и назначаемые выражениями. Оператор UPDATE автоматически выдает исключительные блокировки для строк или таблиц, подлежащих изменению. Эти блокировки освобождаются после выдачи команд COMMIT или ROLLBACK.

Коррелированные изменения

Если запрос выбирает строки из таблицы подлежащей модификации, СУБД будет выполнять запрос для каждой строки (а не один раз на всю операцию UPDATE). Такой тип модификации называется «коррелированными изменениями». Ссылка на столбцы изменяемой таблицы обычно выполняется с помощью алиаса таблицы. Потенциально каждая строка, участвующая в операторе UPDATE, может быть изменена на другое значение в соответствии с коррелированным подзапросом. Нормально операторы UPDATE будут менять строку на то же самое значение.

UPDATE (Встроенный SQL)

Назначение

Изменение данных в указанной таблице.

Предпосылки

Вы должны быть владельцем таблицы или иметь для нее привилегию UPDATE, или иметь привилегию DBA.

Синтаксис

EXEC SQL [AT dbname] [FOR :host_integer

UPDATE [user.]table

SET column [,column]...=({value[,value]...|query})

[WHERE {condition|CURRENT OF cursor_name}]

или

EXEC SQL [AT dbname] [FOR :host_integer

UPDATE [user.]table

SET column1 ={(value)|query}

[,SET column1 ={(value)|query} ]...

WHERE {search_condition|CURRENT OF cursor_name}]

Ключевые слова и параметры

AT dbname

Специфицирует имя неумалчиваемой базы данных.

FOR :host_integer

Ограничивает число одновременно обрабатываемых элементов массива за одну операцию UPDATE.

table

Имя изменяемой таблицы.

column

Перечисляются имена одного или более столбцов, для которых должны быть обеспечены значения. Каждый столбец должен принадлежать таблице, указанной во фразе INTO.

value

Определяется как литерал или выражение.

query

Форма оператора SELECT без фразы INTO.

WHERE clause

Условие поиска для подлежащих модификации строк. Если эта фраза опущена, изменяются все строки таблицы или обзора.

condition

Логическое выражение, содержащее ссылки на host-переменные или массивы. Условие поиска может содержать в себе запрос.

CURRENT OF

Ссылается на последнюю строку, обработанную по команде FETCH, связанной с указанным курсором.

Действуют следующие ограничения:

u Если фраза CURRENT OF описана совместно с UPDATE, курсор должен объявляться с использованием SELECT ... FOR UPDATE.

u курсор должен быть открыт и позиционирован на строку.

u если не выполнялось FETCH или курсор не был открыт, фраза CURRENT OF не возвращает строк.

Если host-переменные являются массивами, оператор UPDATE выполняется один раз для каждого набора компонент. Каждое выполнение оператора UPDATE может изменить ноль, одну или более строк. Если какая-либо из host-переменных, появляющаяся во фразе WHERE — массив, массивами должны быть все host-переменные как во фразе WHERE, так и во фразе SET. Эти массивы могут быть различной размерности, а число обрабатываемых зараз строк определяется минимальной размерностью или значением во фразе FOR (если оно меньше минимальной размерности). Если фраза WHERE опущена, выставляется флажок предупреждения в 5 компоненте SQLCA (sqlwarn). Если ни одна строка не удовлетворяет условию поиска (search_condition), в SQLCODE будет записано условие NOT FOUND. Общее число модифицированных строк также возвращается в SQLCA. Когда в качестве входных host-переменных используются массивы, счетчик отражает общее количество изменений для всех компонент массива, обработанных оператором UPDATE.

VALIDATE INDEX

Назначение

Верификация целостности указанного индекса.

Предпосылки

Необходимо быть владельцем индекса, либо иметь привилегию DBA.

Синтаксис

VALIDATE INDEX [user.]index

Ключевые слова и параметры

user

Владелец индекса.

index

Имя проверяемого индекса. В то время как этом оператор проверяет, что каждое значение индекса указывает на правильный блок данных, он не проверяет того, что каждая строка таблицы имеет соответствующий элемент индекса. СУБД утвердительно сообщит, если индекс правильный; если это не так, необходимо отменить индекс и пересоздать его заново.

Пример

Для проверки индекса с именем ROSEBUD введите:

VALIDATE INDEX ROSEBUD

WHENEVER (Встроенный SQL)

Назначение

Специфицирует, каким образом обрабатываются ошибки и предупреждения. В общем случае, WHENEVER должен быть первым в программе выполнимым оператором. Это гарантирует, что все возникшие ошибки будут обработаны.

Предпосылки

Нет.

Синтаксис

EXEC SQL WHENEVER {NOT FOUND|SQLERROR|SQLWARNING}

{CONTINUE | GOTO label_name | STOP}

Ключевые слова и параметры

NOT FOUND

Идентифицирует любую особую ситуацию, возвращающую код +100 параметры.

SQLERROR

Идентифицируют ситуацию, возвращающую отрицательный код возврата.

SQLWARNING

Идентифицирует предупреждение, означающее не фатальную ошибку.

CONTINUE

Указывает, что программа должна выполнять следующий оператор.

GOTO label_name

Указывает, что программа должна перейти на указанную метку label_name.

STOP

Не выполняет дополнительных действий.

Оператор WHENEVER будет встраиваться в оператор IF для проверки указанного условия после каждого выполнимого оператора EXEC SQL.

Область видимости оператора WHENEVER базируется исключительно на его позиции в программном коде и не касается правил видимости соответствующего языка программирования. Однако метка, специфицированная в операторе WHENEVER, должна быть видима в программе.

Оператор WHENEVER остается в силе, пока не будет перекрыт другим оператором WHENEVER.

Обработка ошибки в самом операторе WHENEVER с целью избежания зацикливания выполняется следующим образом: WHENEVER SQLERROR CONTINUE.

Оператор WHENEVER в основном используется для специфицирования действий, выполняемых при:

u выборке последней строки в запросе

u если INSERT INTO...SELECT... не вставил ни одной строки

Приложения

Словарь основных понятий и терминов

Автоматизированное рабочее место (АРМ, рабочая станция)

Место оператора, оборудованное всеми средствами, необходимыми для выполнения определенных функций. В системах обработки данных и учреждениях обычно АРМ — это дисплей с клавиатурой, но может использоваться также и принтер, внешние ЗУ и др.

Автоматизированные обучающие системы (АОС)

Комплексы программно-технических и учебно-методических средств, обеспечивающих активную учебную деятельность: обучение конкретным знаниям, проверку ответов учащихся, возможность подсказки, занимательность изучаемого материала.

Автоматизированные системы научных исследований (АСНИ)

Предназначены для автоматизации научных экспериментов, а также для осуществления моделирования исследуемых объектов, явлений и процессов, изучение которых традиционными средствами затруднено или невозможно.

Адаптер

Устройство связи компьютера с периферийными устройствами.

Адрес

Номер конкретного байта оперативной памяти компьютера.

Алгебра логики

Раздел математики, изучающий высказывания, рассматриваемые со стороны их логических значений (истинности или ложности) и логических операций над ними.

Алгоpитм

Заранее заданное понятное и точное предписание возможному исполнителю совершить определенную последовательность действий для получения решения задачи за конечное число шагов.

Алфавит

Фиксированный для данного языка набор основных символов, т.е. «букв алфавита», из которых должен состоять любой текст на этом языке. Никакие другие символы в тексте не допускаются.

Антивирусные программы

Программы, предотвращающие заражение компьютерным вирусом и ликвидирующие последствия заражения.

Арифметико-логическое устройство (АЛУ)

Часть процессора, которая производит выполнение операций, предусмотренных данным компьютером.

Архитектура фон Неймана

Архитектура компьютера, имеющего одно арифметико-логическое устройство, через которое проходит поток данных, и одно устройство управления, через которое проходит поток команд.

Архитектура компьютера

Логическая организация, структура и ресурсы компьютера, которые может использовать программист. Определяет принципы действия, информационные связи и взаимное соединение основных логических узлов компьютера.

Ассемблер

См. Язык ассемблера.

ASCII

Читается «аски». Американский стандартный код обмена информацией. Широко используется для кодирования в виде байта букв, цифр, знаков операций и других компьютерных символов.

Аудиоадаптер (Sound Blaster, звуковая плата)

Специальная электронная плата, которая позволяет записывать звук, воспроизводить его и создавать программными средствами с помощью микрофона, наушников, динамиков, встроенного синтезатора и другого оборудования.

База данных

Один или несколько файлов данных, предназначенных для хранения, изменения и обработки больших объемов взаимосвязанной информации.

Байт

Группа из восьми битов, рассматриваемая при хранении данных как единое целое.

Библиотека стандартных подпрограмм

Совокупность подпрограмм, составленных на одном из языков программирования и удовлетворяющих единым требованиям к структуре, организации их входов и выходов, описаниям подпрограмм.

Бит

Наименьшая единица информации в цифровом компьютере, принимающая значения «0» или «1».

Ввод

Считывание информации с внешнего устройства в память компьютера.

Вентиль

См. Логический элемент.

Вещественное число

В информатике — тип данных, содержащий числа, записанные с десятичной точкой и (или) с десятичным порядком.

Видеоадаптер

Электронная плата, которая обрабатывает видеоданные (текст и графику) и управляет работой дисплея. Содержит видеопамять, регистры ввода-вывода и модуль BIOS. Посылает в дисплей сигналы управления яркостью лучей и сигналы развертки изображения.

Винчестер

См. Накопитель на жестких магнитных дисках.

Вирус компьютерный

Специально написанная небольшая программа, которая может «приписывать» себя к другим программам для выполнения каких-либо вредных действий — портит файлы, «засоряет» оперативную память.

Внешняя память

Совокупность запоминающих устройств для длительного хранения данных. В состав внешней памяти входят накопители на гибких и жестких магнитных дисках, оптические и магнито-оптические накопители, накопители на магнитной ленте. Во внешней памяти обычно хранятся архивы программ и данных. Информация, размещенная на внешних носителях, не зависит от того, включен или выключен компьютер.

Второе поколение компьютерной техники

Машины, созданные в 1955-65 гг. Элементная база — дискретные транзисторные логические элементы. Оперативная память на магнитных сердечниках. Высокопроизводительные устройства работы с магнитными лентами, магнитные барабаны и диски. Быстродействие — до сотен тысяч операций в секунду, емкость памяти — до нескольких десятков тысяч слов. Языки высокого уровня, широкий набор библиотечных программ, мониторные системы, управляющие режимом трансляции и исполнения программ.

Вывод

Результаты работы программы, выдаваемые компьютером пользователю, другому компьютеру или во внешнюю память.

Выpажение

В языке программирования — запись правила для вычисления некоторого значения. Строится из констант, пеpеменных и указателей функций, объединенных знаками опеpаций.

Гибкий (флоппи) диск

Круглая пластиковая пластина, покрытая с обеих строн магнитным окислом и помещенная в защитную оболочку. Используется как носитель небольших объемов информации.

Глобальная сеть (ГВС)

См. Сеть компьютерная.

Графический редактор

Программа или комплекс программ, позволяющих создавать и редактировать изображения на экране компьютера: рисовать линии, раскрашивать области экрана, создавать надписи различными шрифтами, обрабатывать изображения, полученные с помощью сканеров. Некоторые редакторы обеспечивают возможность получения изображений трехмерных объектов, их сечений и разворотов.

Графопостроитель

Устройство для вывода из компьютера информации в виде графиков и чертежей на неподвижную или вращающуюся на барабане бумагу.

Джойстик

Стержень-ручка, отклонение которой от вертикального положения приводит к передвижению курсора в соответствующем направлении по экрану дисплея. Часто применяется в компьютерных играх.

Диск

Круглая металлическая или пластмассовая пластина, покрытая магнитным материалом, на которую информация наносится в виде концентрических дорожек, разделенных на секторы.

Дисковод

Устройство, управляющее вращением магнитного диска, чтением и записью данных на нем.

Дисплей

Устройство визуального отображения информации (в виде текста, таблицы, рисунка, чертежа и др.) на экране электронно-лучевого прибора.

Драйверы

Программы, расширяющие возможности операционной системы по управлению устройствами ввода-вывода, оперативной памятью и т.д.; с помощью драйверов возможно подключение к компьютеру новых устройств или нестандартное использование имеющихся устройств.

Идентификатор

Символическое имя переменной, которое идентифицирует ее в программе.

Инструментальные программные средства

Программы, используемые в ходе разработки, корректировки или развития других программ: редакторы, отладчики, вспомогательные системные программы, графические пакеты и др. По назначению близки системам программирования.

Интегральная схема

Реализация электронной схемы, выполняющей некоторую функцию, в виде единого полупроводникового кристалла, в котором изготовлены все компоненты, необходимые для осуществления этой функции.

Интегрированные пакеты программ

Пакеты программ, выполняющие ряд функций, для которых ранее создавались специализированные программы — в частности, текстовые редакторы, электронные таблицы, системы управления базами данных, программы построения графиков и диаграмм.

Интернет

Гигантская всемирная компьютерная сеть, объединяющая десятки тысяч сетей всего мира. Ее назначение — обеспечить любому желающему постоянный доступ к любой информации. Интернет предлагает практически неограниченные информационные ресурсы, полезные сведения, учебу, развлечения, возможность общения с компетентными людьми, услуги удаленного доступа, передачи файлов, электронной почты и многое другое. Интернет обеспечивает принципиально новый способ общения людей, не имеющий аналогов в мире.

Интерпретатор

Разновидность транслятора. Переводит и выполняет программу с языка высокого уровня в машинный код строка за строкой.

Интерфейс

Электронная схема сопряжения двух устройств, обменивающихся информацией.

Информатизация общества

Организованный социально-экономический и научно-технический процесс создания оптимальных условий для удовлетворения информационных потребностей и реализации прав граждан, органов государственной власти, органов местного самоуправления организаций, общественных объединений на основе формирования и использования информационных ресурсов.

Инфоpматика

Дисциплина, изучающая структуру и общие свойства информации, а также закономерности и методы ее создания, хранения, поиска, преобразования, передачи и использования в различных сферах человеческой деятельности. За понятием «информатика» закреплены области, связанные с разработкой, созданием, использованием и материально-техническим обслуживанием систем обработки информации, включая компьютеры и их программное обеспечение, а также организационные, коммерческие, административные и социально-политические аспекты компьютеризации — массового внедрения компьютерной техники во все области жизни людей. Информатика в самом своем существе базируется на компьютерной технике.

Информационная технология

Совокупность методов и устройств, используемых людьми для обработки информации. Охватывает всю вычислительную технику, технику связи и, отчасти, — бытовую электронику, телевизионное и радиовещание.

Информационно-поисковая система (ИПС)

Система, выполняющая функции хранения большого объема информации, быстрого поиска требуемой информации, добавления, удаления и изменения хранимой информации, вывода ее в удобном для человека виде.

Информация

Сведения об объектах и явлениях окружающей среды, их параметрах, свойствах и состоянии, которые воспринимают информационные системы (живые организмы, управляющие машины и др.) в процессе жизнедеятельности и работы. Применительно к обработке данных на компьютерах — произвольная последовательность символов, несущих смысловую нагрузку.

Искусственный интеллект (ИИ)

Дисциплина, изучающая возможность создания программ для решения задач, которые требуют определенных интеллектуальных усилий при выполнении их человеком. Примерами областей использования ИИ являются: игры, логический вывод, обучение, понимание естественных языков, формирование планов, понимание речи, доказательство теорем и визуальное восприятие.

Исполнитель алгоритма

Человек или автомат (в частности, процессор компьютера), умеющий выполнять определенный набор действий. Исполнителя хаpактеpизуют сpеда, элементаpные действия, система команд, отказы.

Итерационный цикл

Вид цикла, для которого число повторений операторов тела цикла заранее неизвестно. На каждом шаге вычислений происходит последовательное приближение и проверка условия достижения искомого результата. Выход из цикла осуществляется в случае выполнения заданного условия.

Каталог (директория, папка)

Оглавление файлов. Доступен пользователю через командный язык операционной системы. Его можно просматривать, переименовывать зарегистрированные в нем файлы, переносить их содержимое на новое место и удалять. Часто имеет иерархическую структуру.

Клавиатура компьютера

Устройство для ввода информации в компьютер и подачи управляющих сигналов. Содержит стандартный набор клавиш печатающей машинки и некоторые дополнительные клавиши — управляющую клавишу, функциональные клавиши, клавиши управления курсором и малую цифровую клавиатуру.

Клиент (рабочая станция)

Любой компьютер, имеющий доступ к услугам сервера. Клиентом также называют прикладную программу, которая от имени пользователя получает услуги сервера. См. Сервер.

Ключевое слово

Слово языка программирования, имеющее определенный смысл для транслятора. Его нельзя использовать для других целей, например, в качестве имени переменной.

Команда

Описание элементарной операции, которую должен выполнить компьютер. Обычно содержит код выполняемой операции, указания по определению операндов (или их адресов), указания по размещению получаемого результата. Последовательность команд образует программу.

Компакт-диск (CD-ROM)

Постоянное ЗУ, выполненное с использованием специальной оптической технологии. В ряду запоминающих устройств занимает место между флоппи- и жестким дисками, являясь одновременно и мобильным и очень емким.

Компилятор

Разновидность транслятора. Читает всю программу целиком, делает ее перевод и создает законченный вариант программы на машинном языке, который затем и выполняется.

Компьютер

Программируемое электронное устройство, способное обрабатывать данные и производить вычисления, а также выполнять другие задачи манипулирования символами. Основу компьютеров образует аппаратура (HardWare), построенная, в основном, с использованием электронных и электромеханических элементов и устройств. Принцип действия компьютеров состоит в выполнении программ (SoftWare) — заранее заданных, четко определенных последовательностей арифметических, логических и других операций.

Компьютеризация

Задачи массового внедрения компьютеров во все области жизни, стоящие перед странами как необходимое важное условие их прогресса и развития, а также последствия, которые будут вызваны этим массовым внедрением компьютеров. Цель компьютеризации — улучшение качества жизни людей за счет увеличения производительности и облегчения условий их труда.

Контроллер

Устройство, которое связывает периферийное оборудование или каналы связи с центральным процессором, освобождая процессор от непосредственного управления функционированием данного оборудования.

Курсор

Светящийся символ на экране дисплея, указывающий позицию, на которой будет отображаться следующий вводимый с клавиатуры знак.

Кэш

См. Сверхоперативная память.

Логический тип

Тип данных, представляемый значениями «истина» или «ложь» («да» или «нет»). Иногда также называется булевским в честь английского математика XIX века Джорджа Буля.

Логический элемент (вентиль)

Часть электронной логической схемы, выполняющая элементарную логическую функцию.

Логическое высказывание

Любoе пpедлoжение, в oтнoшении кoтopoгo мoжно oднoзначнo сказать, истиннo oнo или лoжнo.

Локальная сеть (ЛВС)

См. Сеть компьютерная.

Лэптоп (наколенник)

Портативный компьютер, по своим размерам близкий к портфелю. По быстродействию и памяти примерно соответствует настольным персональным компьютерам.

Манипуляторы (джойстик, мышь. трекболл и др.)

Специальные устройства для управления курсором.

Массив

Последовательность однотипных элементов, число которых фиксировано и которым присвоено одно имя. Компьютерный эквивалент таблицы. Положение элемента в массиве однозначно определяется его индексами.

Математическая модель

Система математических соотношений — формул, уравнений, неравенств и т.д., отражающих существенные свойства объекта.

Машинный язык

Совокупность машинных команд компьютера, отличающаяся количеством адресов в команде, назначением информации, задаваемой в адресах, набором операций, которые может выполнить машина, и др.

Меню

Выведенный на экран дисплея список различных вариантов работы компьютера, по которому можно сделать конкретный выбор.

Микропроцессор

Процессор, выполненный в виде интегральной схемы. Состоит из цепей управления, регистров, сумматоров, счетчиков команд, очень быстрой памяти малого объема.

Микрокомпьютер

Компьютер, в котором в качестве управляющего и арифметического устройства используется микропроцессор.

Модем

Устройство, обеспечивающее преобразование цифровых сигналов компьютера в переменный ток частоты звукового диапазона (модуляцию), а также обратное преобразование (демодуляцию). Используется для соединения компьютера с другими компьютерными системами через телефонную сеть.

Монитор

См. Дисплей.

Мультимедиа

Собирательное понятие для различных компьютерных технологий, при которых используется несколько информационных сред, таких, как графика, текст, видео, фотография, движущиеся образы (анимация), звуковые эффекты, высококачественное звуковое сопровождение. Мультимедиа-компьютер — это компьютер, снабженный аппаратными и программными средствами, реализующими технологию мультимедиа.

Мышь

Устройство управления курсором. Имеет вид небольшой коробки, умещающейся на ладони. Связана с компьютером кабелем. Ее движения трансформируются в перемещения курсора по экрану дисплея.

Накопитель на жестких магнитных дисках (винчестерский накопитель)

Наиболее массовое запоминающее устройство большой емкости, в котором носителями информации являются круглые алюминиевые пластины — платтеры, обе поверхности которых покрыты слоем магнитного материала. Используется для постоянного хранения больших объемов информации.

Ноутбук (блокнот)

Портативный компьютер, по своим размерам близкий к книге крупного формата. Помещается в портфель-дипломат. Обычно комплектуется модемом и снабжается приводом CD-ROM.

Оболочки

Программы, создаваемые для упрощения работы со сложными программными системами, такими, например, как операционная система DOS. Они преобразуют неудобный командный пользовательский интерфейс в дружественный графический интерфейс или интерфейс типа «меню». Оболочки предоставляют пользователю удобный доступ к файлам и обширные сервисные услуги.

Обработка информации

В информатике — любое преобразование информации из одного вида в другой, производимое по строгим формальным правилам.

Оперативная память (ОЗУ)

Быстрое запоминающее устройство не очень большого объема, непосредственно связанное с процессором и предназначенное для записи, считывания и хранения выполняемых программ и данных, обрабатываемых этими программами.

Оператор

Фраза алгоритмического языка, определяющая некоторый законченный этап обработки данных. В состав опеpатоpов входят ключевые слова, данные, выpажения и др.

Операционная система

Комплекс взаимосвязанных программ, предназначенных для автоматизации планирования и организации процесса обработки программ, ввода-вывода и управления данными, распределения ресурсов, подготовки и отладки программ, других вспомогательных операций обслуживания. Важнейшая часть программного обеспечения.

Описание

Раздел программы, идентифицирующий структуры данных, которыми должна манипулировать программа, и описывающий их типы.

Основание системы счисления

Количество различных цифр, используемых для изображения чисел в данной системе счисления.

Отладка (англ. debugging)

Этап компьютерного решения задачи, при котором происходит устранение явных ошибок в программе. Часто производится с использованием специальных программных средств — отладчиков.

Отладчик (англ. debugger)

Программа, позволяющая исследовать внутреннее поведение разрабатываемой программы. Обеспечивает пошаговое исполнение программы с остановкой после каждой оператора, просмотр текущего значения переменной, нахождение значения любого выражения и др.

Пакеты прикладных программ (ППП)

Специальным образом организованные программные комплексы, рассчитанные на общее применение в определенной проблемной области и дополненные соответствующей технической документацией.

Палмтоп (наладонник)

Самый маленький современный персональный компьютер. Умещается на ладони. Магнитные диски в нем заменяет энергонезависимая электронная память. Нет и накопителей на дисках — обмен информацией с обычными компьютерами идет по линиям связи.

Первое поколение компьютерной техники

Машины, созданные на рубеже 50-х годов. В схемах использовались электронные лампы. Набор команд небольшой, схема арифметико-логического устройства и устройства управления простая, программное обеспечение практически отсутствовало. Быстродействие 10 — 20 тысяч операций в секунду.

Переменная

Величина, значение которой может меняться в процессе выполнения программы.

Персональный компьютер

Микрокомпьютер универсального назначения, рассчитанный на одного пользователя и управляемый одним человеком.

Подпрограмма

Самостоятельная часть программы, которая создается независимо от других частей и затем вызывается по имени. Когда имя подпрограммы используется в качестве оператора программы, выполняется вся группа операторов, представляющая тело подпрограммы.

Поколения компьютеров

Условная, нестрогая классификация вычислительных систем по степени развития аппаратных и программных средств, а также способов общения с ними.

Порты устройств

Электронные схемы, содержащие один или несколько регистров ввода-вывода и позволяющие подключать периферийные устройства компьютера к внешним шинам микропроцессора. Последовательный порт обменивается данными с процессором побайтно, а с внешними устройствами — побитно. Параллельный порт получает и посылает данные побайтно.

Постоянная память (ПЗУ)

Энергонезависимое запоминающее устройство, изготовленное в виде микросхемы. Используется для хранения данных, не требующих изменения. Содержание памяти специальным образом «зашивается» в ПЗУ при изготовлении. В ПЗУ находятся программа управления работой самого процессора, программы управления дисплеем, клавиатурой, принтером, внешней памятью, программы запуска и остановки компьютера, тестирования устройств.

Прикладная программа

Любая конкретная программа, способствующая решению какой-либо задачи в пределах данной проблемной области.

Принтер

Печатающее устройство. Преобразует закодированную информацию, выходящую из процессора, в форму, удобную для чтения на бумаге.

Принцип открытой архитектуры

1. Регламентируются и стандартизируются только описание принципа действия компьютера и его конфигурация (определенная совокупность аппаратных средств и соединений между ними). Таким образом, компьютер можно собирать из отдельных узлов и деталей, разработанных и изготовленных независимыми фирмами-изготовителями.

2. Компьютер легко расширяется и модернизируется за счет наличия внутренних расширительных гнезд, в которые пользователь может вставлять разнообразные устройства, удовлетворяющие заданному стандарту, и тем самым устанавливать конфигурацию своей машины в соответствии со своими личными предпочтениями.

Принципы фон-Неймана

1.Принцип программного управления. Программа состоит из набора команд, которые выполняются процессором автоматически друг за другом в определенной последовательности.

2.Принцип адресности. Основная память состоит из перенумерованных ячеек; процессору времени доступна любая ячейка.

3.Принцип однородности памяти. Программы и данные хранятся в одной и той же памяти. Поэтому компьютер не различает, что хранится в данной ячейке памяти — число, текст или команда. Над командами можно выполнять такие же действия, как и над данными.

Программное обеспечение (SoftWare)

Совокупность программ, выполняемых компьютером, а также вся область деятельности по проектированию и разработке программ.

Прокрутка

Имитация программистом за столом выполнения программы на конкретном наборе тестовых данных.

Протокол коммуникации

Согласованный набор конкретных правил обмена информацией между разными устройствами передачи данных. Имеются протоколы для скорости передачи, форматов данных, контроля ошибок и др.

Псевдокод

Система обозначений и правил, предназначенная для единообразной записи алгоритмов. Занимает промежуточное место между естественным и формальным языками.

Регистр

Специальная запоминающая ячейка, выполняющая функции кратковременного хранения числа или команды и выполнения над ними некоторых операций. Отличается от ячейки памяти тем, что может не только хранить двоичный код, но и преобразовывать его.

Регистр команд

Регистр УУ для хранения кода команды на период времени, необходимый для ее выполнения.

Сверхоперативная память

Очень быстрое ЗУ малого объема. Используется для компенсации разницы в скорости обработки информации процессором и несколько менее быстродействующей оперативной памятью.

Семантика

Система правил истолкования отдельных языковых конструкций. Определяет смысловое значение предложений языка. Устанавливает, какие последовательности действий описываются теми или иными фразами языка и какой алгоритм определен данным текстом на алгоритмическом языке.

Сервер

Высокопроизводительный компьютер с большим объемом внешней памяти, который обеспечивает обслуживание других компьютеров путем управления распределением дорогостоящих ресурсов совместного пользования (программ, данных и периферийного оборудования). См. также Клиент.

Сеть компьютерная

Совокупность компьютеров, соединенных с помощью каналов связи и средств коммутации в единую систему для обмена сообщениями и доступа пользователей к программным, техническим, информационным и организационным ресурсам сети. По степени географического распространения сети делятся на локальные, городские, корпоративные, глобальные и др.

Локальная сеть (ЛВС) – связывает ряд компьютеров в зоне, ограниченной пределами одной комнаты, здания или предприятия.

Глобальная сеть (ГВС) — соединяет компьютеры, удаленные географически на большие расстояния друг от друга. Отличается от локальной сети более протяженными коммуникациями (спутниковыми, кабельными и др.).

Городская сеть — обслуживает информационные потребности большого города.

Синтаксис

Набор правил построения фраз языка, позволяющий определить, какие комбинации символов являются осмысленными предложениями в этом языке.

Система команд

Совокупность операций, выполняемых некоторым компьютером.

Система программирования

Система для разработки новых программ на конкретном языке программирования. Предоставляет пользователю мощные и удобные средства разработки программ: транслятор, редактор текстов программ, библиотеки стандартных программ, отладчик и др.

Система счисления

Совокупность приемов и правил, по которым записываются и читаются числа.

Система телеконференций

Основанная на использовании компьютерной техники система, позволяющая пользователям, несмотря на их взаимную удаленность в пространстве, а иногда, и во времени, участвовать в совместных мероприятиях, таких, как организация и управление сложными проектами.

Система управления базами данных (СУБД)

Система программного обеспечения, позволяющая обрабатывать обращения к базе данных, поступающие от прикладных программ конечных пользователей.

Системные программы

Программы общего пользования, выполняемые вместе с прикладными и служащие для управления ресурсами компьютера — центральным процессором, памятью, вводом-выводом.

Системы автоматизированного проектирования (САПР)

Комплексные программно-технические системы, предназначеные для выполнения проектных работ с применением математических методов. Широко используются в архитектуре, электронике, механике и др. В качестве входной информации в САПР используются технические знания специалистов, которые вводят проектные требования, уточняют результаты, проверяют полученную конструкцию, изменяют ее и т.д. В САПР накапливается информация, поступающая из библиотек стандартов (данные о типовых элементах конструкций, их размерах, стоимости и др.).

Системы деловой графики

Программные системы, позволяющие создавать различные виды графиков и диаграмм: гистограммы, круговые и секторные диаграммы и т.д.

Системы научной и инженерной графики

Программные системы, позволяющие в цвете и в заданном масштабе отображать на экране графики двумерных и трехмерных функций, заданных в табличном или аналитическом виде, системы изолиний, в том числе и нанесенные на поверхность объекта, сечения, проекции, карты и др.

Сканер

Устройство для ввода в компьютер документов — текстов, чертежей, графиков, рисунков, фотографий. Создает оцифрованное изображение документа и помещает его в память компьютера.

Сопровождение программ

Работы, связанные с обслуживанием программ в процессе их эксплуатации.

Стример

Устройство для резервного копирования больших объемов информации. В качестве носителя применяются кассеты с магнитной лентой емкостью 1—2 Гбайта и больше.

Структурное программирование

Метод разработки программ, в частности, требующий разбиения программы на небольшие независимые части (модули). Обеспечивает возможность проведения строгого доказательства правильности программ, повышает уверенность в правильности конечной программы.

Сумматор

Электронная логическая схема, выполняющая суммирование двоичных чисел.

Суперкомпьютер

Очень мощный компьютер с производительностью свыше 100 мегафлопов (1 мегафлоп — миллион операций с плавающей точкой в секунду). Представляет собой многопроцессорный и (или) многомашинный комплекс, работающий на общую память и общее поле внешних устройств. Архитектура основана на идеях параллелизма и конвейеризации вычислений.

Схема алгоритма (блок-схема)

Графическое представление алгоритма в виде последовательности блоков, соединенных стрелками.

Счетчик команд

Регистр УУ, содержимое которого соответствует адресу очередной выполняемой команды; служит для автоматической выборки команд программы из последовательных ячеек памяти.

Таблица истинности

Табличное представление логической схемы (операции), в котором перечислены все возможные сочетания значений истинности входных сигналов (операндов) вместе со значением истинности выходного сигнала (результата операции) для каждого из этих сочетаний.

Текстовый редактор

Программа для ввода и изменения текстовых данных (документов, книг, программ). Обеспечивает редактирование строк текста, контекстный поиск и замену частей текста, автоматическую нумерацию страниц, обработку и нумерацию сносок, выравнивание краев абзаца, проверку правописания слов и подбор синонимов, построение оглавлений, распечатку текста на принтере и др.

Тест

Некоторая совокупность данных для программы, а также точное описание всех результатов, которые должна выработать программа на этих данных, в том виде, как эти результаты должны быть выданы программой.

Тестирование

Этап решения задачи на компьютере, в процессе которого проверяется работоспособность программы, не содержащей явных ошибок.

Тип данных

Понятие языка программирования, определяющее структуру констант, переменных и других элементов данных, разрешенные их значения и операции, которые можно над ними выполнять.

Топология

Раздел математики, изучающий свойства фигур, не изменяющиеся при любых деформациях, производимых без разрывов и склеиваний. Понятие топологии широко используется при создании компьютерных сетей.

Топология компьютерной сети

Логический и физический способ соединения компьютеров, кабелей и других компонентов, в целом составляющих сеть. Топология характеризует свойства сетей, не зависящие от их размеров. При этом не учитывается производительность и принцип работы этих объектов, их типы, длины каналов, хотя при проектировании эти факторы очень важны. Наиболее распространенные виды топологий: линейная, кольцевая, древовидная, звездообразная, ячеистая, полносвязная.

Транслятор

Программа-переводчик. Преобразует программу, написанную на одном из языков высокого уровня, в программу, состоящую из машинных команд.

Трекболл

Устройство управления курсором. Небольшая коробка с шариком, встроенным в верхнюю часть ее корпуса. Пользователь рукой вращает шарик и перемещает, соответственно, курсор.

Третье поколение компьютерной техники

Семейства программно совместимых машин с развитыми операционными системами. Обеспечивают мультипрограммирование. Быстродействие внутри семейства от нескольких десятков тысяч до миллионов операций в секунду. Ёмкость оперативной памяти — нескольких сотен тысяч слов. Элементная база — интегральные схемы.

Триггер

Электронная схема, широко применяемая в регистрах компьютера для надежного запоминания одного бита информации. Имеет два устойчивых состояния, которые соответствуют двоичной «1» и двоичному «0».

Упаковщики (архиваторы)

Программы, позволяющие записывать информацию на дисках более плотно, а также объединять копии нескольких файлов в один архивный файл.

Устройство управления (УУ)

Часть процессора, выполняющая функции управления устройствами компьютера.

Файл

Именованная совокупность любых данных, размещенная на внешнем запоминающем устройстве и хранимая, пересылаемая и обрабатываемая как единое целое. Файл может содержать программу, числовые данные, текст, закодированное изображение и др. Имя файла регистрируется в каталоге.

Цикл

Прием в программировании, позволяющий многократно повторять одну и ту же последовательность команд (операторов).

Четвертое поколение компьютерной техники

Теперешнее поколение машин, разработанных после 1970 года. Эти компьютеры проектировались в расчете на эффективное использование современных высокоуровневых языков и упрощение процесса программирования для конечного пользователя. Элементная база — интегральные схемы. Ёмкость ОЗУ — десятки Мегабайт. Машины этого поколения представляют собой персональные компьютеры, либо многопроцессорные и (или) многомашинные комплексы, работающие на общую память и общее поле внешних устройств. Быстродействие до нескольких десятков - сотен миллионов операций в секунду.

Чувствительный экран

Позволяет осуществлять общение с компьютером путем прикосновения пальцем к определенному месту экрана монитора.

Штриховой код (бар-код)

Серия широких и узких линий, в которых зашифрован номер торгового изделия. Имеет большое распространение в организации компьютерного обслуживания торговых предприятий.

Экспертная система

Комплекс компьютерного программного обеспечения, помогающий человеку принимать обоснованные решения. Использует информацию, полученную заранее от экспертов — людей, которые в какой-либо области являются лучшими специалистами. Хранит знания об определенной предметной области. Обладает комплексом логических средств для выведения новых знаний, выявления закономерностей, обнаружения противоречий и др.

Электронный офис

Система автоматизации работы учреждения, основанная на использовании компьютерной техники.

Электронная почта

Система пересылки сообщений между пользователями вычислительных систем, в которой компьютер берет на себя все функции по хранению и пересылке сообщений.

Электронная таблица

Программа, обрабатывающая таблицы, состоящие из строк и граф, на пересечении которых располагаются клетки. В клетках содержится числовая информация, формулы или текст. Значение в числовой клетке таблицы либо записано, либо рассчитано по формуле. В формуле могут присутствовать обращения к другим клеткам.

Язык ассемблера

Система обозначений, используемая для представления в удобочитаемой форме программ, записанных в машинном коде. Перевод программы с языка ассемблера на машинный язык осуществляется специальной программой, которая называется ассемблером и является, по сути, простейшим транслятором.

Язык высокого уровня

Язык программирования, более близкий к естественному языку, чем машинный код или язык ассемблера. Каждый оператор в нем соответствует нескольким командам машинного кода или языка ассемблера.