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

У каждого языка программирования есть синтаксис и семантика. Синтаксис - это совокупность формальных правил написания про­грамм на данном языке, семантика - это смысловое значение напи­санного.

Важным элементом синтаксиса является алфавит языка, который представляет собой набор всех допустимых в языке символов.

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

Например, в Turbo Pascal используются следующие зарезервиро­ванные слова: and, asm, array, begin, case, const, constructor, destructor, div, do, downto, else, end, file, for и др.

При наборе программы эти слона отображаются на экране белым цветом, напоминая нам, что они являются ключевыми.

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

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

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

Любой язык программирования имеет целый ряд встроенных функ­ций, т.е. готовых программ, одно обращение к которым по их имени приводит к получению результата, например sin (х), cos(x), log(x) и т.д. Напомним, что функция sqrt (х) обеспечивает нахождение квадратно­го корня из указанного аргумента.

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

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


Любая программа выполняется в результате ее трансляции - пере­вода записи операторов на язык компьютера. Каждый оператор в про­грамме после трансляции будет представлен набором кодов команд. Эти команды выполняются в оперативной памяти компьютера.

Все константы и переменные размещаются в своих ячейках памяти в соответствии с присвоенными им идентификаторами - именами.

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

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

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

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

Ранее вы познакомились с основными алгоритмическими конструк­циями: линейной, разветвляющейся и циклической. Для реализации этих конструкций используются соответствующие операторы языка программирования. Синтаксис (правила) записи операторов в различ­ных языках программирования могут несколько отличаться. В связи с этим можно использовать список правил оформления операторов языка - неотъемлемую часть ПО (системы) языка программирования, представленную в разделе «Помощь». Часто в системе языка програм­мирования автоматически воспроизводится типовой оператор при наборе первых символов оператора, а пользователю требуется его под­править. Рассмотрим основные операторы, реализующие типовые ал­горитмические конструкции.

Оператор присваивания. Этот оператор работает так: результат вычисления выражения в правой части требуется присвоить в качестве значения переменной Y. Именно потому, что данный оператор выполняет функции не толь­ко вычисления, но и присваивания, в левой его части не может быть выражения, а только имя одной переменной - ячейки памяти ком­пьютера, в которую производится запись результата вычисления.

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

Оператор вывода. Этот оператор предназначен для вывода резуль­татов или на экран монитора или на принтер.

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

Оператор условного перехода передает управление только в случае истинности некоторого условия, а в противном случае - просто игно­рируется.

Смысл этого оператора состоит в том, что если условие истинно, то выполняется оператор или группа операторов, следующих за словом THEN, а если условие ложно, то выполняется оператор или группа операторов, следующих за словом ELSE (иначе). Конструкция ELSE здесь заключена в квадратные скобки. По правилам описания форматов это означает ее необязательность. В случае отсут­ствия в формате конструкции ELSE оператор выполняет также дей­ствия: если условие истинно, то выполняется оператор или группа операторов, следующих за словом THEN, а в противном случае - опе­ратор, следующий за оператором IF в программе. Если используется группа операторов, то они разделяются двоеточиями.

Для реализации циклических алгоритмических конструкций ис­пользуется оператор цикла, в языке Basic это «связка» операторов FOR и NEXT. Первый из них является начальным и главным оператором. Он открывает собой тело цикла, т. е. группу операторов, которые будут циклически выполняться фиксированное число раз.

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

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

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

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

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

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

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

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

Один и тот же язык может быть реализован нескольким способами. Это связано с тем, что теория формальных грамматик допускает различные методы разбора одних и тех же предложений. В соответствии с этим трансляторы разными способами могут получать один и тот же результат (объектную программу) по первоначальному исходному тексту. Существует несколько компиляторов языка Паскаль: Turbo Pascal, MS Pascal, Pascal with Objects, Delphi, Builder. Вместе с тем, все языки программирования обладают рядом общих характеристик и параметров. Эта общность определяет и схожие для всех языков принципы организации трансляторов.

Для любого языка его создателями определяются:

Множество символов, которые можно использовать для записи правильных программ (алфавит);

Множество правильных программ (синтаксис);

- "смысл" каждой правильной программы (семантика).

Рассмотрим пример синтаксического разбора. Пусть в исходном тексте программы встретилась формула a + (b + c) * d. В большинстве языков программирования такая формула определяет иерархию программных объектов, которую можно отобразить в виде дерева (Рис. 17.1). В кружках представлены символы, используемые в качестве элементарных конструкций, а в прямоугольниках задаются составные понятия, имеющие иерархическую и, возможно, рекурсивную структуру.

