Шаг 20.
Язык программирования C#. Начала
Базовые типы и операторы. Преобразование типов

    На этом шаге мы рассмотрим особенности выполнения такого преобразования.

    Нередко в выражения входят значения разных типов. Скажем, может возникнуть необходимость к значению типа float прибавить значение типа int и полученный результат присвоить переменной типа double. Математически здесь все корректно, поскольку речь идет об арифметической операции с числами, результат этой операции представляется в виде действительного числа, а целые числа являются подмножеством чисел действительных. Но в плане программной реализации подобной операции мы имеем дело со значениями трех разных типов. К счастью, в языке C# есть встроенные механизмы, позволяющие сводить к минимуму неудобства, связанные с использованием в выражениях значений разных типов. В ситуациях, подобных описанной выше, выполняется автоматическое преобразование типов. Обычно речь идет о преобразованиях различных числовых типов. Есть несколько правил выполнения таких преобразований. Итак, допустим, имеются два операнда, которые относятся к разным числовым типам. Алгоритм выполнения преобразований такой:

    Эти правила применяются последовательно. Например, если в выражении один из операндов относится к типу decimal, то выполняется попытка преобразовать второй операнд к тому же типу, и если попытка успешная, то вычисляется результат. Он также будет относиться к типу decimal. До выполнения прочих правил дело не доходит. Но если в выражении нет операндов типа decimal, тогда в игру вступает второе правило: при наличии операнда типа double другой операнд преобразуется к этому же тину, такого же типа будет результат. Далее, если в выражении нет ни операнда типа decimal, ни операнда типа double, то применяется третье правило (для типа float), и так далее.

    В этой схеме стоит обратить внимание на два момента. Во-первых, общий принцип состоит в том, что автоматическое преобразование выполняется к большему (в плане диапазона допустимых значений) типу, так, чтобы потеря значения исключалась - то есть автоматическое преобразование выполняется всегда с расширением типа. Отсюда становится понятно, почему при попытке преобразовать значения типа double и float к значению типа decimal возникает ошибка. Хотя для значений типа decimal выделяется 128 битов (и это больше, чем 64 бита для типа double и 32 бита для типа float), но диапазон допустимых значений для типа decimal меньше, чем диапазоны допустимых значений для типов double и float. Поэтому теоретически преобразуемое значение может быть потеряно, а значит, такое автоматическое преобразование не допускается.


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

    Во-вторых, если в выражении оба операнда целочисленные, то даже если ни один из них не относится к типу int (например, два операнда типа short), то каждый из операндов приводится к типу int, и, соответственно, результат также будет типа int. Это простое обстоятельство имеет серьезные последствия. Допустим, мы командой short а=10 объявили переменную а типа short со значением 10. Если после этого мы попытаемся использовать команду а=а+1, то такая команда будет некорректной и программный код не скомпилируется. Причина в том, что числовой литерал 1, использованный в команде, относится к типу int. Поэтому в выражении а+1 текущее значение переменной а преобразуется к типу int, и результат выражения а+1 также вычисляется как int-значение. Таким образом, получается, что мы пытаемся присвоить значение int-типа переменной типа short. А это запрещено, поскольку может произойти потеря значения.


При вычислении выражения а+1 считывается значение переменной а, и это число преобразуется к типу int. Сама переменная как была типа short, так переменной типа short и остается.

    Решение проблемы может состоять в том, чтобы использовать явное приведение типа. Корректная команда выглядит так: а=(short)(а+1). Здесь идентификатор short в круглых скобках перед выражением (а+1) означает, что после вычисления значения выражения полученный результат (значение тина int) следует преобразовать в значение типа short.


