Бьерн Страуструп. Язык программирования С++. Специальное издание (2011) (1004033), страница 33
Текст из файла (страница 33)
Вызовите их с аргументами 'а', 49, 3300, с, ис и зс, где е — это сйаг, ис — или(йле(( сйа(; зс — щие4 сйаг. Какие из этих вызовов допустимы? В каких вызовах компилятор создаст временные переменные? ('1.5) Определите таблицу имен месяцев и количества дней в них. Выведите эту таблицу.
Проделайте это дважды: первый раз воспользуйтесь массивом элементов типа сйаг для названия месяца и массивом типа (л(для количества дней; второй раз примените массив структур, которые содержат и названия месяцев, и число дней. (*2) Выполните тестовые прогоны, чтобы на практике убедиться в эквивалентности кода для случаев итерации по элементам массива с помощью указателей и с помошью индексации (95.3.1).
Если у компилятора есть разные степени оптимизации, убедитесь, влияет ли это (и как) на качество генерируемого машинного кода. (*1.5) Продемонстрируйте пример, когда имеет смысл использовать объявляемое имя в инициализаторе. (*1) Определите массив строк, содержаших названия месяцев. Распечатайте эти строки. Для печати передайте этот массив в функцию. ("2) Читайте последовательность слов из потока ввода. Пусть слово Ди((служит признаком конца ввода. Печатайте слова в порядке, в каком они были введены, но не допускайте повтора одинаковых слов. Модифицируйте программу так, чтобы перед печатью слова предварительно сортировались.
(*2) Напишите функцию, которая подсчитывает количество повторов пар букв в строке типа зтг(пя, а также в символьном массиве с терминальным нулем (строка в С-стиле). Например, пара букв "аЬ" входит в строку "хаЬаасЬахаЬЬ" дважды. (*1.5) Определите структуру Ра(едля хранения дат. Напишите функции для чтения дат из потока ввода, для вывода дат и для их инициализации. Выражения и операторы Преждевременная оптимизация— корень всех зол. — Д. Кнут С другой стороны, мы не можем игнорировать эффективность. — Джон Бентли Пример с калькулятором — ввод — параметры командной строки — обзор операций — побитовые логические операции — инкремент и декремент — свободная память — явное преобразование типов — сводка операторов объявления — операторы выбора — объявления в условиях — операторы цикла — пресловутый лого — комментарии и отступы — советы — упражнения.
б.1. Калькулятор Мы рассмотрим операторы и выражения языка С++ на примере программы калькулятора, реализующего четыре стандартных арифметических действия в форме инфиксных операций над числами с плавающей запятой. Пользователь может также определять переменные. Например, если на входе калькулятора имеется г=2.5 агеа=ре* г* г (где р! — это предопределенная переменная) то в результате работы програм- ма-калькулятор выдаст 2.5 )9.635 где 2.5 — зто результат обработки первой строки, а 29. б35 — второй.
Наш калькулятор состоит из четырех основных частей: синтаксического анализатора (или парсера — рагзег), функции ввода, таблицы символов и управляющей 156 Глава б, Выражения и операторы программы (управляющая, ведущая программа — г)пчег). По сути дела, эта программа является миниатюрным компилятором, где парсер выполняет синтаксический анализ, функция ввода реализует ввод исходных данных и выполняет лексический анализ, в таблице символов хранится постоянная информация, а управляющая программа осуществляет инициализацию, вывод результатов и обработку ошибок.
Мы могли бы добавить к этому калькулятору массу иных возможностей, чтобы он стал более полезным 66.6[20)), но код и без того уже достаточно велик, и кроме того, новые возможности калькулятора ничего бы нам не добавили в плане изучения языка С++. 6.1.1. Синтаксический анализатор Вот формальная гралшатика программы-калькулятора: ргоегат: емтз ЕХРГ 1(М ЕГчТЗ У Е)))1) это конец ввода ехр) !1м: ехргеззгол Р1ИНТ I/ РОТ это точка с запятой ехргеззгоп РВТ)ч' Т ехрг 1(зз ехргезз1оп: ехргеззгол в зегт ехргезз1ол — гегт зегт У выражение зепл: гепл / рпта~у гегт* р«1та~у рг1«лагу рпта«у: )УЮМВЕЯ «1АМЕ 1чАМЕ = ехргеззгол -рп та~у (ехргеззгоп ) «У первичное выражение У число У иин Иными словами, программа есть последовательность выражений, разделенных точками с запятой.
Базовыми элементами выражений служат числа, имена и операции *, / +, — (унарная и бинарная), =. Объявлять имена до их использования необязательно. Применяемый нами стиль синтаксического анализа называется рекурсивным спусков« («есигзп е в(езсещ), В языках типа С-ь+, где вызов функций обходится достаточно дешево, этот стиль еще и эффективен. Каждому порождающему правилу грамматики сопоставляется своя функция, вызывающая другие функции. Терминальные символы (например, ЕГчЭ, Р1ОТВЕВ, е и -) распознаются лексическим анализатором яег гойел(); нетерминальные символы распознаются функциями синтаксического анализа, ехр«(), гегт)) и р«1т)). Как только оба операнда (под)выражения распознаны, выражение тут же вычисляется; в настоящем же компиляторе в этот момент скорее генерируется машинный код. злу 6.1. Калькулятор Входные данные для парсера поставляет функция Ве! го1(еп() .
Самый последний возврат функции лег гойепО хранится в глобальной переменной сигг 1о1(, имеющей тип перечисления Тойеп га1ие: епит Тайен га(ие МАМЕ, Ь1ЮМВЕИ, РЕ((Е= ' » ', М1)Ч!Е= ' — ', РЯ11ЧТ= '; ', АББ1СР1= ' = ', )' Е(ЧРл, М67.='*', Ш'г'='/', ЕР=' (', НР=') ' Толеп»а!ие сигг !оИ=РВ11ЧТ! г(оиЫе ехрг (Ьоо( ее!) ( аоиЫе 1е1(=!ест (иег) !сложение и вычитание ~У "вечно" Тог( ); ) вн«1сл (сигг игл) ( саве РТСБ: 1е!) «= 1егт Игие) Ьгеад; саве М1Л7/Ю: 1е1! -= гегт (ггие) Ьгеад; ггегаив: геии п !ег); Эта функция мало что делает сама по себе.
В манере высокоуровневых функций из крупных программ она вызывает другие функции, которые и делают большую часть работы. Представление лексемы (го/сепв) целочисленным кодом соответствующего символа удобно и эффективно, в том числе и при отладке программы. С этим'не возникает никаких проблем, так как в известных мне системах кодировки символов нет печатных символов с однозначными кодами. Я выбрал Р)гПЧТв качестве начального значения для переменной сигг го!с, поскольку именно это значение она получает после вычисления выражения и вывода результата.
Таким образом, система стартует в нормальном состоянии, что минимизирует потенциальные ошибки и необходимость в специальной инициализации. Каждая функция синтаксического анализа принимает аргумент типа Ьоо1 (54.2), указывающий, должна ли функция вызвать лег го!сел () для получения очередной лексемы. Каждая такая функция вычисляет «свое выражение» и возвра(цает вычисленное значение. Функция ехрг() обрабатывает сложение и вычитание. Она состоит из единственного цикла, выполняющего сложение или вычитание термов (по-английски гегтз — это члены алгебраических выражений, нал которыми выполняются операции сложения/вычитания; сами они представлены в виде произведения/частного выражений): 158 Глава б. Выражения и операторы Оператор ви4геЬ сравнивает значение выражения в круглых скобках с набором констант.
Оператор Ьгеай используется для организации принудительного выхода из оператора вичгеЬ. Константы, стоящие за саве-метками, должны отличаться друг от друга. Если значение проверяемого выражения не совпадает ни с одной из констант, то осуществляется переход на 4е/аи1г-ветвь. В общем случае, программист не обязан реализовывать 4еуаи1г-ветвь в операторе вичгеЬ.
Следует отметить, что выражение вроде 2 — 3+ 4 вычисляется как (2 — 3) + 4, что диктуется определенной выше грамматикой. Странное обозначение /ог(;; ) задает бесконечный цикл. Это вырожденный случай оператора цикла 1ог (84.2); иГЫ1е( ггие ) — еще один пример на эту тему. В итоге оператор вн1геЬ исполняется раз за разом, пока не встретится что-либо отличное от + или —, после чего срабатывает оператор гетги из 4еуаи1г-ветви.
Операции присваивания «= и -= использованы для выполнения сложения или вычитания. Можно было бы писать 1еЯ = 1еЯ «гегт (яие) и 1еЯ = 1еЯ вЂ” гегт (ггие) с тем же самым конечным результатом. Но выражения 1е1((+= гели (ггие) и 1е1(( -= гегт (ггие) не только короче, но и четче фиксируют предполагающиеся к выполнению действия. Каждая операция присваивания является отдельной самостоятельной лексемой, и поэтому выражение а» = 1; ошибочно из-за лишних пробелов между+ и=. Сочетание простых операций присваивания возможно со следующими бинарными (двухоперандными) операциями * 1 Ъ «) " « » так что имеются следующие составные (комбинированные) операции присваивания: «= »= Здесь ь означает операцию целочисленного деления по модулю (то есть нахождение остатка); ь, ( и " — побитовые логические операции «И», «ИЛИ» и «ИСКЛЮЧАЮЩЕЕ ИЛИ»; «и» это операции битового сдвига влево и вправо; в 86.2 дана сводная информация по всем операциям и их смыслу.
Для операндов встроенных типов и любой бинарной (двухоперандной) операции Ф выражение х ©= у равносильно х = х <ф у с той лишь разницей, что х вычисляется лишь один раз. В главах В и 9 обсуждается модульная организация программ. Нашу же программу-калькулятор мы здесь упорядочиваем так, что объявления даются лишь один раз и до их использования.
Единственное исключение касается функции ехрг(), которая вызывает гегт(), которая вызывает ргии(), которая вызывает ехрг(). Этот замкнутый круг нужно как-то разорвать. Объявление 4оиЫе ехрг (Ьоо1); расположенное до определения функции ргии (), решает проблему. Функция гегт() обрабатывает умножение и деление аналогично тому, как ехрг() обрабатывает сложение и вычитание: д умножение и деление 4оиЫв гегт (Ьоо! яег) ( йоиЫе (е1) = рпт (лей 1ог(;; ) вюгухЬ (сиге гоЬ) 159 6.1. Калькулятор ( сазе МОЕ: 1еу) *= рг1т багие) ) Ьгеай ! сазе РЛ'. Ц'(ИоиЫе й = рот (!гие) ) ( 1е1) /= а'; Ьгеай; ) ге!игп еггог ( "а!г!ае Ьу Ьы ) г пега иге: ге!игп 1е1) ! г!оиЫе питЬег га!ие; аппо згггпа га(ие! аоиЫе рпт (Ьоо! яег) ( (/(Ве!) ее! годен (); ~7 обработка первичных выражений зыйсЬ (сигг гой) ( сазе ХЮМВЕВ: ( аоиЬ!в г = питаег га1ие; де! годен (); ге!ига г; ) сазе ХАМЕ: ( аоиЫеь г = гаЫе (зи(пя га!ие) ! гу (де! годви () == АЯЯ16Х) г = ехрг(ггие) ге!ига г; ) сазе М1ХСБ: ге!ига -рггт (!гие); У унарный минус Деление на нуль не определено и обычно приводит к неприятным последствиям.
Поэтому мы проверяем делитель на нуль до выполнения операции деления, и вызываем функцию еггог () в случае нулевого значения делителя. Функция еггог() описывается в 56.1.4. Переменная В определяется в программе ровно там, где она требуется, и туг же при этом инициализируется.