Синтаксическая структура, правильная для одного языка, может быть ошибочной для другого. Например, в языке Лисп приведенное выражение не будет распознано. Однако для этого языка корректным будет являться выражение (* (+ a b c) d).

Рис. 21.1. Дерево синтаксического разбора.

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

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

Алфавит. Синтаксис и семантика:

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

Строчные и прописные латинские буквы:

Символ подчеркивания: «_» Пробел: « » Арабские цифры от 0 до 9:

Перечень знаков операций:

Последовательность ограничителей:

Спецификаторы:

Служебные слова:

Элементарные конструкции языка Паскаль: имена, числа, строки . Именами (или идентификаторами) называются элементы языка - метки, константы, переменные, типы, процедуры, модули, функции, объекты. Имя (идентификатор) в среде Турбо Паскаль включает в себя цифры, буквы латинского алфавита, символ подчеркивания. Отсутствует различие между прописными и строчными буквами (PROGRAM, Program и program - означает одно и то же).

На первом месте в идентификаторе не может стоять цифра (т.е. 1program - не правильно, program3 иprogram2file - такие идентификаторы допускаются). Символ «_» может находиться в любой позиции (т.е. _program,program_, program_file - допустимые идентификаторы). Идентификатор может иметь неопределенную длину, однако только первые 63 символа в нем значимые. Служебные (зарезервированные) слова не могут выступать в качестве имен.

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

В исходном коде программы комментарии заключают или в фигурные скобки»{ … }», или в скобки вида «(* … *)». Комментарии могут занимать неопределенное число строк. В языке Паскаль числа чаще представляются в десятичной системе счисления (целые и действительные). Положительный знак числа не учитывается, поэтому может быть опущен. Целые числа представляются в форме без десятичной точки:

395 -67 7808 +126

А действительные представляются в форме с десятичной точкой:

597.2 1.79 -5.526 8.0004

При случае допускается возможность записи числа с использованием десятичного порядка (обозначается E):

3E09 = 3*10^9 -5.34E6 = -5.34*10^6 29.3E-29 = 29.3*10^(-29)

Я. П. – искусственный язык, разработанный человеком для облегчения процесса решения задачи с помощью ЭВМ. Этот язык позволяет либо описание алгоритма решения задачи – тогда он называется процедурно-ориентированным , либо постановку задачи в математической форме – проблемно-ориентированный.

Языки:

1. Универсальные (задачи из любой предметной области)

2. Специализированные (в узкой области)

Уровень Я.П.

1. Низкий (ассемблер, символьные ср-ва)

2. Высокий

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

По способу трансляции:

1. Компилятор

2. Интерпритатор

3. Генератор

Алфавит – фиксированный набор символов, из которых должен составляться текст.

Синтаксис – система правил, определяемых допустимые конструкции из символов алфавита. Его стараются описать формально.

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

3. Способы формального определения синтаксиса языка программирования

Синтаксис = метаязык + синтаксические диаграммы

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

Набор символов метаязыка:

< > - угловые скобки (заключают языковые конструкции)

{ } – фигурные скобки (заключенные в них понятия встречаются нуль или более раз)

– квадратные скобки (заключенное понятие может быть опущено)

Вертикальная черта (альтернатива, «или», | ), ::=, =.

Символы ЯП в метаязыке – терминальные символы.

Из символов алфавита строятся смысловые языковые конструкции. Простейшими из них являются слова языка .

Лексема – слово языка, последовательность символов, не содержащая пробелов.

Виды лексем:

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

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

3. Оператор – описание, определение, нужное для передачи информации транслятору. Оператор используется для описания действий, является единицей действия.

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



Объекты программы.

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

Выделяют следующие:

1. Константы - неизменяемые величины.

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

2. Переменные

Одним из основных понятий языка Си является объект - именованная область памяти. Частный случай объекта – переменная. Отличительная особенность переменной состоит в возможности связывать с её именем различные значения, совокупность которых определяется типом переменной. При задании значения переменной в соответствующую ей область памяти помещается код этого значения. Доступ к значению переменной наиболее естественно обеспечивает её имя, а доступ к участку памяти возможен только по его адресу. Каждая переменная перед её использованием в программе должна быть определена, т.е. для переменной должна быть выделена память. Размер участка памяти, выделяемой для переменной, и интерпретация содержимого зависят от типа, указанного в определении переменной. Определены целочисленные типы: char – целый длиной не менее 8 бит, short int – короткий целый, int - целый, long – длинный целый. Каждый из целочисленных типов может быть определен либо как знаковый signed либо как беззнаковый unsigned. Стандартом языка введены следующие вещественные типы: float – одинарной точности, double – удвоенной точности, long double – максимальной точности.

