И.Г. Головин, И.А. Волкова - Языки и методы программирования, страница 9
Описание файла
PDF-файл из архива "И.Г. Головин, И.А. Волкова - Языки и методы программирования", который расположен в категории "". Всё это находится в предмете "языки программирования" из 7 семестр, которые можно найти в файловом архиве МГУ им. Ломоносова. Не смотря на прямую связь этого архива с МГУ им. Ломоносова, его также можно найти и в других разделах. .
Просмотр PDF-файла онлайн
Текст 9 страницы из PDF
Язык Паскаль предоставляет единственныйцелочисленный тип данных — integer. Этого вполне достаточно дляучебного языка программирования, но неприемлемо в индустриальном программировании. Языки C++, Java и C# представляютуниверсальную номенклатуру целых типов, которая соответствуетбольшинству современных архитектур. В этих языках определеныоднобайтовые целые числа (char — в C++, byte — в Java и С#), короткие целые (short), основные целые (int), длинные целые (long).Беззнаковые типы. Практически все компьютерные архитектурыв дополнение к знаковым целым числам поддерживают и беззнаковыетипы, т.е.
целочисленные типы, содержащие только неотрицательныезначения. Это обусловлено необходимостью выполнения операцийнад адресами в машинных программах.40Адреса представляются беззнаковым целым типом (вспомним, чтоадрес — это номер ячейки памяти, начинающийся с нуля). Операциинад адресами называются адресной арифметикой.Также причиной использования беззнаковых чисел является то,что при одинаковом размере максимальное значение беззнаковоготипа больше, чем максимум знакового (ведь не требуется хранитьинформацию о знаке). В случае если диапазон целых значений невелик, использование беззнакового типа иногда необходимо.Язык Java не содержит беззнаковых типов, что упрощает реализацию JVM и позволяет избежать ряда проблем, связанных с надежностью программ (см. далее).
Языки C++ и C# для каждого размерацелого типа содержат знаковый и беззнаковый варианты. Преждевсего, это диктуется требованием универсальности.Представление. Языки Паскаль и C++ не фиксируют представление целого типа. Размер и диапазон значений определяютсяреализацией. Это связано с тем, что эти языки были реализованыдля большого числа машинных архитектур, существенно различавшихся по представлению чисел. Фиксация представления дала бынеобоснованное преимущество конкретной архитектуре, посколькуреализации в других архитектурах были бы более сложными и менееэффективными. Язык C++ даже не фиксирует представление однобайтового типа c h ar.
В зависимости от реализации он может бытькак знаковым (sig n e d char), так и беззнаковым (u n sig n ed char).Про размеры типов в языке C++ известно следующее:s iz e o f ( c h a r ) =1s iz e o f (c h a r)< = s iz e o f(s h o rt)< = s iz e o f(in t)< = s iz e o f(lo n g )Здесь s i z e o f — это статическая операция языка C++, котораяприменима как к именам типов, так и к объектам данных, и возвращает размер типа (объекта данных) в байтах.Языки Java и C# полностью регламентируют размер и диапазонзначений всех типов данных. Это связано с тем, что архитектуры,для которых разрабатывались языки, вполне определены (для Javaэто JVM, а для C# — платформа .NET на архитектуре IA-32).
Представление типов описывается в табл. 5.1 и 5.2.Т а б л и ц а 5.1. Целые типы в языке C#РазмерЗнаковыйДиапазонБеззнаковыйДиапазон1sb y te- 1 2 8 ... 127b y te0 ...2 5 52sh o rt- 3 2 7 6 8 ...3 2 7 6 7u sh ort0 ...6 5 5354in t- 2 3l...2 31 - 1u in t0 . .. 2 32- 18lo n g- 2 63...2 63 - 1u lo n g0 . .. 2 64- 141Т а б л и ц а 5.2. Целые типы в языке JavaРазмерИмяДиапазон1b y te- 1 2 8 ... 1272sh ort- 3 2 7 6 8 .„ 3 2 7674in t—231... 2 31 —18lo n g_2бз2 63- 1Надежность.
Надежность языковых конструкций определяетсятем, насколько эти конструкции предохраняют программиста отслучайных ошибок. Наличие беззнаковых типов снижает надежностьработы с целыми числами.Рассмотрим следующий фрагмент программы на С#:int cnt = 0;for (uint i = 256; i >= 0; i--) cnt++;Этот цикл никогда не закончится, так как переменная цикла iвсегда неотрицательная.
Проблема в том, что в большинстве машинных архитектур целочисленные операции сложения и вычитания негенерируют ошибку при выходе результата за пределы допустимогодиапазона. Решение о корректности значения операции возлагаетсяна программиста. Поэтому в приведенном фрагменте программызначение переменной i сначала достигает нуля, а затем вычитание 1дает в результате (на архитектуре х86) сразу значение 232- 1 (максимальное значение типа uint). Результат неверный, но ошибки невозникает, и программа «зацикливается». Аналогичный результатполучается и при переписывании фрагмента на язык C++ (в этомслучае uint следует заменить на unsigned). Правда, язык C# позволяет проконтролировать корректность результата целочисленныхопераций, для чего служит конструкция checked:checked{for (uint i = 256; i >= 0; i--) cnt++;}В блоке, следующем за ключевым словом checked, все целочисленные операции проверяются на корректность, поэтому привозникновении переполнения (при попытке вычесть 1 из нулевогобеззнакового значения) генерируется ошибка.
Разумеется, времявыполнения операций растет, поэтому по умолчанию операции неконтролируются.Заметим, что в языке Java сделать подобную ошибку просто невозможно.42Еще один источник возможных ошибок при работе с целочисленными типами — преобразования значений одного типа в значениядругого типа. Общий синтаксис преобразований типов в языкахC++, Java, С#:(X)выражениеЗдесь X — это имя типа.
Пусть выражение вычисляет значениетипа Y. Тогда можно сказать, что приведенная конструкция осуществляет преобразование из типа Y в X (или приведение типа Yк X). Преобразования арифметических типов бывают двух видов:расширяющие и сужающие. Если множество значений типа Y естьподмножество значений типа X, то преобразование является р а сширяющим, а в противном случае — сужающим.Очевидно, что расширяющие преобразования безопасны, так какпреобразуемое значение сохраняется.
Сужающие преобразованияпотенциально опасны. Рассмотрим фрагмент программы на С#:s h o r t i = -1 ;u sh o rt u i = (u s h o rt)i;i n t k = 256;s b y te sb = (s b y te )k ;Здесь два сужающих преобразования: из s h o r t в u s h o r t (знаковый и беззнаковый типы одного размера) и из i n t в sb y te (знаковыетипы разных размеров). В обоих случаях результат может обескуражить: из -1 получится 65 535, а из 256 получится 0.
Если переписатьданный фрагмент на языке C++, то получим такие же результаты накомпьютерах архитектуры х86. В других архитектурах результат может быть другим, но в любом случае преобразуемое значение будетпотеряно.Конечно, не все сужающие преобразования ведут к ошибке: еслипреобразуемое значение типа Y одновременно входит и в тип X, тооно не изменится при приведении (например, нуль принадлежит всемчисловым типам в наших языках, поэтому его безопасно приводитьк любому числовому типу).В языке C++ корректность сужающих преобразований не контролируется (программист берет на себя всю ответственность), а вязыке C# контролируется только внутри ch eck ed -блока.Еще опасней ситуация при неявных преобразованиях.
Преобразование типа неявное, если оно вставляется или выполняется транслятором. В языках C# и Java неявными могут быть только расширяющие преобразования, а в языке C++ абсолютно все преобразованиямежду любыми двумя арифметическими типами (не только целыми)могут быть неявными.
Говоря другими словами, в языке C++ значение одного арифметического типа можно присваивать переменнойлюбого другого арифметического типа. Компилятор просто вставитнеявное преобразование. При этом никакого контроля корректности43преобразования не проводится. Такая ситуация может приводитьк ошибкам, которые трудно обнаружить.Набор операций. Языки C++, Java, C# поддерживают практически единый набор операций над целыми значениями:• арифметические (сложение «+», вычитание «-», умножение «*»,деление нацело «/», остаток от деления «%»);• побитовые (побитовое или «|», побитовое и «&», побитовая инверсия «~», побитовое исключающее или «А»);• операции сдвига (влево « « » , вправо «>>»).Все операции возвращают тот же тип, что и тип операндов (операнда).Интересно, что отсутствие беззнаковых типов в языке Java обеспечивает появление дополнительной операции: логического сдвигавправо >>>.
Дело в том, что машинные операции сдвига вправоимеют разный эффект для знаковых и беззнаковых типов (подробнее сдвиги рассматриваются, например в [29]). Для беззнаковыхтипов используется логический сдвиг (сдвигаемые биты замещаютсянулями), а для знаковых типов — арифметический сдвиг (знак сохраняется, и сдвиг эквивалентен делению на степень числа 2). Притрансляции операции >> компилятор применяет операцию логического сдвига, если операция применяется к беззнаковому типу,и операцию арифметического сдвига для знаковых типов.
Однаков языке Java есть только знаковые типы (поэтому операция >> всегдапредставляет собой арифметический сдвиг), а операция логическогосдвига (игнорирующая знак) иногда необходима. Вот в этих случаяхи применяется операция >>>.К целым типам применимы операции сравнения (равенство «==»,неравенство «!=», меньше «<», больше «>», меньше или равно «<=»,больше или равно «>=»), которые выдают значения логического типа(см.