Для того чтобы стала более понятной сущность вложенных областей действия, рас смотрим следующий пример программы. // Продемонстрировать область действия кодового блока. 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) будет потеряна. В следующем примере программы демонстрируется ряд преобразований типов, требующих приведения. В этом примере показан также ряд ситуаций, в которых при ведение типов становится причиной потери данных.
// Продемонстрировать приведение типов. using System;
class CastDemo { static void Main() { double x, y; byte b; int i; char ch; uint u; short s; long 1; x = 10.0; у = 3.0; // Приведение типа double к типу int, дробная часть числа теряется. i = (int) (х / у); Console.WriteLine("Целочисленный результат деления х / у: " + i); Console.WriteLine(); // Приведение типа int к типу byte без потери данных, i = 255; b = (byte) i; Console.WriteLine("b после присваивания 255: " + b + " -- без потери данных."); // Приведение типа int к типу byte с потерей данных, i = 257; b = (byte) i; Console.WriteLine("b после присваивания 257: " + b + " -- с потерей данных."); Console.WriteLine(); // Приведение типа uint к типу short без потери данных. u = 32000; s = (short) u; Console.WriteLine("s после присваивания 32000: " + s + " -- без потери данных."); // Приведение типа uint к типу short с потерей данных, u = 64000; s = (short) u; Console.WriteLine("s после присваивания 64000: " + s + " -- с потерей данных."); Console.WriteLine(); // Приведение типа long к типу uint без потери данных. l = 64000; u = (uint) l; Console.WriteLine ("u после присваивания 64000: " + u + " -- без потери данных."); // Приведение типа long к типу uint с потерей данных. l = -12; u = (uint) l; Console.WriteLine("и после присваивания -12: " + u + " -- с потерей данных."); Console.WriteLine(); // Приведение типа int к типу char, b = 88; // код ASCII символа X ch = (char) b; Console.WriteLine("ch после присваивания 88: " + ch); }