3. Функции.



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

Общий вид определения объекта

Тип имя [инициализатор];

Объекты класса static по умолчанию получают значение 0, кл. extern - не получают.

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

Понятие типа данных

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

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

Переменные можно инициализировать в месте их описаний.

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

Понятие простого типа.

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

Значение неделимо ни на какие части

Операции над всем значением в целом.

Делятся на:

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

Доопределяемые программистом - Программист сам определяет множество допустимых значений

Простыми скалярными типами, предопределенными в Си (их называют базовыми типами), являются:

– целые типы: char, int, long int;

– плавающие типы: float, double, long double.

Целый тип char используется еще и для представления значений символов (символьного типа). Целые типы имеют две формы – знаковую (signed) и беззнаковую (un-signed). В сокращенном виде signed может быть опущено.

Состоит из:

множество допустимых значений

множество допустимых операций

размер оперативной памяти, выделяемой для хранения (char - 1б, int - 2б, long - 4б)

внутреннее представление значения (целое положительное число - двоичным значением, отрицательное - двоичным дополнением и т.п.)

структура значения

Сложные типы

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

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

Сложный тип строится по следующим правилам .

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

2. Внутри сложной структуры тип всех элементов может быть: – одинаков – однородная структура, – различен – неоднородная структура.

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

4. Обращение (доступ) к элементам структуры может быть: – непосредственное (прямое) – вычисляемое (по индексу или месту в структуре) или не вычисляемое (по имени элемента); – последовательное – характерное для структур переменного раз-мера. Вид обращения определяется способом объединения компонентов в единую структуру.

5. Значение структуры может храниться либо в оперативной (внутренняя структура), либо во внешней памяти

Массивы, строки

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

Задание переменной регулярного типа (массива) имеет вид

<спецификация типа> <идентификатор> [<константное выражение>]

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

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

Идентификатор – это имя переменной типа массив.

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

Многомерный массив – это массив, элементы которого типа массив. Задание многомерного массива:

<спецификация типа> <идентификатор> []…

Здесь K1 , K2,...,Kn – константные выражения. Причем K1 задает раз-мер массива по первому измерению, K2 – по второму измерению, а Kn – по n-му измерению. Например, описание x задает массив x, состоящий из k1 элементов. Каждый элемент x имеет тип массив, состоящий из k2 элементов. Иначе можно сказать, что x – это двумерный массив (матрица), где k1 – размер по первому измерению, т.е. количество строк в двумерном массиве – матрице; k2 – размер по второму измерению, т.е. количество столбцов в матрице. Таким образом, двумерный массив рассматривается как одномерный массив, каждый элемент которого также одномерный массив. Элементы матрицы хранятся в памяти ЭВМ по строкам.

Для обращения к элементам массива необходимо указать имя массива и место (индекс) элемента в структуре: имя массива [<индекс>] или имя массива [<индекс1>][<индекс2>]…[<индекс n>] соответственно для одномерного и n-мерного массивов. Индекс задается выражением, значение которого должно быть целого типа и определяет номер компонента. Значение индекса принадлежит диапазону от нуля до размера массива, уменьшенного на единицу.

Обратиться к элементу массива можно еще одним способом – используя для этого указатель. Указатель – это переменная, значением которой является адрес другой переменной, т.е. номер (адрес) единицы памяти, которая выделена для переменной. Указатель может ссылаться только на объекты заданного типа. Имя массива является константой-указателем на первый элемент массива.

Строки. Значением «строкового» типа является последовательность символов (слово «строковый» заключено в кавычки, так как в Си явно такой тип не определен и, говоря о строковом типе, мы имеем в виду тип данных, обладающий свойствами строкового типа). «Строковый» тип (или просто строка) в Си рассматривается как подмножество типа массив. Строка задается одномерным массивом, элементы которого есть символы, последний символ массива – „\0‟. Эта «нуль-литера» является признаком конца строки. Литера „\0‟, так же как и другие символы, входит в строку. Размер строки (количество символов) определяется решаемой задачей и ограничивается доступным объемом памяти.

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

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

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

Текст можно представить:

Двумерным массивом - матрицей, строка которой это слово текста, оканчивающееся символом конца строки-‘\0’. Количество столбцов равно максимальной длине слова плюс один (символ ‘\0’). Количество строк равно максимальному числу слов в тексте. Обращение к строке матрицы это обращение к слову. Чтобы создать такую структуру, надо читать текст посимвольно, помещая каждое очередное слово в новую строку матрицы и добавляя к слову символ ‘\0’.

Одномерным массивом – строкой. Такая структура полностью соответствует внешнему представлению текста. Размер массива равен максимальной длине исходного текста с учетом

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

