Полное руководство. С# 4.0
Шрифт:
Неявно типизированные переменные внедрены в C# не для того, чтобы заменить собой обычные объявления переменных. Напротив, неявно типизированные перемен ные предназначены для особых случаев, и самый примечательный из них имеет отно шение к языку интегрированных запросов (LINQ), подробно рассматриваемому в главе
Таким образом, большинство объявлений переменных должно и впредь оставаться явно типизированными, поскольку они облегчают чтение и понимание исходного тек ста программы.
И последнее замечание: одновременно можно объявить только одну неявно типи зированную переменную. Поэтому объявление var s1 = 4.0, s2 = 5.0; // Ошибка!
является неверным и не может быть скомпилировано. Ведь в нем предпринимается попытка объявить обе переменные, s1 и s2, одновременно. Область действия и время существования переменных
Все переменные, использовавшиеся в предыдущих примерах программ, объяв лялись в самом начале метода Main. Но в C# локальную переменную разрешается объявлять в любом кодовом блоке. Как пояснялось в главе 2, кодовый блок начинает ся открывающей фигурной скобкой и оканчивается
В C# к числу наиболее важных относятся области действия, определяемые классом и методом. Рассмотрение области действия класса (и объявляемых в ней переменных) придется отложить до того момента, когда в этой книге будут описываться классы. А до тех пор будут рассматриваться только те области действия, которые определяют ся методом иди же в самом методе.
Область действия, определяемая методом, начинается открывающей фигурной скобкой и оканчивается закрывающей фигурной скобкой. Но если у этого метода име ются параметры, то и они входят в область действия, определяемую данным методом.
Как правило, локальные переменные объявляются в области действия, невидимой для кода, находящегося вне этой области. Поэтому, объявляя переменную в опреде ленной области действия, вы тем самым защищаете ее от доступа иди видоизменения вне данной области. Разумеется, правила области действия служат основанием для ин капсуляции.
Области действия могут быть вложенными. Например, всякий раз, когда создается кодовый блок, одновременно образуется и новая, вложенная область действия. В этом случае внешняя область действия охватывает внутреннюю область. Это означает, что локальные переменные, объявленные во внешней области действия, будут видимы для кода во внутренней области действия. Но обратное не справедливо: локальные перемен ные, объявленные во внутренней области действия, не будут видимы вне этой области.
Для того чтобы стала более понятной сущность вложенных областей действия, рас смотрим следующий пример программы. // Продемонстрировать область действия кодового блока. using System; class ScopeDemo { static void Main { int x; // Эта переменная доступна для всего кода внутри метода Main. х = 10; if(x == 10) { // начать новую область действия int у = 20; // Эта переменная доступна только в данном кодовом блоке. // Здесь доступны обе переменные, х и у. Console.WriteLine("х и у: " + х + " " + у); х = у * 2; } // у = 100; // Ошибка! Переменна у здесь недоступна. // А переменная х здесь по-прежнему доступна. Console.WriteLine("х равно " + х); } }
Как поясняется в комментариях к приведенной выше программе, переменная х объявляется в начале области действия метода Main, и поэтому она доступна для всего последующего кода в пределах этого метода. В блоке условного оператора if объявляется переменная у. А поскольку этот кодовый блок определяет свою собствен ную область действия, то переменная у видима только для кода в пределах данного блока. Именно поэтому строка line у = 100;, находящаяся за пределами этого блока, закомментирована. Если удалить находящиеся перед ней символы коммента рия (//), то во время компиляции программы произойдет ошибка, поскольку пере менная у невидима за пределами своего кодового блока. В то же время переменная х может использоваться в блоке условного оператора if, поскольку коду из этого блока, находящемуся во вложенной области действия, доступны переменные, объявленные в охватывающей его внешней области действия.
Переменные могут быть объявлены в любом месте кодового блока, но они становят ся действительными только после своего объявления. Так, если объявить переменную в начале метода, то она будет доступна для всего остального кода в пределах этого метода. А если объявить переменную в конце блока, то она окажется, по существу, бес полезной, поскольку не будет доступной ни одному коду.
Если в объявление переменной включается инициализатор, то такая переменная инициализируется повторно при каждом входе в тот блок, в котором она объявлена. Рассмотрим следующий пример программы. // Продемонстрировать время существования переменной. using System; class VarInitDemo { static void Main { int x; for(x = 0; x < 3; x++) { int у = -1; // Переменная у инициализируется при каждом входе в блок. Console.WriteLine("у равно: " + у); // Здесь всегда выводится -1 у = 100; Console.WriteLine("у теперь равно: " + у); } } }
Ниже приведен результат выполнения этой программы. у равно: -1 у теперь равно: 100 у равно: -1 у теперь равно: 100 у равно: -1 у теперь равно: 100
Как видите, переменная у повторно инициализируется одним и тем же значением -1 при каждом входе во внутренний цикл for. И несмотря на то, что после этого цик ла ей присваивается значение 100, оно теряется при повторной ее инициализации.
В языке C# имеется еще одна особенность соблюдения правил области действия: не смотря на то, что блоки могут быть вложены, ни у одной из переменных из внутренней области действия не должно быть такое же имя, как и у переменной из внешней об ласти действия. В приведенном ниже примере программы предпринимается попытка объявить две разные переменные с одним и тем же именем, и поэтому программа не может быть скомпилирована. /* В этой программе предпринимается попытка объявить во внутренней области действия переменную с таким же самым именем, как и у переменной, определенной во внешней области действия. *** Эта программа не может быть скомпилирована. *** */ using System; class NestVar { static void Main { int count; for (count = 0; count < 10; count = count+1) { Console.WriteLine("Это
подсчет: " + count); int count; // Недопустимо!!! for(count = 0; count < 2; count++) Console.WriteLine("В этой программе есть ошибка!"); } } } Если у вас имеется некоторый опыт программирования на С или C++, то вам долж но быть известно, что на присваивание имен переменным, объявляемым во внутренней области действия, в этих языках не существует никаких ограничений. Следовательно, в С и C++ объявление переменной count в кодовом блоке, входящем во внешний цикл for, как в приведенном выше примере, считается вполне допустимым. Но в С и C++ та кое объявление одновременно означает сокрытие внешней переменной. Разработчики C# посчитали, что такого рода сокрытие имен может легко привести к программным ошибкам, и поэтому решили запретить его. ## Преобразование и приведение типов В программировании нередко значения переменных одного типа присваиваются переменным другого типа. Например, в приведенном ниже фрагменте кода целое зна чение типа int присваивается переменной с плавающей точкой типа float.int i; float f; i = 10; f = i; // присвоить целое значение переменной типа float Если в одной операции присваивания смешиваются совместимые типы данных, то значение в правой части оператора присваивания автоматически преобразуется в тип, указанный в левой его части. Поэтому в приведенном выше фрагменте кода значение переменной i сначала преобразуется в тип float, а затем присваивается переменной f. Но вследствие строгого контроля типов далеко не все типы данных в С# оказываются полностью совместимыми, а следовательно, не все преобразова ния типов разрешены в неявном виде. Например, типы bool и int несовместимы. Правда, преобразование несовместимых типов все-таки может быть осуществлено путем приведения. Приведение типов, по существу, означает явное их преобразова ние. В этом разделе рассматривается как автоматическое преобразование, так и при ведение типов. ### Автоматическое преобразование типов Когда данные одного типа присваиваются переменной другого типа, неявное преоб разование типов происходит автоматически при следующих условиях: * оба типа совместимы; * диапазон представления чисел целевого типа шире, чем у исходного типа. * Если оба эти условия удовлетворяются, то происходит расширяющее преобразование. Например, тип int достаточно крупный, чтобы вмещать в себя все действительные значения типа byte, а кроме того, оба типа, int и byte, являются совместимыми цело численными типами, и поэтому для них вполне возможно неявное преобразование. Числовые типы, как целочисленные, так и с плавающей точкой, вполне совместимы друг с другом для выполнения расширяющих преобразований. Так, приведенная ниже программа составлена совершенно правильно, поскольку преобразование типа long в тип double является расширяющим и выполняется автоматически.
// Продемонстрировать неявное преобразование типа long в тип double. using System;
class LtoD { static void Main { long L; double D; L = 100123285L; D = L; Console.WriteLine("L и D: " + L + " " + D); } } Если тип long может быть преобразован в тип double неявно, то обратное пре образование типа double в тип long неявным образом невозможно, поскольку оно не является расширяющим. Следовательно, приведенный ниже вариант предыдущей программы составлен неправильно.
// Эта программа не может быть скомпилирована. using System; class LtoD { static void Main { long L; double D; D = 100123285.0; L = D; // Недопустимо!!! Console.WriteLine("L и D: " + L + " " + D); } } Помимо упомянутых выше ограничений, не допускается неявное взаимное преоб разование типов decimal и float иди double, а также числовых типов и char или bool. Кроме того, типы char и bool несовместимы друг с другом. ### Приведение несовместимых типов Несмотря на всю полезность неявных преобразований типов, они неспособны удо влетворить все потребности в программировании, поскольку допускают лишь расши ряющие преобразования совместимых типов. А во всех остальных случаях приходится обращаться к приведению типов. Приведение — это команда компилятору преобразо вать результат вычисления выражения в указанный тип. А для этого требуется явное преобразование типов. Ниже приведена общая форма приведения типов.
(целевой_тип) выражение Здесь целевой_тип обозначает тот тип, в который желательно преобразовать ука занное выражение. Рассмотрим для примера следующее объявление переменных.
double х, у; Если результат вычисления выражения х/у должен быть типа int, то следует за писать следующее.
(int) (х / у) Несмотря на то что переменные х и у относятся к типу double, результат вычисле ния выражения х/у преобразуется в тип int благодаря приведению. В данном приме ре выражение х/у следует непременно указывать в скобках, иначе приведение к типу int будет распространяться только на переменную х, а не на результат ее деления на переменную у. Приведение типов в данном случае требуется потому, что неявное пре образование типа double в тип int невозможно. Если приведение типов приводит к сужающему преобразованию, то часть информа ции может быть потеряна. Например, в результате приведения типа long к типу int часть информации потеряется, если значение типа long окажется больше диапазона представления чисел для типа int, поскольку старшие разряды этого числового значе ния отбрасываются. Когда же значение с плавающей точкой приводится к целочислен ному, то в результате усечения теряется дробная часть этого числового значения. Так, если присвоить значение 1,23 целочисленной переменной, то в результате в ней оста нется лишь целая часть исходного числа (1), а дробная его часть (0,23) будет потеряна. В следующем примере программы демонстрируется ряд преобразований типов, требующих приведения. В этом примере показан также ряд ситуаций, в которых при ведение типов становится причиной потери данных.