C# для чайников
.pdfЧисла с плавающей точкой в С# по умолчанию имеют точность double, так что применяйте везде тип double, если только у вас нет веских причин по ступить иначе. Однако используете ли вы d o u b l e или f l o a t — все равно ваша программа будет считаться программой, работающей с числами с пла вающей точкой.
Более точное преобразование т е м п е р а т у р
Вот как выглядит формула преобразования температур в градусах Фаренгейта в гра дусы Цельсия при использовании переменных с плавающей точкой:
dCelsius = (dFahr - 3 2 . 0 ) * ( 5 . 0 / 9 . 0 ) ;
На прилагаемом к книге компакт-диске имеется double-версия программы преобразования температур ConvertTemperatureWithFloat .
Приведенный далее пример показывает результат работы программы ConvertTem peratureWithFloat:
Введите температуру в градуса х |
Фаренгейта: 100 |
||
Температура |
в |
градусах Цельсия |
равна: 3 7 . 7 7 7 7 7 7 7 7 7 7 7 7 8 |
Press Enter |
to |
t e r m i n a t e . . . |
|
Ограничения переменных с п л а в а ю щ е й т о ч к о й
Вы можете захотеть использовать переменные с плавающей точкой везде и всегда, раз уж они так хорошо решают проблему усечения. Да, конечно, они используют немно го больше памяти, но ведь сегодня это не проблема? Но дело в том, что у чисел с пла вающей точкой имеется ряд ограничений.
Перечисление
Нельзя использовать числа с плавающей точкой для перечисления. Некоторые струк туры С# должны быть подсчитаны (1, 2, 3 и т.д.). И всем известно, что числа 1.0, 2.0, 3.0 можно применять для подсчета количества точно так же, как и 1, 2, 3, но С# ведь этого не знает. Например, при указанной выше точности чисел с плавающей точкой откуда С# знать, что вы не сказали в действительности 1.000001?
Находите ли вы эту аргументацию убедительной или нет — но вы не можете исполь зовать числа с плавающей точкой для подсчета количества.
Сравнение чисел
Вы должны быть очень осторожны при сравнении чисел с плавающей точкой. На пример, 12.5 может быть представлено как 12.500001. Большинство людей не волнуют такие мелкие добавки в конце числа, но компьютер понимает их буквально, и для С# 12.500000 и 12.500001 — это разные числа.
Так, если вы сложите 1.1 и 1.1, вы можете получить в качестве результата 2.2 или 2.200001. И если вы спросите: "Равно ли значение dDouv l e v a r i able. 2.2?", то можете получить совсем не тот ответ, который ожидаете. Такие вопросы вы должны переформу лировать, например, так: "Отличается ли абсолютное значение разности dDouvleVari able и 2.2 менее чем на 0.000001?", другими словами, равны ли два значения с некото рой допустимой ошибкой.
Глава 3. Объявление переменных-значений |
63 |
Процессоры Pentium используют небольшой трюк, который несколько снижает уш занную неприятность,— при работе они применяют специальный формаа в котором для числа с плавающей точкой выделяется 80 бит. При округлении тако| го числа к 64-битовому почти всегда получается результат, который вы ожидаете.
Скорость вычислений
Процессоры х86, использующиеся на старых компьютерах под управление]! Windows, выполняли действия над целыми числами существенно быстрее, чем над чис| лами с плавающей точкой. В настоящее время эта проблема так остро не стоит.
Отношение скоростей работы процессора Pentium III при простом (пожалуй, слиш] ком простом) тесте, состоящем в 300000000 сложений и вычитаний целых чисел и чисем с плавающей точкой, оказалось равным примерно 3 к 1. То есть вместо одного сложешя d o u b l e можно сделать три сложения i n t . (Вычисления с применением умножения и делений могут привести к другим результатам.)
Ограниченность диапазона
В прошлом переменные с плавающей точкой могли представлять значительно боль ший диапазон чисел, чем целые. Сейчас диапазон представления целых чисел существ венно вырос — стоит вспомнить о типе l o n g .
Даже простой тип f l o a t способен хранить очень большие числа, но числа значащих цифр у него ограничено примерно шестью. Например, 12345678 9F означает то же, что и 123456000F . (О том, что такое F в приведенных заган сях, вы узнаете немного позже.)
Как уже объяснялось в предыдущих разделах, и целые, и десятичные числа имеют свои недостатки. Переменным с плавающей точкой присущи проблемы, связанные с во просами округления из-за недостаточной точности представления, целые переменные не могут представлять числа с дробной частью. Бывают ситуации, когда совершенно необ ходимо иметь возможность получить лучшее из обоих миров, а именно числа, которые:
•S подобно числам с плавающей точкой, способны иметь дробную часть;
Sподобно целым числам, должны представлять точные результаты вычислений — т.е. 12.5 должно быть равно 12.5, и ни в коем случае не 12.500001.
К счастью, в С# есть такой тип чисел, называющийся d e c i m a l . Переменная типа d e c i m a l в состоянии представлять числа от 10"28 до 1028 — вполне достаточный диапа зон значений! И все это делается без проблем, связанных с округлением.
О б ъ я в л е н и е п е р е м е н н ы х т и п а decimal
Переменные типа d e c i m a l объявляются и используются так же, как и переменные других типов:
64 |
Часть II. Основы программирования в С# |
decimal |
ml; |
// |
Хорошо |
decimal m2 = 100 ; |
// |
Лучше |
|
decimal |
m3 = 100M; |
// |
Идеально |
Объявление ml выделяет память для переменной ml без ее инициализации. Пока вы не присвоите значение этой переменной, оно будет неопределенным. В этом нет особой проблемы, так как С# не позволит вам использовать переменную без начального при своения ей какого-либо значения.
Второе объявление создает переменную т2 и инициализирует ее значением 100. В этой ситуации неприятным моментом оказывается то, что 100 имеет тип i n t . Поэтому С# вы нужден конвертировать i n t в d e c i m a l перед инициализацией. К счастью, С# понимает, чего именно вы добиваетесь, и выполняет эту инициализацию для вас.
Лучше всего использовать такое объявление, как объявление переменной тЗ с кон стантой 10ОМ типа decimal . Буква М в конце числа указывает, что данная константа имеет тип decimal, так что никакого преобразования не требуется.
Сравнение д е с я т и ч н ы х , ц е л ы х ч и с е л и чисел с плавающей т о ч к о й
Создается впечатление, что числа типа d e c i m a l имеют лишь одни достоинства и ли шены недостатков, присущих типам i n t и double . Переменные этого типа обладают широким диапазоном представления и не имеют проблем округления.
Однако у чисел decimal есть свои неприятности. Во-первых, поскольку они имеют дробную часть, они не могут использоваться в качестве счетчиков, например, в циклах, о ко торых пойдет речь в главе 5, "Управление потоком выполнения".
Вторая проблема не менее серьезна, и заключается в том, что вычисления с этим ти пом чисел гораздо медленнее, чем с простыми целыми числами или даже с числами с плавающей точкой. В уже упоминавшемся тесте с 300000000 сложений и вычитаний работа с числами d e c i m a l примерно в 50 раз медленнее работы с числами i n t . Это от ношение становится еще хуже для более сложных операций. Кроме того, большинство математических функций, таких как синус или возведение в степень, не имеют версий для работы с числами decimal .
Понятно, что числа типа d e c i m a l наиболее подходят для финансовых приложений, где исключительно важна точность, но само количество вычислений относительно невелико.
Логичен ли логический tnun?
И наконец, о переменных логического типа. Тип b o o l имеет только два значения — true и false . Это не шутка — целый тип переменных придуман для работы только с двумя значениями.
Ранее программисты на С и С++ использовали нулевое значение переменной типа i n t для обозначения f a l s e и ненулевое — для обозначения t r u e . В С# этот фокус не проходит.
Переменная типа b o o l объявляется следующим образом: bool thisIsABool = t r u e ;
Нет никаких путей для преобразования переменных b o o l в другой тип переменных (даже если бы вы могли это делать, это бы не имело никакого смысла). В частности, вы
Глава 3. Объявление переменных-значений |
65 |
не можете |
преобразовать b o o l |
в s t r i n g |
(чтобы f a l s e стало |
в i n t (чтобы, скажем, f a l s e превратилось в 0) или " f a l s e " ) .
Программа, которая в состоянии заниматься только вычислениями, могла бы устроить разве что математиков, страховых агентов и военных (да-да— первые вычислительные машины были созданы для расчета таблиц артиллерийских стрельб). Однако в большинст ве приложений программы должны работать не только с цифрами, но и с буквами.
С# рассматривает буквы двумя различными путями — как отдельные символы типа c h a r и как строки символов типа s t r i n g .
Т и п char
Переменная типа c h a r способна хранить только один символ. Символьная константа выглядит как символ, окруженный парой одинарных кавычек:
c h a r с = ' а' ;
Вы можете хранить любой символ из латинского алфавита, кириллицы, арабского, иврита, японских катаканы и хираганы и массы японских, китайских или корейских ие роглифов.
Кроме того, тип c h a r может использоваться в качестве счетчика, т.е. его можно применять в циклах, о которых вы узнаете в главе 5, "Управление потоком выполнения". У символов нет никаких проблем, связанных с округлением.
Переменные типа c h a r не включают информации о шрифтах, так что в пере менной c h a r может храниться, например, вполне корректный иероглиф, но при выводе его без использования соответствующего шрифта вы увидите на экране только мусор.
С п е ц и а л ь н ы е с и м в о л ы
Некоторые символы являются непечатными в том смысле, что вы ничего не увидите при выводе их на экран или на принтер. Наиболее очевидным примером такого символа является пробел ' 1 (кавычка, пробел, кавычка). Другие символы не имеют буквенного эквивалента — например, символ табуляции. Для указания таких символов С# использу ет обратную косую черту, как показано в табл. 3.3.
66 |
Часть II. Основы программирования в С# |
Тип string
Еще одним распространенным типом переменных является s t r i n g . Приведенные да лее примеры показывают, как объявляются и инициализируются переменные этого типа.
// Объявление с отложенной инициализацией string s o m e S t r i n g l ;
someStringl = "Это строка" ;
// Инициализация при объявлении string someString 2 = "Это строка" ;
Константа типа s t r i n g , именуемая также строковым литералом, представляет собой набор символов, окруженный двойными кавычками. Символы в строке могут включать специальные символы, показанные в табл. 3.3. Строка не может быть перенесена на но вую строку в исходном тексте на С#, но может содержать символ новой строки, как по казано в следующем примере:
// Неверная запись строки
string someStrin g = "Это строка
иэто строка" ;
//А вот так - верно
string someStrin g = "Это строка\пи эт о строка" ;
При выводе на экран при помощи вызова C o n s o l e . W r i t e L i n e вы увидите текст, размещенный в двух строках:
Это строка и это строка
Строка не является ни перечислимым типом, ни типом-значением — в процессоре не существует встроенного типа строки. К строкам применим только один распространенный оператор — оператор сложения, который просто объединяет две строки в одну, например: string s = "Это предложение . " + " И эт о тоже. " ;
Приведенный код присваивает строке s значение: "Это предложение . И эт о тоже ."
Строка без символов, записанная как "" (пара двойных кавычек), является корректной строкой для типа s t r i n g , и называется пустой строкой. Пустая строка отличается от нулевого символа ' \ 0 ' и от строки, содержащей любое количество пробелов (" ") .
Кстати, все остальные типы данных в этой главе — типы-значения (value types). Строковый тип типом-значением не является.
Все инструкции С# должны быть реализованы как машинные команды процессо р а — процессора Intel в случае PC. Эти процессоры также имеют собственную концепцию переменных. Например, процессор Intel содержит восемь внутренних хранилищ, именуемых регистрами, каждый из которых может хранить одно зна чение i n t . Не вдаваясь в детали функционирования процессора, можно сказать, что все типы, описываемые в данной главе, за исключением decima l и s t r i n g , являются встроенными для процессора. Таким образом, существует машинная
Глава 3. Объявление переменных-значений |
67 |
команда, суть которой в следующем: прибавить один i n t к другому. Имеется I аналогичная команда и для сложения d o u b l e .
Кроме того, переменные описанных типов (опять же, за исключением s t r i n g ) I имеют фиксированную длину. Переменные типа с фиксированной длиной всегда за-1 нимают одно и то же количество памяти. Так что при присваивании а = Ь С# может! поместить значение b в а без каких-либо дополнительных мер, разработанных для I типов переменной длины. Эта характеристика дает этим типам переменных имя ти-1
пы-значения.
Типы i n t , d o u b l e , b o o l и их "близкие родственники" наподобие беззнаково-1 го i n t являются встроенными типами. Встроенные типы переменных и тип I d e c i m a l известны также как типы-значения. Тип s t r i n g не относится ни I к тем ни к другим.
Типы, о которых речь пойдет в главе 6, "Объединение данных — классы и массивы", определяемые программистом и известные как ссылки, не являются ни встроенными, ни типами-значениями. Тип s t r i n g является ссылочным типом, хотя компилятор С# рас сматривает его специальным образом в силу его широкой распространенности.
Сравнение string и char
Хотя строки имеют дело с символами, тип s t r i n g существенно отличается от типа c h a r . Понятно, что имеются некоторые тривиальные отличия. Так, символ помещается в одинарные кавычки, а строка — в двойные. Кроме того, тип c h a r — это всегда один символ, так что следующий код не имеет смысла — ни в плане сложения, ни в плане
конкатенации: |
|
|
c h a r c l = |
' а ' ; |
|
c h a r |
с 2 = |
' Ь 1 ; |
c h a r |
с З = c l + с 2 ; |
На самом деле этот код почти компилируем, но его смысл существенно отли чается от того, который мы ему приписываем. С# преобразует cl и с2 в значе ния типа i n t , представляющие собой числовые значения соответствующих символов, после чего складывает полученные значения. Ошибка возникает при попытке сохранить полученный результат в с З , так как при размещении значе ния типа i n t в переменной меньшего размера c h a r данные могут быть поте ряны. В любом случае, эта операция не имеет смысла.
С другой стороны, строка может быть любой длины. Таким образом, конкатенация двух строк вполне осмысленна:
s t r i n g s i |
= |
" а " ; |
|
s t r i n g s 2 |
= |
" b " ; |
|
s t r i n g s 3 |
= |
s i + s 2 ; |
/ / Р е з у л ь т а т — " a b " |
В качестве части своей библиотеки С# определяет целый ряд строковых операций, которые будут описаны в главе 9, "Работа со строками в С#".
68 |
Часть II. Основы программирования в С# |
Соглашения по именованию
Программирование и так достаточно сложно, чтобы делать его еще сложнее. Чтобы код на С# было легче читать, обычно используются определенные со глашения по именованию переменных, которым желательно следовать, чтобы код был
понятен другим программистам.
Имена всех объектов, кроме переменных, начинаются с прописной буквы, а имена переменных— со строчной. Делайте эти имена как можно более ин формативными (зачастую это приводит к тому, что имена состоят из нескольких слов). Слова должны начинаться с прописной буквы, и лучше, если между ними не будет символов подчеркивания — например, t h i s I s A L o n g V a r i a b l e N a m e .
Первая буква имени переменной указывает ее тип. Большинство таких букв три виальны — f для f l o a t , d для double, s для s t r i n g и так далее. Единственным нарушающим правило символом является п для i n t . Есть еще одно исключение — по традиции, уходящей в программирование на Фортране, отдельные буквы i, j и к также используются как распространенные имена переменных типа i n t .
Венгерская запись постепенно выходит из моды, по крайней мере в кругах программи стов .NET. Тем не менее я все еще остаюсь ее поклонником, поскольку она позволяет
мне знать тип каждой переменной в |
программе, не обращаясь к ее объявлению. |
В последних версиях Visual Studio вы |
можете просто подвести курсор к переменной |
и получить информацию о ее типе в окне подсказки, что делает венгерскую запись менее полезной. Однако вместо того чтобы встревать в "религиозные войны" по поводу того или иного способа именования, выберите тот, который вам по душе, и следуйте ему.
В жизни очень мало абсолюта, но он присутствует в С#: любое выражение имеет зна чение и тип. В объявлении наподобие i n t п легко увидеть, что переменная п имеет тип int. Разумно предположить, что тип результата вычисления п+1 также i n t . Но что можно сказать о типе константы 1?
Тип константы зависит от двух вещей: ее значения и наличия необязательной буквы в конце. Любое целое величиной до примерно 2 миллиардов (см. табл. 3.1) рассматрива ется как i n t . Числа, превышающие это значение, трактуются как l o n g . Любые числа с плавающей точкой рассматриваются как d o u b l e .
В табл. 3.4 показаны константы, объявленные как имеющие конкретные типы, т.е., в частности, с буквенными дескрипторами в конце. Строчные эти буквы или пропис ные—значения не имеет, например записи lu и 1U равноценны.
Глава 3. Объявление переменных-значений |
69 |
Окончание табл. 3.'
Константа Тип
1 . 0
1 . OF 1М
t r u e f a l s e
1а 1
•\ п '
1\ x l 2 3 1
"a s t r i n g "
d o u b l e f l o a t
d e c i m a l
b o o l |
|
b o o l |
|
c h a r |
|
c h a r |
(СИМВОЛ НОВОЙ строки) |
c h a r |
(символ с шестнадцатеричным числовым значением 123) |
s t r i n g
s t r i n g (пустая строка)
Человек не рассматривает числа, используемые для счета, как разнотипные. Напри мер, нормальный человек (не программист на С#) не станет задумываться, глядя на чис ло 1, знаковое оно или беззнаковое, "короткое" или "длинное". Хотя для С# все эти типы различны, даже он понимает, что все они тесно связаны между собой. Например, в при веденном далее фрагменте исходного текста величина типа i n t преобразуется в long:
i n t nValue = 10 ; |
|
|
lon g l v a l u e ; |
|
|
l v a l u e = nValue ; |
// Это присваивание |
корректно |
Переменная типа i n t |
может быть преобразована в long, поскольку любое значение |
|
типа i n t может храниться в переменной типа l o n g |
и оба типа представляют собой чис |
ла, пригодные для перечислений. С# выполняет такое преобразование автоматически, без каких-либо комментариев.
Однако обратное преобразование может вызвать проблемы. Например, приведенный далее фрагмент исходного текста содержит ошибку:
l o n g l v a l u e = 10 ; |
|
i n t nValue ; |
|
nValue = l v a l u e ; |
// Неверно! |
Некоторые значения, которые могут храниться в переменной long, не помещаются в переменной типа i n t (ну, например, 4 миллиарда). С# в такой ситуации генерирует сообщение об ошибке, поскольку в процессе преобразования данные могут быть утеря ны. Ошибку такого рода обычно довольно сложно обнаружить.
Но что, если вы точно знаете, что такое преобразование вполне допустимо? Напри мер, несмотря на то что переменная l v a l u e имеет тип long, в данной конкретной про грамме ее значение не может превышать 10 0. В этом случае преобразование перемен ной l v a l u e типа l o n g в переменную nValue типа i n t совершенно корректно.
Вы можете пояснить С#, что отлично понимаете, что делаете, посредством оператора
приведения |
типов: |
|
l o n g l v a l u e = 10 ; |
|
|
i n t nValue ; |
|
|
nValue = |
( i n t ) l v a l u e ; |
// Все в порядке |
70 |
Часть II. Основы программирования в С# |
Глава 3. С |
При приведении вы размещаете имя требующегося типа в круглых скобках непосред ственно перед преобразуемым значением. Приведенная выше запись гласит: "Не волнуй ся и преобразуй l v a l u e в тип i n t — я знаю, что делаю, и всю ответственность беру на себя". Конечно, такое утверждение может показаться излишне самоуверенным, но зачас- тую оно совершенно справедливо.
Перечислимые числа могут быть преобразованы в числа с плавающей точкой автома тически, но обратное преобразование требует использования оператора приведения ти
пов, например: |
|
double dValue |
= 1 0 . 0 ; |
long lvalue = |
( l o n g ) d V a l u e ; |
Все приведения к типу d e c i m a l и из него нуждаются в применении оператора при ведения типов. В действительности все числовые типы могут быть преобразованы в дру гие числовые типы с помощью такого оператора. Однако ни bool , ни s t r i n g не могут быть непосредственно приведены ни к какому иному типу.
Встроенные функции С# могут преобразовывать числа, символы или логиче ские переменные в их строковые "эквиваленты". Например, вы можете преоб разовать значение t r u e типа b o o l в строку " t r u e " ; однако такое преобразо вание нельзя рассматривать как непосредственное. Эти два значения — совер шенно разные вещи.
Глава 3. Объявление переменных-значений |
71 |
Глава4
Операторы
>Выполнение арифметических действий I Логические операции
>Составные логические операторы
атематики создают переменные и выполняют над ними различные дейст вия, складывая их, умножая, а иногда — представьте себе — даже интегри руя. В главе 3, "Объявление переменных-значений", описано, как объявлять и опреде
лять переменные, но там ничего не говорится о том, как их использовать после объяв ления, чтобы получить что-то полезное. В этой главе рассматриваются операции, ко торые могут быть произведены над переменными. Для выполнения операций требуют ся операторы, такие как +, -, =, < или &. Здесь речь пойдет об арифметических, ^логических и других операторах.
I Все множество арифметических операторов можно разбить на несколько групп: про стые арифметические операторы, операторы присваивания и специальные операторы, присущие только программированию. После такого обзора арифметических операторов нужен обзор логических операторов, но о них будет рассказано несколько позже.
Простейшие о п е р а т о р ы
•С большинством из этих операторов вы должны были познакомиться еще в школе. Они перечислены в табл. 4.1. Обратите внимание, что в программировании для обозна чения умножения используется звездочка (*), а не крестик (х).