Для значения типа int в памяти выделяется 32 бита. Значение типа short запоминается с помощью 16 битов. Преобразование значения типа int в значение типа short выполняется путем отбрасывания содержимого в старших 16 битах int-значения. Если int - число не очень большое (не выходит за диапазон short-значения), то старшие биты заполнены нулями (для положительного числа), и при их отбрасывании значение потеряно не будет. Но даже если в старших битах не нули, то биты все равно отбрасываются, и значение может быть потеряно. Поэтому преобразование значения типа int в значение типа short - это потенциально опасное преобразование. Такие преобразования автоматически не выполняются. В некотором смысле инструкция (short) свидетельствует о том, что программист понимает риск и берет ответственность на себя.

    Есть еще одно важное обстоятельство: при инициализации переменной short командой short а=10 переменной типа short присваивается литерал 10, который является значением типа int. Хотя формально здесь тоже вроде бы противоречие, но при присваивании целочисленной переменной целочисленного литерала, если последний не выходит за допустимые пределы для переменной данного типа, приведение типов выполняется автоматически.


    Еще один пример: командой byte а=1, b=2, с объявляем три переменные (а, b и с) типа byte. Переменные а и b инициализируются со значениями 1 и 2 соответственно. Но если мы захотим воспользоваться командой с=а+b, то такая команда также будет некорректной. В отличие от предыдущего случая здесь нет литерала типа int. Здесь вычисляется сумма а+b двух переменных типа byte, и результат присваивается (точнее, выполняется попытка присвоить) переменной типа byte. Проблема же в том, что, хотя в выражении а+b оба операнда относятся к типу byte, они автоматически преобразуются к типу int (в соответствии с последним пунктом в списке правил автоматического преобразования типов), и поэтому результатом выражения а+b является значение типа int. Получается, что мы пытаемся присвоить byte-переменной int-значение, а это неправильно. Чтобы исправить ситуацию, необходимо использовать явное приведение типа. Корректная версия команды присваивания выглядит как с=(byte)(a+b).


Если преобразование целых чисел в действительные в случае необходимости выполняется автоматически, то обратное преобразование требуется выполнять в явном виде. Например, если нам нужно преобразовать значение типа double в значение типа int, то перед соответствующим выражением указывается инструкция (int). Преобразование выполняется отбрасыванием дробной части действительного числа. Например, если командой double х=12.6 объявляется переменная х типа double со значением 12.6, а командой int y объявляется переменная y типа int, то в результате выполнения команды у= (int)х переменная у получит значение 12. При этом значение переменной х не меняется.

    Арифметическое выражение может содержать не только числовые, но и символьные операнды. В таких случаях char-операнд автоматически преобразуется к int-типу. Например, выражение 'А'+'В' имеет смысл. Результат такого выражения - целое число 131. Объяснение простое: значения тина char (то есть символы) хранятся в виде целых чисел. Это последовательность из 16 битов. Вопрос только в том, как ее интерпретировать. Для целых чисел такая последовательность интерпретируется как двоичное представление числа. Для символов "преобразование" двоичного кода выполняется в два этапа: сначала по двоичному коду определяется число, а затем но этому числу определяется символ в кодовой таблице. Проще говоря, 16 битов для char-значения - это код символа в кодовой таблице. Если символ нужно преобразовать в целое число, то вместо символа используется его код из кодовой таблицы. У буквы 'А' код 65, а у буквы 'В' код 66, поэтому сумма символов 'А'+'В' дает целочисленное значение 131.

    Преобразование типа char в тип int в случае необходимости выполняется автоматически. Обратное преобразование (из целого числа в тип char) выполняется в явном виде. Например, результатом команды char s=(char)65 является объявление символьной переменной s со значением 'А'. Принцип преобразования целого числа в символ такой: преобразуемое целочисленное значение определяет код символа в кодовой таблице. Так, поскольку у буквы 'А' код 65, то значением выражения (char)65 будет символ 'А'.


Учитывая правила автоматического преобразования типов и способ реализации числовых литералов, можно дать следующую рекомендацию: если нет объективной необходимости поступать иначе, то для работы с целочисленными значениями разумно использовать переменные типа int, а для работы с действительными значениями имеет смысл использовать переменные типа double.

    На следующем шаге мы рассмотрим объявление переменных.




Предыдущий шаг Содержание