Четверг, 28.03.2024, 15:14
Приветствую Вас Гость | RSS

Лекции

Меню сайта
Форма входа
Категории раздела
ТАУ (Теория автоматического управления) [31]
лекции по ТАУ
Экология [151]
учебник
Бухгалтерский учет и налогообложение в строительстве [56]
Дементьев А.Ю. - Практическое пособие
Психология [104]
Пип
информатика [80]
с# Карли Ватсон
современные стулья [0]
новинки
Поиск

Главная » 2010 » Февраль » 11 » Дополнительные сведения о переменных
00:14
Дополнительные сведения о переменных
Дополнительные сведения
о переменных
Теперь, когда вы больше узнали о языке С#, самое время вернуться назад
и рассмотреть наиболее сложные темы, касающиеся переменных.
Первой темой, к обсуждению которой мы приступим в этой главе, является
преобразование типов, которое описывает преобразование значений одного типа
в значения другого типа. Мы уже несколько раз сталкивались с примерами таких
преобразований, однако теперь рассмотрим эту тему с формальной точки зрения.
Усвоение этой информации поможет намного лучше понять, что происходит при
использовании в одном выражении различных типов переменных (преднамеренно
или случайно), и более точно управлять манипуляциями над данными. Это позво-
лит не отвлекаться при написании кода и избежать неприятных сюрпризов.
Вы познакомитесь с новыми типами переменных:
• Перечислимый тип — тип переменных, которые принимают
дискретные значения из множества, определяемого пользователем,
и которые могут нести дополнительную смысловую нагрузку.
• Структуры — сложные типы переменных, которые создаются
из определяемого пользователем набора других типов переменных.
• Массивы — это тип, который содержит большое количество
переменных одного и того же типа и который позволяет осуществлять
индексный доступ к отдельным значениям.
Эти типы оказываются несколько сложнее, чем те, с которыми вы знакомились
до сих пор, однако они могут существенно упростить нам жизнь.
После того как мы завершим изучение этих тем, мы рассмотрим еще один по-
лезный аспект — манипуляции над строками.
Преобразование типов
В начале книге мы уже обсуждали тот факт, что любые данные, независимо от
своего типа, представляют собой последовательность двоичных битов, т. е. после-
довательность, состоящую из нулей и единиц. Собственно значение переменной
зависит от того, каким образом эти биты интерпретируются. Простейшим приме-
ром такого подхода может служить тип char, который содержит номер символа из
множества символов Unicode. Этот номер хранится абсолютно так же, как и число
типа ushort: в обоих случаях это число, лежащее в диапазоне от 0 до 65 535.
78 Глава 5
Однако в большинстве случаев оказывается, что дая различных типов перемен-
ных используются разные способы представления данных. Отсюда следует, что даже
в тех ситуациях, когда имеется физическая возможность перенести последователь-
ность битов из одной переменной в переменную какого-либо другого типа (напри-
мер, обеим переменным для хранения требуется одинаковая область памяти, или
у той переменной, в которую осуществляется запись, оказывается достаточно мес-
та для размещения всех исходных битов), результаты могут совершенно не совпа-
дать с теми, которые вы ожидали!
Вместо такой переписи битов из одной переменной в другую приходится исполь-
зовать преобразования типов данных.
Существует два вида преобразования типов:
• Неявное преобразование — когда преобразование типа А в тип В
возможно при любых обстоятельствах, а правила проведения такого
преобразования достаточно просты, чтобы доверить его компилятору.
• Явное преобразование — когда преобразование типа А в тип В
возможно только при определенных обстоятельствах либо
правила преобразования настолько сложны, что возникает
необходимость выполнения каких-либо дополнительных действий.
Мы последовательно рассмотрим оба типа.
Неявные преобразования
Неявное преобразование не требует со стороны программиста никакой допол-
нительной работы и никакого дополнительного кода. Рассмотрим пример кода:
varl - var2;
Такое присваивание может включать в себя проведение неявного преобразова-
ния типов в том случае, если тип переменной var2 допускает проведение неявного
преобразования в тип переменной varl; однако с тем же успехом в этом присваи-
вании могут быть задействованы две переменные одного и того же типа, и тогда
никакого неявного преобразования типов вообще не требуется.
Давайте рассмотрим пример.
Значения типа ushort и char являются фактически взаимозаменяемыми, посколь-
ку оба они предполагают хранение чисел в диапазоне между 0 и 65 535. Можно
осуществлять преобразования этих типов друг в друга неявно, что иллюстрируется
следующим кодом:
ushort destinationVar;
char sourceVar = ' a ' ;
destinationVar = sourceVar;
Console.WriteLine("sourceVar val: {0}*, sourceVar);
Console.WriteLine("destinationVar val: {0}" , destinationVar);
В этом коде значение, хранящееся в переменной sourceVar, переносится в пе-
ременную destinationVar. Затем две команды Console.WriteLine о дают следую-
щий выходной поток:
sourceVar val: a
destinationVar val: 97
Хотя в обеих переменных хранится одна и та же информация, она интерпрети-
руется совершенно по-разному в зависимости от типа переменной.
Тип
byte
sbyte
short
ushort
int
uint
long
ulong
float
char
Допускает безопасное преобразование в
short, ushort, int, uint, long, ulong,
float, double, decimal
short, int, long, float, double, decimal
int, long, float, double, decimal
int, uint, long, ulong, float, double,
decimal
long, float, double, decimal
long, ulong, float, double, decimal
float, double, decimal
float, double, decimal
double
ushort, int, uint, long, ulong, float,
double, decimal
Дополнительные сведения о переменных 79
Для простых типов существует большое
количество неявных преобразований; для ти-
пов bool и string неявных преобразований
не существует, зато они имеются у числен-
ных типов. В таблице слева, приведенной
для справки, показаны преобразования чис-
ленных типов, которые могут выполняться
компилятором неявно (не забывайте, что
значения типа char хранятся в виде чисел,
поэтому мы рассматриваем char как числен-
ный тип).
Не волнуйтесь: эту таблицу не придется
заучивать наизусть, поскольку существует
довольно простой способ определять, какие
именно преобразования компилятор может
выполнять неявно. Если вернуться к главе 3,
то там можно найти диапазоны допустимых
значений для каждого простого численного типа. Правило неявных преобразова-
ний для рассматриваемых типов можно сформулировать следующим образом: лю-
бой тип А, диапазон допустимых значений которого целиком находится внутри
диапазона допустимых значений типа В, может быть преобразован неявным обра-
зом в этот тип.
Это правило имеет очень простое обоснование. Если вы попытаетесь присвоить
переменной некоторое значение, а это значение окажется выходящим за пределы
тех значений, которые она может принимать, то возникнут определенные пробле-
мы. Так, например, в переменной типа short могут храниться значения до 32 767,
а максимально допустимое значение типа byte ограничено 255, поэтому если мы
попытаемся преобразовать значение типа short в значение типа byte, это может
создать проблему. Если значение типа short лежит в диапазоне между 256 и 32 767,
то оно просто не уместится в одном байте.
Однако в том случае, если вам точно известно, что значение типа short
меньше 255, должна ведь существовать какая-то возможность выполнить такое
преобразование, не правда ли?
Простой ответ на этот вопрос гласит: да, такая возможность существует. Не-
много более развернутый ответ таков: да, такая возможность существует, но для
этого необходимо воспользоваться явным преобразованием. При выполнении яв-
ного преобразования вы как бы говорите: "Хорошо, я предупрежден об опасности
подобных действий, и я принимаю всю ответственность за возможные последствия
на себя".
Явные преобразования
Как и предполагает их название, явные преобразования выполняются в тех
случаях, когда мы явным образом обращаемся к компилятору с просьбой преобра-
зовать значение одного типа в другой. Именно поэтому для их выполнения требу-
ется наличие дополнительного кода, причем формат этого кода может варьироваться
в зависимости от конкретного метода выполнения преобразования. Прежде чем
приступить к рассмотрению кода, применяемого для выполнения явных преобразо-
ваний, давайте узнаем, что произойдет в том случае, если для явного преобразова-
ния не будет использоваться никакого кода.
SO Глава 5
В качестве примера, несколько модифицировав код из предыдущего раздела,
попытаемся преобразовать значение типа short в значение типа byte:
byte destinationVar;
short sourceVar = 7; *
destinationVar = sourceVar;
Console.WriteLine('sourceVar val: {0}", sourceVar);
Console.WriteLine("destinationVar val: {0}", destinationVar);
При попытке выполнить компиляцию этого кода будет выведено следующее со-
общение об ошибке:
Cannot implicitly convert type 'short' to 'byte'
(Невозможно неявно преобразовать тип 'short' в 'byte')
К счастью для нас, компилятор С# умеет обнаруживать отсутствие необходи-
мых явных преобразований!
Для того чтобы такой код можно было откомпилировать, нужно добавить код,
который выполнял бы это преобразование явно. Наиболее простой способ добить-
ся этого в данном контексте — это привести переменную типа short к переменной
типа byte. Приведение типа, как правило, означает принудительный перевод дан-
ных из одного типа в другой и имеет следующий синтаксис:
{destinationType) sourceVar
Эта запись приведет к преобразованию значения переменной sourceVar к типу
destinationType.
Обратите внимание, что такое возможно только
в определенных ситуациях. Для типов, мало похожих
друг на друга или вообще не имеющих ничего общего,
преобразование типов с помощью приведения,
вероятнее всего, не будет определено.
Теперь можно модифицировать наш пример таким образом, чтобы добиться
преобразования из типа short в тип byte:
byte destinationVar;
short sourceVar = 7;
destinationVar = (byte)sourceVar;
Console.WriteLine(*sourceVar val: {О}", sourceVar);
Console.WriteLine("destinationVar val: {0}", destinationVar);
Результатом будет:
sourceVar val: 7
destinationVar val: 7
А что происходит в тех случаях, когда мы пытаемся загнать в переменную зна-
чение, которое там не помещается? Давайте модифицируем нашу программу:
byte destinationVar;
short sourceVar = 281;
destinationVar = (byte)sourceVar;
Console.WriteLine(''sourceVar val: {0}*, sourceVar);
Console.WriteLine(*destinationVar val: {0}*, destinationVar);
В этом случае мы получим следующий результат:
sourceVar val: 281
destinationVar val: 25
Дополнительные сведения о переменных 81
Итак, что же произошло? Посмотрим на двоичное представление этих двух чи-
сел, а также максимально допустимого значения для типа byte, равного 255:
281 = 100011001
25 = 000011001
255 = 011111111
Легко заметить, что потерян самый левый бит исходного значения. Немедленно
возникает вопрос: каким образом это можно предсказать? Совершенно очевидно,
что будут встречаться ситуации, когда придется явно приводить один тип к друго-
му, и было бы неплохо знать заранее, какие данные при этом будут утрачиваться.
Не умея этого делать, можно столкнуться с серьезными ошибками, например,
в приложениях для работы с банковскими счетами.
Один из способов предвидеть потерю битов — проверка исходного значения
и его сравнение с известными предельными значениями новой переменной. Каким
образом это можно выполнить, будет рассказано в следующей главе при объясне-
нии логики работы приложений и управления порядком выполнения. Но имеется
и другой способ, который заставляет систему во время работы уделять особое вни-
мание данному преобразованию. Попытка присвоить переменной слишком большое
значение приводит к переполнению, и это как раз та ситуация, которую требуется
контролировать.
Контекст проверки на переполнение задается с помощью двух ключевых слов:
checked (проверяемое) и unchecked (непроверяемое), которые используются следу-
ющим образом:
checked(выражение)
unchecked(выражение)
Итак, давайте потребуем осуществить проверку на переполнение в предыдущем
примере:
byte destinationVar;
short sourceVar = 281; .„„,.,.....
destinationVar = checked((byte)sourceVar);
Console.WriteLineCsourceVar val: {0}*, sourceVar);
Console.WriteLine("destinationVar val: {0}", destinationVar);
Процесс выполнения кода будет
прерван появлением сообщения об
Anunhar jrredin ошибке (см. рис. слева) (этот пример
откомпилирован в проекте с названием
OverflowCheck).
Однако в том случае, если заменить
КЛЮЧевое СЛОВО checked на unchecked,
получится тот же результат и никакой
ошибки не возникнет. Такой вариант
идентичен поведению по умолчанию,
с которым мы познакомились выше.
Наряду с этими ключевыми словами имеется возможность поменять конфигу-
рацию приложения, чтобы оно работало так, как если бы в любом выражении
данного типа использовалось ключевое слово checked, кроме тех выражений, в ко-
торых явно использовано ключевое слово unchecked (другими словами, мы можем
менять установки, которые используются по умолчанию для выполнения проверок
на переполнение). Для этого потребуется изменить свойства нашего приложения
в VS. Щелкните правой кнопкой мыши по названию проекта в окне Solution Explorer
82 Глава 5
ZffWwyps
и выберите опцию Properties.
Откройте папку Configuration
Properties в левой части окна;
появится список из трех пунктов:
Build, Debugging и Advanced.
Свойство, которое нам требует-
ся изменить, является одной из
установок компилятора, поэтому
необходимо выбрать пункт Build.
В расположенную вверху группу
свойств (Code Generation) вхо-
дит опция под названием Check
for Arithmetic Overflow/Underflow
(см. рис. слева). По умолчанию
эта установка имеет значение
false, однако при замене ее на
true приложение будет работать
в режиме checked, подробно
описанном выше.
Выполнение явных преобразований
с помощью команд преобразования
Способ явного преобразования, использовавшийся во многих предыдущих при-
мерах, несколько отличается от того, с чем мы столкнулись в настоящей главе.
Прежде мы осуществляли преобразования строковых значений в числа посред-
ством таких команд, как convert.ToDoubieо, которые совершенно очевидно не
смогут работать с любой строкой.
Если, например, попытаться преоб-
разовать строку "Number" в число типа
double С ПОМОЩЬЮ Convert.ToDoubleO,
то в процессе выполнения кода будет
открыто диалоговое окно с сообщением:
"Необрабатываемая исключительная
ситуация типа 'System.FormatException'
в mscorlib.dll. Дополнительная информа-
ция: Входная строка имеет неверный Conttnue
формат." (см. рис. справа).
Как видите, операция не выполнена. Для того чтобы подобный тип преобразо-
вания мог осуществиться, передаваемая строка должна быть допустимым пред-
ставлением числа и это число не должно приводить к переполнению. Допустимым
называется такое представление числа, которое содержит необязательный знак
(плюс или минус), ноль или несколько цифр, необязательную десятичную точку,
за которой следует одна или несколько цифр, а также необязательный символ "е"
или "Е", за которым следуют необязательный знак и одна или несколько цифр,
и ничего более, кроме пробелов (располагающихся перед или после этой после-
довательности). Используя все эти дополнительные опции, мы можем распозна-
вать строки типа -1.245е-24 как число.
[Microsoft Visual Studio Debugger
/f\ An unhandled exception of type 'System.FormatException' occurred in
ьЦяДщ$ vmseorJib.dlf -
Дополнительные сведения о переменных 83
Команда Результат
Convert.ToBoolean(val)
Convert.ToByte(val)
Convert.ToChar(val)
Convert.ToDecimal(val)
Convert.ToDouble(val)
Convert.Tolntl6(val)
Convert.Tolnt32(val)
Convert.Tolnt б 4(val)
Convert.ToSByte(val)
Convert.ToSingle(val)
Convert.ToString(val)
Convert.ToUIntl6(val)
Convert.ToUInt3 2(val)
Convert.ToUInt64(val)
val преобразовано в bool
val преобразовано в byte
val преобразовано в char
val преобразовано в decimal
val преобразовано в double
val преобразовано в short
val преобразовано в int
val преобразовано в long
val преобразовано в sbyte
val преобразовано в float
val преобразовано в string
val преобразовано в ushort
val преобразовано в uint
val преобразовано в ulong
Существует много явных преобразо-
ваний, которые могут быть определены
таким же образом (см. табл. слева).
Здесь val может быть переменной
почти любого типа (а если она не может
быть обработана данными командами,
то компилятор выдаст соответствующее
сообщение).
К сожалению, как можно заметить из
приведенной таблицы, имена команд
преобразования несколько отличаются
от названий типов, принятых в С#; на-
пример, для того чтобы произвести пре-
образование в тип int, следует исполь-
зовать команду Convert.Tolnt32 (). Это
объясняется тем, что имена в командах
ВЗЯТЫ ИЗ Пространства Имен System
.NET Framework и не являются родными
для С#. Зато благодаря этому их можно
использовать из любых других языков,
совместимых с .NET.
Необходимо отметить один важный момент: в процессе таких преобразований
всегда осуществляется контроль за переполнением, при этом ключевые слова
checked и unchecked, а также установки, заданные для всего проекта, влияния не
имеют.
Рассмотрим пример, который охватывает многие типы преобразований, описан-
ные в этом разделе.
Практикум: использование преобразований типов
1. Создайте новое консольное приложение с именем ch05Ex0i
В директории C:\BegCSharp\Chapter5.
2. Добавьте следующий код в ciassi.cs:
static void Main (string [] args)
short shortResult, shortVal = 4;
int integerVal =67;
long longResult;
float floatVal - 10.5F;
string stringResult, stringVal = "17"; ' ^
bool boolVal = true;
Console.WriteLine("Variable Conversion Examples\n");
doubleResult - floatVal * shortVal;
Console.WriteLine("Implicit, -> double: {0} * {1} -> {2}", floatVal,
shortVal, doubleResult);
shortResult = (short)floatVal;
Console.WriteLine("Explicit, -> short: {0} -> {1}", floatVal,
shortResult);
84 Глава 5
stringResuit = Convert.ToString(boolVal) +
Convert.ToString(doubleVal);
Console. WriteLine( "Explicit, -> string: \"{0}\" + \"{1}\" ->
boolVal, doubleVal, stringResuit);
longResult = integerVal + Convert.Tolnt64(stringVal);
Console.WriteLine( "Mixed, -> long: {0} + {1} -> {2)",
integerVal, stringVal, longResult);
3. Запустите программу:
U&rdable Conversion Examples
Inplpliciti. ~> double: 18,5 * 4 -> 42
Explicit,,
Explicit,
Hixed.
Press aos?
->. long:
h
hort: 10*5 -> IB
tring: "True" * "99.999" -> True99.999
ong: 6? • 1 7 » 84
to cootiene
/Ca/c это работает
В этом примере содержатся все типы преобразований, с которыми вы успели
познакомиться. Они выполняются как при обычном присваивании (это отражено
в приведенных выше коротеньких примерах), так и в выражениях. Нам нужно
рассмотреть оба случая, поскольку выполнение любого не унарного оператора —
а не только оператора присваивания — может привести к преобразованию типов.
Например:
shortVal * floatVal
Здесь перемножаются значения типа short и типа float. В подобных ситуаци-
ях, когда выполнение преобразования явно не указано, если это возможно, будет
выполняться неявное преобразование. В данном примере единственным преобра-
зованием, имеющим смысл, является преобразование short в значение типа float
(поскольку преобразование из float в short должно задаваться явно), поэтому
именно оно и будет использовано.
Однако при желании мы можем переопределить такое поведение:
shortVal * (short)floatVal
Это вовсе не означает, что при выполнении данной операции
будет получено значение типа short. Поскольку результатом
перемножения двух значений типа short с большой вероятностью
может оказаться число, превосходящее 32 767 (максимально
допустимое значение, которое может содержаться в типе short),
то в действительности эта операция возвращает значение
типа int.
Явные преобразования, определяемые с помощью этого синтаксиса изменения
типов, имеют такое же старшинство, как и другие унарные операторы (например,
оператор + + , используемый в качестве префикса): их старшинство максимально.
Дополнительные сведения о переменных #5
Когда встречается выражение, в котором используются смешанные типы, то
преобразования осуществляются по мере выполнения отдельных операторов в со-
ответствии с их старшинством. Это означает, что могут происходить некоторые
"промежуточные" преобразования. Например:
doubleResult = floatVal + (shortVal * floatVal);
Первый оператор, который должен выполняться в данном случае,— это опера-
тор *, который, как только что было сказано, приведет к преобразованию значения
shortval к типу float. Затем будет выполняться оператор 4-, которому не потре-
буется производить никаких преобразований, поскольку он будет работать с двумя
значениями типа float (floatval и значением типа float, получившимся в резуль-
тате выполнения операции shortval * floatVal). Наконец, значение типа float
будет преобразовано в значение типа double при выполнении оператора =.
На первый взгляд весь этот процесс преобразования типов может показаться
сложным, однако при разбивке выражения на составные части с учетом порядка
выполнения операций все становится совершенно понятно.





















Категория: информатика | Просмотров: 1246 | Добавил: basic | Рейтинг: 0.0/0
Всего комментариев: 0
Имя *:
Email *:
Код *:
Календарь
«  Февраль 2010  »
ПнВтСрЧтПтСбВс
1234567
891011121314
15161718192021
22232425262728
Статистика

Онлайн всего: 1
Гостей: 1
Пользователей: 0

krutoto.ucoz.ru
Бесплатный конструктор сайтов - uCoz