C# 4.0: полное руководство - Герберт Шилдт 11 стр.


*/

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. Но вследствие строгого контроля типов далеко не все типы данных в C# оказываются полностью совместимыми, а следовательно, не все преобразования типов разрешены в неявном виде. Например, типы 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 l;

x = 10.0;

y = 3.0;

// Приведение типа double к типу int, дробная часть числа теряется,

i = (int)(x / y) ;

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("и после присваивания 64000: " + u + " -- без потери данных.");

// Приведение типа long к типу uint с потерей данных.

l = -12;

u = (uint)1;

Console.WriteLine("и после присваивания -12: " + u + " - с потерей данных.");

Console.WriteLine();

// Приведение типа int к типу char,

b = 88; // код ASCII символа X

ch = (char) b;

Console.WriteLine("ch после присваивания 88: " + ch);

}

}

Вот какой результат дает выполнение этой программы.

Целочисленный результат деления х / у: 3

b после присваивания 255: 255 -- без потери данных.

b после присваивания 257: 1 - с потерей данных.i

s после присваивания 32000: 32000 -- без потери данных,

s после присваивания 64000: -1536 -- с потерей данных.

u после-присваивания 64000: 64000 - без потери данных,

u после присваивания -12: 4294967284 -- с потерей данных.

ch после присваивания 88: X

Рассмотрим каждую операцию присваивания в представленном выше примере программы по отдельности. Вследствие приведения результата деления х/у к типу int отбрасывается дробная часть числа, а следовательно, теряется часть информации.

Когда переменной b присваивается значение 255, то информация не теряется, поскольку это значение входит в диапазон представления чисел для типа byte. Но когда переменной b присваивается значение 257, то часть информации теряется, поскольку это значение превышает диапазон представления чисел для типа byte. Приведение типов требуется в обоих случаях, поскольку неявное преобразование типа int в тип byte невозможно.

Когда переменной s типа short присваивается значение 32 000 переменной и типа uint, потери данных не происходит, поскольку это значение входит в диапазон представления чисел для типа short. Но в следующей операции присваивания переменная и имеет значение 64 000, которое оказывается вне диапазона представления чисел для типа short, и поэтому данные теряются. Приведение типов требуется в обоих случаях, поскольку неявное преобразование типа uint в тип short невозможно.

Далее переменной и присваивается значение 64 000 переменной 1 типа long. В этом случае данные не теряются, поскольку значение 64 000 оказывается вне диапазона представления чисел для типа uint. Но когда переменной и присваивается значение -12, данные теряются, поскольку отрицательные числа также оказываются вне диапазона представления чисел для типа uint. Приведение типов требуется в обоих случаях, так как неявное преобразование типа long в тип uint невозможно.

И наконец, когда переменной char присваивается значение типа byte, информация не теряется, но приведение типов все же требуется.

Преобразование типов в выражениях

Помимо операций присваивания, преобразование типов происходит и в самих выражениях. В выражении можно свободно смешивать два или более типа данных, при условии их совместимости друг с другом. Например, в одном выражении допускается применение типов short и long, поскольку оба типа являются числовыми. Когда в выражении смешиваются разные типы данных, они преобразуются в один и тот же тип по порядку следования операций.

Преобразования типов выполняются по принятым в C# правилам продвижения типов. Ниже приведен алгоритм, определяемый этими правилами для операций с двумя операндами.

ЕСЛИ один операнд имеет тип decimal, ТО и второй операнд продвигается к типу decimal (но если второй операнд имеет тип float или double, результат будет ошибочным).

ЕСЛИ один операнд имеет тип double, ТО и второй операнд продвигается к типу double.

ЕСЛИ один операнд имеет тип float, ТО и второй операнд продвигается к типу float.

ЕСЛИ один операнд имеет тип ulong, ТО и второй операнд продвигается к типу ulong (но если второй операнд имеет тип sbyte, short, int или long, результат будет ошибочным).

ЕСЛИ один операнд имеет тип long, ТО и второй операнд продвигается к типу long.

ЕСЛИ один операнд имеет тип uint, а второй - тип sbyte, short или int, ТО оба операнда продвигаются к типу long.

ЕСЛИ один операнд имеет тип uint, ТО и второй операнд продвигается к типу uint. ИНАЧЕ оба операнда продвигаются к типу int.

Относительно правил продвижения типов необходимо сделать ряд важных замечаний. Во-первых, не все типы могут смешиваться в выражении. В частности, неявное преобразование типа float или double в тип decimal невозможно, как, впрочем, и смешение типа ulong с любым целочисленным типом со знаком. Для смешения этих типов требуется явное их приведение.

Во-вторых, особого внимания требует последнее из приведенных выше правил. Оно гласит: если ни одно из предыдущих правил не применяется, то все операнды продвигаются к типу int. Следовательно, все значения типа char, sbyte, byte, ushort и short продвигаются к типу int в целях вычисления выражения. Такое продвижение типов называется целочисленным. Это также означает, что результат выполнения всех арифметических операций будет иметь тип не ниже int.

Следует иметь в виду, что правила продвижения типов применяются только к значениям, которыми оперируют при вычислении выражения. Так, если значение переменной типа byte продвигается к типу int внутри выражения, то вне выражения эта переменная по-прежнему относится к типу byte. Продвижение типов затрагивает только вычисление выражения.

Но продвижение типов может иногда привести к неожиданным результатам. Если, например, в арифметической операции используются два значения типа byte, то происходит следующее. Сначала операнды типа byte продвигаются к типу int. А затем выполняется операция, дающая результат типа int. Следовательно, результат выполнения операции, в которой участвуют два значения типа byte, будет иметь тип int. Но ведь это не тот результат, который можно было бы с очевидностью предположить. Рассмотрим следующий пример программы.

// Пример неожиданного результата продвижения типов!

using System;

class PromDemo {

static void Main() {

byte b;

b = 10;

b = (byte)(b * b); // Необходимо приведение типов!!

Console.WriteLine("b: "+ b);

}

}

Как ни странно, но когда результат вычисления выражения b*b присваивается обратно переменной b, то возникает потребность в приведении к типу byte! Объясняется это тем, что в выражении b*b значение переменной b продвигается к типу int и поэтому не может быть присвоено переменной типа byte без приведения типов. Имейте это обстоятельство в виду, если получите неожиданное сообщение об ошибке несовместимости типов в выражениях, которые, на первый взгляд, кажутся совершенно правильными.

Аналогичная ситуация возникает при выполнении операций с символьными операндами. Например, в следующем фрагменте кода требуется обратное приведение к типу char, поскольку операнды ch1 и ch2 в выражении продвигаются к типу int.

char ch1 = 'a', ch2 = 'b';

Назад Дальше