Область видимости типов переменных 


Мы поможем в написании ваших работ!



ЗНАЕТЕ ЛИ ВЫ?

Область видимости типов переменных



Область видимости типов переменных

 

Область видимости типов переменных

 

Область видимости, или контекст переменной — это часть кода, в пределах которого доступна данная переменная. В общем случае такая область определяется описанными ниже правилами:

  • Поле, также известное как переменная-член класса, находится в области видимости до тех пор, пока в этой области находится содержащий поле класс.
  • Локальная переменная находится в области видимости до тех пор, пока закрывающая фигурная скобка не укажет конец блока операторов или метода, в котором она объявлена.
  • Локальная переменная, объявленная в операторах цикла for, while или подобных им, видима в пределах тела цикла.

 

Конфликты областей видимости локальных переменных

 

Использование в больших программах одних и тех же имён переменных в разных частях программы является обычной практикой. Это нормально до тех пор, пока области видимости этих переменных не перекрываются и находятся в совершенно разных частях программы, таким образом, исключая любую неоднозначность. Однако следует иметь в виду, что локальные переменные с одним и тем же именем не могут быть объявлены дважды в одном и том же контексте, поэтому нельзя поступить так, как показано ниже:

 

int х = 20;

//...

int х = 30;

 

Рассмотрим следующий пример кода:

 

using System;

using System.Collections.Generic;

using System.Linq;

using System.Text;

 

namespace LC_Console

{

class Program

{

static void Main(string[] args)

{

for (int i = 0; i < 10; i++)

{

Console.Write("{0} ", i);

} // Здесь i покидает область видимости

Console.WriteLine();

// Можно вновь объявить i

for (int i = 0; i > -10; i--)

{

Console.Write("{0} ", i);

} // i снова покидает область видимости

Console.ReadLine();

var j = i * i; // Данный код не выполниться, т.к

// i не определена в текущем контексте

}

}

}

 

Важно отметить, что переменная i объявляется в этом коде два раза в пределах одного и того же метода. Это можно делать, поскольку переменные i объявляются в двух отдельных циклах, поэтому каждая из них локальна в пределах собственного цикла.

 

Другой пример:

 

using System;

using System.Collections.Generic;

using System.Linq;

using System.Text;

 

namespace LC_Console

{

class Program

{

public static int Main()

{

int j = 20;

for (int i = 0; i < 10; i++)

{

int j = 30; // Так делать нельзя - j всё ещё в контексте

Console.WriteLine(j + i);

}

return 0;

}

}

}

 

При попытке скомпилировать этот код, среда разработки Visual Studio 2010 выдаст следующее сообщение об ошибке с описанием:

 

«Невозможно объявить локальную переменную с именем "j" в этой области видимости, т.к. она придаст другое значение "j", которая уже используется в области видимости "родительский или текущий" для обозначения чего-то другого».

 

Дело в том, что переменная j, которая определена перед началом цикла for, внутри цикла всё ещё находится в области видимости и не может из неё выйти до завершения метода Main. Хотя вторая переменная j (недопустимая) объявлена в контексте цикла, этот контекст вложен в контекст метода Main. Компилятор не может различить эти две переменных, поэтому не допустит объявления второй из них.

 

Конфликты областей видимости полей и локальных переменных

 

В некоторых случаях два идентификатора с одинаковыми именами (хотя и не совпадающими полностью уточнёнными именами) и одинаковой областью видимости можно различить, и тогда компилятор допускает объявление второй переменной. Причина в том, что С# делает принципиальное различие между переменными, объявленными на уровне типа (полями) и переменными, объявленными в методах (локальными). Рассмотрим следующий фрагмент кода:

 

using System;

using System.Collections.Generic;

using System.Linq;

using System.Text;

 

namespace LC_Console

{

class Program

{

static int j = 20;

 

public static void Main()

{

int j = 30;

Console.WriteLine(j);

Console.WriteLine("Для продолжения нажмите любую клавишу... ");

Console.ReadKey();

return;

}

}

}

/* Выведет:

* 30

* Для продолжения нажмите любую клавишу...

*/

 

