Происхождение модификатора register



 

Модификатор register был впервые определен в языке С. Первоначально он применялся только к переменным типа int и char или к указателям и заставлял хранить переменные этого типа в регистре ЦП, а не в ОЗУ, где хранятся обычные переменные. Это означало, что операции с регистровыми переменными могли выполняться намного быстрее, чем операции с остальными (хранимыми в памяти), поскольку для опроса или модификации их значений не требовался доступ к памяти.

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

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

Чтобы показать влияние, оказываемое register ‑переменными на быстродействие программы, в следующем примере измеряется время выполнения двух циклов for , которые отличаются друг от друга только типом управляющих переменных. В программе используется стандартная библиотечная С++‑функция clock() , которая возвращает количество импульсов сигнала времени системных часов, подсчитанных с начала выполнения этой программы. Программа должна включать заголовок <ctime> .

 

 

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

На заметку. При написании этой книги была использована среда Visual C++, которая игнорирует ключевое слово register. Visual C++ применяет оптимизацию "как считает нужным". Поэтому вы можете не заметить влияния спецификатора register на выполнение предыдущей программы. Однако ключевое слово register все еще принимается компилятором без сообщения об ошибке. Оно просто не оказывает никакого воздействия.

 

Перечисления

 

В C++ можно определить список именованных целочисленных констант. Такой список называется перечислением (enumeration). Эти константы можно затем использовать везде, где допустимы целочисленные значения (например, в целочисленных выражениях). Перечисления определяются с помощью ключевого слова enum , а формат их определения имеет такой вид:

 

 

Под элементом список_перечисления понимается список разделенных запятыми имен, которые представляют значения перечисления. Элемент список_переменных необязателен, поскольку переменные можно объявить позже, используя имя типа перечисления. В следующем примере определяется перечисление apple и две переменные типа apple с именами red и yellow .

 

 

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

 

 

Эту инструкцию можно записать и так.

 

 

Ключевое слово enum объявляет перечисление.

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

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

 

 

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

 

 

на экран будут выведены числа 0 4 .

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

 

 

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

 

 

Теперь переменная fruit будет содержать значение Golden_Del , поскольку эта apple ‑константа связывается со значением 1 . Как отмечено в комментарии, несмотря на то, что эта инструкция стала корректной, ее стиль оставляет желать лучшего, что простительно лишь в особых обстоятельствах.

Используя инициализатор, можно указать значение одной или нескольких перечислимых констант. Это делается так: после соответствующего элемента списка перечисления ставится знак равенства и нужное целое число. При использовании инициализатора следующему (после инициализированного) элементу списка присваивается значение, на единицу превышающее предыдущее значение инициализатора. Например, при выполнении следующей инструкции константе Winesap присваивается значение 10 .

 

 

 

 

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

 

 

He забывайте, что символ McIntosh – это просто имя для некоторого целочисленного значения, а не строка. Следовательно, при выполнении предыдущего кода на экране отобразится числовое значение константы McIntosh , а не строка "McIntosh" . Конечно, можно создать код ввода и вывода символов перечисления в виде строк, но он выходит несколько громоздким. Вот, например, как можно отобразить на экране названия сортов яблок, связанных с переменной fruit .

 

 

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

 

 

Результаты выполнения этой программы таковы.

 

 

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

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

 

Ключевое слово typedef

 

Ключевое слово typedef позволяет создать новое имя для существующего типа данных.

В C++ разрешается определять новые имена типов данных с помощью ключевого слова typedef . При использовании typedef ‑имени новый тип данных не создается, а лишь определяется новое имя для уже существующего типа. Благодаря typedef ‑именам можно сделать машинозависимые программы более переносимыми: для этого иногда достаточно изменить typedef ‑инструкции. Это средство также позволяет улучшить читабельность кода, поскольку для стандартных типов данных с его помощью можно использовать описательные имена. Общий формат записи инструкции typedef таков,

 

 

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

Например, с помощью следующей инструкции можно создать новое имя для типа float ,

 

 

Эта инструкция является предписанием компилятору распознавать идентификатор balance как еще одно имя для типа float . После этой инструкции можно создавать float ‑переменные с использованием имени balance .

 

 

Здесь объявлена переменная с плавающей точкой over_due типа balance , который представляет собой стандартный тип float , но имеющий другое название.

 

Еще об операторах

 

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

 

Поразрядные операторы

 

Поразрядные операторы обрабатывают отдельные биты.

Поскольку C++ нацелен на то, чтобы позволить полный доступ к аппаратным средствам компьютера, важно, чтобы он имел возможность непосредственно воздействовать на отдельные биты в рамках байта или машинного слова. Именно поэтому C++ и содержит поразрядные операторы. Поразрядные операторы предназначены для тестирования, установки или сдвига реальных битов в байтах или словах, которые соответствуют символьным или целочисленным С++‑типам. Поразрядные операторы не используются для операндов типа bool , float , double , long double , void или других еще более сложных типов данных. Поразрядные операторы (они перечислены в табл. 9.1) очень часто используются для решения широкого круга задач программирования системного уровня, например, при опросе информации о состоянии устройства или ее формировании. Теперь рассмотрим каждый оператор этой группы в отдельности.

 


Дата добавления: 2018-09-22; просмотров: 377; Мы поможем в написании вашей работы!

Поделиться с друзьями:






Мы поможем в написании ваших работ!