Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

C# для чайников

.pdf
Скачиваний:
183
Добавлен:
27.03.2015
Размер:
15.52 Mб
Скачать

Числа с плавающей точкой в С# по умолчанию имеют точность 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. Обратите внимание, что в программировании для обозна­ чения умножения используется звездочка (*), а не крестик (х).

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]