9. Неоднородные типы (структура)

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

Программист сам описывает неоднородный (структурный) тип, задавая его «внутреннее строение»: количество элементов, их тип и имена. Описание неоднородного типа:

struct <имя структурного типа>

{ <определения элементов> };

Здесь struct – служебное слово – спецификатор структурного типа, <имя структурного типа> – идентификатор типа, произвольно выбираемый программистом (<имя структурного типа> может быть опущено), <определения элементов> – совокупность одного или более описаний объектов, каждый из которых определяет тип элемента, вводимого структурного типа.

Определение объекта (например, переменной) именованного структурного типа имеет вид struct <имя структурного типа> < список структур>;

или <имя структурного типа> < список структур> ; где < список структур> – список выбранных программистом имен структур.

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

{ <определения элементов> } < список структур>;

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

При определении структура может быть инициализирована . При определении объекта структурного типа ему выделяется память в таком количестве, чтобы могли разместиться данные всех элементов. Размер памяти в байтах, выделенный для структуры можно получить с помощью операции sizeof, например sizeof (struct point). Для обращения к элементам структуры используется уточненное имя (первичное выражение) вида <имя структуры> . <имя элемента структуры> Здесь точка означает операцию доступа к элементу структуры, у нее самый высокий приоритет. Уточненные имена элементов структур обладают всеми правами объектов соответствующих типов. Над элементами структуры можно выполнять операции, допустимые для их типа.

Структура может быть параметром функции и возвращаемым (основным) функцией значением.

Указатель

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

спецификатор-типа [ модификатор ] * описатель.

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

В качестве модификаторов при объявлении указателя могут выступать ключевые слова const, near, far, huge. Ключевое слово const указывает, что указатель не может быть изменен в программе. Размер переменной объявленной как указатель, зависит от архитектуры компьютера и от используемой модели памяти, для которой будет компилироваться программа. Указатели на различные типы данных не обязательно должны иметь одинаковую длину.

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

Файлы.

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

Размер файла (т.е. длина последовательности) никак не оговаривается при объявлении файла и ограничивается только емкостью устройств внешней памяти. Для указания конца структуры используется признак конца файла (end of file).

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

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

Открывать и закрывать потоки;

Анализировать условие достижения конца потока (конца файла) и ошибки ввода-вывода;

Получать и устанавливать указатель текущей позиции в потоке;

Управлять буферизацией потока и размером буфера.

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

При открытии файла в текстовом режиме прочитанная из потока последовательность символов преобразуется, если это необходимо из символьного представления во внутреннее представление. Например, если при форматном вводе читается числовая информация, то происходит преобразование прочитанной последовательности символов в двоичное целое или число с плавающей точкой в соответствии со спецификацией формата; при форматном выводе числовой информации происходит преобразование из внутреннего представления числа в последовательность символов, изображающих число. Последовательность символов, хранящаяся в текстовом файле, может быть разбита на строки. При записи в текстовый поток символа новой строки ‘\n’ он заменяется последовательностью символов CR (“возврат каретки”) и LR (“перевод строки”). При

чтении из текстового файла последовательность символов CR и LR преобразуется в один символ новой строки ‘\n’.

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

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

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

Этот процесс продолжается пока не исчерпана форматная строка или не достигнут конец файла или не произошла ошибка. В первом случае функция возвращает количество объектов, получивших значение при вводе, при достижении конца файла – возвращает константу EOF, в случае ошибки – значение –1.

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

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

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

Способы задания языков

Понятие языка. Формальное определение языка

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

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

Цепочка символов a является цепочкой над алфавитомV: a(V ),если в нее входят только символы, принадлежащие множеству символов V. Для любого алфавита V пустая цепочка l может как являться, так и не являться цепочкой l (V ). Это условие оговаривается дополнительно.

Если V – некоторый алфавит, то:

V⁺ –множество всех цепочек над алфавитом V без l;

V* - множество всех цепочек над алфавитом V , включая l.

Справедливо равенство:V* =V + È{λ}

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

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

Два языка L(V) и L"(V) совпадают (эквивалентны): L’(V)=L(V), если L’(V)⊆L(V) и L(V)⊆L’(V).

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

Два языка L(V) и L’(V) почти эквивалентны, если L’(V)È{l}= L(V)È{l}. Множества допустимых цепочек символов почти эквивалентных языков могут различаться только на пустую цепочку символов.

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



Язык задать можно тремя способами:

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

2. Указанием способа порождения цепочек языка (заданием грамматики языка)

3. Определением метода распознавания цепочек языка.

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

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

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

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

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

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

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

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