Этот код компилируется, несмотря на то, что здесь в контексте метода Main присутствуют две переменных с именем j: переменная j, определённая на уровне класса и существующая до тех пор, пока не будет уничтожен класс (когда завершится метод Main, а вместе с ним и программа), и переменная j, определенная внутри Main. В данном случае новая переменная с именем j, объявленная в методе Main, скрывает переменную уровня класса с тем же именем. Код после компиляции выведет на экран в консольном приложении значение «30».

Основные сведения о типах данных

Основные сведения о типах данных

 

Типы данных

 

Типы данных имеют особенное значение в С#, поскольку это строго типизированный язык. Это означает, что все операции подвергаются строгому контролю со стороны компилятора на соответствие типов, причём недопустимые операции не компилируются. Следовательно, строгий контроль типов позволяет исключить ошибки и повысить надёжность программ. Для обеспечения контроля типов все переменные, выражения и значения должны принадлежать к определённому типу. Такого понятия, как переменная без типа, в данном языке программирования вообще не существует. Более того, тип значения определяет те операции, которые разрешается выполнять над ним. Операция, разрешённая для одного типа данных, может оказаться недопустимой для другого.

В С# имеются две общие категории встроенных типов данных: типы значений и ссылочные типы. Они отличаются по содержимому переменной. Концептуально разница между ними состоит в том, что тип значения (value type) хранит данные непосредственно, в то время как ссылочный тип (reference type) хранит ссылку на значение.

Эти типы сохраняются в разных местах памяти: типы значений сохраняются в области, известной как стек, а ссылочные типы — в области, называемой управляемой кучей. Ниже представлена схема типов данных C#:

 

Рис. 1. 1. Схема типов данных в C#

Итак, каждая переменная и константа имеет тип, как и каждое выражение, результатом вычисления которого является значение. Каждая сигнатура метода задаёт тип для каждого входного параметра и для возвращаемого значения. Библиотека классов платформы .NET Framework определяет набор встроенных числовых типов, а также более сложных типов, представляющих широкое разнообразие логических конструкций, например, файловую систему, сетевые подключения, коллекции и массивы объектов и даты. Типичная программа C# использует типы из библиотеки классов, а также пользовательские типы, моделирующие принципы, относящиеся к проблемной области программы.

 

К сведениям, хранимым в типе, может относиться следующее:

  • Место для хранения, необходимое для переменной типа.
  • Максимальное и минимальное значения, которые могут быть представлены.
  • Содержащиеся члены (методы, поля, события и т. д.).
  • Базовый тип, которому он наследует.
  • Расположение, в котором будет выделена память для переменных во время выполнения.
  • Разрешенные виды операций.

 

Компилятор использует сведения о типе, чтобы убедиться, что все операции, выполняемые в коде, являются типобезопасными. Например, при объявлении переменной типа int, компилятор позволяет использовать в дополнение переменную и операции вычитания. При попытке выполнить эти же операции в переменной типа bool, компилятор вызовет ошибку.

 

Компилятор внедряет сведения о типе в исполняемый файл в качестве метаданных. Среда CLR использует эти метаданные во время выполнения для дальнейшего обеспечения безопасности типа при выделении и освобождении памяти.

 

Задание типов в объявляемых параметрах

 

При объявлении переменной или константы в программе необходимо либо задать ее тип, либо использовать ключевое слово var, чтобы дать возможность компилятору определить его. В следующем примере показаны некоторые объявления переменных, использующие встроенные числовые типы и сложные пользовательские типы:

 

// объявление

float temperature;

string name;

// Объявление с инициализацией

char firstLetter = 'C';

var limit = 3;

int[] source = { 0, 1, 2, 3, 4, 5 };

var query = from item in source

where item <= limit

select item;

 

Типы параметров метода и возвращаемые значения задаются в сигнатуре метода. В следующей сигнатуре показан метод, который требует int в качестве входного аргумента, и который возвращает строку:

 

public string GetName(int ID)

{

if (ID < names.Length)

return names[ID];

else

return String.Empty;

}

private string[] names = { "Spencer", "Sally", "Doug" };

 

