МИНИСТЕРСТВО ОБРАЗОВАНИЯ И НАУКИ РФ федеральное государственное бюджетное образовательное учреждение высшего профессионального образования «Мурманский государственный гуманитарный университет» (ФГБОУ ВПО «МГГУ») УЧЕБНО-МЕТОДИЧЕСКИЙ КОМПЛЕКС ДИСЦИПЛИНЫ ОПД.Ф. 05 ЯЗЫКИ ПРОГРАММИРОВАНИЯ И МЕТОДЫ ТРАНСЛЯЦИИ ОСНОВНАЯ ОБРАЗОВАТЕЛЬНАЯ ПРОГРАММА ПОДГОТОВКИ СПЕЦИАЛИСТА ПО СПЕЦИАЛЬНОСТИ (специальностям) 010501 Прикладная математика и информатика (код и наименование специальности/тей) Утверждено на заседании кафедры математики и математических методов в экономике факультета физико-математического образования, информатики и программирования (протокол № 6 от 27 февраля 2013 г.) Зав. кафедрой _______________О.М. Мартынов РАЗДЕЛ 1. Программа учебной дисциплины. Структура программы учебной дисциплины. 1.1. Автор программы: старший преподаватель Зятикова С.Н. 1.2. Рецензенты: профессор, доктор ф.-м. наук Маренич Е.Е., кандидат ф.-м. н., доцент Маренич А.С. 1.3. Пояснительная записка: Приведенная программа написана на основе Государственного образовательного стандарта специальности 010501 – Прикладная математика и информатика. Целью преподавания этого курса является изучение формальных способов описания языков программирования; типов данных, способов и механизмов управления данными; методов и основных этапов трансляции; конструкций распределенного и параллельного программирования. Они необходимых для создания фундамента освоения новых языков программирования. Они могут понадобиться в практической и исследовательской работе по специальности. Вместе с другими предметами изучение данной дисциплины должно способствовать развитию точного научного мышления, повышению программистской культуры. В процессе изучения курса "Языки программирования и методы трансляции" студент должен получить следующие знания: 1. 2. 3. 4. Знание формальных способов описания языков программирования. Знания основных положений современной концепции типа данных. Знание методов и основных этапов трансляции. Знакомство с конструкциями распределенного и параллельного программирования. В процессе изучения курса студент должен приобрести следующие умения: 1. Умение использовать формальные способы описания языков программирования. 2. Умение применять на практике современные концепции типов данных. 3. Умение использовать конструкции распределенного и параллельного программирования. 1.4. Извлечение (в виде ксерокопии) из ГОС ВПО специальности (направления), включающее требования к обязательному минимуму содержания дисциплины и общее количество часов (выписка). ОПД.Ф.05 Языки программирования и методы трансляции: основные понятия языков программирования; синтаксис, семантика, формальные способы описания языков программирования; типы данных, способы и механизмы управления данными; методы и основные этапы трансляции; конструкции распределенного и параллельного программирования. -1- 1.5. Объем дисциплины и виды учебной работы: № п/п 1 Шифр и наименование специальности 010501 – Прикладная математика и информатика Курс Семестр Виды учебной работы в часах Трудое Всего ЛК ПР ЛБ Сам. мкость аудит. работа Вид итогового контроля 26 зачет 1 1 153 26 26 136 2 17 18 20 экзамен 20 1.6. Содержание дисциплины. 1.6.1. Разделы дисциплины и виды занятий (в часах). Примерное распределение учебного времени: № п/п 1 2 3 4 Наименование раздела, темы Количество часов Основные понятия языков программирования Основы теории формальных языков Типы данных Трансляция Всего ауд. ЛК ПР ЛБ Сам.раб. 10 6 4 – 3 68 24 34 20 4 14 22 10 10 26 10 10 6 4 4 1.6.2. Содержание разделов дисциплины. Основные понятия языков программирования. Понятие языка программирования. Эволюция языков программирования. Классификация языков программирования. Сравнительный обзор языков программирования: Фортран, Алгол-60, Кобол, Бейсик, ПЛ/I, Алгол-68, Форт, Пролог, Си, Модула-2, Ада, С++, Оберон, Оберон-2, Ява. Основы теории формальных языков. Формальные языки. Словарь, цепочка. Способы определения языка, примеры. Порождающие грамматики Н. Хомского. Порождение предложений языка. Сентенции и сентенциальные формы. Дерево разбора. Эквивалентность и однозначность грамматик. Иерархия порождающих грамматик по Н. Хомскому. Автоматные грамматики и конечные автоматы. Построение и преобразование графа переходов конечного автомата для заданной автоматной грамматики. Использование конечного автомата для распознавания автоматного языка. Синтаксические диаграммы автоматной грамматики. Регулярные выражения и регулярные множества. Эквивалентность регулярных выражений и автоматных грамматик. Контекстно-свободные грамматики. Однозначность КС-грамматики. Левосторонний и правосторонний вывод. Нисходящий и восходящий разбор КС-языков. Общий алгоритм распознавания КС-языков. Самовложение в КС-грамматиках. Синтаксический анализ КС-языков методом рекурсивного спуска. Требование детерминированного распознавания. LL(k) и LL(1) - грамматики. Левая и правая рекурсия. Грамматика и синтаксический анализ арифметических выражений. Включение действий в синтаксис. Семантические процедуры. Табличный LL(1) – анализатор. Польская запись выражений. Алгоритм вычисления выражений в польской записи. Перевод арифметических выражений в польскую запись. Метод стека с приоритетами трансляции выражений в польскую запись (алгоритм Э. Дейкстры). Интерпретация выражений. Язык программирования С++: структура программы, синтаксис и семантика, операторы. -2- Типы данных. Классификация типов данных. Простые типы данных. Преобразование типов. Структурированные типы данных, линейные списки, деревья. Иерархические структуры данных. Эквивалентность и совместимость типов. Способы и механизмы управления данными: механизмы пересылки данных, механизмы размещения данных, механизмы доступа к данным. Реализация различных типов данных в языке программирования С++. Трансляция. Лексический анализатор. Таблицы транслятора. Области действия имен. Виртуальная машина для простого языка. Трансляция описаний. Распределение памяти для переменных. Трансляция выражений. Трансляция операторов. Трансляция процедур без параметров. Трансляция процедур с параметрами-значениями и локальными переменными. Трансляция процедур-функций. Генерация кода для параметровпеременных. Трансляция линейных массивов. Язык ассемблера для виртуальной машины. Конструкция простого двухпроходного ассемблера. Автоматизация построения и мобильность трансляторов. 1.6.3. Темы для самостоятельного изучения. № п/п 1 2 3 4 Наименование раздела дисциплины. Тема. Форма самостоятельной работы Форма контроля выполнения самостоятельной работы защита рефератов Основные понятия рефераты языков программирования вопросы для выполнение тестов Основы теории формальных языков самостоятельного Типы данных Трансляция изучения вопросы для выполнение тестов самостоятельного изучения вопросы для коллоквиум самостоятельного изучения Количество часов 3 6 4 4 1.7. Методические рекомендации по организации изучения дисциплины. 1.7.1. Тематика и планы практических занятий по изученному материалу Практические занятия по теме «Основные понятия языков программирования» (4 часа) Вопросы для обсуждения: 1. Понятие языка программирования. 2. Эволюция языков программирования. 3. Классификация языков программирования. Литература: 1. Алгоритмы и программирование на алгоритмическом языке PL/1: Методические указания / Сост. Кривошеев В.А., Кирякова Г.С., Легалов А.И. Красноярск, КПИ, 1981. 2. Алиев Ю.А., Козлов О.А. Алгоритмизация и языки программирования Pascal, C++, Visual Basic: Учебно-справочное пособие. – М.: Финансы и статистика, 2002. -3- Баррон Д. Введение в языки программирования. М., Мир, 1980 Бек Л. Введение в системное программирование. М., Мир, 1988 Дал У., Дейкстра Э., Хоор К. Структурное программирование. М., Мир, 1975 Кауфман В. Ш. Языки программирования. Концепции и принципы. - М.: Радио и связь, 1993. - 432 с. 7. Кнут Д. Искусство для программирования для ЭВМ. Т2: Получисленные алгоритмы. - М.: Мир, 1977. 8. Кнут Д. Искусство для программирования для ЭВМ. Т3: Сортировка и поиск. - М.: Мир, 1978. 9. Кондратьева С.Д. Введение в структуры данных. М.: Изд-во МГТУ, 2000. 10. Основные концепции структур данных и реализация в C++: Пер. с англ. / Браунси К. - М.: Издат. дом «Вильямс», 2002. 11. Себеста У. Основные концепции языков программирования. - М.: Вильямс, 2001. 3. 4. 5. 6. Практические занятия по теме «Основы теории формальных языков» (22 часа) Вопросы для обсуждения: 1. Порождающие грамматики Н. Хомского. Порождение предложений языка. Сентенции и сентенциальные формы. 2. Дерево разбора. Эквивалентность и однозначность грамматик. 3. Построение и преобразование графа переходов конечного автомата для заданной автоматной грамматики. 4. Использование конечного автомата для распознавания автоматного языка. 5. Синтаксические диаграммы автоматной грамматики. 6. Контекстно-свободные грамматики. Однозначность КС-грамматики. Левосторонний и правосторонний вывод. Нисходящий и восходящий разбор КСязыков. Общий алгоритм распознавания КС-языков. Самовложение в КСграмматиках. 7. Синтаксический анализ КС-языков методом рекурсивного спуска. 8. Грамматика и синтаксический анализ арифметических выражений. 9. Включение действий в синтаксис. Семантические процедуры. 10. Табличный LL(1) – анализатор. Литература: 1. Рейуорд-Смит В. Дж. Теория формальных языков. Вводный курс. М., Радио и связь, 1988 2. Ахо А., Ульман Дж. Теория синтаксического анализа, перевода и компиляции. М., Мир, 1978 3. Баррон Д. Введение в языки программирования. М., Мир, 1980 4. Кнут Д. Искусство для программирования для ЭВМ. Т2: Получисленные алгоритмы. - М.: Мир, 1977. 5. Кнут Д. Искусство для программирования для ЭВМ. Т3: Сортировка и поиск. - М.: Мир, 1978. Практические занятия по теме «Типы данных» (10 часов) Вопросы для обсуждения: 1. Классификация типов данных. Простые типы данных. Преобразование типов. -4- Структурированные типы данных, линейные списки, деревья. Иерархические структуры данных. Механизмы пересылки данных. Механизмы размещения данных, механизмы динамического распределения памяти. Указатели и ссылки. 6. Механизмы доступа к данным. Идентификаторы. Индексная адресация. 7. Реализация различных типов данных в языке программирования С++. 2. 3. 4. 5. Литература: 1. Дейл, Нелл. Программирование C++: Учебник для студентов вузов и преподавателей информатики/ Ч. Уимз, М. Хедингтон. - М.: ДМК, 2000. 2. Задачи по программированию. Язык С++: учебно-методическое пособие. – Мурманск: МГПУ, 2003. – Ч.1. 3. Касаткин А.И. Системное программирование. Минск: Выш. шк., 1993. 4. Кнут Д. Искусство программирования для ЭВМ. Т.1,2. - М.: Мир, 1976. 5. Редькина А.В. Программирование на языке Турбо-Паскаль. Учеб. пособие. Красноярск: КГТУ, 1999. 6. Романов Е.Л. Практикум по программированию на С++: Уч. пособие. СПб: БХБПетербург, 2004. 7. Справочник по языку С++. – Мурманск, МГПУ. – 2003. 8. Страуструп Б. Язык программирования C++: Пер. с англ. М.: BINOM, 1999. Практические занятия по теме «Трансляция» (10 часов) Вопросы для обсуждения: 1. Лексический анализатор. Таблицы транслятора. Области действия имен. 2. Виртуальная машина для простого языка. 3. Трансляция описаний. Распределение памяти для переменных. 4. Трансляция выражений. 5. Трансляция операторов. 6. Трансляция процедур без параметров. 7. Трансляция процедур с параметрами-значениями и локальными переменными. 8. Трансляция процедур-функций. Генерация кода для параметров-переменных. 9. Трансляция линейных массивов. 10. Язык ассемблера для виртуальной машины. Конструкция простого двухпроходного ассемблера. 11. Автоматизация построения и мобильность трансляторов. Литература: 1. Ахо А., Ульман Д., Сети Р. Компиляторы: принципы, технологии, инструментарий. - М.: Вильямс, 2001. 2. Бек Л. Введение в системное программирование. М., Мир, 1988 3. Грис Д. Конструирование компиляторов для цифровых вычислительных машин. М. Мир, 1975 4. Дал У., Дейкстра Э., Хоор К. Структурное программирование. М., Мир, 1975 5. Костельцов А.В. Построение интерпретаторов и компиляторов: использование программ BIZON, BYACC, ZUBR. - М.: Наука и техника, 2001. 6. Лебедев В. Н. Введение в системы программирования. М., Статистика, 1975 -5- 7. Лингер Р., Миллс Х., Уитт Б. Теория и практика структурного программирования. М., Мир, 1982 8. Льюис Ф., Розенкранц Д., Стирнз Р. Теоретические основы проектирования компиляторов. - М.: Мир, 1979.* 9. Пратт Т. Языки программирования: разработка и реализация. М., Мир, 1979 10. Свердлов С. З. Введение в методы трансляции. Вологда, Русь, 1994 11. Себеста У. Основные концепции языков программирования. - М.: Вильямс, 2001. 12. Соммервил И. Инженерия программного обеспечения. - М.: Вильямс, 2002. 13. Язык компьютера. М., Мир, 1989 14. Свердлов С. З. Введение в методы трансляции. Вологда, Русь, 1994 1.8. Учебно-методическое обеспечение дисциплины. 1.8.1. Литература (основная, дополнительная) Основная литература. 1. Алгоритмы и программирование на алгоритмическом языке PL/1: Методические указания / Сост. Кривошеев В.А., Кирякова Г.С., Легалов А.И. Красноярск, КПИ, 1981. 2. Алиев Ю.А., Козлов О.А. Алгоритмизация и языки программирования Pascal, C++, Visual Basic: Учебно-справочное пособие. – М.: Финансы и статистика, 2002. 3. Ахо А., Ульман Д., Сети Р. Компиляторы: принципы, технологии, инструментарий. - М.: Вильямс, 2001. 4. Баррон Д. Введение в языки программирования. М., Мир, 1980 5. Бек Л. Введение в системное программирование. М., Мир, 1988 6. Грис Д. Конструирование компиляторов для цифровых вычислительных машин. М. Мир, 1975 7. Дал У., Дейкстра Э., Хоор К. Структурное программирование. М., Мир, 1975 8. Дейл, Нелл. Программирование C++: Учебник для студентов вузов и преподавателей информатики/ Ч. Уимз, М. Хедингтон. - М.: ДМК, 2000. 9. Задачи по программированию. Язык С++: учебно-методическое пособие. – Мурманск: МГПУ, 2003. – Ч.1. 10. Касаткин А.И. Системное программирование. Минск: Выш. шк., 1993. 11. Кауфман В. Ш. Языки программирования. Концепции и принципы. - М.: Радио и связь, 1993. - 432 с. 12. Кнут Д. Искусство для программирования для ЭВМ. Т3: Сортировка и поиск. - М.: Мир, 1978. 13. Кнут Д. Искусство программирования для ЭВМ. Т.1,2. - М.: Мир, 1976. 14. Кондратьева С.Д. Введение в структуры данных. М.: Изд-во МГТУ, 2000. 15. Костельцов А.В. Построение интерпретаторов и компиляторов: использование программ BIZON, BYACC, ZUBR. - М.: Наука и техника, 2001. 16. Лебедев В. Н. Введение в системы программирования. М., Статистика, 1975 17. Лингер Р., Миллс Х., Уитт Б. Теория и практика структурного программирования. М., Мир, 1982 18. Льюис Ф., Розенкранц Д., Стирнз Р. Теоретические основы проектирования компиляторов. - М.: Мир, 1979.* 19. Основные концепции структур данных и реализация в C++: Пер. с англ. / Браунси К. - М.: Издат. дом «Вильямс», 2002. 20. Пратт Т. Языки программирования: разработка и реализация. М., Мир, 1979 21. Редькина А.В. Программирование на языке Турбо-Паскаль. Учеб. пособие. Красноярск: КГТУ, 1999. 22. Рейуорд-Смит В. Дж. Теория формальных языков. Вводный курс. М., Радио и связь, 1988 -6- 23. Романов Е.Л. Практикум по программированию на С++: Уч. пособие. СПб: БХБПетербург, 2004. 24. Свердлов С. З. Введение в методы трансляции. Вологда, Русь, 1994 25. Себеста У. Основные концепции языков программирования. - М.: Вильямс, 2001. 26. Соммервил И. Инженерия программного обеспечения. - М.: Вильямс, 2002. 27. Справочник по языку С++. – Мурманск, МГТУ. – 2003. 28. Страуструп Б. Язык программирования C++: Пер. с англ. М.: BINOM, 1999. Дополнительная литература 1. Аммерааль Леен. STL для программистов на C++. Пер. с англ. - М.: ДМК, 1999 240 с., ил. 2. Ахо А., Ульман Дж. Теория синтаксического анализа, перевода и компиляции. М., Мир, 1978 3. Браун П. Макропроцессоры и мобильность программного обеспечения. - М.: Мир, 1977. 4. Буч Г. Объектно-ориентированный анализ и проектирование с примерами приложений на C++, 2-е изд./Пер. с англ. - М.: "Издательство Бином", СПб.: "Невский диалект", 1998 г. -560 с. 5. Вайнгартен Ф. Трансляция языков программирования. - М.: Мир, 1977. 6. Вирт Н. Алгоритмы + структуры данных = программы. - М.: Мир, 1985. 7. Вирт Н. Алгоритмы и структуры данных. - М.: Мир, 1989. 8. Грис Д. Наука программирования. М.: Мир, 1984. 9. Дейкстра Э. Дисциплина программирования. М.: Мир, 1978. 10. Маккиман У. Генератор Компиляторов. - М.: Статистика, 1980. 11. Сибуя М., Ямамото Т. Алгоритмы обработки данных. - М.: Мир, 1986. 12. Фостер Дж. Автоматический синтаксический анализ. - М.: Мир, 1975. 13. Хантер Р. Проектирование и конструирование компиляторов/ Пер. с англ.: - М.: Финансы и статистика, 1984. - 232 с. 14. Шишмарев А.И., Заморин А.П. Англо-русско-немецко-французский толковый словарь по вычислительной технике. М.: Издательство "Русский язык", 1978. 15. Язык компьютера. М., Мир, 1989 16. Языки программирования Ада, Си, Паскаль. Сравнение и оценка. / Под ред. Фьюэра А.Р., Джехани Н. - М.: Радио и связь, 1989. 1.9. Материально-техническое обеспечение дисциплины. 1.9.1. Перечень используемых технических средств. Персональные компьютеры на базе процессора Intel Celeron 1.10 Гц, 128 МВ ОЗУ. 1.9.2. Перечень используемых пособий. Задачи по программированию. Язык С++: учебно-методическое пособие. – Мурманск: МГПУ, 2003. – Ч.1. Справочник по языку С++. – Мурманск, МГТУ. – 2003. 1.9.3. используются авторские разработки в электронном гипертекстовом формате по лабораторным работам. 1.10. Примерные зачетные тестовые задания. Разработать программу по выполнению лексического анализа для языка программирования, удовлетворяющего ниже перечисленным требованиям, где n – последняя цифра и m – предпоследняя цифра. Упрощенный язык программирования должен обязательно включать: -7- 1. Оператор присваивания «=» и только одну из следующих арифметических операций: + – * / ++ ** %% (сложение n=0, n=7) (вычитание n=1, n=8) (умножение n=2, n=9) (деление n=3) (логическое сложение n=4) (логическое умножение n=5) (сложение по модулю 2 n=6) 2. Один из следующих операторов: оператор цикла, если m=0 или m=9, построенный в соответствии с предложенным синтаксисом конструкции: DO <имя параметра> = m,n BEGIN <тело цикла> END; оператор цикла, если m=1, либо m=8, построенный в соответствии с предложенным синтаксисом конструкции: FOR <имя параметра цикла> = m TO n <тело цикла> NEXT; оператор цикла, если m=2, либо m=7, построенный в соответствии с предложенным синтаксисом конструкции: FOR <имя параметра цикла> = m TO n DO BEGIN <тело цикла> END; условный оператор, если m=3, либо m=6, построенный в соответствии с предложенным синтаксисом конструкции: IF <имя параметра цикла> = m THEN BEGIN <операторы> END, где <условие> задается одной из форм: a<b, либо a=b, либо a>b; оператор процедуры, если m=4, либо m=5, построенный в соответствии с предложенным синтаксисом конструкции: PROCEDURE <имя процедуры> = m BEGIN <тело процедуры> для вызова процедуры используется оператор CALL <имя процедуры>; 3. Оператор вывода переменных WRITE (<список переменных через запятую>); 4. Программа языка имеет структуру VAR <список переменных через запятую>: INTEGER -8- END, BEGIN <операторы программы> END 1.11. Примерный перечень вопросов к зачету. 2. Понятие языка программирования. 3. Эволюция языков программирования. 4. Классификация языков программирования. 5. Сравнительный обзор языков программирования: Фортран, Алгол-60, Кобол, Бейсик, ПЛ/I, Алгол-68, Форт, Пролог, Си, Модула-2, Ада, С++, Оберон, Оберон-2, Ява. 6. Формальные языки. Словарь, цепочка. Способы определения языка, примеры. 7. Порождающие грамматики Н. Хомского. Порождение предложений языка. 8. Сентенции и сентенциальные формы. Дерево разбора. Эквивалентность и однозначность грамматик. 9. Иерархия порождающих грамматик по Н. Хомскому. 10. Автоматные грамматики и конечные автоматы. 11. Построение и преобразование графа переходов конечного автомата для заданной автоматной грамматики. 12. Использование конечного автомата для распознавания автоматного языка. 13. Синтаксические диаграммы автоматной грамматики. 14. Регулярные выражения и регулярные множества. Эквивалентность регулярных выражений и автоматных грамматик. 15. Контекстно-свободные грамматики. Однозначность КС-грамматики. Левосторонний и правосторонний вывод. 16. Нисходящий и восходящий разбор КС-языков. Общий алгоритм распознавания КСязыков. 17. Самовложение в КС-грамматиках. 18. Синтаксический анализ КС-языков методом рекурсивного спуска. 19. Требование детерминированного распознавания. LL(k) и LL(1) - грамматики. 20. Левая и правая рекурсия. 21. Грамматика и синтаксический анализ арифметических выражений. 22. Включение действий в синтаксис. Семантические процедуры. 23. Табличный LL(1) – анализатор. 24. Польская запись выражений. 25. Алгоритм вычисления выражений в польской записи. 26. Перевод арифметических выражений в польскую запись. 27. Метод стека с приоритетами трансляции выражений в польскую запись (алгоритм Э. Дейкстры). 28. Интерпретация выражений. 29. Классификация типов данных. Простые типы данных. Преобразование типов. 30. Структурированные типы данных. Массивы. 31. Структурированные типы данных. Структуры. 32. Структурированные типы данных. Линейные списки. 33. Структурированные типы данных. Деревья. 34. Иерархические структуры данных. 35. Эквивалентность и совместимость типов. 36. Способы и механизмы управления данными: механизмы пересылки данных, механизмы размещения данных, механизмы доступа к данным. 37. Реализация различных типов данных в языке программирования С++. 38. Лексический анализатор. Таблицы транслятора. Области действия имен. 39. Виртуальная машина для простого языка. 40. Трансляция описаний. Распределение памяти для переменных. 41. Трансляция выражений. 42. Трансляция операторов. -9- Трансляция процедур без параметров. Трансляция процедур с параметрами-значениями и локальными переменными. Трансляция процедур-функций. Генерация кода для параметров-переменных. Трансляция линейных массивов. Язык ассемблера для виртуальной машины. Конструкция простого двухпроходного ассемблера. 49. Автоматизация построения и мобильность трансляторов. 43. 44. 45. 46. 47. 48. 1.12. Комплект экзаменационных билетов. ФЕДЕРАЛЬНОЕ АГЕНТСТВО ПО ОБРАЗОВАНИЮ Государственное образовательное учреждение высшего профессионального образования «Мурманский государственный педагогический университет» (МГПУ) Кафедра: алгебры, геометрии и прикладной математики Наименование дисциплины: Языки программирования и методы трансляции,1 курс, ПМИ, 2 семестр Экзаменационный билет №1 1.Определение системы программирования (СП). Виды СП. Варианты основных компонентов СП. Основные характеристики языков программирования. 2. Стратегия нисходящего анализа. Зав. кафедрой АГ и ПМ Е.Е. Маренич Декан ФПМПЭ Е.Е. Маренич Утверждено на заседании кафедры. Протокол № 9 от 30.05.2007 г. ФЕДЕРАЛЬНОЕ АГЕНТСТВО ПО ОБРАЗОВАНИЮ Государственное образовательное учреждение высшего профессионального образования «Мурманский государственный педагогический университет» (МГПУ) Кафедра: алгебры, геометрии и прикладной математики Наименование дисциплины: Языки программирования и методы трансляции,1 курс, ПМИ, 2 семестр Экзаменационный билет №2 1. Определение языков посредством множеств. 2. Устранение цепных правил. Зав. кафедрой АГ и ПМ Е.Е. Маренич Декан ФПМПЭ Е.Е. Маренич Утверждено на заседании кафедры. Протокол № 9 от 30.05.2007 г. ФЕДЕРАЛЬНОЕ АГЕНТСТВО ПО ОБРАЗОВАНИЮ Государственное образовательное учреждение высшего профессионального образования «Мурманский государственный педагогический университет» (МГПУ) Кафедра: алгебры, геометрии и прикладной математики Наименование дисциплины: Языки программирования и методы трансляции,1 курс, ПМИ, - 10 - 2 семестр Экзаменационный билет №3 1. Понятие о формальной грамматике. 2. Устранение левой рекурсии. Зав. кафедрой АГ и ПМ Е.Е. Маренич Декан ФПМПЭ Е.Е. Маренич Утверждено на заседании кафедры. Протокол № 9 от 30.05.2007 г. ФЕДЕРАЛЬНОЕ АГЕНТСТВО ПО ОБРАЗОВАНИЮ Государственное образовательное учреждение высшего профессионального образования «Мурманский государственный педагогический университет» (МГПУ) Кафедра: алгебры, геометрии и прикладной математики Наименование дисциплины: Языки программирования и методы трансляции,1 курс, ПМИ, 2 семестр Экзаменационный билет №4 1. Определение формальной грамматики. 2. Определение автомата с магазинной памятью. Зав. кафедрой АГ и ПМ Е.Е. Маренич Декан ФПМПЭ Е.Е. Маренич Утверждено на заседании кафедры. Протокол № 9 от 30.05.2007 г. ФЕДЕРАЛЬНОЕ АГЕНТСТВО ПО ОБРАЗОВАНИЮ Государственное образовательное учреждение высшего профессионального образования «Мурманский государственный педагогический университет» (МГПУ) Кафедра: алгебры, геометрии и прикладной математики Наименование дисциплины: Языки программирования и методы трансляции,1 курс, ПМИ, 2 семестр Экзаменационный билет №5 1. Классификация грамматик. 2. Взаимосвязь автоматов с магазинной памятью и контекстно-свободных грамматик. Построение автомата с магазинной памятью. Построение расширенного автомата с магазинной памятью. Зав. кафедрой АГ и ПМ Е.Е. Маренич Декан ФПМПЭ Е.Е. Маренич Утверждено на заседании кафедры. Протокол № 9 от 30.05.2007 г. ФЕДЕРАЛЬНОЕ АГЕНТСТВО ПО ОБРАЗОВАНИЮ Государственное образовательное учреждение высшего профессионального образования «Мурманский государственный педагогический университет» (МГПУ) Кафедра: алгебры, геометрии и прикладной математики - 11 - Наименование дисциплины: Языки программирования и методы трансляции,1 курс, ПМИ, 2 семестр Экзаменационный билет №6 1. Механизмы распознавания и преобразования. 2. Задача и дерево разбора. Зав. кафедрой АГ и ПМ Декан ФПМПЭ Утверждено на заседании кафедры. Протокол № 9 от 30.05.2007 г. Е.Е. Маренич Е.Е. Маренич ФЕДЕРАЛЬНОЕ АГЕНТСТВО ПО ОБРАЗОВАНИЮ Государственное образовательное учреждение высшего профессионального образования «Мурманский государственный педагогический университет» (МГПУ) Кафедра: алгебры, геометрии и прикладной математики Наименование дисциплины: Языки программирования и методы трансляции,1 курс, ПМИ, 2 семестр Экзаменационный билет №7 1. Регулярные выражения. Метод проверки регулярности заданного языка. 2. Нормальная форма Хомского. Зав. кафедрой АГ и ПМ Декан ФПМПЭ Утверждено на заседании кафедры. Протокол № 9 от 30.05.2007 г. Е.Е. Маренич Е.Е. Маренич ФЕДЕРАЛЬНОЕ АГЕНТСТВО ПО ОБРАЗОВАНИЮ Государственное образовательное учреждение высшего профессионального образования «Мурманский государственный педагогический университет» (МГПУ) Кафедра: алгебры, геометрии и прикладной математики Наименование дисциплины: Языки программирования и методы трансляции,1 курс, ПМИ, 2 семестр Экзаменационный билет №8 1. Свойства регулярных выражений. 2. Устранение прямой левой рекурсии. Зав. кафедрой АГ и ПМ Е.Е. Маренич Декан ФПМПЭ Е.Е. Маренич Утверждено на заседании кафедры. Протокол № 9 от 30.05.2007 г. ФЕДЕРАЛЬНОЕ АГЕНТСТВО ПО ОБРАЗОВАНИЮ Государственное образовательное учреждение высшего профессионального образования «Мурманский государственный педагогический университет» (МГПУ) - 12 - Кафедра: алгебры, геометрии и прикладной математики Наименование дисциплины: Языки программирования и методы трансляции,1 курс, ПМИ, 2 семестр Экзаменационный билет №9 1. Определение конечного автомата. 2. Устранение -правил. Зав. кафедрой АГ и ПМ Е.Е. Маренич Декан ФПМПЭ Е.Е. Маренич Утверждено на заседании кафедры. Протокол № 9 от 30.05.2007 г. ФЕДЕРАЛЬНОЕ АГЕНТСТВО ПО ОБРАЗОВАНИЮ Государственное образовательное учреждение высшего профессионального образования «Мурманский государственный педагогический университет» (МГПУ) Кафедра: алгебры, геометрии и прикладной математики Наименование дисциплины: Языки программирования и методы трансляции,1 курс, ПМИ, 2 семестр Экзаменационный билет №10 1. Задачи преобразования. 2. Устранение недостижимых символов. Зав. кафедрой АГ и ПМ Е.Е. Маренич Декан ФПМПЭ Е.Е. Маренич Утверждено на заседании кафедры. Протокол № 9 от 30.05.2007 г. ФЕДЕРАЛЬНОЕ АГЕНТСТВО ПО ОБРАЗОВАНИЮ Государственное образовательное учреждение высшего профессионального образования «Мурманский государственный педагогический университет» (МГПУ) Кафедра: алгебры, геометрии и прикладной математики Наименование дисциплины: Языки программирования и методы трансляции,1 курс, ПМИ, 2 семестр Экзаменационный билет №11 1. Устранение недостижимых состояний. 2. Левая факторизация правил. Зав. кафедрой АГ и ПМ Е.Е. Маренич Декан ФПМПЭ Е.Е. Маренич Утверждено на заседании кафедры. Протокол № 9 от 30.05.2007 г. ФЕДЕРАЛЬНОЕ АГЕНТСТВО ПО ОБРАЗОВАНИЮ Государственное образовательное учреждение высшего профессионального образования «Мурманский государственный педагогический университет» (МГПУ) Кафедра: алгебры, геометрии и прикладной математики - 13 - Наименование дисциплины: Языки программирования и методы трансляции,1 курс, ПМИ, 2 семестр Экзаменационный билет №12 1. Построение детерминированного конечного автомата. 2. Разновидности автоматов с магазинной памятью. Зав. кафедрой АГ и ПМ Декан ФПМПЭ Утверждено на заседании кафедры. Протокол № 9 от 30.05.2007 г. Е.Е. Маренич Е.Е. Маренич ФЕДЕРАЛЬНОЕ АГЕНТСТВО ПО ОБРАЗОВАНИЮ Государственное образовательное учреждение высшего профессионального образования «Мурманский государственный педагогический университет» (МГПУ) Кафедра: алгебры, геометрии и прикладной математики Наименование дисциплины: Языки программирования и методы трансляции,1 курс, ПМИ, 2 семестр Экзаменационный билет №13 1. Объединение эквивалентных состояний. 2. Нормальная форма Грейбах. Зав. кафедрой АГ и ПМ Е.Е. Маренич Декан ФПМПЭ Е.Е. Маренич Утверждено на заседании кафедры. Протокол № 9 от 30.05.2007 г. ФЕДЕРАЛЬНОЕ АГЕНТСТВО ПО ОБРАЗОВАНИЮ Государственное образовательное учреждение высшего профессионального образования «Мурманский государственный педагогический университет» (МГПУ) Кафедра: алгебры, геометрии и прикладной математики Наименование дисциплины: Языки программирования и методы трансляции,1 курс, ПМИ, 2 семестр Экзаменационный билет №14 1. Построение конечного автомата по регулярному выражению. Методика построения конечного автомата. 2. Построение конечного автомата по регулярной грамматике. Зав. кафедрой АГ и ПМ Е.Е. Маренич Декан ФПМПЭ Е.Е. Маренич Утверждено на заседании кафедры. Протокол № 9 от 30.05.2007 г. ФЕДЕРАЛЬНОЕ АГЕНТСТВО ПО ОБРАЗОВАНИЮ Государственное образовательное учреждение высшего профессионального образования «Мурманский государственный педагогический университет» (МГПУ) Кафедра: алгебры, геометрии и прикладной математики - 14 - Наименование дисциплины: Языки программирования и методы трансляции,1 курс, ПМИ, 2 семестр Экзаменационный билет №15 1. Построение регулярной грамматики по конечного автомата. 2. Проверка существования языка. Зав. кафедрой АГ и ПМ Декан ФПМПЭ Утверждено на заседании кафедры. Протокол № 9 от 30.05.2007 г. Е.Е. Маренич Е.Е. Маренич ФЕДЕРАЛЬНОЕ АГЕНТСТВО ПО ОБРАЗОВАНИЮ Государственное образовательное учреждение высшего профессионального образования «Мурманский государственный педагогический университет» (МГПУ) Кафедра: алгебры, геометрии и прикладной математики Наименование дисциплины: Языки программирования и методы трансляции,1 курс, ПМИ, 2 семестр Экзаменационный билет №16 1. Определение языков посредством множеств. 2. LL(k)-грамматики. Зав. кафедрой АГ и ПМ Е.Е. Маренич Декан ФПМПЭ Е.Е. Маренич Утверждено на заседании кафедры. Протокол № 9 от 30.05.2007 г. ФЕДЕРАЛЬНОЕ АГЕНТСТВО ПО ОБРАЗОВАНИЮ Государственное образовательное учреждение высшего профессионального образования «Мурманский государственный педагогический университет» (МГПУ) Кафедра: алгебры, геометрии и прикладной математики Наименование дисциплины: Языки программирования и методы трансляции,1 курс, ПМИ, 2 семестр Экзаменационный билет №17 1. Определение формальной грамматики. 2. Стратегия восходящего анализа. . Зав. кафедрой АГ и ПМ Е.Е. Маренич Декан ФПМПЭ Е.Е. Маренич Утверждено на заседании кафедры. Протокол № 9 от 30.05.2007 г. ФЕДЕРАЛЬНОЕ АГЕНТСТВО ПО ОБРАЗОВАНИЮ Государственное образовательное учреждение высшего профессионального образования «Мурманский государственный педагогический университет» (МГПУ) Кафедра: алгебры, геометрии и прикладной математики - 15 - Наименование дисциплины: Языки программирования и методы трансляции,1 курс, ПМИ, 2 семестр Экзаменационный билет №18 1. Классификация грамматик. 2. LR ( k ) -грамматики. Зав. кафедрой АГ и ПМ Декан ФПМПЭ Утверждено на заседании кафедры. Протокол № 9 от 30.05.2007 г. Е.Е. Маренич Е.Е. Маренич ФЕДЕРАЛЬНОЕ АГЕНТСТВО ПО ОБРАЗОВАНИЮ Государственное образовательное учреждение высшего профессионального образования «Мурманский государственный педагогический университет» (МГПУ) Кафедра: алгебры, геометрии и прикладной математики Наименование дисциплины: Языки программирования и методы трансляции,1 курс, ПМИ, 2 семестр Экзаменационный билет №19 1. Свойства регулярных выражений. 2. Иерархия контекстно-свободных грамматик. Зав. кафедрой АГ и ПМ Е.Е. Маренич Декан ФПМПЭ Е.Е. Маренич Утверждено на заседании кафедры. Протокол № 9 от 30.05.2007 г. ФЕДЕРАЛЬНОЕ АГЕНТСТВО ПО ОБРАЗОВАНИЮ Государственное образовательное учреждение высшего профессионального образования «Мурманский государственный педагогический университет» (МГПУ) Кафедра: алгебры, геометрии и прикладной математики Наименование дисциплины: Языки программирования и методы трансляции,1 курс, ПМИ, 2 семестр Экзаменационный билет №20 1. Построение детерминированного конечного автомата. 2. Грамматика простого предшествования. Зав. кафедрой АГ и ПМ Е.Е. Маренич Декан ФПМПЭ Е.Е. Маренич Утверждено на заседании кафедры. Протокол № 9 от 30.05.2007 г. ФЕДЕРАЛЬНОЕ АГЕНТСТВО ПО ОБРАЗОВАНИЮ Государственное образовательное учреждение высшего профессионального образования «Мурманский государственный педагогический университет» (МГПУ) Кафедра: алгебры, геометрии и прикладной математики - 16 - Наименование дисциплины: Языки программирования и методы трансляции,1 курс, ПМИ, 2 семестр Экзаменационный билет №21 1. Объединение эквивалентных состояний. 2. Вычисление матрицы предшествования. Алгоритм предшествования. Зав. кафедрой АГ и ПМ Декан ФПМПЭ Утверждено на заседании кафедры. Протокол № 9 от 30.05.2007 г. построения матрицы Е.Е. Маренич Е.Е. Маренич ФЕДЕРАЛЬНОЕ АГЕНТСТВО ПО ОБРАЗОВАНИЮ Государственное образовательное учреждение высшего профессионального образования «Мурманский государственный педагогический университет» (МГПУ) Кафедра: алгебры, геометрии и прикладной математики Наименование дисциплины: Языки программирования и методы трансляции,1 курс, ПМИ, 2 семестр Экзаменационный билет №22 1. Объединение эквивалентных состояний. 2. Проверка существования языка. Зав. кафедрой АГ и ПМ Е.Е. Маренич Декан ФПМПЭ Е.Е. Маренич Утверждено на заседании кафедры. Протокол № 9 от 30.05.2007 г. ФЕДЕРАЛЬНОЕ АГЕНТСТВО ПО ОБРАЗОВАНИЮ Государственное образовательное учреждение высшего профессионального образования «Мурманский государственный педагогический университет» (МГПУ) Кафедра: алгебры, геометрии и прикладной математики Наименование дисциплины: Языки программирования и методы трансляции,1 курс, ПМИ, 2 семестр Экзаменационный билет №23 1. Построение детерминированного конечного автомата. 2. Устранение недостижимых символов. Зав. кафедрой АГ и ПМ Е.Е. Маренич Декан ФПМПЭ Е.Е. Маренич Утверждено на заседании кафедры. Протокол № 9 от 30.05.2007 г. ФЕДЕРАЛЬНОЕ АГЕНТСТВО ПО ОБРАЗОВАНИЮ Государственное образовательное учреждение высшего профессионального образования «Мурманский государственный педагогический университет» (МГПУ) Кафедра: алгебры, геометрии и прикладной математики - 17 - Наименование дисциплины: Языки программирования и методы трансляции,1 курс, ПМИ, 2 семестр Экзаменационный билет №24 1. Построение конечного автомата по регулярному выражению. Методика построения конечного автомата. 2. Левая факторизация правил. Зав. кафедрой АГ и ПМ Декан ФПМПЭ Утверждено на заседании кафедры. Протокол № 9 от 30.05.2007 г. Е.Е. Маренич Е.Е. Маренич ФЕДЕРАЛЬНОЕ АГЕНТСТВО ПО ОБРАЗОВАНИЮ Государственное образовательное учреждение высшего профессионального образования «Мурманский государственный педагогический университет» (МГПУ) Кафедра: алгебры, геометрии и прикладной математики Наименование дисциплины: Языки программирования и методы трансляции,1 курс, ПМИ, 2 семестр Экзаменационный билет №25 1. Построение регулярной грамматики по конечного автомата. 2. Определение автомата с магазинной памятью. Зав. кафедрой АГ и ПМ Декан ФПМПЭ Утверждено на заседании кафедры. Протокол № 9 от 30.05.2007 г. Е.Е. Маренич Е.Е. Маренич ФЕДЕРАЛЬНОЕ АГЕНТСТВО ПО ОБРАЗОВАНИЮ Государственное образовательное учреждение высшего профессионального образования «Мурманский государственный педагогический университет» (МГПУ) Кафедра: алгебры, геометрии и прикладной математики Наименование дисциплины: Языки программирования и методы трансляции,1 курс, ПМИ, 2 семестр Экзаменационный билет №25 1. Построение конечного автомата по регулярной грамматике. 2. Грамматика простого предшествования. Зав. кафедрой АГ и ПМ Е.Е. Маренич Декан ФПМПЭ Е.Е. Маренич Утверждено на заседании кафедры. Протокол № 9 от 30.05.2007 г. 1.13. Примерная тематика рефератов. 1. Синтаксические диаграммы автоматной грамматики. 2. Трансляция линейных массивов - 18 - 3. Автоматизация построения и мобильность трансляторов 4. Нисходящий и восходящий разбор КС-языков. Общий алгоритм распознавания КСязыков. 5. Самовложение в КС-грамматиках. 6. Синтаксический анализ КС-языков методом рекурсивного спуска. 7. Требование детерминированного распознавания. LL(k) и LL(1) - грамматики. 8. Левая и правая рекурсия. 9. Грамматика и синтаксический анализ арифметических выражений. 10. Включение действий в синтаксис. Семантические процедуры. 11. Табличный LL(1) – анализатор. 12. Польская запись выражений. 1.14. Примерная тематика курсовых работ – не предусмотрено стандартом. 1.15. Примерная тематика квалификационных (дипломных) работ - не предусмотрено стандартом. 1.16. Методика(и) исследования (если есть) - нет. 1.17. Бально-рейтинговая система, используемая преподавателем для оценивания знаний студентов по данной дисциплине. Используется пятибалльная система оценивания знаний студентов. РАЗДЕЛ 2. Методические указания по изучению дисциплины (или ее разделов) и контрольные задания для студентов заочной формы обучения. Нет заочной формы обучения по данной дисциплине. РАЗДЕЛ 3. Содержательный компонент теоретического материала. Предварительные сведения Данные в компьютере представляются с помощью электрических импульсов. Элемент электрической цепи может находиться либо во включенном, либо в выключенном состоянии. Включенный элемент обычно изображается единичным, а выключенный -нулевым значением. Любые данные могут быть представлены с помощью достаточного количества нулей и единиц. Данные, записанные с помощью нулей и единиц называются записанными в двоичной или бинарной форме. В двоичной системе счисления (или системе счисления по основанию 2) для представления чисел используются только цифры 1 и 0. Термин "бит" применяется для обозначения отдельной единицы или нуля. Например, последовательность 1101000110 содержит 10 битов. Байт - это группа из 8 битов. Память компьютера представляет собой множество ячеек. Каждая из этих ячеек имеет адрес. В ячейке может храниться либо 0, либо 1. Т.е. каждая ячейка содержит один бит информации. Единственный язык программирования, инструкции которого компьютер может выполнять непосредственно - это машинный язык (machine language) или машинный код, то есть встроенная в компьютер система элементарных команд. В таблице 1.1 приведены примеры элементарных команд и их двоичного представления. Таблица 1.1. Примеры элементарных команд Специалисты по информатике создают языки программирования высокого уровня, которые легче использовать, чем машинный код, поскольку они ближе к естественным языкам. Программа, называемая компилятором, переводит программы, написанные на языке высокого уровня (таком как C++, Pascal, FORTRAN, COBOL, Java или Ada), в машинный язык. Если программа написана на языке высокого уровня, то ее можно выполнить на любом компьютере с соответствующим компилятором. Программа на языке высокого уровня называется исходной программой. Для компилятора исходная программа является просто входными данными. Компилятор транслирует ее в программу на машинном языке, которая называется объектной программой. Некоторые компиляторы выводят также листинг - текст программы с сообщениями об ошибках и другой полезной информацией. - 19 - Одно из преимуществ стандартных языков высокого уровня состоит в том, что они позволяют писать переносимый или машинно-независимый код. Например, программа, написанная на C++, может исполняться на различных компьютерах, в то время как программа, написанная на машинном языке, не переносима с одного компьютера на другой. Важно понять, что компиляция (compilation) и выполнение программы - это два отдельных процесса. Во время компиляции запускается компилирующая программа. При выполнении объектная программа загружается в память компьютера, замещая собой компилятор. Затем компьютер запускает объектную программу и выполняет команды в соответствии с ее содержанием. Большинство компьютеров состоит из шести основных блоков: запоминающего устройства, арифметико-логического устройства, устройства управления, устройств ввода, устройств вывода и вспомогательных запоминающих устройств или устройств внешней памяти. Оперативное запоминающее устройство (ОЗУ, RAM) - это упорядоченная последовательность ячеек, каждая из которых может хранить элемент данных. Оперативное запоминающее устройство - внутреннее запоминающее устройство в компьютере, непосредственно связанное с центральным процессором и хранящее данные, необходимые для его работы. Оно представляет собой упорядоченную последовательность ячеек, каждая из которых может хранить элемент данных. Каждая ячейка имеет уникальный адрес, на который ссылаются, если надо записать данные в ячейку или прочесть из нее информацию. Ячейки ОЗУ называются ячейками памяти или адресами памяти. Узел компьютера, исполняющий команды, называется центральным процессором. Как правило, центральный процессор состоит из двух частей: А) Арифметико-логического устройства, которое выполняет арифметические (сложение, вычитание, умножение и деление) и логические (сравнение двух величин) операции. В) Устройства управления, которое контролирует работу остальных узлов (устройств), чтобы программные команды выполнялись в правильном порядке. Устройства ввода-вывода - узлы компьютера, которые получают данные для обработки (ввод) и выводят ее результаты (вывод). При выполнении программы компьютер последовательно производит ряд действий, называемых циклом выборки-исполнения: Устройство управления отыскивает в памяти (выбирает) очередную команду. Эта команда переводится в управляющий сигнал. Управляющий сигнал сообщает нужному устройству (АЛУ, ОЗУ, устройству ввода-вывода) о необходимости исполнить команду. Последовательность действий повторяется сначала. Домашнее задание – «Двоичная, восьмеричная и шестнадцатеричная системы счисления» Правила перевода из одной системы счисления в другую. §1. Основные понятия языка С++ Язык C++ был разработан Бьерном Страуструпом на основе языка программирования Си и с немногими изменениями сохраняет Си как подмножество. Основными составляющими любого языка программирования являются Алфавит языка (символы) – это основные неделимые знаки, с помощью которых пишутся все тексты на языке. - 20 - Лексема, или элементарная конструкция (слово) – минимальная единица языка, имеющая самостоятельный смысл. Выражение (словосочетание) задает правило вычисления некоторого значения. Оператор (предложение) задает конечное описание некоторого действия. Для описания сложного действия часто требуется последовательность операторов. Операторы могут быть объединены в составной оператор (блок). Операторы бывают исполняемые и неисполняемые. Исполняемые операторы задают действия над данными. Неисполняемые операторы служат для описания данных, поэтому их часто называют операторами описания или просто описаниями. Каждый элемент языка определяется синтаксисом и семантикой. Синтаксис устанавливает формальные правила записи команд на языке программирования, а семантика определяет их смысл и правила использования. Объединенная единым алгоритмом совокупность описаний и операторов образует программу на алгоритмическом языке. Алфавит языка С++ включает Прописные и строчные латинские буквы Арабские цифры от 0 до 9 Специальные символы: _ “ {} , | [] + – % / \ ; ‘ : ? < > = ! & # ~ ^ . * Неизображаемые символы: пробел, табуляция, переход на новую строку Из символов алфавита формируются лексемы языка: Ключевые (служебные, зарезервированные) слова Идентификаторы Константы Знаки операций Разделители Ключевые слова это лексемы языка, которые имеют специальное значение для компилятора. Их можно использовать только в том смысле, в котором они определены. В стандарте С++ определено 63 ключевых слова. Все они приведены в справочнике. Примеры ключевых слов: if, else, int, new. Отметим, что в С++ различается строчное и прописное написание букв. Поэтому слово Else не будет распознано в качестве ключевого. Идентификаторы. В С++ идентификатор представляет собой имя, которое присваивается функции, переменной или иному элементу, определенному пользователем. В идентификаторе могут использоваться латинские буквы, цифры и знак подчеркивания. Нельзя!!! Чтобы первым символом идентификатора была цифра Использовать пробелы внутри имен Использовать в качестве идентификаторов ключевые слова Необходимо стараться использовать такой идентификатор, который бы отражал смысловую характеристику элемента, которому он принадлежит. Константы (литералы) – это фиксированные значения, которые не могут быть изменены программой. Различаются целые, вещественные, символьные и строковые константы. Компилятор, выделив константу в качестве лексемы, относит ее к одному из типов по ее внешнему виду. Форматы констант, соответствующие каждому типу Константа Формат Пример Целая Десятичный: последовательность десятичных цифр, 8, 0, 87345 начинающаяся не с нуля, если это не число нуль Восьмеричный: нуль, за которым следуют 01.020, 07155 восьмеричные цифры (0,1,2,3,4,5,6,7) Шестнадцатеричный: 0х или 0Х, за которым 0хА, 0Х1В8 следуют шестнадцатеричные цифры (0,1,2,3,4,5,6,7,8,9,A,B,C,D,E,F) Вещественная Десятичный 5.7, .001, 35 Экспоненциальный (e или E) 0.2E6, .11e-3, 5E10 Символьная Символы, заключенные в апострофы ‘A’, ‘*’, ‘\n’ Строковая Последовательность символов, заключенная в “\tПример строковой кавычки константы\n” Некоторые символы невозможно ввести в исходный текст программы с клавиатуры. Они либо не имеют графического изображения, либо имеют в С++ специальное назначение. Поэтому в С++ существует ряд специальных последовательностей символов, начинающихся с обратной косой черты. Они называются управляющими или escape-последовательностями. Управляющие последовательности в С++ код значение - 21 - Возврат на одну позицию Подача страницы (для перехода к началу следующей страницы) Перевод строка Возврат каретки Горизонтальная табуляция Двойная кавычка Одинарная кавычка (апостроф) Обратная косая черта Вертикальная табуляция Звуковой сигнал Вопросительный знак Восьмеричная константа (N – сама восьмеричная константа) Шестнадцатиричная константа (N – сама Шестнадцатиричная константа) Типы данных языка С++ В C++ каждый элемент данных должен принадлежать к какому-либо определенному типу данных. Тип определяет, в каком виде данные представлены в компьютере, а также какие преобразования компьютер может к ним применять. В С++, как и в любом другом языке программирования есть стандартные (встроенные) типы данных. Но пользователь может определять и свои собственные типы данных (о том как это делается речь пойдет позже). Приведем схему, отображающую классификацию типов данных \b \f \n \r \t \” \’ \\ \v \a \? \N \xN Структурированные типы данных и адресные типы будут рассмотрены позднее. Сейчас остановимся на простых типах. Существует 4 спецификатора типа, уточняющих внутреннее представление и диапазон значений стандартных типов: short (короткий) long (длинный) signed (знаковый) unsigned (беззнаковый) Целые типы int размер данного типа не определяется стандартом, а зависит от компьютера и компилятора. Для 16-разрядного процессора под величины этого типа отводится 2 байта, для 32-разрядного – 4 байта. Спецификатор short перед именем типа указывает компилятору, что под число требуется отвести 2 байта независимо от разрядности процессора. Спецификатор long означает, что целая величина будет занимать 4 байта. Таким образом, на 16-разрядном компьютере эквивалентны int и short int, а на 32разрядном – int и long int. Внутреннее представление величины целого типа целое число в двоичном коде. При использовании спецификатора signed старший бит числа интерпретируется как знаковый (0 – положительное число, 1 - отрицательное). Спецификатор unsigned позволяет представлять только положительные числа, поскольку старший разряд рассматривается как часть кода числа. Таким образом, диапазон значений типа int зависит от спецификатора. Диапазоны значений и размер памяти, отводимый под различные типы, на наших компьютерах мы будем определять на лабораторных занятиях. Отметим только, что для определения размера памяти используется оператор sizeof Примеры sizeof(char) sizeof a+b Символьный тип char. Под величину символьного типа отводится количество байт, достаточное для размещения любого символа из набора символов для данного компьютера. Как правило это 1 байт. Тип char, - 22 - как и другие целые типы может быть со знаком и без знака. В величинах со знаком можно хранить значения от -128 до 127. При использовании спецификатора unsigned значения могут находиться в пределах от 0 до 255. Этого достаточно для хранения любого символа из 256-символьного набора ASCII (американский стандартный код для обмена информацией. Таблица кодов ASCII содержит коды литер алфавита С++ – двоичные, восьмеричные и шестнадцатеричные). Величины типа char используются также для хранения целых чисел, не превышающих границы указанных диапазонов. Логический тип bool. Величины логического типа могут принимать только значения true и false, являющиеся зарезервированными словами. Внутренняя форма представления значения false – 0 (нуль). Любое другое значение интерпретируется как true. При преобразовании к целому типу true имеет значение 1. Вещественные типы (типы с плавающей точкой). Десятичным разделителем С++ является . . Действительные (вещественные) числа представлены в памяти в форме с плавающей точкой Тип void. Относится к основным типам языка, но множество значений этого типа пусто. Он используется для определения функций, которые не возвращают значения, для указания пустого списка аргументов функции, как базовый тип для указателей и в операции приведения типов (обо всем этом будет говориться позже). К этому типу нельзя применять оператор sizeof. Структура программы Простейшая программа на языке С++ имеет следующую структуру: директивы препроцессора void main() { описание объектов; операторы; } Конкретный пример //Первая программа #include<iostream.h> void main() { cout<<”Hallo, Свое_имя\n”; } (1) (2) (3) (4) (5) (6) Первая строка этой программы состоит из комментария – текста пояснительного содержания, встраиваемого в программу. В С++ поддерживается два типа комментариев: однострочный комментарий, такой как в строке (1). Он начинается с пары символов // и заканчивается в конце строки. Многострочный комментарий, который начинается символами /* и заканчивается ими же, но переставленными в обратном порядке (*/). Текст программы который закомментирован (расположенный за // или между /* и */) компилятором игнорируется. Вторая строка обрабатывается не компилятором C++, а специальной программой, называемой препроцессором (preprocessor). Понятие препроцессора является одним из ключевых в языке C++. Препроцессор - это программа, действующая как фильтр на этапе компиляции. Перед тем, как попасть на вход компилятора, исходная программа проходит через препроцессор Строка, начинающаяся символом решетки (#), не является выражением языка C++ (и потому не заканчивается точкой с запятой). Она называется директивой препроцессора. Препроцессор расширяет директиву #include, вставляя содержимое указанного файла (в нашем случае файла iostream.h) непосредственно в исходную программу. Файлы, которые появляются в директиве #include, обычно заканчиваются на .h, что означает файл заголовков. Файлы заголовков содержат объявления констант, переменных и функций, необходимых для работы программы. Из файла iostream.h в нашей программе используются сout и <<. Чтобы использовать математические функции необходимо подключить math.h - 23 - Третья строка содержит объявление функции с именем main. Слово void перед main показывает, что функция main ничего не возвращает. Любая программа на C++ должна содержать функцию с именем main. С этой функции начинается выполнение программы. Строки (4) и (6) означают начало и конец тела функции main. Тело функции это последовательность объявлений, определений и операторов. Определения и объявления должны размещаться до операторов , которые их используют. Каждый оператор заканчивается ; . В строке (5) содержит инструкцию вывода данных на консоль. При ее выполнении на экране компьютера отобразится сообщение Hallo, Свое_имя и курсор переведется на новую строчку cout означает стандартный поток вывода << – оператор вывода. Любая выполняемая инструкция завершается точкой с запятой. Объявление и инициализация переменных Общий формат инструкции объявления переменных выглядит так Тип список_переменных; Здесь тип означает допустимый в С++ тип данных, а список_переменных может состоять из одного или нескольких имен (идентификаторов), разделенных запятыми. При объявлении для хранения значений переменной выделяется определенная область памяти. Размер выделяемой памяти зависит от типа объявленной переменной (как было оговорено ранее он может быть определен с помощью sizeof). При объявлении переменной можно присвоить некоторое значение, т.е. инициализировать ее, записав после ее имени знак равенства и начальное значение. Общий формат инициализации имеет следующий вид: Тип имя_переменной = значение; Примеры: int I = 2; char h = ‘Q’; double x = 0.34; long int i=254125, j=-967; Именованные константы Объявление именованных констант выглядит следующим образом: const тип имя_константы = значение; Примеры const float PI = 3,14159; const double E = 2,7… ; Путем введения именованных констант часто можно сделать программу более удобной для чтения. Кроме того, ее легче изменять. Именованные константы также повышают надежность программы: их использование защищает программиста от опечаток. Операторы Оператор - это символ, который указывает компилятору на выполнение конкретных математических действий или логических манипуляций. В С++ имеется четыре общих класса операторов: арифметические, поразрядные, логические и операторы отношений. Арифметические операторы Оператор + – Действие Сложение Вычитание, а также унарный минус - 24 - Умножение Деление Деление по модулю Декремент Инкремент * / % -++ Необходимо иметь в виду, что после применения оператора деления / к целому числу, остаток отбрасывается. Например, результат целочисленного деления 10/3 будет равен 3. Деление по модулю позволяет получить остаток от деления нацело. Например 10%3 = 1. Это означает, что оператор % нельзя применять к типам с плавающей точкой. Унарный минус, по сути, представляет собой умножение значения своего единственного операнда на –1. Операторов инкремент и декремент кроме С++ нет ни в одном другом языке программирования. Оператор инкремента выполняет сложение операнда с числом 1, а оператор декремента вычитает 1 из своего операнда. Операторы инкремента и декремента могут стоять как перед своим операндом (префиксная форма), так и после него (постфиксная форма). Таким образом, инструкция х=х+1 аналогична инструкции х++ или ++х, а инструкция х=х–1 аналогична инструкции х– – или – –х, Не всегда результат применения префиксной и постфиксной форм операторов инкремента и декремента дает один и тот же результат. Пример Инструкции х = 10; y = ++x; х = 10; y = x++; результат y = x = 10 y = 10 x = 11 Приоритет арифметических операций Приоритет Операторы Наивысший ++ – – – (унарный) * / % низший + – Операторы одного уровня старшинства вычисляются компилятором слева направо. Порядок вычислений может быть изменен с помощью круглых скобок. Операторы отношений и логические операторы Используются для получения результатов в виде значений истина/ложь. Согласно стандарту С++ результат выполнения операторов отношений и логических операторов имеет тип bool, т.е. при выполнении этих операторов получаются значения true (1) или false (0). Операнды, участвующие в операциях «выяснения» отношений, могут иметь практически любой тип, главное, чтобы их можно было сравнивать. Операнды логических операторов должны иметь тип bool. Поскольку в С++ любое ненулевое число оценивается как истинное, а нуль эквивалентен ложному значению, то логические операторы можно использовать в любом выражении, которое дает нулевой или ненулевой результат. Операторы отношений == != значение Равно Не равно - 25 - > < >= <= Больше Меньше Больше или равно Меньше или равно Логические операторы && || ! значение И ИЛИ НЕ Приоритет операторов отношений и логических операторов Приоритет Наивысший низший Операторы ! > >= < == != && || <= Преобразование типов в выражениях Если задано объявление int someInt; float someFloat; то переменная someInt может содержать только целые значения, a someFloat - только вещественные. На первый взгляд, оператор someFloat = 12; присваивает переменной someFloat целое значение 12. Однако компьютер откажется хранить в переменной soraeFloat любое значение, кроме значения типа float. Поэтому компилятор вставляет в программу дополнительные инструкции, которые сначала преобразуют 12 в 12.0, а затем сохраняют 12.0 в переменной someFloat. Такое неявное (автоматическое) преобразование значения из одного типа в другой называется приведением типов. Выражение someInt = 4.8; также вызывает приведение типов. Когда вещественное значение присваивается переменной типа int, дробная часть числа отбрасывается. В результате переменной someInt присваивается значение 4. Если в выражении смешаны различные типы литералов (констант) и переменных, компилятор преобразует их к одному типу. Во-первых все char- и short int – значения автоматически преобразуются (с расширением «типоразмера») к типу int. Этот процесс называется целочисленным расширением. Во-вторых все операнды преобразуются (также с расширением «типоразмера») к типу самого большого операнда. Этот процесс называется расширением типа, причем он выполняется пооперационно. Например, если один операнд имеет тип int, а другой – long int, то тип int расширяется в тип long int. Или, если хотя бы один из операндов имеет тип double, любой другой операнд приводится к типу double. Это означант, что такие преобразования как из типа char в тип double вполне допустимы. После преобразования оба операнда будут иметь один и тот же тип, а результат операции – тип, совпадающий с типом операндов. Пример char ch; int i; float f; double d; - 26 - result = (ch/i) + int (f*d) – double int (f+i) float double float double double Приведение типов Чтобы сделать программы максимально понятными и свободными от ошибок, используется приведение типов ( по другому, явное преобразование типов). Оператор преобразования типов C++ состоит из имени типа, после которого в скобках записывается преобразуемое выражение Пример someFloat = float (3*someInt + 2); someInt = int (5.2 / someFloat – anotherFloat); Отметим, что оба выражения someInt = someFloat + 8.2; someInt = int(someFloat + 8.2) приводят к одинаковому результату. Единственное различие состоит в том, что второе выражение понятнее. Если используется операция преобразования, то и самому программисту, и тем, кто читает программу, становится ясно, что смешивание типов допущено преднамеренно, а не является результатом просмотра. Пользуясь явным преобразованием типов можно округлить вещественное число до ближайшего целого перед тем, как сохранить значение в переменной целого типа: someInt = int (someFloat + 0.5); Проверьте на бумаге, какое значение присваивается переменной someInt, если someFloat содержит значение 4.7. Проделайте то же самое, предположив, что someFloat равняется 4.2. (Данный способ округления предполагает, что someFloat -произвольное положительное число.) Управляющие операторы Условный оператор if … else Формат записи: if (условное выражение) { тело if } else { тело else }; if, else – служебные слова Если тело if или else состоит из одной инструкции, то фигурные скобки можно опускать. Работа оператора: Если условие истинно, то выполняется операторы тела if иначе – операторы тела else. Далее управление передается на следующему за условным оператору. - 27 - Если в случае, когда значение условия – ЛОЖЬ, никаких специальных действий производить не нужно, то пользуются сокращенной формой записи условного оператора if (условное выражение) { тело if }; Условное выражение необязательно ограничивать операторами отношений и логическими операторами или операндами типа bool. Главное, чтобы результат вычисления условного выражения можно было интерпретировать как значение ИСТИНА или ЛОЖЬ. Пример // Программа, считывающая с клавиатуры два целых числа и отображающая частное от деления первого на второе. #include<iostream.h> void main() { int a,b; cout<<”Введите два числа: ”; cin>>a>>b; if(b) // альтернатива if(b!=0) cout<<a/b<<’\n’; else cout<<”На нуль делить нельзя. \n”; } Условная трехместная операция это короткий способ записи условного оператора. В общем случае синтаксис этого оператора следующий: <условное выражение>?<выражение1>:<выражение2> Если условное выражение истинно, то вычисляется <выражение1>, которое становится результатом, если ложно, то результатом будет <выражение2>. Примеры х=(у<0)? -у: у; // х=|у| cout<<(у<0)? -у: у; //вывод на экран абсолютного значения переменной у Оператор выбора switch Оператор switch (в переводе – переключатель) предназначен для организации многонаправленного ветвления, которая позволяет выбрать одну из множества альтернатив. Общий формат записи оператора switch таков: switch ( условное_выражение ) { case выражение_1: { блок операторов_1; } case выражение_2: { блок операторов_2; } - 28 - ....................... case выражение_n: { блок операторов_n; } default: { блок операторов; } }; Работа оператора: выполнение оператора начинается с вычисления выражения (), а затем управление передается первому оператору из списка Здесь switch, case, default - ключевые слова (пер. с англ.: переключатель, случай, невыполнение обязательств); Результат условного выражения должен иметь целочисленное или символьное значение; выражение_1, выражение_2, …, выражение_n константы или константные выражения того же типа, что и условное выражение. Работа оператора начинается с вычисления значения условного_выражения. Если полученное значение совпадает с одним из выражений выражение_1, …, выражение_n, то выполняется та группа операторов, которой предшествует эта константа, а потом все группы операторов, расположенные ниже. Но так как часто программист использует этот оператор, чтобы выполнить только одну группу, то в конце каждой группы используется специальный оператор break, осуществляющий выход из оператора switch (а также из всех операторов цикла). Если такой константы не обнаруживается, то выполняется оператор, следующий за ключевым словом default. Ветвь default может отсутствовать, и в этом случае управление передается оператору, следующему за оператором switch. Пример 1 //Программа, реализующая простейший калькулятор на 4 действия #include<iostream.h> void main() { int a,b,res; char op; cout<<”Введите первый операнд: \n”; cin>>a; cout<<”Введите знак операции: \n”; cin>>op; cout<<”Введите второй первый операнд: \n”; cin>>b; bool f=true; switch (op) { case ‘+’: { res=a+b; break;} case ‘-’: { res=a-b; break;} case ‘*’: { res=a*b; break;} case ‘/’: { res=a/b; break;} default: { cout<<”Неизвестная операция\n”; f=false;} } if (f) cout<<”Результат: ”<<res<<’\n’; } Оператор цикла с предусловием Формат записи: while ( условное_выражение ) { блок операторов }; Здесь while - ключевое слово (перев. с англ.: пока) Оператор работает следующим образом: сначала вычисляется условное выражение и, если получается истинное значение, выполняются - 29 - операторы тела цикла, а затем снова проверяется условие. Если значение условного выражения ложно, то осуществляется выход из цикла. Таким образом, если условие было ложно при первом входе в цикл, то операторы тела цикла не выполнятся ни разу. Очевидно, один из операторов тела цикла должен влиять на значение условного выражения, поскольку иначе цикл будет повторяться бесконечно. //Пример бесконечного цикла int i=1; while(i<5) cout<<"Доброе утро! "; Чтобы получить работающий фрагмент программы, добавим в тело цикла оператор, увеличивающий значение i: i=1; while (i<5) { printf("Доброе утро! "); i=i+1; } // Программа, находящая среднее арифметическое последовательности чисел вводимых с клавиатуры. Последовательность чисел вводится с клавиатуры и завершается стоп-кодом. Использование стоп-кода в данном случае состоит в том, что какое-то числовое значение заведомо исключается из входной последовательности и используется как стоп-код. Если заранее известно, что число – 1 никогда не появится в последовательности, то это число можно использовать для указания ее конца. #include <iostream.h> void main () { float number, //Вводимое число sum=0; //Сумма вводимых чисел int с = 0; //Количество вводимых чисел const float stopcod = -1; cout<<"Введите первое число последовательности:"; cin>>number; while (number != stopcod) { sum = sum + number; c=c+l; cout<<"Введите следующее число последовательности:"; cin>>number; } if (c == 0) cout<<"Среднее значение равно нулю"; else cout<<"Среднее значение равно = "<<sum/c; } Одним из классических примеров применения оператора цикла с предусловием является организация вычисления сумм с заданной точностью. Вычислить сумму с заданной точностью е значит завершить суммирование членов ряда тогда, когда очередной член ряда окажется меньше е по абсолютной величине. // вычисляется сумма ряда s = 1 + ½ + 1/3 + ¼ + ... c задаваемой пользователем точностью #include <iostream.h> void main() { int i; float e, //Точность вычисления ряда k, //Очередной член ряда sum = 0; //Сумма ряда i= 1; cout<<"Введите точность:"; cin>>e; k = 1.0/i; while ( k > e ) { sum = sum+k; i = i+1; k = 1.0/i; - 30 - }; cout<<"Сумма ряда ="<<sum<<'\n'; } Инструкция while(cin>>) Работу данной инструкции покажем на примере // Программа, находящая среднее арифметическое последовательности чисел // вводимых с клавиатуры не используя стоп-кода #include <iostream.h> void main () { float number,sum=0; int c=0; cout<<"Input the nambers:"; while (cin>>number) //пока number будет иметь тип float { sum = sum+number; c=c+1; }; if (c == 0) cout<<"Среднее значение равно нулю"; else cout<<"Среднее значение равно = "<<sum/c; } Оператор цикла с постусловием Формат записи: do { блок операторов; } while (условное выражение); Здесь do, while - ключевые слова (перев. с англ.: выполнять и пока). Работа оператора: сначала выполняются операторы, расположенные в теле цикла, затем вычисляется условное выражение и, если получается ложное значение, осуществляется выход из цикла. Если значение выражения истинно, то выполнение операторов тела цикла повторяется, а затем снова проверяется условие. Итак, операторы тела цикла выполняются хотя бы раз, а потом все зависит от условия выхода из цикла. Один из операторов тела цикла должен влиять на значение условного выражения, поскольку иначе цикл будет повторяться бесконечно. Пример // Программа, в которой цикл выполняется до тех пор, пока пользователь не введет число 100. #include<iostream.h> int main() { int num; do { cout<<”Input the number (100 for exit)\n”; cin>>num; } while(num!=100); } Оператор цикла for Формат записи: for (операторы_1; условное_выражение; операторы_2) { блок операторов }; операторы – список операторов разделенных запятой. - 31 - Работа оператора: выполняются операторы_1 вычисляется значение условного выражения. Если оно истинно, то выполняется блок операторов тела цикла for. Здесь for - ключевое слово (перев. с англ, для); п. ц. - переменная цикла. В разделе инициализации этой переменной присваивается некоторое начальное значение. Этот раздел может иметь вид переменная = значение; тип переменная = значение; //если переменная описана ранее //если переменная не описана ранее //она будет видна только в теле цикла модификация_п.ц. – оператор присваивания, задающий изменение переменной цикла. Оператор работает таким образом: после инициализации переменной проверяется условие выхода из цикла и, если получается истинное значение, выполняется оператор, являющийся телом цикла. Затем изменяется переменная цикла и снова проверяется условие и т.д. Если значение выражения ложно, то осуществляется выход из цикла. Если начальное значение переменной цикла больше конечного значения, то операторы тела цикла не выполняются. Можно сказать, что оператор цикла со счетчиком - это оператор цикла с предусловием. Следующий оператор не приведет к выполнению каких-либо действий: int i; for (i=1; i<0; i++) cout<<i; Приведенный ниже оператор распечатает целые числа от 1 до 10: for( i=1; i<10; i++) cout<<i; Оператор цикла со счетчиком позволяет: • Применять операцию уменьшения для счета в порядке убывания for( int i=10; i>0; i -- ) cout<<"секунд ! \n"<<i; cout<<"Пуск ! \n"; • Вести счет двойками, десятками и т.д. for( i=5; i<100; i+=15) cout<<i; В этом примере будут напечатаны 5, 20, 35, 50, 65, 80, 95. • Использовать символы в качестве переменной цикла #include<iostream.h> void main() { char ch; for( ch='a'; ch<'z'; ch++) cout<<ch<<'\t'; cout<<'\n'; При выполнении этого оператора будут выведены все английские буквы от а до z, Символы в памяти размещаются в виде чисел, поэтому в данном фрагменте на самом деле счет ведется с использованием целых чисел. • Проверять выполнение некоторого произвольного условия, отличного от условия, налагаемого на число итераций #include<iostream.h> void main() { for(int i=2; i*i*i<100; i++) cout<<i*i*i<<'\t'; cout<<'\n'; } Результат: 8 27 64 - 32 - Операторы передачи управления break, continue, goto Оператор continue позволяет немедленно перейти к выполнению следующей итерации цикла. Пример //Программа поиска четных чисел в диапазоне от 0 до 100 #include<iostream.h> void main() { int x; for(x=0;x<=100;x++) { if(x%2) continue; cout<<x<<’\t’; } } В циклах while и do-while оператор continue передает управление непосредственно инструкции, проверяющей условное выражение. А в цикле for после выполнения оператора continue сначала производится модификация переменной цикла, а затем как обычно проверяется условие и т.д. Оператор break позволяет немедленно выйти из цикла. Управление передается инструкции, следующей после цикла. Если имеются вложенные циклы (в С++ разрешено использовать до 256 уровней вложения), то необходимо помнить, что оператор break приводит к выходу только из того цикла, в теле которого он записан. Оператор goto – это оператор безусловного перехода. Он позволяет перейти из одного места программы в другое, помеченное меткой. Метка это идентификатор С++, за которым поставлено двоеточие. Она должна находиться в одной функции с оператором goto, который ссылается на эту метку. Пример. Оператор goto удобно использовать для выхода из глубоко вложенных циклов, в которых применением инструкции break не обойтись, поскольку она обеспечивает выход лишь из самого внутреннего цикла. for(...) { for(...) { while(...) { if(...) goto stop; .................................... } stop: cout<<”Ошибка в программе.\n”; Указатели Известно, что данные хранятся в ячейках памяти компьютера. Все ячейки памяти пронумерованы. Номера ячеек памяти называются адресами. Указатели используются для работы с адресами. Указатель - это некоторое символическое представление адреса. Мы будем работать с переменными, хранящими эти адреса. Описываются такие переменные следующим образом: тип*идентификатор; Такое описание синтаксически отличается от описания простой переменной только наличием знака * перед именем переменной. Как видно из описания, указатель всегда связывается с переменной какого-то - 33 - определенного типа. Это позволяет определить, сколько байт памяти необходимо выделить по указанному адресу. В переменной типа указатель хранится адрес первого байта, выделенного участка памяти: int *ptr, *ptrl; float *p, *pl; Над указателями можно выполнять следующие операции: • Одному указателю можно присвоить значение другого указателя, если они ссылаются на один и тот же тип: ptr = ptrl; p1 = р; • Значение указателя можно увеличить или уменьшить на константную величину: ptr++; ptrl = ptrl-5; p1 = p1 + 2; На самом деле ptr увеличивается не на 1, а на столько, сколько байт занимает целое число. Переменная ptrl уменьшается на 5 умноженное на количество байт, выделяемых под целое число. • Указателю можно присвоить значение адреса. Для получения адреса используется значок & : int a, *ptr; Dtr = &a; • Можно использовать операцию косвенной адресации. Эта операция обозначается значком * и позволяет получить доступ к значению переменной, на которую ссылается указатель (разыменовать указатель): int n = 12, *ptr, a ; ptr = &n; а = *ptr; //разыменование указателя После выполнения этого фрагмента переменная а будет иметь значение 12. Понятие ссылки включено в С++ главным образом для поддержки способа передачи параметров «по ссылке» и для использования в качестве ссылочного типа значения, возвращаемого функцией. Под ссылкой понимается другое (альтернативным) имя уже существующего объекта. Она должна быть инициализирована при объявлении: Тип & имя_ссылки = переменная; Пример int x=3; int &y=x; cout<<y<<'\n'; x+=5; cout<<y<<'\n'; //выведется 3 //выведется 8 Ограничения при использовании ссылок: Нельзя ссылаться на ссылочную переменную Нельзя создавать указатель на ссылку Над ссылками нельзя производить какие либо операции (в отличии от указателей) § Функции Программа в языке C++ состоит из функций. Функции - относительно самостоятельные фрагменты программы, спроектированные для решения конкретных задач и снабженные именем. С одной из них, главной, мы уже знакомы. Функции аналогичны программам в миниатюре и имеют общее название подпрограммы. Главное отличие простой функции от главной в том, что с главной функции начинается выполнение программы, а все остальные функции вызываются либо главной, либо из других функций. Применение функций (подпрограмм) дает возможность сэкономить память за счет размещения многократно повторяющихся частей программ в функции, а также возможность конструировать программу как набор отдельных подпрограмм, что, в свою очередь, позволяет работать группе программистов над сложной задачей. Даже в том случае, если некоторая задача решается в одной программе и одним программистом, лучше оформить ее решение в виде подпрограмм, поскольку функции повышают уровень модульности, облегчают чтение, внесение изменений и коррекцию ошибок в программе. Различают понятия объявления и определения функций. Формат объявления функций в С++: - 34 - Тип_возвращаемого_значения имя_функции(список параметров); Примеры: float f (double, float); int ab (int *a); int *b(); //функция возвращает указатель void par (void); При объявлении функции можно указывать только типы параметров, не указывая имена. Список параметров необязателен и может отсутствовать (круглые скобки опускать нельзя). Формат определения функций в С++: Тип_возвращаемого_значения имя_функции(список параметров с указанием их типов); { тело функции; } Помещать определение функции можно как до, так и после определения функций, которые ее используют. Главное, чтобы перед использованием эта функция была объявлена. Мы будем помещать определения функций после препроцессорных директив перед main. Определения нельзя помещать в тело других функций. Вызов функции производится при помощи оператора вызова функции: имя(список_фактических_параметров); Он записывается в том месте программы, где по логике решения задачи требуется выполнение операторов, определенных в теле функции. После выполнения операторов тела функции управление передается оператору, следующему за оператором вызова. В качестве типов, возвращаемых функциями, могут быть указатели и ссылки. В теле функций, возвращающих какое-либо значение (не типа void) обязательно должен присутствовать оператор возврата return. После ключевого слова return записывается выражение, значение которого вставится вместо имени функции в точке вызова. Функция не может возвращать массив или функцию. В качестве результата, возвращаемого функцией, могут быть значения только простого и ссылочного типа (см. позже). Оператор return приводит к немедленному выходу из функции. Если функция имеет тип void, то оператор return может быть использован без возвращаемого значения. Пусть имеется следующее описание функции: int max (int a, int b) { if (a>b) return a; else return b; } Функция max может использоваться в выражении с = max(5,3); m = max (c-d, c+d) + 2*max (c*d, c%d); либо cout<<"max="<<max(c*d, c*d); В функциях могут быть описаны собственные константы, типы, переменные. В этом случае они называются локальными. Их область действия распространяется только на те функции, в которых они описаны. Переменные, описанные вне функций, в том числе и вне главной функции, называются глобальными. Их область действия распространяется на все функции, расположенные в том же файле после описания. Имена локализованных переменных могут совпадать с ранее объявленными глобальными именами. В этом случае считается, что локальное имя "закрывает" глобальное и делает его недоступным, т.е. одноименные локальные и глобальные переменные - это разные переменные. Память под глобальные переменные отводится до начала выполнения программы и освобождается в момент завершения программы. Доступ к ним возможен из любой функции. Память под локальные переменные выделяется в момент вызова подпрограммы и освобождается после завершения её выполнения. Доступ к ним возможен только из той подпрограммы, в которой они описаны. Если функция содержит аргументы, то соответствующие им переменные называются формальными параметрами функции. Область видимости этих переменных ограничивается рамками тела функции. Передача параметров функциям - 35 - Передавать параметры функциям можно по значению, по указателю и по ссылке. При передаче данных по значению функция получает конкретные значения переменных. Пример //Иллюстрация передачи параметра по значению #include<iostream.h> double mod(double a) { if (a<0) return -a; else return a; } void main() { double s=-876; cout<<mod(s)<<'\n'; } Параметр данном примере передается по значению. В этом случае под a выделяется дополнительная память, и туда копируется значение соответствующего фактического параметра s. Таким образом, в подпрограмме используются копии фактических параметров, что и позволяет в качестве последних применять выражения. Так как при передаче параметров по значению в функции работаем с копией, то изменение формального параметра не приводит к изменению соответствующего фактического параметра. Если же в изменении фактического параметра возникает необходимость, то параметры передаются по указателю или ссылке. Пример //Иллюстрация передачи параметра по указателю #include<iostream.h> void mod(double a, double *y) { (a<0)? *y=-a: *y=a; } void main() { double s=-876, x; mod(s,&x); cout<<x<<'\n'; } В этой программе функция имеет два параметра: один параметр, передаваемый по значению, и один параметр - по указателю. Параметр y, передаваемый по указателю, используется для возврата из функции значения, так как в этом случае передается не копия переменной, а её адрес. Таким образом, изменение формального параметра приводит к изменению фактического параметра в вызывающей функции. Параметры, передаваемые по указателю, не требуют дополнительной памяти и дополнительного времени для создания копий параметров. При работе с такими данными, как массивы, структуры, передача параметров возможна только по указателю. Рекурсивные функции Рекурсия представляет собой процесс определения чего-либо на собственной основе. В области программирования под рекурсией понимается процесс вызова функцией самой себя. Функцию, которая вызывает саму себя, называют рекурсивной. // программа вычисления n! #include<iostream.h> - 36 - int f(int i) { if (i==0) return 1; else return i*f(i-1); } void main() { int n; cout<<"Input n: "; cin>>n; cout<<n<<"! = "<<f(n)<<'\n'; } Когда функция вызывает сама себя, в системном стеке выделяется память для новых локальных переменных и параметров и тело функции выполняется с самого начала с этими новыми переменными. Рекурсивный вызов не создает новой копии функции. Новыми являются только аргументы. При написании рекурсивной функции необходимо включить в нее инструкцию проверки условия, которая бы обеспечивала выход из функции без выполнения рекурсивного вызова. Рекурсивные функции чаще всего применяют для компактной реализации рекурсивных алгоритмов. Некоторые задачи (особенно те, которые связаны с искусственным интеллектом) просто созданы для рекурсивных решений. Недостатками рекурсии является расход времени и памяти на повторные вызовы функции и передачу ей копий параметров и опасность переполнения стека. Использование функций, расположенных в другом файле #include<имя_файла> #include”имя_файла” Если имя файла заключено в угловые скобки, то поиск осуществляется в специальных каталогах, определенных конкретной реализацией С++. Если имя файла заключено в кавычки, поиск файла выполняется в текущем рабочем каталоге. Если заданный файл не найден, поиск повторяется с использованием первого способа (как если бы имя файла было заключено в угловые скобки). Пример // Текст файла m.txt // Текст рабочего файла double mod(double a) { if (a<0) return -a; else return a; } #include<iostream.h> #include"m.txt" void main() { double s=-2005; cout<<mod(s)+1<<'\n'; } Как указать путь к файлу: Пример: #include"z:\\data.txt" #include<z:\\data.txt> Указатели на функции - 37 - Несмотря на то, что функция не является переменной, она, тем не менее, занимает физическую область памяти, некоторый адрес которой можно присвоить указателю. Адрес функции можно получить, используя имя функции без круглых скобок и аргументов. Адрес, присваиваемый функции, является входной точкой этой функции. Если некоторый указатель ссылается на функцию, то ее можно вызвать с помощью этого указателя. // Пример #include<iostream.h> double mod(double a) { if (a<0) return -a; else return a; } void main() { double (*p)(double); double s=-2005; p=mod; cout<<(*p)(s)+1<<'\n'; cout<<p(s-1)<<'\n'; //объявляется указатель p на некоторую функцию //одного аргумента типа double и возвращающую //значение типа double //p – указатель на функцию mod //первый способ вызова функции через указатель //второй способ вызова функции через указатель } В этом примере первый и второй способы вызова функции с помощью указателя эквивалентны. Однако первый способ позволяет показать, что здесь реализован вызов функции через указатель p, а не вызов функции с именем p. Указатели на функции позволяют также передавать функции в качестве аргументов другим функциям. Пример //Программа, получающая целое число n и возвращающая сумму факториалов // 1!+...+n! и сумму квадратов 1^2+...+n^2 #include<iostream.h> int f(int k) { if (k==0) return 1; else return k*f(k-1); } //рекурсивная функция, вычисляющая k! int q(int k) { return k*k; } //возвращает квадрат числа k int sum(int (*p)(int), int n) //указатель на функцию и возвращает //искомую сумму { int s=0, i; for(i=1; i<=n; i++) //получает в качестве одного из аргументов - 38 - s+=p(i); return s; } void main() { int n; cout<<"Input n: "; cin>>n; cout<<"1!+...+n! = "<<sum(f,n)<<'\n'; cout<<"1^2+...+n^2 = "<<sum(q,n)<<'\n'; } § Массивы Массив это один из видов составных типов данных С++. Составной тип данных следует понимать как набор единиц данных, объединенных под одним именем. Массив – упорядоченная последовательность данных одного типа. Формат объявления одномерного массива: тип имя_массива[размер]; Здесь тип это тип элементов массива (не обязательно простой); имя_массива – произвольный идентификатор С++; размер – количество элементов, которые будут храниться в массиве. Примеры int mas1[10]; float * mas2[5]; //массив из 10 элементов типа int //массив из пяти из указателей на float Нельзя объявлять массив, элементами которого являются ссылки. Размер массива должен быть целочисленной константой. Примеры int i=10; int mas1[i]; //Ошибка! const int i=10; int mas1[i]; //Ошибки нет! Инициализация одномерного массива в программе Инициализировать одномерный числовой массив в программе можно двумя способами: тип имя_массива[размер]={список_элементов_через_запятую}; тип имя_массива[]={список_элементов_через_запятую}; Если числовой массив объявлен и инициализирован без указания размера, то он определяется компилятором автоматически и равен числу элементов списка. Если размер массива явно указан, задание большего числа элементов приведет к ошибке. Если в списке инициализации не достает элементов, то недостающие элементы полагаются равными нулю. Инициализация символьного массива (строки) может производится аналогично числовому: Примеры - 39 - char w[5]= {'A','B','C'}; char v[]= {'A','B'}; Символьный массив можно инициализировать также строковым литералом (списком символов, заключенным в двойные кавычки); при этом необходимо учитывать, что последним элементом массива будет неизображаемый символ ‘\0’. Примеры char w[5]= "ABC"; char v[]= "AB"; char u[4]= "ПМПЭ"; сhar z[]= ""; //Ошибка! (размерность=5) //нулевая строка, состоит из ‘\0’ В С++ все массивы занимают смежные ячейки памяти. Другими словами, элементы массива в памяти расположены последовательно друг за другом. Ячейка с наименьшим адресом относится к первому элементу массива, а с наибольшим – к последнему. Имя массива является указателем на его первый элемент. Доступ к отдельному элементу массива можно получить двумя способами: 1) по индексу; 2) по указателю. Элементы массива нумеруются с нуля. Поэтому номер последнего элемента массива размера n равен n – 1. Чтобы получить доступ к элементу массива по индексу, достаточно после имени массива указать номер элемента в квадратных скобках: имя_массива[номер_элемента] Пример //Программа создает массив mas1 из 10 целых чисел, заполняет его случайными //числами и выводит на экран #include<iostream.h> #include<cstdlib> void main() { int i; const k=10; int mas1[k]; for(i=0; i<k; i++) mas1[i]=rand(); for(i=0; i<k; i++) cout<<mas1[i]<<'\t'; } Доступ к элементу по указателю осуществляется следующим образом: *(имя_массива+номер_элемента) Пример int i, *p; int mas1[10]; p=mas; for(i=0; i<k; i++) *(p+i)=rand(); for(i=0; i<k; i++) { cout<<*p<<' '; p++; - 40 - } В С++ указатель, который ссылается на массив можно индексировать так, если это было бы имя массива. Пример int i, *p; int mas1[10]; p=mas; for(i=0; i<10; i++) p[i]=rand(); Несмотря на тесную связь между массивами и указателями, в общем случае указатели и массивы не являются взаимозаменяемыми. Пример int mas[10]; for(i=0; i<10; i++) { *mas=i; //здесь все нормально mas++; //ошибка – переменную mas нельзя инкрементировать } Ошибка возникла из-за того, что имя массива является константой и его значение изменению не подлежит. В связи с этим в С++ нельзя также присвоить один массив другому: int a[5], b[5]; ... a=b //Ошибка! Считывание строк с клавиатуры и их вывод на экран – можно осуществлять по аналогии с числовыми массивами. Но С++ позволяет это делать проще: Пример char str[10]; cout<<”Введите свое имя: ”; cin>>str; cout<<”Hello, ”<<str<<’\n’; Оператор >> прекращает считывание строки как только встречает символ пробела, табуляции или новой строки. Поэтому, если пользователь вводит несколько слов, то при использовании оператора >> в символьный массив считается только первое слово. Для решения этой проблемы можно использовать библиотечную функцию gets(). Общий формат ее вызова таков: gets(имя_массива) Функция gets() считывает вводимые пользователем символы до тех пор, пока он не нажмет клавишу Enter. Для вызова функции gets() в программу необходимо включить заголовок <cstdio>. При работе с массивами следует иметь в виду, что в С++ не выполняется никакой проверки «нарушения границ». Компилятор не выдает сообщение о выходе за границы массива и ответственность за соблюдение границ массива лежит только на программисте. Передача массивов функциям Возможны 3 способа передачи одномерных массивов функциям в качестве аргументов. Рассмотрим их на примере - 41 - Пример //функция, выводящая целочисленный массив на экран //n-размер массива void display(int *mas, int n) //2-способ: void display(int mas[n]) //n- размер массива, константа //3-способ: void display(int mas[], int n) //n- размер массива //эквивалентно первому способу // int mas[] воспринимается компилятором как указатель на int { for(i=0; i<n; i++) { cout<<mas[i]<<’\t’; } cout<<’\n’; } Многомерные массивы Многомерные массивы описываются как массивы массивов: тип имя_массива[размер1][размер2]...[размерN]; Например, двумерный массив - это одномерный массив, элементами которого являются одномерные массивы. Запись int a[m][n]; означает, что объявлен одномерный массив a из m элементов a[0],...,a[m-1], являющихся в свою очередь одномерными массивами размера n; a[0],...,a[m-1] – указатели на первые элементы этих массивов. Инициализация многомерных массивов можно производить по аналогии с одномерными: Пример int a[3][2]={1,2,3,4,5,6}; При инициализации многомерного массива список инициализаторов каждой размерности можно заключить в фигурные скобки. Примеры int a[3][2]={{1,2},{3,4},{5,6}}; int b[3][2]={{1},{3,4},{5,6}}; //Элемент b[1][2] будет равен 0 Доступ к отдельному элементу двумерного массива, как и к элементу одномерного можно получить двумя способами: 1) по индексу имя_массива[индекс_1][индекс_2]...[индекс_N]; 2) по указателю. *(...*(*(имя_массива + индекс_1) + индекс_2) + ... + индекс_N); Пример //Программа, возвращающая сумму элементов двумерного массива void main() { int a[3][2]={{1,2},{3,4},{5,6}}; int i,j,s=0; for(i=1;1<3;i++) for(j=1;1<2;i++) s+=a[i][j]; //s+=*((*a+i)+j) cout<<”s=”<<s<<’\n’; - 42 - } Если в объявлении или определении функции в качестве аргумента используется многомерный массив, то его первая размерность может не указываться, а все последующие размерности должны быть обязательно указаны. Пример //функция вывода двумерного массива на экран void display(int a[3][2]) // или (int a[][2]) // или (int a[][2], int m) и m исп. в цикле { int i,j; for(i=0;i<3;i++) { for(j=0;j<2;j++) cout<<a[i][j]<<'\t'; cout<<'\n'; } } void main() { int mas[3][2]={{1},{3,4},{5,6}}; display(mas); } Динамическое распределение памяти. Динамические массивы. Для программы на С++ существует два основных способа хранения информации в основной памяти компьютера. Первый из них состоит в использовании переменных. Область памяти, предоставляемая переменным, закрепляется за ними во время компиляции и освобождается только после завершения работы программы. Второй способ заключается в использовании системы динамического распределения памяти. В этом случае память выделяется по мере необходимости из раздела свободной памяти, который называется «кучей» (heap). Поскольку объем «кучи» конечен, она может когда-нибудь исчерпаться. Благодаря системе динамического распределения памяти программа может создавать переменные и освобождать выделенную под них область памяти во время выполнения. Эта система широко используется для работы с такими структурами данных как связные списки и двоичные деревья, которые изменяют свой размер по мере их использования. Для динамического выделения области памяти используется оператор new. Формат записи: переменная-указатель = new тип_переменной; Динамическую переменную можно инициализировать при объявлении: переменная-указатель = new тип_переменной(значение); Для освобождения динамической области памяти и возвращения ее в «кучу» используется оператор delete. Формат записи: delete переменная-указатель; Пример #include<iostream.h> void main() { int *p = new int (2005); cout<<*p+1<<'\n'; delete p; } С помощью оператора new можно выделять память и для массивов. Такие массивы называются динамическими. Формат объявления одномерного динамического массива: переменная-указатель = new тип [размер]; Для освобождения памяти, выделенной для динамически созданного одномерного массива используется следующий формат записи оператора delete: delete [] переменная-указатель; - 43 - #include<iostream.h> void main() { int i,n; cout<<"Input size of array: "; cin>>n; int *a = new int [n]; cout<<"Input the elements of array: "; for(i=0;i<n;i++) cin>>a[i]; cout<<"=============================\n"; cout<<"n:\tn^2:\n"; for(i=0;i<n;i++) cout<<a[i]<<'\t'<<a[i]*a[i]<<'\n'; delete [] a; } Многомерные динамические массивы формируются из одномерных. Формат объявления двумерного динамического массива: int ** переменная-указатель = new тип * [размер1]; for(i=0;i<размер1;i++) переменная-указатель[i] = new тип [размер2]; Для освобождения памяти, выделенной для динамически созданного одномерного массива используется следующий формат записи оператора delete: for(i=0;i<размер1;i++) delete [ ] переменная-указатель[i]; delete [ ] переменная-указатель; Пример //Программа, возвращающая произведение матриц, задаваемых пользователем #include<iostream.h> #include<process.h> int ** mult(int ** A, int ** B, int m, int k, int n) { int i,j,r; int **C = new int* [m]; for(i=0;i<m;i++) C[i] = new int [n]; for(i=0;i<m;i++) for(j=0;j<n;j++) { C[i][j]=0; for(r=0;r<k;r++) C[i][j]+=A[i][r]*B[r][j]; }; return C; //-----------------удаление массива С------------for(i=0;i<m;i++) delete [] C[i]; delete [] C; } void main() { int m, k, //число строк первой матрицы //число столбцов первой матрицы n, //число строк второй матрицы l, //число столбцов второй матрицы i,j; - 44 - //--------------------------------------------------------cout<<"Input the row and column number of array A: "; cin>>m>>k; int **A = new int* [m]; for(i=0;i<m;i++) A[i] = new int [k]; cout<<"Input the elements of array A: "; for(i=0;i<m;i++) for(j=0;j<k;j++) cin>>A[i][j]; //--------------------------------------------------------cout<<"Input the row and column number of array B: "; cin>>l>>n; if(l!=k) { cout<<"!!!\n"; exit(0); } int **B = new int* [k]; for(i=0;i<k;i++) B[i] = new int [n]; cout<<"Input the elements of array B: "; for(i=0;i<k;i++) for(j=0;j<n;j++) cin>>B[i][j]; //--------------------------------------------------------int**p; p=mult(A,B,m,k,n); //-----------------удаление массива А------------for(i=0;i<m;i++) delete [] А[i]; delete [] А; //-----------------удаление массива В------------for(i=0;i<m;i++) delete [] В[i]; delete [] В; //-----------------------------------------------for(i=0;i<m;i++) { for(j=0;j<n;j++) cout<<p[i][j]<<'\t'; cout<<'\n'; }; } Как показывает пример, функция может не только получать массив в качестве аргумента, но и возвращать указатель на массив. Файловые потоки ввода-вывода Стандартная библиотека С++ содержит 3 класса для работы с файлами: ifstream – класс входных потоков ofstream – класс выходных потоков fstream – класс двунаправленных файловых потоков ofstream Программа fstream Файл ifstream Для работы с этими файлами к программе необходимо подключить (с помощью препроцессорной директивы include) заголовочные файлы <ifstream.h>, <ofstream.h>, <fstream.h> соответственно. - 45 - Использование файлов в программе предполагает следующие операции: 1. создание потока; 2. открытие потока и связывание его с файлом; 3. обмен (ввод/вывод); 4. уничтожение потока; 5. закрытие файла. (1) Объявляются потоки (потоковые переменные) в программе так же, как и любые другие переменные: имя_класса имя_потока; Именем потока может являться любой идентификатор С++. (2) Открытие потока и связывание его с файлом осуществляется с помощью функции open(). Ее вызов производится с помощью оператора « . »: имя_потока.open(“имя_файла”,режим); Режимы могут быть следующими ios::in //Открыть файла для ввода данных ios::out //Открыть файла для вывода данных ios::ate //Установить указатель на конец файла ios::app //Открыть для добавления в конец (можно применять //только для файлов, открытых для вывода данных) ios::trunc //Открыть файл и уничтожить его содержимое ios::noncreate //Открыть только существующий файл с данным именем. ios::noreplace //Создать и открыть только не существующий файл ios::binary //Открыть в двоичном режиме. По умолчанию все файлы //открываются в текстовом режиме, что может приводить к //преобразованиям некоторых символов. При открытии //файла в текстовом режиме никакого преобразования //символов не выполняется. Если необходимо задать несколько режимов, то используется разделитель |. Пример ifstream mystream; mystream.open(“filename”, ios::noncreate | ios::binary); Если открываемый файл не существует и не использован режим ios::noncreate, то, при использовании компиляторов фирмы Microsoft, он создается автоматически. При использовании компиляторов фирмы Borland, новый файл не создается. Не открытый в результате неудачного выполнения функции open() поток при использовании в логическом выражении устанавливается равным значению ЛОЖЬ. Этот факт может служить для проверки успешного открытия файла: if(!mystream) { cout<<”Error: File doesn’t open/n”; //обработка ошибки, например функция exit() } Можно также проверять факт успешного открытия файла с помощью функции is_open(). Эта функция возвращает значение ИСТИНА, если поток связан с открытым файлом и ЛОЖЬ – в противном случае. Вызов функции is_open() также производится с помощью оператора « . ». Пример if(!mystream.is_open()) { cout<<”Error: File doesn’t open/n”; //обработка ошибки, например функция exit() } Существует более простой способ связать поток с конкретным файлом (без использования функции open()): имя_потока(“имя_файла”,режим); (3) Для чтения из файла и записи данных в файл могут использоваться обычные операторы ввода и вывода >> и <<. Пример int n; mystream>>n; //считываем из файла в переменную mystream<<n; //записываем в файл переменную n Как и при использовании cout, оператор >> прекращает считывание строки, как только встречает символ пробела, табуляции или новой строки. Чтение из файла и запись в файл можно производить также с помощью специальных функций, некоторые из которых позволяют избежать описанный выше недостаток оператора >>: get(), put(), read(), write(), getline() и др. - 46 - (6) Для уничтожения потока и закрытия файла используется функция close(). Она не имеет параметров и не возвращает никакого значения. Доступ к ней осуществляется с помощью оператора « . » Пример: mystream.close(); //==================================================================== //программа, получающая имя файла, содержащего последовательность //целых чисел, и возвращающая максимальное из них //==================================================================== #include<fstream.h> //содержит iostream #include<process.h> int func(char*name) { double x,max; int k=0; fstream Stream; Stream.open(name,ios::in|ios::nocreate); if(!Stream) { cout<<"Error: file "<<name<<" doesn't open!\n"; exit(1); }; Stream>>x; max=x; while(Stream>>x) { if(x>max) max=x; } Stream.close( ); return max; } void main( ) { char Filename[25]; cout<<"Input name of file = "; cin>>Filename; cout<<func(Filename)<<"\n"; } //======================================================================= //======================================================================= Для вывода на экран русского текста, можно определить в отдельном текстовом файле функцию Rus(): #include<windows.h> char* Rus(const char* text); char bufRus[256]; char* Rus(const char* text) { CharToOem(text, bufRus); return bufRus; } и присоединять текст этого файла к программе с помощью include. Пример использования: cout<<Rus("Введите свое имя "); МЕТОДЫ ТРАНСЛЯЦИИ Глава 1. Общие сведения о системах программирования. Параграф 1. Типовая система программирования. Пункт 1. Определение СП. Виды. - 47 - Система программирования представляет собой совокупность программных средств автоматизации разработки программ, их отладки и подготовки к выполнению: трансляторы с их языками программирования, редактор связей, загрузчик, средства отладки, библиотеки трансляторов и вспомогательные программы (утилиты). СП выполняется под управлением ОС и может иметь с ней общие компоненты, например, загрузчик, библиотеки, утилиты. СП можно разделить на самостоятельные и встроенные по отношению к другим программным комплексам. Самостоятельные СП являются системами общего назначения. Встроенные СП являются составной частью другого программного комплекса. Такие СП применяются в СУБД и наиболее развитых пакетах прикладных программ. Пункт 2. Варианты основных компонентов СП. Трансляторы. Это программы, выполняющие преобразование программы, представленной на одном языке, в эквивалентную ей программу на другом языке. Варианты трансляторов в зависимости от назначения: 1. Компилятор. Переводит программу с одного языка на язык более низкого уровня (машинноориентированный или, чаще всего, машинный). При переводе на машинно-ориентированный язык он строит программу, подобную программе на языке ассемблера. 2. Ассемблер. Это компилятор на языке ассемблера. 3. Интерпретатор. Выполняет программу в машинно-независимой форме посредством подпрограмм, входящих в его состав. Обычно он предварительно переводит программу на некоторый более удобный промежуточный язык. Промежуточная программа не сохраняется. 4. Инкрементный транслятор. Предназначен специально для повторной трансляции фрагментов программы и дополнений к ней без повторной трансляции всей программы. 5. Компилятор переднего плана. Переводит программу на промежуточный машинно-независимый язык. Позволяет сохранять промежуточную программу. 6. Препроцессор. Переводит программу с макрорасширения входного языка транслятора на этот входной язык. Обычно реализуется как префиксная часть некоторых трансляторов. 7. Макрогенератор. Это препроцессор для ассемблера. Переводит программу с макроязыка на язык ассемблера. 8. Кросс-компилятор. Транслируя программу на одной машине (платформе), формирует объектный код для другой машины (платформы). 9. Компилятор компиляторов. Переводит формальное описание языка программирования в транслятор для этого языка. 10. Детранслятор. Восстанавливает программу на ЯП по ее эквиваленту на языке машины. 11. Дисассемблер. Это детранслятор для языка ассемблера. Компоновщики. Это общее название системных программ, выполняющих подготовку объектных и загрузочных модулей к их выполнению. Типовые функции компоновщиков, которые их различают: 1. перемещение – настройка сегментов на размещение их в новом адресном пространстве. 2. связывание – объединение двух или более модулей в единую программу. 3. перекрытие – настройка модулей программы на выполнение с перекрытиями групп сегментов в основной памяти. 4. загрузка – размещение программы в основной памяти для выполнения. Параграф 2. Классификация языков программирования. Основные характеристики ЯП: мощность, уровень и целостность. 1. Мощность. Характеризуется разнообразием задач, алгоритмы которых можно записать, используя этот язык. Очевидно, самым мощным ЯП является язык процессора, так как любую задачу, сформулированную на каком-либо языке программирования, можно записать и на языке компьютера. 2. Уровень языка. Определяется сложностью решения задач с использованием этого языка. Чем проще записывается решение задач, чем меньше объем полученных исходных программ, тем выше уровень языка. 3. Целостность. Обусловливается свойствами совокупности понятий, служащих для описания этого языка, и включает 3 взаимосвязанных аспекта: А) экономия понятий предполагает достижение максимальной мощности языка с помощью минимального числа понятий. Б) независимость понятий означает, что между ними не должно быть взаимного влияния: если какое-либо понятие используется в разных контекстах, то правила его использования должны быть одни и те же. В) единообразие понятий требует согласованного единого подхода к описанию и использованию всех понятий. - 48 - Глава 2. Основные понятия и определения формальных языков. Параграф 1. Формальные грамматики и языки. Пункт 1. Определение языков посредством множеств. В основе каждого языка лежит алфавит. Алфавит определяется множеством символов, применяемых для построения строк (цепочек, предложений) языка. Пример 1. Алфавит H 16-ричных цифр и алфавит B двоичных цифр: H 0,1, 2,3, 4,5,6,7,8,9, A, B, C, D, E, F ; B 0,1 . Строка – это любая последовательность символов алфавита, расположенных один за другим. Пустой называется строка, содержащая нуль символов. Такую строку будем обозначать через . Строку из k символов a будем обозначать через a , например и . Строки символов можно именовать, например , , , x, y, z и др. Длину некоторой строки k будем обозначать 3 0 . A* используют для изображения множества всех строк, включая пустую, составленных из * символов, входящих в алфавит A . Очевидно, A A . Запись A* можно представить в виде сцепления (конкатенации) xy строк, где x A* и y A* . Строку x в такой записи называют префиксом (головой - head), а строку y - суффиксом (хвостом - tail) строки . Если для строки нужно обозначить ее голову, содержащую k символов, то пишут hk ( ) . Также для обозначения хвоста длины k пишут tk ( ) . * Строку y в строке xyz , где , x, y, z A , называют подстрокой строки . * Формальным языком L в алфавите A называют произвольное подмножество множества A . Любую строку Поскольку формальные языки представляют собой множества, над языками можно выполнять операции объединения, сцепления и др. Особыми конкатенациями являются L L L и L L Итерация языка L или замыкание Клини - сцепление произвольного числа строк некоторого формального языка L : L Ln . n0 n 1 L , L LL 0 n Ln1L, n 1. L обозначает нуль или более сцеплений языка L . Позитивное замыкание содержит одно или более сцеплений языка: L L LL L L, L L . Задать формальный язык L в алфавите A - значит либо перечислить все строки языкового подмножества множества A , либо указать правила их образования. n n Например, в алфавите A 0,1 запись L 0 1 | n 0 определяет язык L как пустую строку и любое количество строк, каждая из которых состоит из строки нулей и последующей строки единиц такой же длины. Перебирая значения ограниченном n , получаем L ,01,0011,000111,... , что при любом n является подмножеством множества A* . Пункт 2. Понятие о формальной грамматике. Формальная грамматика – это математическая система, определяющая язык посредством порождающих правил. В отличие от формулы для определения множества формальная грамматика содержит ряд взаимосвязанных правил для получения (порождения, генерации) строк языка. Пример. Даны два нумерованных правила: 1) S 0S1 2) S Символы 0 и 1 принадлежат алфавиту языка, а S - вспомогательный символ. Строки языка получаются путем применения этих правил в любой комбинации, причем вспомогательный символ в строке языка не должен присутствовать. Вспомогательных символов может быть любое необходимое количество. Рассмотрим применение данных правил в последовательности 1, 1, 2: - 49 - S 1 0S1 1 00S11 2 0011 . Это есть вывод строки 0011 из символа S . Руководствуясь правилами 1 и 2, можно вывести все строки соответствующего языка L 0n1n | n 0 и никакие другие. Таким образом, формальная грамматика есть своеобразный алгоритм порождения всех строк языка, который эта грамматика описывает. Пункт 3. Определение формальной грамматики. Формальная грамматика определяется как четверка VT ,VN , P, S , в которой приняты следующие обозначения: VT - алфавит языка, строки которого порождает грамматика. Элементы множества VT называют терминалами; VN - вспомогательный алфавит, символы которого обозначают допустимые комбинации VT и VN . Элементы множества VN называют нетерминалами. VT VN . Объединение этих множеств V VT VN называют словарем грамматики; элементов множеств P - множество порождающих правил. Каждое правило состоит из пары левая часть правила, а V * - правая часть. Правила записываются в виде , , где V , где строка содержать хотя бы один нетерминал. Очевидно, справедлива запись - должна P V V * (это прямое произведение множеств, результат – пара, один элемент которой из одного множества, а другой – из другого); S VN - начальный символ (аксиома) грамматики. С него начинается вывод, генерирующий любую строку языка. Грамматику обычно именуют. Имя грамматики можно использовать вместо определяющей ее четверки. Обозначим грамматику, генерирующую язык L 0 1 | n 0 , как n n G0 . Тогда грамматику G0 можно определить так: G0 0,1 , S , S 0S1, S , S . Каждая строка, которую можно вывести из начального символа грамматики, называется сентенциальной формой. К сентенциальным формам также относят аксиому S грамматики, поскольку вывод за нуль шагов тоже считается выводом. Сентенциальная форма может содержать любые символы словаря грамматики – как терминалы, так и нетерминалы. Сентенциальная форма, состоящая только из терминалов, представляет собой строку языка (или предложение языка). Обозначение G применяют в случае, когда хотят показать, что вывод V * из V происходит при однократном применении одного правила грамматики G . В случаях, когда не возникает неопределенности относительно применяемой грамматики, обозначение грамматики можно не указывать. из строки за один или * более шагов применения правил грамматики G . Запись означает вывод из за нуль или более шагов применения правил грамматики G . Запись вида Таким образом, если 0 , 1 ,..., n V * и применяют для обозначения вывода строки 0 1 ,1 2 ,..., n1 n , n 1 , то обычно пишут 0 1 2 ... n1 n При или короче: 0 n . n 0 используется общая запись 0 * n . Число шагов вывода n называют степенью и иногда обозначают n . * * Строка V , для которой существует вывод S , где S - аксиома грамматики G , есть * * сентенциальная форма, а строка VT , для которой имеется вывод S , есть строка языка L , отношения порождаемого грамматикой G . Формально L G VT* , S * . - 50 - Это определение языка L , порождаемого грамматикой G , как множества таких строк, которые образованы только из терминалов и выводятся из начального символа грамматики G . Параграф 2. Генерация, распознавание и преобразование языков. Пункт 1. Классификация грамматик. Будем обозначать: a, b, c и т.д. - терминал, A, B, C и т.д. – нетерминал, , , , и т.д. – строка из символов словаря грамматики. Как правило, одну и ту же строку языка можно генерировать, используя разные грамматики. Грамматики, генерирующие один и тот же язык, называют эквивалентными. Различные грамматики обладают различными свойствами. Одной из основополагающих характеристик свойств грамматик является вид правил грамматик. Существует классификация грамматик по виду правил, предложенная американским математиком Хомским, известная как иерархия Хомского. Рассмотрим расширенную иерархию Хомского. В расширенной иерархии имеется 4 уровня (типа, класса) грамматик. G VT ,VN , P, S является наиболее общей. Называют грамматикой без ограничений. В такой грамматике порождающие правила имеют вид , а на строки и не 1. Грамматика типа 0. накладывается никаких ограничений, за исключением того, что левая часть правила не должна быть пустой ( V , V * ). Другие типы грамматик (1, 2, 3) получаются путем усиления ограничений на строки правилах грамматики. 2. Грамматика типа 1 имеет правила вида выполняется отношение , где V , V * и в и для длин строк . Грамматику типа 1 называют контекстно-зависимой грамматикой, КЗ- грамматикой, или неукорачивающей грамматикой. Расширение допускает не более одного есть правила вида A , где A VN . 3. Грамматика типа 2 имеет правила вида A , где -правила, то A VN и V * . Ее называют контекстно-свободной грамматикой (КС-грамматикой). 4. Грамматика типа 3 имеет правила двух видов: A a и A aB , где a VT , A VN , B VN . Грамматика типа 3 называется регулярной грамматикой. Расширение допускает единственное -правило S , но в этом случае аксиома S не должна появляться в правых частях правил. Пункт 2. Механизмы распознавания и преобразования. Разработка и применение языков обычно требуют проработки по крайней мере 3 основных проблем. 1. Должен быть создан механизм порождения языка, то есть система описания строк языка. Такой механизм называется генератором. Наиболее распространенными типами генераторов являются рассмотренные классы формальных грамматик. Генератор задает правила образования строк языка. 2. Кроме задачи написания правильных строк возникает противоположная задача – задача проверки правильности имеющихся строк. То есть это задача проверки принадлежности заданной строки заданному языку. Для этого создается механизм распознавания – распознаватель. Схема распознавателя: Распознаватель можно представить схематично в виде совокупности входной ленты, управляющего устройства и вспомогательной памяти. Входную ленту рассматривают как - 51 - последовательность ячеек, каждая из которых содержит один символ некоторого конечного входного алфавита. Обычно входная головка только читает, то есть содержимое входной ленты распознаватель не меняет. Управляющее устройство – это программа управления распознавателем. Она задает конечное множество состояний распознавателя и определяет переходы из состояния в состояние в зависимости от прочитанного символа входной ленты и содержимого вспомогательной памяти. Вспомогательная память служит для хранения информации, которая зависит от состояния распознавателя. Память может быть организована в виде магазина (стека). Объем памяти не ограничивается. Иногда вспомогательная память может отсутствовать. Последовательность шагов распознавателя: 1. чтение текущего символа входной ленты 2. анализ входного символа, содержимого памяти и текущего состояния 3. перемещение (если нужно) входной головки 4. изменение содержимого памяти 5. изменение состояния распознавателя. Схема преобразователя: На каждом шаге работы преобразователь пишет на выходную ленту строку выходных символов. Строка, полученная на выходной ленте, считается переводом входной строки, если входная строка переводит преобразователь из начального состояния в заключительное. Глава 3. Регулярные грамматики и языки. Параграф 1. Описание регулярных языков. Пункт 1. Регулярные выражения. Регулярная грамматика – это КС-грамматика G VT ,VN , P, S с правилами вида A a и A aB , где a VT и A, B VN , причем A и B могут быть одинаковыми нетерминалами. Допускается также правило S при условии, что S не появляется в правых частях правил. В теории формальных языков рассматриваются праволинейные и леволинейные грамматики, которые отличаются только положением (справа и слева) нетерминала в правой части правила. Отличие их от определенной нами регулярной грамматики в том, что a VT , то есть * a может быть строкой терминалов, в том числе и пустой. Язык называется регулярным, если он может быть порожден регулярной грамматикой. Справедливы следующие утверждения: Если L1 и L2 - регулярные языки, то замыкание Клини L*1 , сцепление L1 L2 и объединение L1 L2 - тоже регулярные языки. Это свойство замкнутости регулярных языков относительно указанных операций. Метод проверки регулярности заданного языка. В основе метода лежит лемма о разрастании языка, смысл которой в следующем: В достаточно длинной строке регулярного языка всегда можно найти непустую подстроку, повторение которой произвольное количество раз порождает новые строки того же языка. Также удобным средством формального определения регулярных языков (множеств) являются регулярные выражения. Регулярные выражения над некоторым алфавитом определяются следующим образом: 1) - регулярное выражение (обозначает пустое регулярное множество ); - 52 - 2) строки); - регулярное выражение (обозначает регулярное множество , состоящее из пустой a - регулярное выражение (обозначает множество a ); 4) если p и q - регулярные выражения, обозначающие множества P и Q , то посредством 3) операций над выражениями определяются выражения следующих трех видов: а) p | q или p q - регулярное выражение (обозначает объединение P Q ), где символ | или + называют операцией или (альтернативы); б) pq или p q - регулярное выражение (обозначает множество PQ xy | x P, y Q ), где символ «точка» называют операцией сцепления (конкатенации); в) p* - регулярное выражение (обозначает множество P* ), где символ «*» называют операцией итерации. № п/п 1 Пункт 2. Свойства регулярных выражений. Два регулярных выражения равны, если они определяют одно и то же множество. Рассмотрим основные алгебраические свойства регулярных выражений: Свойство № п/п Свойство 10 * * * | | * | | | | 11 12 | | 4 13 * * * 5 | | 14 * * 6 | | 15 7 16 * * 8 17 9 * | * * | 18 2 3 * | * * * | * * * | | | * * * * * * * Параграф 2. Конечный автомат. Пункт 1. Определение конечного автомата. Конечный автомат (КА) – это простейший распознаватель без вспомогательной памяти. Его входная головка читает символы входной ленты и перемещается на одну ячейку вправо на каждом шаге работы автомата. КА имеет конечное множество управляющих состояний, среди которых выделены начальное состояние и заключительные состояния. КА создается для конкретного регулярного языка. Закончив просмотр входной строки, КА должен выдать заключение о том, принадлежит или не принадлежит эта строка соответствующему языку. КА является эффективным способом определения регулярных языков. Конечный автомат M определяется пятеркой объектов: M Q, , , q0 , F , где Q - конечное множество состояний автомата; - конечное множество допустимых входных символов; - функция переходов: отображение множества Q в множество P Q , то есть : Q P Q ,где P Q - множество всех подмножеств множества Q (степень множества Q ); q0 - начальное состояние автомата, q0 Q ; F - множество заключительных состояний, F Q . - 53 - Функция переходов служит для определения очередного состояния q ' КА по текущему состоянию q и текущему входному символу a : q, a , q ' | q, a Q , q ' P Q , q ' q, a . Из определения функции переходов следует, что она неоднозначна, так как текущей паре q, a соответствует множество q ' очередных состояний КА. КА с такой функцией переходов является недетерминированным (НКА). Если в качестве функции переходов использовать отображение : Q Q , то соответствующий КА будет детерминированным (ДКА). ДКА является частным случаем НКА. Параграф 3. Преобразование конечных автоматов. Пункт 1. Задачи преобразования. На практике более предпочтительным является детерминированный КА. Также желательно, чтобы КА не содержал лишних состояний. Кроме того, переход к конечным автоматам с указанными свойствами не должен приводить к изменению языка, принимаемого КА. В связи с этим возникают две основные задачи эквивалентного преобразования КА: 1) преобразование недетерминированного конечного автомата в детерминированный конечный автомат; 2) минимизация КА. Разрешимость таких преобразований формулируется следующими утверждениями: 1. для любого НКА существует ДКА, принимающий тот же регулярный язык. 2. каждое регулярное множество распознается единственным для данного множества ДКА с минимальным числом состояний. Конечный автомат может содержать лишние состояния двух сортов: недостижимые состояния – состояния, к которым нет пути из начального состояния, и эквивалентные состояния – состояния, дублирующие друг друга. Пункт 2. Устранение недостижимых состояний. Рассмотрим алгоритм устранения недостижимых состояний. Алгоритм состоит в следующем. Вход: КА M Q, , , q0 , F . Выход: КА M ' Q ', , ', q0 , F ' без недостижимых состояний. 1. Поместить начальное состояние КА в список достижимых состояний Q Д . 2. Для новых элементов списка достижимых состояний пополнить список группой их состоянийпреемников, отсутствующих в списке. Повторять п.2, пока список достижимых состояний не перестанет меняться. Исключить из множества Q состояний КА все состояния, отсутствующие в списке Q Д 3. 4. достижимых состояний: Q ' Q Q Д . 5. Исключить недостижимые заключительные состояния и функции переходов, содержащие недостижимые состояния: F ' F Q Д , ' q, a p | q Q Q Д . Пункт 3. Объединение эквивалентных состояний. Рассмотрим алгоритм объединения эквивалентных состояний. Если исходный КА освобожден от недостижимых состояний, то алгоритм дает минимальный КА. Вход: КА M Q, , , q0 , F без недостижимых состояний. M ' Q ', , ', q0 , F ' . 1. Разбить множество Q на две группы неэквивалентных состояний по условию подобия Выход: Минимальный КА 2. 3. 4. 5. (заключительные и незаключительные). Разбить очередную группу состояний на группы неэквивалентных состояний по условию преемственности (для каждого входного символа). Выполнять п.2, пока остается хотя бы одна группа состояний, которую можно разбить. Обозначить очередную оставшуюся неразбитой группу состояний (она содержит только эквивалентные состояния) и включить обозначение в таблицу новых обозначений состояний. Выполнять п.4, пока остается хотя бы одна необозначенная группа. - 54 - 6. Определить эквивалентный КА M ' в новых обозначениях. Порядок разбиения на группы не влияет на правильность решения. Пункт 4. Построение детерминированного КА. Рассмотрим алгоритм преобразования недетерминированного КА в эквивалентный детерминированный. Оба КА принимают один и тот же язык. Вход: Недетерминированный КА M H Q, , , q0 , F . Выход: Детерминированный КА M Д Q ', , ', q0 , F ' . 1. Пометить первый столбец таблицы переходов ДКА M Д начальным состоянием (множеством начальных состояний) НКА 2. По множеству состояний MH . S , помечающему столбец таблицы переходов M Д , в котором позиции еще не заполнены, определить те состояния M H , которые могут быть достигнуты из S при каждом входном символе x . Поместить каждое найденное множество R (в том числе ) в M Д . Формально: соответствующие позиции столбца таблицы переходов S ' S , x s | s t, x для некоторого t S . ), записанного в позиции столбца S , проверить наличие столбца R в таблице переходов КА M Д . Если столбца R нет, добавить его. 3. Для каждого нового множества R (кроме 4. Если в таблице переходов КА M Д есть столбец с незаполненными позициями, перейти к пункту 2. 5. В множество F ' КА M Д включить каждое множество, помечающее столбец таблицы переходов M Д и содержащее q F КА M H . 6. Составить таблицу обозначений множеств состояний и определить КА M Д в новых обозначениях. Параграф 4. Взаимосвязь способов определения регулярных языков. Пункт 1. Построение КА по регулярному выражению. Конечный автомат можно получить из регулярного выражения путем построения функции переходов или диаграммы состояний автомата. Узлы диаграммы помечают произвольными уникальными именами, например A, B, C и т.д. или S0 , S1 , S2 и т.д. Дуги диаграммы именуют символами алфавита языка. 1. 2. Методика построения конечного автомата: изобразить начальное состояние автомата в виде поименованного узла диаграммы с входящей дугой. просмотреть очередной элемент регулярного выражения и построить соответствующую часть диаграммы: а) путь для строки символов алфавита a1a2 an n 1 : путь для той же строки, но под знаком итерации - a1a2 an * Sn S0 , то есть цикл длины n: б) дуга для альтернативных символов алфавита - 55 - a1 | a2 | | an n 2 дуга для той же конструкции, но под знаком итерации - a1 | a2 | | an * S1 S0 , то есть цикл длины 1: в) для n альтернативных строк a1a2 ai | b1b2 bj | путей, исходящих из одного узла и завершающихся общим узлом Ai B j для той же конструкции под знаком итерации путей, но при Ai B j г) для a1 a1 1 2 ai | b1b2 Gk S0 , то есть n циклов длины i, j , , k 2 - n Gk S1 : bj | | g1 g 2 g k - также n * , k соответственно. n альтернативных итераций строк ai | b1 * bj | | g1 * g k n, i, j, * узла, в каждом из которых имеется цикл длины i, j , а для a a gk n, i, j, | g1g2 , k 2 - n путей, исходящих из одного , k соответственно: n итераций строк, то есть для конструкции вида ai b1 * bj * g1 gk , - такая же диаграмма, дополненная дугами b1 от Ai к B1 , * g1 от Ai к G1 , g1 от B j к G1 и т.д. 3. определить множество состояний Q как совокупность имен всех узлов диаграммы. 4. определить множество заключительных состояний F как совокупность имен узлов, из которых либо ни одна дуга не выходит, либо исходящая дуга начинает необязательно реализуемую часть диаграммы. Эквивалентные заключительные состояния объединить. 5. по окончании просмотра всех элементов регулярного выражения определить КА формально и выполнить его проверку. - 56 - Пункт 2. Построение регулярной грамматики по КА. Регулярную грамматику G VT ,VN , P, S можно построить по конечному автомату M Q, , , q0 , F следующим образом. 1. принять имя начального состояния q0 КА в качестве начального символа S (аксиомы) грамматики. Имена состояний q Q автомата принять в качестве нетерминалов N VN , а входные символы a - в качестве терминалов a VT грамматики. переписать функции переходов КА вида 2. состояние, символ новое состояние в форме порождающих правил грамматики состояние символ новое состояние пополнить множество полученных правил правилами вида 3. состояние символ для функций переходов состояние, символ заключительное состояние q0 F (начальное состояние является также заключительным), добавить для аксиомы грамматики -правило q0 . 4. исключить из полученного множества правил грамматики бесполезные правила вида A aH , где A, H VN , a VT и H - бесполезный нетерминал (не порождает строки терминалов). 5. при наличии в грамматике G одновременно правил вида S и S aS , где S - аксиома и a VT , преобразовать грамматику в целях исключения S из правой части. Если Пункт 3. Построение КА по регулярной грамматике. Обычно при разработке языка его описывают порождающими правилами, то есть создают генератор языка. Для проверки правильности предложений языка конструируют распознаватель. Регулярной грамматике соответствует простейший распознаватель – конечный автомат. G V ,VN , P, S T Преобразование регулярной грамматики выполнить, используя следующую методику. 1. Пополнить грамматику правилом A aH , где в КА M Q, , , q0 , F можно A VN , a VT и H - новый (бесполезный) A a , если в грамматике нет соответствующего ему A aB , где B VN . 2. Начальный символ S (аксиому) грамматики принять в качестве начального состояния q0 S КА. нетерминал, для каждого правила вида правила Из нетерминалов образовать множество множество 3. 4. Q VN H состояний автомата, а из терминалов – VT символов входного алфавита. Каждое правило нетерминал терминал правый нетерминал функцию переходов нетерминал, терминал правый нетерминал . Сформировать множество заключительных состояний как множество из правил преобразовать в правых нетерминалов нетерминал терминал правый нетерминал , для которых имеются нетерминал терминал . 5. Если в грамматике имеется правило S , где S - аксиома грамматики, поместить S в соответствующие правила 6. множество заключительных состояний. Если получен недетерминированный конечный автомат, преобразовать его в детерминированный. Глава 4. Контекстно-свободные грамматики и языки. Параграф 1. Определение КС-языков. - 57 - Пункт 1. Задача и дерево разбора. Процесс порождения любой строки КС-языка можно представить в виде вывода. Вывод строки V * из строки i i 1 V * - это последовательность строк 1 , 2 ,..., n1 n таких, что i 1, 2,..., n 1 . для Различают левосторонний и правосторонний вывод. Левосторонний вывод получается путем применения правила грамматики каждый раз к самому левому нетерминалу сентенциальной формы. Правосторонний вывод отличается тем, что на каждом шаге замещается самый правый нетерминал сентенциальной формы. С понятием вывода тесно связано понятие разбора строки языка. 1) разбор – это задача выяснения принадлежности заданной строки языку, порождаемому заданной грамматикой. 2) разбор – это последовательность правил грамматики, определенным образом соответствующая выводу. Левосторонний разбор – последовательность порождающих правил, применяемых для генерирования строки языка посредством левостороннего вывода. Правосторонний разбор является обратной последовательностью порождающих правил, используемых для генерирования строки языка посредством правостороннего вывода. Все возможные выводы и разборы некоторой строки языка можно представить графически в виде дерева, которое называют деревом разбора. Пункт 2. Проверка существования языка. Обозначим N Z | Z VN , Z * x, x VT* и рассмотрим алгоритм проверки существования языка. G V ,V , P, S T N Вход: КС-грамматика . Выход: Если L (G ) , признак pr 1 , если L(G ) , pr 0 . . 1. Положить N 0 2. Вычислить N1 N 0 A | ( A ) P и ( N 0 VT ) * . N1 N 0 ( N изменилось), то положить N 0 N1 и перейти к пункту 2, иначе положить N N1 . 4. Если S N , то pr 0 (язык существует), иначе pr 1 (язык не существует). Если 3. Существуют бесполезные символы грамматики Их общее X G. * W X | X V , ( S X x ); , , x VT можно разделить на 3 группы: * а) нетерминалы, не порождающие терминальных строк X | X V , ( X б) порождающие недостижимые X | X V , (S * нетерминалы, * X ), ( X x ); , V ; x V * в) недостижимые терминалы множество * * X | X V , (S * * T ; x), x VT* ; терминальные строки X ); , V * . Нетерминалы (достижимые и недостижимые), не порождающие терминальных строк, можно вычислить и удалить, используя в качестве основы алгоритм проверки существования языка. АЛГОРИТМ: Вход: КС-грамматика G VT ,VN , P, S Выход: КС-грамматика . G ' VT ,V 'N , P ', S , у которой L(G ') L(G ) и для всех Z V 'N существуют выводы Z x , где x VT . * 1. Положить * N0 . - 58 - 2. Вычислить N1 N 0 A | ( A ) P и ( N 0 VT ) 3. Если * . N1 N 0 ( N меняется), то положить N 0 N1 и перейти к пункту 2, иначе положить N N1 . 4. V 'N VN N , N Б VN V 'N , P ' P PБ , где PБ P - это множество содержащих бесполезные нетерминалы то есть X NБ , Вычислить правил, PБ X P, A X P для всех X N Б , где A V ' N ; , V * . Пункт 3. Устранение недостижимых символов. Вход: КС-грамматика G VT ,VN , P, S Выход: КС-грамматика . G ' V 'T ,V 'N , P ', S , у которой L(G ') L(G ) и для всех Z V 'N существуют S * Z , где , (V ')* . 1. Положить W0 S . выводы 2. Вычислить W1 W0 X | X V , A X P, A W0 ; , V * . W1 W0 , то положить W0 W1 и перейти к пункту 2; иначе положить W W1 . 4. Вычислить V 'N VN W , V 'T VT W , VБ V W , P ' P PБ , где PБ P - это X VБ , множество правил, содержащих недостижимые символы то есть 3. Если PБ X P для всех X VБ , где V * . Параграф 2. Эквивалентные преобразования КС-грамматик. Пункт 1. Устранение -правил. Наличие в грамматике правил вида A часто создает проблемы ее применения. Поэтому во многих случаях оказывается полезным преобразование грамматики G , содержащей -правила, в эквивалентную грамматику G ' без таких правил. Грамматику без -правил называют также -свободной грамматикой. Если L(G ) , то в эквивалентной грамматике G ' не должно быть -правила ни для какого нетерминала, кроме аксиомы. При этом аксиома не должна встречаться в правых частях правил грамматики. Алгоритм решения рассматриваемой задачи состоит в следующем. G VT ,VN , P, S . Вход: КС-грамматика Выход: КС-грамматика 1. В исходной грамматике A * : G ' VT ,V 'N , P ', S ' без -правил, для которой L(G ') L(G ) . G найти -порождающие нетерминальные символы A VN , такие что 1.1. Положить N 0 A | A P . 1.2. Вычислить N 1 N 0 B | B P, V0 . * N1 N 0 , то положить N 0 N1 и перейти к пункту 1.2; иначе положить N N1 . 2. Из множества P правил исходной грамматики G перенести в множество P ' все правила, за исключением -правил: P ' P A P для всех A VN . 1.3. Если 3. Пополнить множество P ' правилами, которые получаются из каждого правила этого множества путем исключения одного и более -порождающих нетерминалов в правой части; полученные при этом -правила в множество P ' не включать. 4. Если S N , то P ' P S ' , S ' S, V 'N VN S ' , где V S ' ; иначе V 'N VN , S ' S . Пункт 2. Устранение цепных правил. - 59 - A B , где A, B VN . Цепные правила могут быть причиной Цепное правило – это правило вида нежелательных свойств грамматики, таких, например, как циклы. Алгоритм устранения цепных правил осуществляет замещение правой части каждого цепного правила, используя для этого не цепные правила. АЛГОРИТМ: Вход: КС-грамматика G VT ,VN , P, S без -правил. G ' VT ,VN , P ', S без цепных правил. Выход: Эквивалентная КС-грамматика 1. Для каждого нетерминала A вычислить N 1.1. Положить N 0A A . A B | A * B, где B VN : 1.2. Вычислить N1 N 0 C | B C P, B N 0 , C VN . A A A 1.3. Если N 1 N 0 , то положить N 0 N 1 и перейти к пункту 1.2; иначе положить N A 2. A A Построить множество P ' так: если A B P не является цепным правилом P ' правило A для каждого A , такого, что P ' A для всех B P, где VN и B N A . включать A в N 1A . VN , то B N A , то есть Пункт 3. Левая факторизация правил. Левую факторизацию можно выполнять для правил грамматики, определяющих один и тот же нетерминал и имеющих одинаковое начало (префикс) правых частей. Цель такого преобразовании – устранение одинаковых префиксов. При проведении левой факторизации выполняются действия, подобные вынесению за скобки общего левого множителя в алгебраических выражениях. Алгоритм преобразования состоит в следующем. Вход: КС-грамматика G VT ,VN , P, S . Выход: Эквивалентная КС-грамматика G ' VT ,V 'N , P ', S без одинаковых префиксов в правых частях правил, определяющих нетерминал. 1. Записать все правила для нетерминала X , имеющие одинаковые префиксы правила с альтернативами (вариантами): X 1 | 2 | | n ; 1 , 2 , V * , в виде одного , n V * ; | n . Вынести за скобки влево префикс 3. Обозначить 4. Пополнить множество нетерминалов новым нетерминалом Y и заменить правила, подвергшиеся факторизации, новыми правилами для X и Y . Повторить пункты 1 – 4 для всех нетерминалов грамматики, для которых это возможно и необходимо. 5. новым X Y , Y 1 | 2 | каждой строки-альтернативы: X 1 | 2 | 2. нетерминалом | n . Y выражение, оставшееся в скобках: Пункт 4. Устранение прямой левой рекурсии. Вход: КС-грамматика G VT ,VN , P, S . G ' VT ,V 'N , P ', S без прямой левой рекурсии. 1. Вынести из грамматики все правила для рекурсивного нетерминала X : X X 1 | X 2 | | X m X VN ; 1 , 2 , , m V * Выход: Эквивалентная КС-грамматика X 1 | 2 | 2. Ввести новый нетерминал рекурсивным нетерминалом X : | n 1 , 2 , , n V * Y так, чтобы он описывал любое окончание строки, порождаемой Y 1Y | 2Y | | mY Y 1 | 2 | | m 3. Заменить в рекурсивном правиле для X правую часть, используя нетерминал Y и все не рекурсивные правила для X , так, чтобы генерируемый язык не изменился: - 60 - X 1Y | 2Y | X 1 | 2 | | nY | n Y 1Y | 2Y | Y 1 | 2 | | mY |m 4. Пополнить множество нетерминалов грамматики новым нетерминалом Y . Пополнить множество правил грамматики правилами, полученными в пункте 3. 5. Повторить действия пунктов 1 – 4 для всех рекурсивных нетерминалов грамматики, после чего полученные множества нетерминалов и правил принять в качестве V ' N и P . Замечание: если Y правила -правила не будут помехой в грамматике, то в пункте 2 алгоритма можно применить для Y 1Y | 2Y | | mY . Y Тогда в пункте 3 будут получены такие правила: X 1Y | 2Y | | nY Y 1Y | 2Y | | mY . Y Параграф 3. Нормальная форма КС-грамматики. Пункт 1. Нормальная форма Хомского. Каждая КС-грамматика G VT ,VN , P, S эквивалентна грамматике в нормальной форме Хомского с правилами вида 1) A BC , где A, B, C VN ; 2) 3) A a , где a VT ; S , если L G , причем S не встречается в правых частях правил. Вход: Приведенная КС-грамматика G VT ,VN , P, S . Выход: Эквивалентная КС-грамматика G ' VT ,V 'N , P ', S в НФХ. A a P . Включить в P ' каждое правило вида 1, то есть A BC P . Включить в P ' каждое правило вида 3, то есть S P (если оно есть в P ). Для каждого правила A X1 X K P , где k 2 , включить в P ' правила: 1. Включить в P ' каждое правило вида 2, то есть 2. 3. 4. A X '1 X 2 X2 Xk X k X '2 X 3 Xk , X k 2 X k 1 X k X 'k 2 X k 1 X k X k 1 X k X 'k 1 X 'k где Xi X k - новый нетерминал; X i , если X i VN X 'i X 'i новый нетерминал, если X i VT 5. Для каждого правила A X1 X 2 P , где хотя бы один из символов X 1 и X 2 является терминалом, включить в P ' согласно обозначениям правило A X '1 X '2 . 6. Для каждого нового нетерминала вида X ' , введенного в пункте 4 или в пункте 5, согласно обозначениям, включить в P ' правило вида X ' X , где X VT . - 61 - 7. Пополнить множество нетерминалов V 'N VN новые нетерминалы . При реализации алгоритма составляется таблица для обозначения терминалов и таблица для обозначения строк из двух и более символов. Пункт 2. Нормальная форма Грейбах. Каждая КС-грамматика G VT ,VN , P, S эквивалентна грамматике в нормальной форме Грейбах с правилами вида 1) A a , где A VN , a VT , V ; * 2) S , если L(G ) , причем S не встречается в правых частях правил. Вход: Приведенная КС-грамматика G VT ,VN , P, S без левой рекурсии. Выход: Эквивалентная КС-грамматика 1. Упорядочить нетерминалы 3. VN A1 , A2 , , An , связав с каждым из них целое число так, Ai Aj , где Ai , Aj VN , V * чтобы для правила вида 2. G ' VT ,V 'N , P ', S в НФГ. (*) выполнялось отношение i j . Положение нетерминалов, не связанных правилом (*), в упорядоченной последовательности выбрать произвольно. Положить P ' P и i n 1 , где n #VN - количество нетерминалов грамматики. Если i 0 , то перейти к пункту 5; иначе заменить в P ' каждое правило вида (*) правилами Ai 1 | 2 | | m , где Aj 1 | 2 | | m Aj все правила для и 1 , 2 , , m V * . 4. 5. Положить i i 1 и перейти к пункту 3. l VT Заменить в каждом правиле вида A a 1 2 a VT , 1 , 2 , , k V и l 1 , 2 , , k , новым нетерминалом X l . 6. k , где X l добавить в P ' новые правила вида X l l и вычислить Для новых нетерминалов V 'N VN новые нетерминалы из пункта 5 . Пункт 3. Устранение левой рекурсии. Рассмотрим общий алгоритм устранения левой рекурсии. Основная идея применяемого для этой цели метода состоит в нумерации нетерминалов грамматики и преобразовании правил вида Ai , где Ai VN , V * , i - номер нетерминала, либо к форме Ai , где a VT , V * , либо к форме Ai A j , где Ai , Aj VN , V * , i j (**) . Ai не содержит прямой левой рекурсии и не может порождать левый рекурсивный цикл. Правило вида (**) в случае i j содержит прямую левую рекурсию, а в случае i j Правило вида участвует в формировании левого рекурсивного цикла. Прямая левая рекурсия замещается прямой правой рекурсией. Для устранения левого рекурсивного цикла в таком правиле последовательно раскрывается нетерминал A j . В результате левый рекурсивный цикл сначала сводится к прямой левой рекурсии, которая затем устраняется обычным образом. Вход: Приведенная КС-грамматика G VT ,VN , P, S . Выход: Эквивалентная КС-грамматика G ' VT ,V 'N , P ', S без левой рекурсии. VN A1 , A2 , , An , положить i 1, P ' P . 1. Пронумеровать нетерминалы 2. Просмотреть правила P ' , выделить правила для нетерминала рекурсию i j : - 62 - Ai , имеющего прямую левую Ai Ai1 | Ai 2 | Ai 1 | 2 | | Ai m | p , где , V * и ни одна из строк не начинается с Ak VN , если k i . Введя новый нетерминал прямой правой: Ai 1 A 'i | 2 A 'i | Ai 1 | 2 | | p A 'i |p A 'i 1 A 'i | 2 A 'i | A 'i 1 | 2 | A 'i , заменить выделенные правила, чтобы свести прямую левую рекурсию к | m A 'i |m Ai начинаются с терминала или Ak k i . Теперь все правые части правил для i n , положить V 'N VN новые нетерминалы и закончить работу; иначе положить i i 1, j 1 . 4. Выделить из P ' правила вида Ai A j и заменить их правилами Ai 1 | 2 | | m , 3. Если используя все правила A j 1 | 2 | с терминала или 5. | m . Теперь все правые части правил для Ai начинаются Ak k i . Если j i 1 , перейти к пункту 2; иначе положить j j 1 и перейти к пункту 4. Параграф 4. Автомат с магазинной памятью. Пункт 1. Определение МП-автомата. Формально МП-автомат можно M можно представить в виде семерки: M Q, , Г , , q0 , Z0 , F , где Q - конечное множество состояний автомата; - конечный входной алфавит; Г - конечный магазинный алфавит; - магазинная функция, отображение множества множества Q * : Q в множество всех подмножеств : Q * P Q * ; q0 - начальное состояние автомата, q0 Q ; Z 0 - символ, который находится в магазине в начальный момент, Z0 ; F - множество заключительных состояний автомата, F Q . Конфигурацией МП-автомата называется тройка q, , Q * * , где q Q - текущее состояние автомата; * - часть входной строки, первый символ которой находится под входной головкой; * - содержимое магазина. Шаг работы МП-автомата представим в виде отношения |- на конфигурациях. Если одним из значений магазинной функции q Q, a , Z является q ' Q, , * то можно писать q, a, Z | q ',, . Это общая запись, которая подразумевает частные случаи. 1. a . Автомат находится в текущем состоянии q , читает входной символ a , имеет в вершине стека символ Z . Он переходит в очередное состояние q ' , сдвигает входную головку на Случай - 63 - 2. ячейку вправо и заменяет верхний символ Z строкой магазинных символов. Вариант означает, что Z удаляется из стека. Случай a . Отличается от случая 1 тем, что входной символ a просто не принимается во внимание, и входная головка не сдвигается. Такой шаг работы МП-автомата называется -шагом, причем -шаг может выполняться даже после завершения чтения всей входной строки. Начальной конфигурацией МП-автомата называется конфигурация конфигурация q, , , где q0 ,, Z0 , а заключительной – q F . В заключительном состоянии входная строка полностью прочитана. , если существует путь по конфигурациям q0 ,, Z0 | q, , Таким образом, МП-автомат допускает входную строку * для некоторых q F и . Значит, язык формально можно определить так: * L , распознаваемый (принимаемый) МП-автоматом M , L( M ) | * и q0 , , Z 0 | * q, , для некоторых q F и * . Пункт 2. Разновидности МП-автоматов. Иногда определяют МП-автомат, который принимает строку, если после завершения ее чтения стек автомата будет пуст. В этом случае нет необходимости выделять множество заключительных состояний F Q , а описание заключительной конфигурации имеет вид q, , , где q Q . Говорят, что такой МП-автомат принимает строку языка опустошением магазина. МП-автомат называют детерминированным (ДМП-автоматом), если, находясь в любой конфигурации, он может выбрать не более одной следующей конфигурации. Это означает, что при любых значениях q Q , a q, a, Z имеет не более и Z ( Z * для расширенного автомата) магазинная функция одного значения. В противном случае МП-автомат является недетерминированным. Существует соответствие КС-грамматик и МП-автоматов, которое можно сформулировать так: существуют КС-языки, МП-автоматы и расширенные МП-автоматы, определяющие один и тот же КС-язык. Пункт 3. Взаимосвязь МП-автоматов и КС-грамматик. Построение МП-автомата. По заданной КС-грамматике, генерирующей некоторый язык L , можно построить МП-автомат, принимающий этот язык. Будем строить автомат, выполняющий левосторонний разбор. Такой автомат должен строить вывод строки, начиная с аксиомы грамматики. Используем стек для размещения текущей сентенциальной формы, первоначально это аксиома. Очередная сентенциальная форма получается заменой верхнего нетерминала стека. Пусть, кроме того, автомат обладает только одним состоянием и принимает входную строку опустошением магазина. Описанный МП-автомат можно построить следующим образом. Вход: КС-грамматика Выход: МП-автомат G VT ,VN , P, S . M Q, , Г , , q0 , Z0 , F такой, что L( M ) L(G ) . Q q, q0 q, F , VT VN , VT , Z0 S. 1. Положить 2. Для каждого правила вида q, , A q, . A P , где V * , сформировать магазинную функцию вида Эти функции предписывают замещать нетерминал в вершине стека по правилу грамматики. 3. Для каждого a VT сформировать магазинную функцию вида q, a, a q, , которая выталкивает из стека символ, совпадающий с входным, и перемещает читающую головку. Эти функции обеспечивают опустошение стека. Построение расширенного МП-автомата. Вход: КС-грамматика G VT ,VN , P, S . Выход: Расширенный МП-автомат 1. Положить M Q, , Г , , q0 , Z0 , F такой, что L( M ) L(G ) . Q q, r, q0 q, F r, VT VN #, VT , Z0 #. - 64 - 2. Для каждого правила вида q, , q, A , A P , где V * , сформировать магазинную функцию вида предписывающую заменять правую часть правила в вершине стека нетерминалом из левой части, независимо от текущего символа входной строки. a VT сформировать магазинную функцию вида q, a, q, a , которая 3. Для каждого 4. помещает символ входной строки в вершину стека, если там нет правой части правила, и перемещает читающую головку. Предусмотреть магазинную функцию для перевода автомата в заключительное состояние q, ,# S r, . Параграф 5. Нисходящие распознаватели языков. Пункт 1. Стратегия нисходящего анализа. Пример 1. Дана КС-грамматика G1 VT ,VN , P, S , где VN S , X ,Y , VT a, b, d , p, q, x, y и множество P состоит из шести правил: 1) S pX 2) S qY 3) X aXb 4) X x 5) Y Yd 6) Y y . Требуется выполнить нисходящий анализ строки paaxbb . Обратим внимание, что каждый нетерминал грамматики имеет по два правила, но они начинаются с разных терминалов. Этим свойством грамматики будем пользоваться при выборе правила, сопоставляя очередной символ входной строки с головным символом правой части. Построим дерево разбора: Процесс нисходящего анализа сведем в таблицу: № Входн С П п/п ая строка тек равило 1 1 paaxbb 2 paaxbb 3 aaxbb 4 aaxbb 5 axbb 6 axbb 7 xbb 8 xbb - 65 - 3 3 4 9 bb 1 b 1 0 1 Пункт 2. LL(k)-грамматики. В КС-грамматиках можно выделить обширное подмножество грамматик, обладающих сходными свойствами и обеспечивающих детерминированный нисходящий разбор строк порождаемых языков. Это подмножество называют LL(k)-грамматиками, причем буквы L, L, k означают следующее: L – просмотр строки языка осуществляется слева направо (левосторонний просмотр); L – строится левосторонний вывод (левосторонний разбор); K – используется k предварительно просматриваемых символов входной строки для выбора очередного правила грамматики. Чем больше k, тем более сложные языки описывает грамматика и тем сложнее распознаватели этих языков. В большинстве практических приложений можно ограничиться LL(1)-грамматиками. S-грамматики являются частным случаем LL(1)-грамматик. В LL(1)-грамматиках головными символами в правых частях правил наряду с терминалами могут быть и нетерминалы. КС-грамматику называют LL(1)-грамматикой, если не пересекаются множества направляющих символов для правил, определяющих один и тот же нетерминал грамматики. Обозначим DS ( P, ) - множество направляющих символов для правила P , где P VN и V * . Формально DS ( P, ) S F P . Множество S называют множеством символов-предшественников для строки и определяют так: S a | a VT , V , * a , V * . Отсюда следует, что S - это множество терминалов, которые получаются как головные символы строк, выводимых из непустой строки Множество F P . назовем множеством символов-последователей для нетерминала P и определим его так: F P a | a VT , * , S * xPay; S , P VN ; x, y V * , S аксиома F P - это множество терминалов, которые могут появляться в сентенциальных формах непосредственно за нетерминалом P . Напомним, что сентенциальная форма – это любое порождение начального символа S грамматики. Множество F P принимается во внимание Эта запись означает, что только в том случае, если существует вывод (то есть вывод пустой строки из правой части правила для нетерминала P ). Проверку принадлежности КС-грамматики классу LL(1)-грамматик можно выполнить на основе определения LL(1)-грамматики, то есть путем вычисления пересечения множеств направляющих символов для альтернативных правых частей правил нетерминала. Кроме того имеются признаки, по которым достаточно просто установить, что заданная КС-грамматика не является LL(1)-грамматикой. К таким признакам относятся: одинаковые головные символы в альтернативных правых частях правил для нетерминала, например A aX | aY | aZ или S RX | Rab ; * наличие левой рекурсии (прямой или косвенной), например A Ab | a . Параграф 6. Восходящие распознаватели языков. Пункт 1. Стратегия восходящего анализа. В основе стратегии восходящего анализа лежит правосторонний разбор. Напомним: правосторонний разбор строки – это обратная последовательность правил грамматики, используемых для построения правостороннего вывода этой строки. Исходной сентенциальной формой разбора является заданная строка языка, а целевой сентенциальной формой – аксиома грамматики. Таким образом, вывод строится, начиная с его конца, а соответствующее дерево разбора «растет» от листьев к корню и слева направо. - 66 - Обычно восходящий анализ выполняется как последовательность операций «перенос» и «свертка». Входная строка просматривается слева направо. Операция «перенос» состоит в добавлении очередного символа входной строки в стек распознавателя. Операция «свертка» производит замену строки верхних символов стека, совпадающей с правой частью правила, нетерминалом из левой части этого правила. Определение в стеке строки для свертки выполняется каждый раз таким образом, чтобы продолжить формирование правостороннего вывода, то есть получить очередную правовыводимую сентенциальную форму. Пункт 2. LR ( k ) -грамматики. В стратегии восходящего анализа ключевое место занимает понятие основа. Дадим его определение. Пусть G VT ,VN , P, S - КС-грамматика, S * A * x - правый вывод в ней, причем A VN ; , V ; x, VT . * * можно свернуть слева к правовыводимой сентенциальной форме A с помощью правила A . Строка в данной Говорят, что правовыводимую сентенциальную форму сентенциальной форме называется ее основой. Справа от основы всегда располагается терминальная строка. КС-грамматика правостороннего вывода G VT ,VN , P, S S 0 1 правовыводимой сентенциальной форме будет i LR(1)-грамматикой, если для произвольного m z , где i V * , i 1,..., m , в каждой i , читая ее слева направо, можно выделить основу и определить, каким нетерминалом ее можно заменить, используя для этого не более одного символа справа от основы. Пункт 3. Иерархия КС-грамматик. Установлена иерархия КС-грамматик, гарантирующих существование достаточно простых детерминированных распознавателей, пригодных для языков программирования. Класс LR (1) -грамматик является наиболее общим из представленных. Он включает в себя и класс LL(1) -грамматик. Это объясняется тем, что восходящий анализатор располагает более обширной информацией для принятия решения о применении очередного правила, чем нисходящий. Нисходящий пользуется только одним входным символом, а восходящий имеет в стеке весь левый контекст, включая и правую часть правила. Восходящий распознаватель для SLR (1) -грамматики использует только дополнительны входной символ. LALR (1) -распознаватель, кроме того, анализирует некоторый левый контекст, что эквивалентно заглядыванию вперед, на сентенциальную форму строящегося вывода. При разработке языка программирования его формальную грамматику ориентируют на удобство работы программистов. Поэтому она может отличаться от грамматик, приведенных в классификации. В интересах разработки компилятора выполняются эквивалентные преобразования грамматики языка для приведения ее к нужному классу. Приведение грамматики к классу LL(1) обычно требует больше усилий, чем приведение к классу LR (1) . Параграф 7. Простое предшествование. - 67 - Пункт 1. Грамматика простого предшествования. Такие грамматики являются подмножеством LR (1) -грамматик. Они позволяют выделить основу правовыводимой сентенциальной формы с помощью особых отношений, которые называют отношениями предшествования Вирта-Вебера. Отношения предшествования определены на множестве V VN VT , то есть на множестве символов словаря грамматики. Для выделения основы используют 3 вида отношений, которые обозначают , , и называют «меньше с точкой», «равно с точкой» и «больше с точкой» соответственно. Если - правовыводимая сентенциальная форма и - основа, то V , V , V и существуют следующие отношения предшествования между символами сентенциальной формы: Между всеми смежными символами строки выполняется отношение либо , либо ; Между последним символом строки и первым символом основы (строки ) выполняется отношение ; Между смежными символами основы выполняется отношение ; Между последним символом основы и первым символом строки выполняется отношение . Грамматикой предшествования называют КС-грамматику, обладающую свойствами: 1. грамматика – приведенная (не содержит -правил, бесполезных символов и циклов); 2. для каждой пары символов словаря грамматики выполняется не более одного отношения предшествования; 3. разные порождающие правила грамматики имеют различные правые части. Грамматики, обладающие свойством 3, называются обратимыми. Пункт 2. Вычисление матрицы предшествования. Отношения предшествования вычисляют и хранят в квадратной матрице. Ее называют матрицей предшествования. Рассмотрим алгоритм построения матрицы предшествования, основанный на использовании множества самых левых символов L(U ) и множества самых правых символов R (U ) относительно нетерминала U . L(U ) Z | U Z , R (U ) Z | U Z . Эти формулы можно преобразовать в более удобные для программирования эквивалентные рекурсивные формулы: L(U ) Z | U Z P или U U ' ' P и Z L(U ') , R(U ) Z | U Z P или U 'U ' P и Z R (U ') , где U ,U ' VN , Z V , , ' V * . 1. 2. Алгоритм построения матрицы предшествования: Для каждого нетерминала U найти правила грамматики языка, содержащие U в левой части. Из правой части в множество L(U ) включить самый левый, а в множество R (U ) - самый правый символ. Просмотреть каждое полученное множество. Если L(U ) содержит нетерминалы U ',U '' и т.д., то L(U ) дополнить элементами из L(U ') , L(U '') и т.д., отсутствующими в L(U ) . Выполнить подобные операции для множества R (U ) . 3. Повторять пункт 2 алгоритма, пока множества L(U ) и R (U ) не перестанут меняться. 4. Просмотреть правые части правил грамматики и записать в матрицу предшествования отношение 5. Просмотреть правые части правил грамматики и записать в матрицу предшествования отношение 6. Просмотреть правые части правил грамматики и записать в матрицу предшествования отношение для символов, определенных по формуле для символов, расположенных рядом, согласно формуле X Y U xXYy P . для символов, определенных по формуле X Y U xXBy P и Y L(B) . X a U xBCy P и X R( B), a L(C) или a C . 7. Вычислить отношения предшествования для концевых маркеров LM X X L( S ) ; Y RM Y R( S ) . - 68 - LM и RM по формулам Данный алгоритм можно использовать для проверки принадлежности применяемой грамматики к классу грамматик предшествования. Каждая пара символов словаря грамматики предшествования должна иметь не более одного отношения предшествования. РАЗДЕЛ 4. Словарь терминов (глоссарий). Предварительные сведения Байт Оперативное запоминающее устройство центральный процессор Устройства ввода-вывода это группа из 8 битов внутреннее запоминающее устройство в компьютере, непосредственно связанное с центральным процессором и хранящее данные, необходимые для его работы. Оно представляет собой упорядоченную последовательность ячеек, каждая из которых может хранить элемент данных. Каждая ячейка имеет уникальный адрес, на который ссылаются, если надо записать данные в ячейку или прочесть из нее информацию Узел компьютера, исполняющий команды узлы компьютера, которые получают данные для обработки (ввод) и выводят ее результаты (вывод). Основные понятия языка С++ Алфавит языка (символы) Выражение (словосочетание) Исполняемые операторы комментарий Лексема, или элементарная конструкция (слово) Массив Неисполняемые операторы Оператор Оператор (предложение) Рекурсивная функция семантика Синтаксис Указатель Функция это основные неделимые знаки, с помощью которых пишутся все тексты на языке задает правило вычисления некоторого значения задают действия над данными текст пояснительного содержания, встраиваемый в программу минимальная единица языка, имеющая самостоятельный смысл. упорядоченная последовательность данных одного типа служат для описания данных, поэтому их часто называют операторами описания или просто описаниями это символ, который указывает компилятору на выполнение конкретных математических действий или логических манипуляций задает конечное описание некоторого действия Функция, которая вызывает саму себя определяет смысл правил записи команд и правила использования устанавливает формальные правила записи команд на языке программирования это некоторое символическое представление адреса относительно самостоятельный фрагмент программы, спроектированный для решения конкретных задач и снабженный именем Общие сведения о системах программирования Ассемблер Детранслятор Дисассемблер загрузка Инкрементный транслятор Интерпретатор Компилятор Компилятор компиляторов Компилятор Это компилятор на языке ассемблера Восстанавливает программу на ЯП по ее эквиваленту на языке машины Это детранслятор для языка ассемблера размещение программы в основной памяти для выполнения Предназначен специально для повторной трансляции фрагментов программы и дополнений к ней без повторной трансляции всей программы Выполняет программу в машинно-независимой форме посредством подпрограмм, входящих в его состав. Обычно он предварительно переводит программу на некоторый более удобный промежуточный язык. Промежуточная программа не сохраняется Переводит программу с одного языка на язык более низкого уровня (машинноориентированный или, чаще всего, машинный). При переводе на машинноориентированный язык он строит программу, подобную программе на языке ассемблера Переводит формальное описание языка программирования в транслятор для этого языка Переводит программу на промежуточный машинно-независимый язык. Позволяет - 69 - переднего плана Компоновщики Конечный автомат (КА) Кросс-компилятор Макрогенератор перекрытие перемещение Препроцессор связывание сохранять промежуточную программу Это общее название системных программ, выполняющих подготовку объектных и загрузочных модулей к их выполнению простейший распознаватель без вспомогательной памяти Транслируя программу на одной машине (платформе), формирует объектный код для другой машины (платформы) Это препроцессор для ассемблера. Переводит программу с макроязыка на язык ассемблера настройка модулей программы на выполнение с перекрытиями групп сегментов в основной памяти настройка сегментов на размещение их в новом адресном пространстве Переводит программу с макрорасширения входного языка транслятора на этот входной язык. Обычно реализуется как префиксная часть некоторых трансляторов объединение двух или более модулей в единую программу Основные понятия и определения формальных языков Вспомогательная память Итерация языка L или замыкание Клини Строка Управляющее устройство Формальная грамматика служит для хранения информации, которая зависит от состояния распознавателя. Память может быть организована в виде магазина (стека). Объем памяти не ограничивается. Иногда вспомогательная память может отсутствовать сцепление произвольного числа строк некоторого формального языка L : L Ln . n0 это любая последовательность символов алфавита, расположенных один за другим это программа управления распознавателем. Она задает конечное множество состояний распознавателя и определяет переходы из состояния в состояние в зависимости от прочитанного символа входной ленты и содержимого вспомогательной памяти это математическая система, определяющая язык посредством порождающих правил. В отличие от формулы для определения множества формальная грамматика содержит ряд взаимосвязанных правил для получения (порождения, генерации) строк языка. Регулярные грамматики и языки Конечный автомат (КА) это простейший распознаватель без вспомогательной памяти. Его входная головка читает символы входной ленты и перемещается на одну ячейку вправо на каждом шаге работы автомата. КА имеет конечное множество управляющих состояний, среди которых выделены начальное состояние и заключительные состояния конфигурация Пара минимальный КА Конечный автомат, не эквивалентных состояний Регулярная грамматика это КС-грамматика q, Q * , где - текущий остаток входной строки содержащий недостижимых и G VT ,VN , P, S с правилами вида A a A aB , где a VT и A, B VN , причем A и B могут быть одинаковыми нетерминалами. Допускается также правило S при условии, что S не появляется в правых частях правил. и Контекстно-свободные грамматики и языки Грамматика предшествования КС-грамматика, обладающая свойствами: 1. грамматика – приведенная (не содержит -правил, бесполезных символов и циклов); 2. для каждой пары символов словаря грамматики выполняется не более одного отношения предшествования; 3.разные порождающие правила грамматики имеют различные правые части. - 70 - Левосторонний разбор МП-автомат называют детерминированным (ДМПавтоматом) последовательность порождающих правил, применяемых для генерирования строки языка посредством левостороннего вывода если, находясь в любой конфигурации, он может выбрать не более одной следующей конфигурации. Это означает, что при любых значениях q Q , a и Z ( Z * для расширенного автомата) магазинная функция Правосторонний разбор разбор -свободная грамматика Цепное правило q, a, Z имеет не более одного значения. является обратной последовательностью порождающих правил, используемых для генерирования строки языка посредством правостороннего вывода это последовательность правил грамматики, определенным образом соответствующая выводу Грамматика без -правил это правило вида A B , где A, B VN . РАЗДЕЛ 5. Практикум по решению задач по темам лекций. § 1 Основные понятия языка С++ п.1. Простейшие программы. 1. Написать программу, выводящую на экран слова «Hello, World!» 2. Написать программу, выводящую на экран слова «Hello, World!» дважды: a) в строку; b) в столбик. 3. Написать программу, выводящую на экран слова «Hello, World!» трижды: a) в строку; b) в столбик; c) два раза в строку, один раз в столбик. 4. Как в программу задания №3 включить функцию exit( ) так, чтобы слова «Hello, World!» выводились дважды. п.2. Функция sizeof( ). 1. Написать программу, вычисляющую размер памяти, отводимой компилятором под типы: 1.1. int ; 1.2. short int ; 1.3. long ; 1.4. float ; 1.5. double ; 1.6. bool ; 2. Написать программу, вычисляющую размер памяти, отводимой под символы: 2.1. 1; 2.2. А (латинский алфавит) ; 2.3. А (русский алфавит) ; 2.4. а (латинский алфавит) ; 2.5. а (русский алфавит) ; 2.6. \а (звуковой сигнал) ; 2.7. \n (перевод строки) ; п. 3. Основные свойства фундаментальных типов. 1. Написать программу, вычисляющую число битов в байте. Сравнить вычисленное значение со стандартным. В программе использовать макросы из файла limits.h. 2. Написать программу, вычисляющую предельные значения для типов: 1.1. int ; 1.2. short int ; 1.3. long ; 1.4. float ; 1.5. double ; 1.6. char ; 1.7. signed char ; 1.8. unsigned char ; - 71 - 3. 1.9. signed int ; 1.10. long double. В программе использовать макросы из файла limits.h. Написать программу, вычисляющую для типа float: 3.1. количество верных десятичных цифр; 3.2. минимальное нормализованное число с плавающей точкой; 3.3. машинный ноль; 3.4. максимальное число с плавающей точкой. В программе использовать макросы из файла float.h. п. 4. Коды ASCII. 1. Написать программу, вычисляющую коды символов: 1.1. 1; 1.2. А (латинский алфавит); 1.3. А (русский алфавит); 1.4. а (латинский алфавит); 1.5. а (русский алфавит); 1.6. \а (звуковой сигнал); 1.7. \n (перевод строки); 1.8. \v (вертикальная табуляция); 1.9. \t (горизонтальная табуляция); 1.10. ? (вопросительный знак); 1.11. ! (восклицательный знак); 1.12. \ (обратная косая черта, бэкслэш). 2. Определить в какой кодировке (MS-DOS или MS Windows) компилятор Visual C++ выводит на экран буквы русского алфавита. 3. Написать программу, выводящую на экран имя и фамилию студента, используя коды ASCII. § 2 Стандартные математические функции. Операторы. п. 1. Стандартные математические функции. 1. Написать программу, получающую double r и вычисляющую: 1.1. длину окружности радиуса r; 1.2. площадь круга радиуса r. Вычисление числа производить с помощью стандартных математических функций asin, acos, atan. При этом нужно учитывать следующие соотношения: arcsin1=/2, arccos0=/2, arctg1=/4 п. 2. Условная трёхместная операция. 1. 2. Написать программу, получающую double x и вычисляющую | операции. Написать программу, получающую double a, b и вычисляющую: 2.1. 2.2. x| с помощью условной трёхместной maxa, b; min a, b . п. 3. Вычисление логических выражений. 1. Написать программу, получающую int a, b, c и вычисляющую логические значения функции: a b c ; 1.1. 1.2. 1.3. 2. a b c ; (a b) (c a) . Написать программу, получающую int a, b и вычисляющую значения операторов: a b; 2.1. 2.2. 2.3. 2.4. a ! b ; a b; a b . - 72 - § 3 Основные операторы языка С++ п. 1. Оператор if. Все задания этого пункта нужно выполнить, используя оператор if. 1. Написать программу, получающую double x и вычисляющую | x | . 2. Написать программу, получающую double a, b и вычисляющую: 2.1. 3. maxa, b; min a, b . 2.2. Написать программу, получающую double a, b, c и вычисляющую с помощью вложенных операторов if : 3.1. 3.2. maxa, b, c; min a, b, c. п. 2. Оператор switch. Все задания этого пункта нужно выполнить, используя оператор switch. 1. Написать программу, получающую int n и определяющую его чётность. п. 3. Оператор for. Все задания этого пункта нужно выполнить, используя оператор for (вложенные операторы for). 1. Написать программу, получающую число n 0 и вычисляющую факториал n!. Программа должна выводить сообщение в случае недостаточности выбранного типа для хранения числа n!. Использовать типы: int, unsigned int, long int, unsigned long int. 2. Написать программу, получающую число n и вычисляющую: 2.1. произведение первых n натуральных нечётных чисел; 2.2. произведение первых n натуральных чисел, имеющих при делении на 3 остаток 1. Программа должна выводить сообщение в случае недостаточности выбранного типа для хранения вычисляемого числа. Использовать типы: int, unsigned int, long int, unsigned long int. 3. Написать программу, получающую число n и вычисляющую: 3.1. сумму квадратов первых n натуральных чисел 12 22 ... n 2 ; 3.2. сумму первых n нечётных натуральных чисел 1+3+5+…+(2n+1). Программа должна выводить сообщение в случае недостаточности выбранного типа для хранения вычисляемого числа. Использовать типы: int, unsigned int, long int, unsigned long int. 4. Написать программу, получающую число n и вычисляющую n-й член последовательности ( xn )n1 , заданной рекуррентно: 4.2. x n1 x n 2 n n; n 1, x1 1; xn 1 2 xn 1 n; n 1, x1 1 ; 4.3. xn1 3xn 1 n2 ; n 1, x1 1. 4.1. Программа должна выводить сообщение в случае недостаточности выбранного типа для хранения вычисляемого числа. Использовать типы: int, unsigned int, long int, unsigned long int. 5. Написать программу, получающую число Последовательность Фибоначчи n 0 и вычисляющую n-е число Фибоначчи. n n0 определена в разделе «Обозначения». Программа должна выводить сообщение в случае недостаточности выбранного типа для хранения вычисляемого числа. Использовать типы: int, unsigned int, long int, unsigned long int. 6. Написать программу, получающую число n и вычисляющую: 1 2 n ; сумму первых n чисел Фибоначчи: 1 2 n . 6.1. произведение первых n чисел Фибоначчи: 6.2. Программа должна выводить сообщение в случае недостаточности выбранного типа для хранения вычисляемого числа. Использовать типы: int, unsigned int, long int, unsigned long int. 7. Написать программу, получающую числа a, b, c и выводящую на экран таблицы истинности следующих формул алгебры логики: 7.1. a b c ; - 73 - 7.2. 7.3. 8. a b c ; a b c . n Написать программу, получающую число long int n, 8.1. 8.2. 8.3. и вычисляющую сумму: 1 1 1 1 1n1 ; 2 3 4 n 1 1 1 1 S 1 ; 2 3 4 n 2 3 2 2 2 2 n1 . S 1 2 3 4 n S 1 п. 4. Оператор while. Все задания этого пункта нужно выполнить, используя оператор while (вложенные операторы while). 1. Написать программу, получающую число n 0 и вычисляющую факториал n!. Программа должна выводить сообщение в случае недостаточности выбранного типа для хранения числа n!. Использовать типы: int, unsigned int, long int, unsigned long int. 2. Написать программу, получающую число n и вычисляющую: 2.1. произведение первых n натуральных нечётных чисел; 2.2. произведение первых n натуральных чисел, имеющих при делении на 3 остаток 1. Программа должна выводить сообщение в случае недостаточности выбранного типа для хранения вычисляемого числа. Использовать типы: int, unsigned int, long int, unsigned long int. 3. Написать программу, получающую число n и вычисляющую: 3.1. сумму квадратов первых n натуральных чисел 12 22 ... n 2 ; 3.2. сумму первых n нечётных натуральных чисел 1+3+5+…+(2n+1). Программа должна выводить сообщение в случае недостаточности выбранного типа для хранения вычисляемого числа. Использовать типы: int, unsigned int, long int, unsigned long int. 4. Написать программу, получающую число заданной рекуррентно: n и вычисляющую n-й член последовательности, 4.1. x n1 x n 2 n n; n 1, x1 1; 4.2. xn 1 2 xn 1 n; n 1, x1 1 ; 4.3. xn1 3xn 1 n2 ; n 1, x1 1. Программа должна выводить сообщение в случае недостаточности выбранного типа для хранения вычисляемого числа. Использовать типы: int, unsigned int, long int, unsigned long int. 5. Написать программу, получающую число Последовательность Фибоначчи n 0 и вычисляющую n-е число Фибоначчи. n n0 определена в разделе «Обозначения». Программа должна выводить сообщение в случае недостаточности выбранного типа для хранения вычисляемого числа. Использовать типы: int, unsigned int, long int, unsigned long int. 6. Написать программу, получающую число n и вычисляющую: 6.1. произведение первых n чисел Фибоначчи: 1 2 n ; 6.2. сумму первых n чисел Фибоначчи: 1 2 n . Программа должна выводить сообщение в случае недостаточности выбранного типа для хранения вычисляемого числа. Использовать типы: int, unsigned int, long int, unsigned long int. 7. Написать программу, получающую числа int a, b, c и выводящую на экран таблицы истинности следующих формул алгебры логики: 7.1. a b c ; 7.2. a b c ; 7.3. a b c . - 74 - 8. Написать программу, получающую число long int n, n и вычисляющую сумму: 1 1 1 1 1n1 ; 2 3 4 n 1 1 1 1 8.2. S 1 ; 2 3 4 n 2 2 2 23 2 n1 8.3. S 1 . 2 3 4 n 8.1. S 1 9. Вычислить машинный ноль для типов: 9.1. double; 9.2. float. 10.Реализовать иллюстративную программу, заимствованную из книги [6]: include<iostream.h> void main( ) { int n=1; while(n>0) n++; cout<<n<<”\n”; } 10.1. 10.2. объяснить, почему не произойдёт “зацикливание” программы; ввести в программу таймер, измеряющий время работы программы. 11.Написать программу, получающую числа unsigned int m, n, m, n 0 и вычисляющую: 11.1. нод(m,n) с помощью алгоритма Евклида; mn 11.2. нок(m,n) по формуле: нок (m, n) . нод(m, n) 12.Написать программу, получающую число double x и вычисляющую значение функции f (x) . Программа должна сравнить вычисленное значение со значением соответствующей библиотечной функции. f ( x) sin x 12.1. (1) r r 0 x 2r 1 (2r 1)! x x3 x 2r 1 x 2r 1 (1) r 1 (1) r , x 1! 3! (2r 1)! (2r 1)! 12.2. f ( x) cos x (1) r r 0 1 12.3. x2 2! (1) r 1 f ( x) (1 x) . x 2r (2r )! x 2r 2 x 2r (1) r , x (2r 2)! (2r )! . ( 1) ( r 1) xr r ! r 0 ( 1) ( r 2) r 1 ( 1) ( r 1) r x x x 1! (r 1)! r! x 1;1 . 1 В концах интервала сходимость биномиального ряда определяется следующими таблицами. - 75 - , 0 абсолютно сходится x 1 0 1 неабсолютно сходится 1 расходится 0 абсолютно сходится x 1 0 расходится п. 5. Операторы break и continue в циклах. Все задания этого пункта нужно выполнить, используя операторы break и continue. 1. Написать программу , получающую число int n, n>1 и определяющую будет ли число n простым. Для составного числа n вычислить наименьший простой делитель. Для определения простоты числа использовать следующую теорему. Теорема 1. Пусть n , n 1 . Если число n не делится ни на одно натуральное число d, 2 d n , то n – простое число. Наименьшее натуральное число d, 2 d n , делящее n, 2. 3. является наименьшим простым делителем числа n. Написать программу , получающую число int N, N>1 и выводящую на экран все простые числа, не превосходящие N. Написать программу , получающую число int N, N>1 и вычисляющую сумму всех простые чисел, не превосходящих N. п. 6. Цикл while с условием while(cin>>n). Все задания этого пункта нужно выполнить, используя оператор while с условием while(cin>>n). 1. Написать программу, подсчитывающую количество чисел, введённых с клавиатуры, и вычисляет их сумму. 2. Написать программу, находящую наибольшее и наименьшее из произвольного количества чисел, введённых с клавиатуры. 3. Написать программу, находящую наибольший общий делитель и наименьшее общее кратное произвольного количества целых чисел, введённых с клавиатуры. § 4 Функции п. 1. Работа с функциями. 1. Придумать функцию, не имеющую аргументов и не возвращающую значения. С помощью этой функции программа должна выполнять какие-то реальные действия. 2. Написать программу, получающую double x и вычисляющую | x | . В программе должны быть использованы следующие функции: Функция main: 1. вызывает функцию inout; 2. выводит запрос на продолжение работы и в соответствии с ответом пользователя производит дальнейшие действия. Функция inout: 1. запрашивает double x, получает его с клавиатуры; 2. передаёт x как параметр функции mod; 3. выводит на экран результат работы функции mod. Функция mod: 1. получает double x как параметр; 2. вычисляет | x | ; 3. возвращает | x | . 3. Написать программу, получающую double x и вычисляющую значение функции f (x) . В программе должны быть использованы следующие функции: Функция main: 1. вызывает функцию inout; 2. выводит запрос на продолжение работы и в соответствии с ответом пользователя производит дальнейшие действия. Функция inout: 1. запрашивает double x, получает его с клавиатуры; 2. передаёт x как параметр функции f; 3. выводит на экран значение f(x). Функция f: 1. получает double x как параметр; 2. передаёт x как параметр функции mod; 3. возвращает f (x) . Функция mod описана в задании 2. 3.1. 3.2. 3.3. f ( x) (| x | 2 | x 3 |) /(| x | 1) ; f ( x) || x 1 | 2 | ; f ( x) (| x | | x 4 |) /(| x | 2) ; - 76 - 3.4. f ( x) (| x | x 4 ||) /(| x | 2 x 3||) . п. 2. Функции, расположенные в другом файле. Функция mod, рассматриваемая в задачах этого пункта, должна быть расположена в файле mod.txt. 1. Написать программу, получающую double x и вычисляющую значение функции f (x) . В программе должны быть использованы следующие функции: Функция main: 1. вызывает функцию inout; 2. выводит запрос на продолжение работы и в соответствии с ответом пользователя производит дальнейшие действия. Функция inout: 1. запрашивает double x, получает его с клавиатуры; 2. передаёт x как параметр функции f; 3. выводит на экран значение f(x). Функция f: 1. получает double x как параметр; 2. передаёт x как параметр функции mod; 3. возвращает f (x) . Функция mod описана в задании 2 пункта «Работа с файлами». 1.1. f ( x) (| x | 2 | x 3 |) /(| x | 1) ; 1.2. 1.3. 1.4. f ( x) || x 1 | 2 | ; f ( x) (| x | | x 4 |) /(| x | 2) ; f ( x) (| x | x 4 ||) /(| x | 2 x 3||) . п. 3. Рекурсивные функции. Все задания этого пункта должны быть реализованы с помощью рекурсивных функций. 1. Написать программу, получающую число n 0 и вычисляющую факториал n!. В программе должны быть использованы следующие функции: Функция main: 1. вызывает функцию inout; 2. выводит запрос на продолжение работы и в соответствии с ответом пользователя производит дальнейшие действия. Функция inout: 1. запрашивает int n, n 0 , получает его с клавиатуры; 2. передаёт n как параметр функции fact; 3. выводит на экран значение fact(n). Функция fact – рекурсивная функция, получающая int n как параметр, вычисляющая n! и возвращающая его значение функции inout. Программа должна выводить сообщение в случае недостаточности выбранного типа для хранения числа n!. Использовать типы: int, unsigned int, long int, unsigned long int. 2. Написать программу, получающую числа коэффициент mn n, m 0 , n m 0 и вычисляющую биномиальный . Функция main: 1. вызывает функцию inout; 2. выводит запрос на продолжение работы и в соответствии с ответом пользователя производит дальнейшие действия. Функция inout: 1. запрашивает n, m 0 , получает их с клавиатуры; 2. передаёт n, m как параметры функции bin; 3. выводит на экран значение bin(n,m). Функция bin: 1. получает параметры n, m; 2. вычисляет биномиальный коэффициент помощью формулы: mn m!(nn! m)! Написать программу, получающую числа коэффициент mn n, m 0 , n m 0 n! и и вычисляющую биномиальный . Функция main: 1. вызывает функцию inout; 2. выводит запрос на продолжение работы и в соответствии с ответом пользователя производит дальнейшие действия. - 77 - с , вызывая функцию fact; 3. возвращает значение биномиального коэффициента. Функция fact – рекурсивная функция, получающая int n как параметр, вычисляющая возвращающая его значение. 3. mn Функция inout: 1. запрашивает n, m 0 , получает их с клавиатуры; 2. передаёт n, m как параметры функции bin; 3. выводит на экран значение bin(n,m). Функция bin – рекурсивная функция: 1. получает параметры n, m; 2. вычисляет биномиальный коэффициент mn с помощью рекуррентных соотношений: mn nm1 mn11 n0 nn 1, n 0 , ; 3. возвращает вычисленное значение. § 5 Указатели. Ссылки. Массивы. п. 1. Указатели. Все программы этого пункта должны передавать соответствующие параметры по указателю. Написать программу, вычисляющую сумму всех положительных чисел, введённых с клавиатуры, и номер первого положительного числа. Например, при вводе с клавиатуры чисел: -1, -2, -3, 0, 0, 0, 1, 2, 3 сумма равна 6, номер первого положительного равен 7. Функция main: 1. вызывает функцию inout; 2. выводит запрос на продолжение работы и в соответствии с ответом пользователя производит дальнейшие действия. Функция inout: 1. объявляет переменную int i; 2. передаёт переменную i по указателю функции func; 3. выводит на экран результат работы. Функция func: 1. получает указатель на переменную i; 2. запрашивает и получает неопределённое количество чисел, вводящихся с клавиатуры; 3. переменной i присваивается номер первого положительного элемента; 4. вычисляет сумму положительных элементов и возвращает вычисленную сумму. п. 2. Ссылки. Все программы этого пункта должны передавать соответствующие параметры по ссылке. Написать программу, вычисляющую сумму всех положительных чисел, введённых с клавиатуры, и номер первого положительного числа. Например, при вводе с клавиатуры чисел: -1, -2, -3, 0, 0, 0, 1, 2, 3 сумма равна 6, номер первого положительного равен 7. Функция main: 1. вызывает функцию inout; 2. выводит запрос на продолжение работы и в соответствии с ответом пользователя производит дальнейшие действия. Функция inout: 1. объявляет переменную int i; 2. передаёт переменную i по ссылке функции func; 3. выводит на экран результат работы. Функция func: 1. получает ссылку на переменную i; 2. запрашивает и получает неопределённое количество чисел, вводящихся с клавиатуры; 3. переменной i присваивается номер первого положительного элемента; 4. вычисляет сумму положительных элементов и возвращает вычисленную сумму. п. 3. Указатели на функции. Все программы этого пункта нужно выполнить с использованием указателей на соответствующие функции. 1. Написать программу, в которой определены значения функций: f 1( n) , f 2( n) , f 3(n) для чисел int n: f 1(n) 2n n2 ; f 2(n) 2n2 n3 ; f 3(n) 2n2 n3 . Программа должна вычислять суммы: n f 1(r ) f 1(0) f 1(1) r 0 n f 2(r ) f 2(0) f 2(1) r 0 - 78 - f 1(n) ; f 2(n) ; n f 3(r ) f 3(0) f 3(1) f 3(n) . r 0 Функция main: 1. вызывает функцию inout; 2. выводит запрос на продолжение работы и в соответствии с ответом пользователя производит дальнейшие действия. Функция inout: 1. запрашивает и получает int n; 2. вызывает значения h(&f1,n), h(&f2,n), h(&f3,n); 3. выводит на экран результат работы. Функция h: 1. получает указатель на функцию f и параметр int n; 2. вычисляет и возвращает сумму n f (r ) . r 0 Программа должна использовать оператор typedef для определения типа «указатель на функцию». п. 4. Массивы. 1. Написать программу, задающую массивы char mas1[ ], char mas2[ ], char mas3[ ] инициализируя их. Программа должна вычислить размер массивов и продемонстрировать различие в выводе на экран символьных и числовых массивов с помощью оператора вывода <<. 2. Написать программу, демонстрирующую ввод с клавиатуры символьного массива mas1 с помощью оператора ввода >>. Определить массивы char mas2[ ] и int mas3[ ] и заполнить их с клавиатуры. 3. Написать программу, которая задает инициализацией массив int mas1[ ], вычисляет его размерность dim, создаёт новый массив int mas2[dim] и копирует содержимое mas1[ ] в mas1[ ]. 4. Написать программу, которая задает инициализацией массив int mas1[ ], вычисляет его размерность dim и находит наибольший элемент массива. п. 5. Многомерные массивы 1. Написать программу, задающую двумерный массив int mas[ ][ ], инициализирующую его и выводящую элементы массива на экран в виде матрицы. 2. Написать программу, задающую двумерный массив char mas[ ][ ], инициализирующую его и выводящую элементы массива на экран в виде матрицы. п. 6. Динамические массивы. 1. Написать программу, запрашивающую размерность int dim динамического массива mas, создающую динамический массив mas, заполняющую его с клавиатуры и выводящую на экран результат работы. 2. Написать программу, запрашивающую размерность int dim двумерного динамического массива mas, создающую динамический массив mas, заполняющую его с клавиатуры, интерпретируя вводимый одномерный массив как двумерный, и выводящую на экран двумерный массив в виде матрицы. п. 7. Вывод русского текста на экран Выполнение задания этого пункта рассчитано на применение компилятора Microsoft Visual C++ 6.0. 1. Написать программу которая выводит на экран русский текст. В программе должны быть функции printrus и code. Объявление функции printrus должно иметь вид: void printrus(char[ ]). Функция printrus выполняет следующие действия: 1. вызывает функцию code; 2. выводит на экран русский текст. Функция code выполняет следующие действия: 1. получает литеры; 2. вместо букв русского алфавита возвращает ’\ шестнадцатеричный код буквы’. § 6 Работа с файлами Задания данного параграфа рассчитаны на использование компилятора MS Visual C++ 6.0. Программы, создаваемые в данном параграфе, должны проверять правильность открытия файлов и выдавать сообщение об ошибке при их неправильном открытии. 1. Написать программу, которая: 1.1. создает поток stream; 1.2. создаёт новый файл table1.txt и записывает в него “Hello, World 2003!”; 1.3. создаёт новый файл table2.txt с помощью потока stream и записывает в него “Hello, World 2004!”; 1.4. пытается открыть несуществующий файл table3.txt и записать в него “Hello, World 2005!”, не создавая при этом нового файла. 2. Написать программу, которая: 2.1. создаёт новый файл table1.txt с помощью потока stream1, и открывает его для записи в конец файла; - 79 - 2.2. открывает с помощью потока stream2 файл table2.txt, в котором записано неопределённое количество целых чисел и копирует содержимое файла table2.txt в файл table1.txt. Файл table2.txt должен быть создан заранее и заполнен числами типа int. Формальные грамматики и языки Пример 1 Для строки 1001 подстроками будут могут быть ,1, 0,10, 00, 01,100, 001,1001 . Примерами головы и хвоста h1 1 и t2 01 . Пример 2 Для A 0,1 множество A* бесконечно: A* ,0,1,00,01,10,11,000,001,101,010,011,100,... Пример 3 Примеры описания языков через множества: a b c | n 0 a b | m, n 0 x | m четное число n n n m n m Пример 3 Даны два нумерованных правила: 1) S 0S1 2) S Представим вывод строки 0011 в виде дерева: S 0 1 S 0 1 S Пример 4 Построим грамматику G1 для языка L1 a mb n | m, n 0 . В этом языке строки не являются симметричными, какими они были в языке G0 0 1 | n 0 . Поэтому n n правила грамматики будут другими. Проанализируем ряд строк, принадлежащих заданному языку: , a, b, aab, abb, abbbbb, aaaaabb . Общим для всех строк является то, что каждая из них представляет собой сцепление из 2-х строк. Первая строка символов a , вторая – строка символов b , причем любая из этих строк может быть пустой. Эту общность можно описать правилом: S AB , где A и B - строки, составленные только из a и только из b соответственно. Составим правила: 1. S AB 2. A aA 3. A 4. B bB 5. B С учетом полученного множества G1 a, b , S , A, B , P, S . правил P грамматика G1 может быть определена как Применим последовательность правил (1,2,2,3,4,5) для генерации строки - 80 - aab : S 1 AB 2 aAB 2 aaAB 3 aaB 4 aabB 5 aab Генерация, распознавание и преобразование языков Классификация грамматик Пример 1 Грамматика G1 a, b , S , A, B , P, S с правилами: P S AB, A aA, A , B bB, B Эквивалентна грамматике 1. S aS 2. S a 3. S b 4. S bY 5. Y b 6. Y bY 7. S G2 a, b , S , Y , P, S с правилами: Действительно, цепочки символов a , включая пустую строку, генерируются правилами 1, 2, 7 грамматики G2 . Цепочки символов b можно получить применением правил 3, 4, 5, 6, 7. Сцепление (конкатенацию) строки из символов a со строкой из символов b можно получить, применяя правила 1, 3, 4, 5, 6. В практических приложениях эквивалентные грамматики полезны тем, что одни из них могут оказаться более удобными для использования, чем другие. Во всех рассмотренных выше примерах левая часть правила всегда содержала один нетерминал. Из формального определения грамматики следует, что это только частный случай строки, образованной из символов словаря (терминалов и нетерминалов). Пример 2 Грамматика G3 a , S , N , Q, R , P, S с правилами P : 1. S QNQ 2. QN QR 3. RN NNR 4. RQ NNQ 5. N a 6. Q Описывает множество строк языка a | n 0: a, aa, aaaa, aaaaaaaa 2n левой части по 2 нетерминала. Вывод строки aaaa и т.д. Правила 2, 3, 4 имеют в имеет вид: S 1 QNQ 2 QRQ 4 QNNQ 2 QRNQ 3 QNNRQ 4 QNNNNQ aaaa . Особенность этой грамматики состоит в том, что в некоторых правилах слева и справа имеется одинаковый контекст. Так, правило 4 позволяет заменять R строкой NN , только если за R следует Q . Язык, порожденный грамматикой G3 контекстно-зависимый, а не контекстно-свободный, т.е. не существует КС-грамматики, генерирующей этот язык. Язык, порождаемый грамматикой G0 , - контекстно-свободный и не регулярный. - 81 - Однако тот факт, что язык можно генерировать с помощью нерегулярной КС-грамматики, не обязательно означает, что он не регулярный. Грамматика G1 контекстно-свободная, но нерегулярная, а язык, порождаемый ею, - регулярный, так как его можно также описать посредством эквивалентной регулярной грамматики с аксиомой S ,VT a, b ,VN S , Y , Z и правилами: 1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. S S a S b S aZ S bY Z a Z b Z aZ Z bY Y b Y bY Описание регулярных языков Регулярные выражения. Пример 1 Примеры регулярных выражений и их значений. 1. Выражение 01 обозначает множество 01 , т.е. единственную строку 01 . 2. Выражение 0 |1 обозначает множество 3. Выражение , 0,1 , т.е. две строки: 0 и 1. 1* обозначает множество всех строк, образованных из единиц, включая пустую строку т.е. множество 1 . * 4. Выражение 0 |1 обозначает множество строк, которое может быть образовано из символов * 0и 1 , включая и пустую строку, т.е. множество 0,1 . * * 5. Выражение 01 обозначает множество всех строк, образованных из единиц, включая пустую строку; при этом каждой такой строке обязательно предшествует символ 01 * 0 ; это множество . 6. Выражение 0 |1* обозначает множество, состоящее из нуля и любой строки единиц, включая пустую, т.е. множество 7. Выражение 0,1 . * 0 | 10 равно выражению 0 |10 * * согласно старшинству операций и обозначает множество, состоящее из нуля и любой строки нулей, начинающейся с единицы, т.е. * 8. Выражение 0 |1 011 обозначает множество строк, образованных из символов пустую, обязательно оканчивающихся строкой - 82 - * 0 и 1 , включая 011 , т.е. множество 0,1 011 . * 0,10 . 9. Выражение a*b | ca* обозначает множество строк b, c, ab, aab, ca, caa и т.д., т.е. множество a b,ca . * * Свойства регулярных выражений. Пример 1. Возьмем множество букв Б a, b, c и множество цифр Ц 1, 2,3, 4,5 . Они являются регулярными множествами и, значит, регулярными языками. Создадим из них новые регулярные языки и опишем их регулярными выражениями. 1. Б | Ц - множество букв и цифр: a, b, c,1, 2,3, 4,5 . 2. Б * - пустая строка и множество всех строк из букв. 3. БЦ - все строки, состоящие из буквы с последующей цифрой. 4. Б Б | С - множество всех строк, образованных из букв и цифр и обязательно начинающихся с * буквы. Замечание. Некоторые простые, на первый взгляд, языки нельзя описать регулярными выражениями. Конечный автомат Способы представления функции переходов. 1. Командный. Каждая команда КА записывается в форме 2. Табличный. Строки соответствуют входным символам p, a q или p, a q , где p , q Q, a a , а столбцы – состояниям Q . В каждой позиции таблицы указывают новое состояние – значение функции q p, a . Если позиция содержит более одного состояния, то функция переходов неоднозначна, и КА – недетерминированный. Если позиция пуста, то функция переходов не полностью определена. 3. Графический. Пусть M Q, , , q0 , F - КА. Тогда графом переходов (или диаграммой состояний) автомата M будет неупорядоченный ориентированный помеченный граф. Его узлы помечены именами состояний автомата, и имеется дуга p, q , если существует такой символ a ,0020то q p, q и p, q Q . Дуга p, q помечается списком, содержащим все такие a , что q p, a . Введем дополнительные соглашения: будем снабжать узел, соответствующий начальному состоянию автомата, входящей стрелкой с надписью «начало». Заключительное состояние будем обозначать двумя кругами. Дополненный таким образом граф переходов и будет графом КА. Пример 1. - 83 - Дан КА: M1 Q, , , q0 , F и функции переходов, представленные командным способом: Q A, B, C ; a, b A, a B, B, b C , C , a A q0 A; F A, C Построить функции переходов табличным и графическим способом (граф переходов и граф КА). a b A B C B A C B а A Преобразование конечных автоматов b а C Устранение недостижимых состояний. Пример 1. Устранить недостижимые состояния КА M Q, , , q0 , F , где Q S0 , S1 ,..., S8 , 0,1 , Q0 S0 , F S1, S2 , S6 , S7 , S0 S1 S2 S3 S4 S5 S6 S7 S8 0 S1 1 S5 S2 S7 S2 S5 S5 S7 S5 S6 S3 S1 S8 S0 S0 S1 S3 S6 Алгоритм: Q0 S0 Q1 S0 , S1 , S5 Q1 Q0 ? Нет. Q0 S0 , S1 , S5 . Переход к п.2 Q1 S0 , S1 , S5 , S2 , S7 , S3 Q1 Q0 ? Нет. Q0 S0 , S1 , S5 , S2 , S7 , S3 . Переход к п.2 Q1 S0 , S1 , S5 , S2 , S7 , S3 Q1 Q0 ? Да. QД S0 , S1 , S5 , S2 , S7 , S3 QН S4 , S6 , S8 ; Q S0 , S1 , S2 , S3 , S5 , S7 F S1 , S2 , S7 S0 S1 S2 S3 S5 S7 0 1 S2 S7 S2 S5 S5 S7 S3 S1 S0 S1 S1 S5 Объединение эквивалентных состояний. Пример 1. Объединить эквивалентные состояния КА M Q, , , q0 , F , где Q A, B, C, D, E, F , a, b,1 , q0 A, F E, F , КА M не содержит недостижимых состояний, функция переходов представлена таблицей: - 84 - A B C a B D b D C 1 F E F C E Алгоритм: Q A, B, C, D , EF , выбрана группа 1 для шага 2. A, C ,B, D,E, F Разбиение группы 1 по входу в b : Q A, C , B, D , E , F Разбиение группы 1 по входу в a:Q Разбиение группы 1 по входу в 1: Q A , C , B, D , E, F Разбиение группы 1 закончено, но есть группа 2. Разбиение группы 2 по входу в Разбиение группы 2 по входу в Разбиение группы 2 по входу в a : Q A , C , B, D , E, F b : Q A , C , B, D , E, F 1: Q A , C , B, D , E, F Все группы просмотрены, разбиение закончено. Множество B,D E,F Обозначение X Y Q ' A, X , C , Y , F ' Y ' A X a b 1 X C X Y C Y Y Построение детерминированного КА Пример 1. Преобразовать НКА ДКА M н Q, , , q0 , F , где Q A, B, C, D, E , , , q0 A, F E в M н Q ', , ', q0 , F . Функция переходов: A B C D E B, D C E E E Алгоритм: Шаг 1,2,3 Действия и результаты A ' A B, D 4 2,3 ' B, D C E Имеется столбец 4 2 ' ' A A B , D B ,D Имеется столбец B, D с пустыми позициями. Переход к п.2 ' ' A B, D A B, D B, D E C C с пустыми позициями. Переход к п.2 C E - 85 - C B, D E B, D C E E Все столбцы есть Нет столбцов с пустыми позициями. Переход к п.5 3 4 5 F' E Множество Обозначение B, D Q A, B, C, E 6 B ' A B C C E E E B Взаимосвязь способов определения регулярных языков Построение КА по регулярному выражению. Пример 1. Построить КА M 1 по заданному регулярному выражению 1 0 |1 . * Выявим алфавит соответствующего языка: Выберем обозначения состояний КА: 0,1 . S1 , S2 ,... ; S1 - начальное состояние. Построим диаграмму состояний КА: 1,0 1 S1 S2 Определим множество состояний автомата: Q S1 , S2 . Множество заключительных состояний автомата F S2 содержит только один элемент S 2 , т.к. диаграмма не содержит больше узлов, удовлетворяющих требованиям шага 4. Дадим полное определение КА M 1 : M1 Q S1 , S2 , 0,1 , , q0 S1, F S2 . S1 ,1 S 2 Функция переходов : S2 , 0 S2 или S1 ,1 S 2 S1 0 1 S2 S2 S2 S2 Построение регулярной грамматики по КА Пример 1. Построить регулярную грамматику G , соответствующую КА Шаг 1 2 3 Конечный автомат M 1 M1 Регулярная грамматика G Q S1 , S2 Нетерминалы 0,1 Терминалы q0 S1 Аксиома VN S1 , S2 VT 0,1 S1 S1 ,1 S2 ; S2 , 0 S2 ; S1 1S2 ; S2 0S2 ; S2 ,1 S2 S2 1S2 F S2 S1 1; S2 0; S2 1 Правила соответствуют грамматике класса 3. Построение КА по регулярной грамматике Пример 1. - 86 - E Построить КА M 5 Q, , , q0 , F по регулярной грамматике G5 VT 0,1 ,VN S1 , S2 , P, S S1 , где P S1 1S2 ; S1 1; S2 0S2 ; S2 0; S2 1S2 ; S2 1. Решение. 1. Пополнять грамматику не требуется. 2. автомата: Q VN 3. q0 S S1; множество состояний конечного Начальное состояние конечного автомата: S1 , S2 ; множество символов входного алфавита: V 0,1. T Варианты сформированной функции переходов: S1 S1 ,1 S 2 S2 S2 0 S2 1 или S2 , 0 S2 S2 ,1 S2 S2 4. Множество заключительных состояний: 5. -правила для аксиомы грамматики нет. 6. Конечный автомат F S2 . M 5 – детерминированный, функция переходов – неполная (нет значения для S1 ,0 ); граф КА: 1,0 S1 1 S2 Определение КС-языков Задача и дерево разбора Пример 1. Построить вывод для строки aab в грамматике с порождающими правилами S AB; A aA; A ; B bB; B . Левосторонний: 1,2,2,3,4,5 S 1 AB 2 aAB 2 aaAB 3 aaB 4 aabB 5 aab Правосторонний: 3,2,2,5,4,1 S 1 AB 4 AbB 5 Ab 2 aAb 2 aaAb 3 aab Обратный порядок следования порождающих правил во втором случае объясняется тем, что правосторонний разбор обычно связан с приведением строки языка к начальному символу, а не с получением строки из начального символа. Построим дерево разбора: - 87 - Проверка существования языка Пример 1. Существует ли язык, определяемый грамматикой с правилами: S AB, A aA, A a, B b , где VT a, b ,VN S , A, B , S - аксиома. N0 1. 2. N1 A, B 3. N1 N 0 ? Нет. N0 N1 A, B 2. N1 S , A, B 3. N1 N 0 ? Нет. N0 N1 S , A, B 2. N1 S , A, B 3. N1 N 0 ? Да. N N1 S , A, B 4. S N , L G , pr 0 . Язык существует. Устранение недостижимых символов. Пример 1. Преобразовать грамматику с правилами: Q ab; B b; C db. 1. W0 Q 2. W1 Q, a, b 3. W1 W0 ? Нет. W0 W1 Q, a, b 2. W1 Q, a, b 3. W1 W0 ? Да. W =W1 Q, a, b 4. VN Q ,VT a, b ,VБ B, C, d , P Q ab - 88 - Эквивалентные преобразования КС-грамматик. Устранение –правил. Пример 1. Преобразовать грамматику с аксиомой S ,VT a, b ,VN A, B, S и правилами: S AB; A aA | ; B bB | в эквивалентную грамматику без –правил: 1.1 N0 A, B 1.2 N1 A, B, S 1.3 N1 N 0 ? Нет. N0 N1 A, B, S 1.2 N1 A, B, S 1.3 N1 N 0 ? Да. N N1 A, B, S 2 P ' S AB, A aA, B bB 3 P ' S AB, A aA, B bB, S A, S B, A a, B b S N ? Да. P ' Z S | , S AB | A | B, A aA | a, B bB | b , 4 V Z , S , A, B , Z - аксиома. ' N Устранение цепных правил Пример 1. Преобразовать грамматику с аксиомой S ,VT ;, zVN S , X , Y и правилами S X , X Y , Y Y ;| z в эквивалентную грамматику без цепных правил. 1.1 N0S S 1.2 N1S S , X 1.3 N1S N 0S ? Нет. N0S N1S S , X 1.2 N1S S , X , Y 1.3 N1S N 0S ? Нет. N0S N1S S , X , Y 1.2 N1S S , X , Y 1.3 N1S N 0S ? Да. N S S , X , Y 1.1 N0X X 1.2 N1X X , Y 1.3 N1X N 0X ? Нет. N0X N1X X , Y 1.2 N1X X , Y 1.3 N1X N 0X ? Да. N X X , Y 1.1 N0Y Y 1.2 N1Y Y 1.3 N1Y N 0Y ? Да. N Y Y 2 P ' S Y ;| z, X Y ;| z, Y Y ;| z Левая факторизация правил - 89 - Произвести левую факторизацию правил S aSb, S aSc, S d грамматики с аксиомой S ,VT a, b, c, d ,VN S S aSb | aSc 1 3 S aS b | c S aSW ,W b | c 4 VN S ,W , P ' S aSW , S d ,W b,W c 5 Других объектов факторизации нет. 2 Устранение прямой левой рекурсии Пример 1: Исключить прямую левую рекурсию в грамматике с аксиомой S ,VT a, b, d ,VN S , A, B, C и правилами S Aa, A Bb, B Cc | d , C Ccbz | dbz C Ccbz, C dbz 1 Y cbzY , Y cbz 2 C dbzY , C dbz , Y cbzY , Y cbz 3 VN S , A, B, C , Y , P S Aa, A Bb, B Cc | d , 4 C dbzY | dbz , Y cbzY | cbz Другой рекурсии нет. 5 VN VN , P P Нормальная форма КС-грамматики Нормальная форма Хомского Пример 1: Преобразовать в НФХ грамматику с аксиомой S ,VT a, b ,VN S , A, B и правилами S aAB | BA, A BBB | a, B AS | b Шаг Действия и результаты 1 P A a, B b 2 P A a, B b S BA, B AS Правила S нет 1) правило S aAB 3 Терминал Обозначение Строка a N1 AB N2 Обозначение S N1 N 2 , N 2 AB 2) правило A BBB Новые правила: 4 Терминал a Строка Обозначение N1 Обозначение Новые правила: Правило 7 BB N3 A bN3 , N3 BB Таких правил нет 5 6 AB N2 N1 a из таблицы обозн. Терминалов – в P : P S N1 N2 | BA, A BN3 | a, B AS | b, N2 AB, N3 BB N1 a VN VN Обозначениятерминаловистрок S , A, B, N1 , N2 , N3 - 90 - Нормальная форма Грейбах Пример 1: Преобразовать в НФГ грамматики с аксиомой S ,VT ;, ,*, a, b ,VN S , A, C, B и правилами S AC ;, A B C , C ab, B * | b Шаг 1 2 3 4 3 4 3 4 3 5 S A C 1 2 3 Действии и результаты B Положение символа безразлично 4 C в списке i 3 i 0? Нет. Правил вида C B в грамматике нет i2 i 0? Нет. Имеется единственное правило A B C. Оно замещается правилами A * C и A b C : P S AC;, A * C | b C, C ab, B *| b i 1 i 0? Нет. Имеется единственное правило S AC ; Оно замещается правилами S * CC ; и S b CC : P S * CC;| b CC;, A * C | b C, C ab, B *| b i 0 i 0? Да Терминал Обозначение N1 ; b N3 N2 P S *N1CCN2 | bN1CCN2 , A *N1C | bN1C, C aN3 , B *| b 6 P S *N1CCN 2 | bN1CCN 2 , A *N1C | bN1C , C aN 3 , B * | b N1 , N 2 ;, N3 b ;VN S , A, B, C , N1 , N 2 , N 3 Устранение левой рекурсии Пример 1: Устранить левую рекурсию в грамматике с аксиомой S ,VT a, b, c, d , z ,VN S , A, B, C и правилами S Aa, A Bb, B Cc | d , C Az | a Шаг 1 Действия и результаты S A B C 1 2 3 4 2 3 Правил вида S Sa нет i 4 ? Нет. i 2, j 1 4 5 Правил вида A Sa нет j 1 ? Да. Переход к п.2 2 3 Правил вида A Aa нет i 4 ? Нет. i 3, j 1 4 5 Правил вида B Sa нет j 2 ? Нет. j 2 .Переход к п.4 4 5 Правил вида B Aa нет j 2 ? Да. Переход к п.2 i 1, P ' P, n 4 - 91 - 2 3 Правил вида B Ba нет i 4 ? Нет. i 4, j 1 4 5 Правил вида C Sa нет j 3 ? Нет. j 2 .Переход к п.4 4 Есть правило 5 j 3 ? Нет. j 3 .Переход к п.4 Есть правило C Bbz . Заменяем его на C Ccbz | dbz : 4 5 2 C Az . Заменяем его на C Bbz : P ' S Aa, A Bb, B Cc | d , C Bbz | a P ' S Aa, A Bb, B Cc | d , C Ccbz | dbz | a j 3 ? Да. Переход к п.2 Есть правила C Ccbz | dbz | a . Заменяем их на C dbz | a | dbzX | aX , X cbz | cbzX с новым не терминалом X : P ' S Aa, A Bb, B Cc | d , C dbz | a | dbzX | aX , X cbz | cbzX VN S , A, B, C , X 3 i 4? Да. Конец работы Автомат с магазинной паматью Разновидности МП–автоматов Пример 1: M Q, , , , q0 , Z 0 , Q A , , I , O , Q0 A, Z 0 I 0 , МП-автомат A, , I A, OI ; A, , O A, OO ; A , , O A, ; A, , I A, A, , I | A, , O I | A, , O OI Решение: | A, , O I | A, , OI | A, , I | A, , . Распознать строку Язык, принимаемый МП-автоматом в данном примере, это КС-язык парных круглых скобок. Этот же язык генерирует КС-грамматика с правилами P S S , S SS , S . Из формального определения МП-автомата следует, что он может менять каждый раз только один символ в вершине стека (см. третий сомножитель в магазинной функции : Q P Q ). Этот МП-автомат не может, кроме того, продолжать работу при пустом стеке, т.к. использовать расширенный МП-автомат, т.е. МП-автомат с магазинной функцией : Q * P Q * , то указанные ограничения будут сняты. - 92 - . Однако если Взаимосвязь МП-автоматов и КС-грамматик Нисходящие распознаватели языков Стратегии нисходящего анализа Пример 1: Дана КС-грамматика G1 VT ,VN , P, S , где VN S , X , Y ,VT a, b, d , p, q, x, y и множество P состоит из шести правил: 1)S pX 2)S qY 3) X aXb 4) X x 5)Y Yd 6)Y y Выполнить нисходящий анализ строки paaxbb . Решение: Каждый нетерминал грамматики имеет по 2 правила, но они начинаются с разных терминалов. Этим свойством грамматики и будем пользоваться при выборе правила, сопоставляя очередной символ входной строки с головным символом правой части. Нисходящий анализ строки paaxbb № п./п. Входная строка Стек Правило paaxbb 1 1 S pX X aXb Xb aXbb Xbb xbb bb b paaxbb aaxbb aaxbb axbb axbb xbb 2 3 4 5 6 7 8 9 10 11 xbb bb b 3 3 4 Дерево разбора строки paaxbb LL(k)-грамматики Пример 1: В грамматике с аксиомой 1) R X C 4) X qX R,VT c, d , g , q ,VN X , Y , R и правилами 2) R Yd 5)Y g 3) X q 6)Y gY Найти множество символов-предшественников правила S X C и множество символов-последователей F R для R XC . - 93 - 1) определить множество S X C a | a VT , X C * возможные строки, начинающиеся терминалом: X C 2) вывод X C РАЗДЕЛ * Характер изменений в программе Номер и дата протокола заседания кафедры, на котором было принято данное решение все 3 qc; X C 4 qX C ; значит, ; не существует, поэтому примем 6. Изменения в рабочей утверждения программы. , V * , для чего вывести из строки X C F R . программе, которые Подпись заведующего кафедрой, утверждающего внесенное изменение произошли после Подпись декана факультета (проректора по учебной работе), утверждающего данное изменение РАЗДЕЛ 7. Учебные занятия по дисциплине ведут: Ф.И.О., ученое звание и степень преподавателя асс. Будкин К.А. Учебный год Факультет 2010 – 2011 ФМОИП - 94 - Специальность 010501 - Прикладная информатика математика и