После объявления переменной она не может быть повторно объявлена с новым типом, и ей нельзя присвоить значение, несовместимое с ее объявленным типом. Например, нельзя объявить int и затем присвоить ему логическое значение true. Однако значения могут быть преобразованы в другие типы, например, при их присвоении новым переменным или при передаче в качестве аргументов метода. Преобразование типов, которое не приводит к потере данных, автоматически выполняется компилятором. Для преобразования, которое может привести к потере данных, необходимо приведение в исходном коде.

 

Встроенные типы

 

C# предоставляет стандартный набор встроенных числовых типов для представления целых чисел, значений с плавающей запятой, логических выражений, текстовых символов, десятичных значений и других типов данных. Существуют также встроенные типы string и object. Они доступны для использования в любой программе C#.

 

Пользовательские типы

 

Конструкции структура, класс, интерфейс и перечисление используются для создания собственных пользовательских типов. Сама библиотека классов платформы.NET Framework является коллекцией пользовательских типов, предоставленной корпорацией Microsoft, которую можно использовать в собственных приложениях. По умолчанию наиболее часто используемые типы в библиотеке классов доступны в любой программе C#. Другие становятся доступными только при явном добавлении ссылки проекта на сборку, в которой они определены. Если компилятор имеет ссылку на сборку, то можно объявить переменные (и константы) типов, объявленных в сборке в исходном коде.

 

Система общих типов CST

 

Важно понимать две фундаментальные точки о системе типов в.NET Framework:

  • Она поддерживает принцип наследования. Типы могут быть производными от других типов, которые называются базовыми типами. Производный тип наследует (с некоторыми ограничениями) методы, свойства и другие члены базового типа. Базовый тип, в свою очередь, может быть производным от какого-то другого типа, при этом производный тип наследует члены обоих базовых типов в иерархии наследования. Все типы, включая встроенные числовые типы, например, System.Int32 (ключевое слово C#: int), в конечном счёте являются производными от одного базового типа, который является System.Object (ключевое слово C#: Object).Эта унифицированная иерархия типов называется «Система общих типов CTS» (CTS).
  • Каждый тип в CTS определен либо как тип значения, либо как ссылочный тип. Сюда включены все пользовательские типы в библиотеке классов платформы.NET Framework, а также собственные пользовательские типы. Типы, определяемые с помощью ключевого слова struct, являются типами значений; все встроенные числовые типы являются структурами. Типы, определяемые с помощью ключевого слова class, являются ссылочными типами. Правила времени компиляции и поведение времени выполнения ссылочных типов отличается от правил времени компиляции и поведения времени выполнения типов значений.

 

В следующем примере показана связь между типами значений и ссылочными типами в CTS:


Рис. 5. 1. Связь между типами значений и ссылочными типами

 

Можно увидеть, что наиболее часто используемые типы все организованы в пространстве имён System. Однако пространство имен, в котором содержится тип, не имеет отношения к тому, является ли этот тип типом значения или ссылочным типом. Рассмотрим сначала типы значений.

 

Типы значений

 

Типы значений являются производными от System.ValueType, являющегося производным от System.Object. Типы, производные от System.ValueType, имеют особое поведение в среде CLR. Переменные типа значения напрямую содержат их значения, что означает, что память встроена в контекст, в котором объявлена переменная. Не существует отдельного размещения кучи или служебных данных сборки мусора для переменных типа значения.

Существует две категории типов значений: структура и перечисление.

Встроенные числовые типы являются структурами, и к их свойствам и методам можно получить доступ.

 

Типы значений являются запечатанными, что означает, например, что нельзя произвести тип от System.Int32, и нельзя определить структуру для наследования от любого пользовательского класса или структуры, поскольку структура может наследовать только от System.ValueType. Однако структура может реализовать один или несколько интерфейсов. Можно выполнить приведение типа структуры в тип интерфейса; это приведет к операции упаковки-преобразования для создания программы-оболочки структуры внутри объекта ссылочного типа в управляемой куче. Операции упаковки-преобразования возникают при передаче типа значения методу, принимающему System.Object в качестве входного параметра.

 

Дополнительные сведения о упаковке-преобразовании-распаковки можно получить в разделе Упаковка-преобразование и распаковка-преобразование (Руководство по программированию на C#) (http://msdn.microsoft.com/ru-ru/library/yz2be5wk.aspx).

 

Ключевое слово struct используется для создания собственных пользовательских типов значений. Обычно структура используется как контейнер для небольшого набора связанных переменных, как показано в следующем примере:

 

public struct CoOrds

{

public int x, y;

 

public CoOrds(int p1, int p2)

{

x = p1;

y = p2;

}

}

 

Другой категорией типов значений является перечисление. Перечисление определяет набор именованных интегральных констант. Например, перечисление System.IO.FileMode в библиотеке классов платформы.NET Framework содержит набор именованных констант целого типа, которые задают, как должен быть открыт файл. Это определено, как показано в следующем примере:

 

public enum FileMode

{

CreateNew = 1,

Create = 2,

Open = 3,

OpenOrCreate = 4,

Truncate = 5,

Append = 6,

}

 

Значение константы System.IO.FileMode.Create равно 2. Однако имя намного более значимо для пользователей, читающих исходный код, и по этой причине лучше использовать перечисления вместо литеральных номеров констант.

Все перечисления наследуются от System.Enum, который наследуется от System.ValueType. Все правила, применимые к структурам, также применяются к перечислениям.

 

Целочисленные типы

 

В С# определены девять целочисленных типов: char, byte, sbyte, short, ushort, int, uint, long и ulong. Но тип char применяется, главным образом, для представления символов и поэтому рассматривается отдельно. Остальные восемь целочисленных типов предназначены для числовых расчётов. Ниже представлены их диапазон представления чисел и разрядность в битах:

 

Тип C# Тип CTS Разрядность в битах Диапазон
byte System.Byte   0:255
sbyte System.SByte   -128:127
short System.Int16   -32768: 32767
ushort System.UInt16   0: 65535
int System.Int32   -2147483648: 2147483647
uint System.UInt32   0: 4294967295
long System.Int64   -9223372036854775808: 9223372036854775807
ulong System.UInt64   0: 18446744073709551615

 

Как следует из приведенной выше таблицы, в С# определены оба варианта различных целочисленных типов: со знаком и без знака. Целочисленные типы со знаком отличаются от аналогичных типов без знака способом интерпретации старшего разряда целого числа. Так, если в программе указано целочисленное значение со знаком, то компилятор С# сгенерирует код, в котором старший разряд целого числа используется в качестве флага знака. Число считается положительным, если флаг знака равен нулю, и отрицательным, если он равен 1. Отрицательные числа практически всегда представляются методом дополнения до двух, в соответствии с которым все двоичные разряды отрицательного числа сначала инвертируются, а затем к этому числу добавляется 1.

Вероятно, самым распространённым в программировании целочисленным типом является тип int. Переменные типа int нередко используются для управления циклами, индексирования массивов и математических расчетов общего назначения. Когда же требуется целочисленное значение с большим диапазоном представления чисел, чем у типа int, то для этой цели имеется целый ряд других целочисленных типов. Так, если значение нужно сохранить без знака, то для него можно выбрать тип uint, для больших значений со знаком — тип long, а для больших значений без знака — тип ulong. В качестве примера ниже приведена программа, вычисляющая расстояние от Земли до Солнца в сантиметрах. Для хранения столь большого значения в ней используется переменная типа long:

 

using System;

using System.Collections.Generic;

using System.Linq;

using System.Text;

 

namespace LC_Console

{

class Program

{

static void Main(string[] args)

{

long result;

const long km = 149800000; // Расстояние в км.

result = km * 1000 * 100;

Console.WriteLine(result);

Console.WriteLine("Для продолжения нажмите любую клавишу... ");

Console.ReadKey();

return;

}

}

}

/* Выведет:

* 14980000000000

* Для продолжения нажмите любую клавишу...

*/

 

Всем целочисленным переменным значения могут присваиваться в десятичной или шестнадцатеричной системе обозначений. В последнем случае требуется префикс Ох:

 

long x = 0x12ab;

 

Если возникает какая-то неопределённость относительно того, имеет ли целое значение тип int, uint, long или ulong, то по умолчанию принимается int. Чтобы явно специфицировать, какой другой целочисленный тип должно иметь значение, к числу можно добавлять следующие символы:

 

uint ui = 1234U;

long l = 1234L;

ulong ul = 1234UL;

 

U и L можно также указывать в нижнем регистре, хотя строчную «L» легко зрительно спутать с цифрой 1 (единица).

Типы с плавающей запятой

 

Типы с плавающей точкой позволяют представлять числа с дробной частью. В С# имеются две разновидности типов данных с плавающей точкой: float и double. Они представляют числовые значения с одинарной и двойной точностью соответственно. Так, разрядность типа float составляет 32 бита, что приближенно соответствует диапазону представления чисел от 5Е-45 до 3,4Е+38. А разрядность типа double составляет 64 бита, что приближенно соответствует диапазону представления чисел от 5Е-324 до1,7Е+308.

Тип данных float предназначен для меньших значений с плавающей точкой, для которых требуется меньшая точность. Тип данных double больше, чем float, и предлагает более высокую степень точности (15 разрядов).

Если нецелочисленное значение жестко кодируется в исходном тексте (например, 12. 3), то обычно компилятор предполагает, что подразумевается значение типа double. Если значение необходимо специфицировать как float, потребуется добавить к нему символ F (или f):

 

float f = 12.3F;

 

Десятичный тип данных

 

Для представления чисел с плавающей точкой высокой точности предусмотрен также десятичный тип decimal, который предназначен для применения в финансовых расчетах. Этот тип имеет разрядность 128 бит для представления числовых значений в пределах от 1Е-28до 7,9Е+28. Для обычных арифметических вычислений с плавающей точкой характерны ошибки округления десятичных значений. Эти ошибки исключаются при использовании типа decimal, который позволяет представить числа с точностью до 28 (а иногда и 29) десятичных разрядов. Благодаря тому, что этот тип данных способен представлять десятичные значения без ошибок округления, он особенно удобен для расчётов, связанных с финансами:

 

using System;

using System.Collections.Generic;

using System.Linq;

using System.Text;

 

namespace LC_Console

{

class Program

{

static void Main(string[] args)

{

// Расчёт стоимости капиталовложения с

// фиксированной нормой прибыли

decimal money, percent;

int i;

const byte years = 10;

money = 1000.0m;

percent = 0.05m;

for (i = 1; i <= years; i++)

{

money *= 1 + percent;

}

Console.WriteLine("Общий доход за {0} лет: {1} $$", years, money);

Console.WriteLine("Для продолжения нажмите любую клавишу... ");

Console.ReadKey();

}

}

}

/* Выведет:

* Общий доход за 10 лет: 1628,89... $

* Для продолжения нажмите любую клавишу...

*/

 

Символьный тип

 

В С# символы представлены не 8-разрядным кодом, как во многих других языках программирования, например С++, а 16-разрядным кодом, который называется юникодом (Unicode). В юникоде набор символов представлен настолько широко, что он охватывает символы практически из всех естественных языков на свете. Если для многих естественных языков, в том числе английского, французского и немецкого, характерны относительно небольшие алфавиты, то в ряде других языков, например китайском, употребляются довольно обширные наборы символов, которые нельзя представить8-разрядным кодом. Для преодоления этого ограничения в С# определен тип char, представляющий 16-разрядные значения без знака в пределах от 0 до 65 535. При этом стандартный набор символов в 8-разрядном коде ASCII является подмножеством юникода в пределах от 0 до 127. Следовательно, символы в коде ASCII по-прежнему остаются действительными в С#.

Для того чтобы присвоить значение символьной переменной, достаточно заключить это значение (т.е. символ) в одинарные кавычки:

 

char ch;

ch = 'Z';

 

Несмотря на то что тип char определён в С# как целочисленный, его не следует путать со всеми остальными целочисленными типами. Дело в том, что в С# отсутствует автоматическое преобразование символьных значений в целочисленные и обратно. Например, следующий фрагмент кода содержит ошибку:

 

char ch;

ch = 8; // Ошибка

 

Наравне с представлением char как символьных литералов, их можно представлять как 4-разрядные шестнадцатеричные значения Unicode (например, '\u0041'), целочисленные значения с приведением (например: (char) 65) или же шестнадцатеричные значения (например: '\x0041'). Кроме того, они могут быть представлены в виде управляющих последовательностей.

 

Логический тип данных

 

Тип bool представляет два логических значения: «истина» и «ложь». Эти логические значения обозначаются в С# ключевыми словами true и false соответственно. Следовательно, переменная или выражение типа bool будет принимать одно из этих логических значений. Кроме того, в С# не определено взаимное преобразование логических и целых значений. Например, 1 не преобразуется в значение true, а 0 — в значение fals e.

 

Ссылочные типы

 

Тип, определенный как класс, делегат, массив или интерфейс, является ссылочным типом. Во время выполнения при объявлении переменной ссылочного типа переменная содержит значение null до явного создания экземпляра объекта с помощью оператора new или назначения его объекту, который был создан в другом месте, с помощью new.

 

Интерфейс должен быть инициализирован вместе с объектом класса, который его реализует. Если MyClass реализует IMyInterface, то нужно создать экземпляр IMyInterface.

 

При создании объекта память размещается в управляемой куче, и переменная хранит только ссылку на расположение объекта. Для типов в управляемой куче требуются служебные данные и при их размещении, и при их удалении функциональной возможностью автоматического управления памятью среды CLR, также известной как сборка мусора. Однако сборка мусора также в высокой степени оптимизирована, и в большинстве сценариев она не создает проблем с производительностью.

 

Все массивы являются ссылочными типами, даже если их члены являются типами значений. Массивы являются неявно производными от класса System.Array, но объявляются и используются они с упрощенным синтаксисом, предоставленным C#, как показано в следующем примере:

 

// Объявляем массив и инициализируем данные

int[] nums = { 1, 2, 3, 4, 5 };

// Получаем значения свойства System.Array.Length для массива (число элементов)

int len = nums.Length;

 

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

 

Типы литеральных значений

 

В C# литеральные значения получают тип от компилятора. Можно задать, как числовой литерал должен быть типизирован, путём добавления буквы в конце номера. Например, чтобы задать, что значение 4,56 должно обрабатываться как число с плавающей запятой, добавьте после номера «f» или «F»: 4.56f. При отсутствии добавленной буквы компилятор определит тип для литерала.

 

Универсальные типы

 

Тип может быть объявлен с одним или несколькими параметрами типа, служащими в качестве место заполнителя для фактического типа (устойчивого типа), который клиентский код предоставит при создании экземпляра типа.Такие типы называются универсальными типами. Например, тип платформы.NET Framework System.Collections.Generic.List<T> имеет один параметр типа, которому в соответствии с соглашением предоставлено имя T. При создании экземпляра типа необходимо задать тип объектов, которые будут содержаться в списке, например, строку:

 

List<string> strings = new List<string>();

 

Использование параметра типа делает возможным повторное использование этого же класса для хранения любого типа элемента, не преобразовывая каждый элемент в объект. Универсальные классы коллекции называются строго типизированными коллекциями, поскольку компилятор знает определенный тип элементов коллекции и может вызвать ошибку во время компиляции, если, к примеру, будет произведена попытка добавить целое число к объекту strings в предыдущем примере.

 

Дополнительные сведения см. в разделе Универсальные шаблоны (Руководство по программированию на C#) (http://msdn.microsoft.com/ru-ru/library/512aeb7t.aspx).

 



Поделиться:


Последнее изменение этой страницы: 2016-08-15; просмотров: 886; Нарушение авторского права страницы; Мы поможем в написании вашей работы!

infopedia.su Все материалы представленные на сайте исключительно с целью ознакомления читателями и не преследуют коммерческих целей или нарушение авторских прав. Обратная связь - 3.15.229.113 (0.187 с.)