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

Объектно-ориентированное программирование.-7

.pdf
Скачиваний:
11
Добавлен:
05.02.2023
Размер:
4.54 Mб
Скачать

ort[0] = new int[4];

ort[1] = new int[] { 11, 22, 33, 44, 55 }; ort[2] = new int[] { 1, 2, 3 };

Console.WriteLine("Размерность ort: " + ort.Rank); Console.WriteLine("Элементов в ort: " + ort.Length);

for (int i = 0; i < ort.Length; i++)

{

for (int j = 0; j < ort[i].Length; j++)

{

Console.Write(ort[i][j] + " ");

}

Console.WriteLine();

}

Вывод на консоль:

Размерность

ort: 1

Элементов в

ort: 3

0

0

0

0

 

 

11

 

22

33

44

55

1

2

3

 

 

 

 

 

 

 

 

 

Как мы видим, массив «ort» считается одномерным. Просто его элементами являются другие массивы. Отсюда и различия в синтаксисе с прямоугольными массивами. В массиве ort[0] элементы не инициализированы, поэтому имеют значения по умолчанию (для типа int это 0). Описание массива ort можно было сделать и одной строкой:

int[][] ort = { new int[4], new int[] { 11, 22, 33, 44, 55 }, new int[] { 1, 2, 3 } };

Класс System.Array

Класс Array является абстрактным (см. главу 4). Это означает, что запрещено создавать его экземпляры. Можно создавать только экземпляры производных классов, перегружающих его абстрактные методы. Основные члены класса Array представлены в табл. В.1 (приложение В).

Все остальные массивы (одномерные, прямоугольные и ортогональные) являются потомками класса Array (рис. 3.4). Сам класс Array наследует-

ся от System.Object и реализует четыре интерфейса – ICollection, ICloneable, IEnumerable и IList (см. п. 4.9.4).

Очевидно, что вариантов создания массивов очень много (в зависимости от типа элементов, количества размерностей и, в случае с ортогональными массивами – длиной каждого измерения), и невозможно предусмотреть отдельный класс для каждого случая. Как же компилятор решает эту пробле-

121

му?

 

Object

ICollection

ICloneable

IEnumerable

Array

IList

int[][]

 

double[,]

 

 

 

 

 

 

Рис. 3.4 – Иерархия классов массивов в языке C#

На самом деле, любой массив создается как экземпляр класса Array, но компилятор предоставляет им некую обертку, которая позволяет им считаться потомками класса Array:

Console.WriteLine("Класс типа double[,]: " + m); Console.WriteLine("Базовый класс для double[,]: " +

m.GetType().BaseType);

Console.WriteLine("Класс типа int[][]: " + ort); Console.WriteLine("Базовый класс для int[][]: " + ort.GetType().BaseType);

Результат работы данного фрагмента кода:

Класс типа double[,]: System.Double[,] Базовый класс для double[,]: System.Array Класс типа int[][]: System.Int32[][]

Базовый класс для int[][]: System.Array

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

зуется метод Array.CreateInstance:

Array dyn1 = Array.CreateInstance(typeof(double), 4, 5);

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

for (int i = 0; i < dyn1.GetLength(0); i++)

{

for (int j = 0; j < dyn1.GetLength(1); j++)

{

Console.Write(dyn1.GetValue(i, j) + " ");

}

Console.WriteLine();

}

122

Array dyn2 = Array.CreateInstance(typeof(int[]), 2);

Console.WriteLine("dyn2: " + dyn2); dyn2.SetValue(new int[3], 0); dyn2.SetValue(new int[4], 1);

for (int i = 0; i < dyn2.Length; i++)

{

for (int j = 0; j < ((int[])dyn2.GetValue(i)).Length; j++)

{

Console.Write(((int[])dyn2.GetValue(i)).GetValue(j) + " ");

}

Console.WriteLine();

}

Результат работы:

dyn1: System.Double[,]

0

0

0

0

0

0

0

0

0

0

0

0

0

0

0

0

0

0

0

0

dyn2: System.Int32[][]

0

0

0

 

0

0

0

0

В данном примере при помощи метода CreateInstance были созданы массивы типа double[,] и int[][].

3.1.4. Анонимные типы

Объявляемые в области метода переменные могут иметь неявный тип var. Локальная переменная с неявным типом имеет строгую типизацию, как если бы тип был задан явно, только тип определяет компилятор. Следующие два объявления переменной «i» функционально являются эквивалентами:

var i = 10; // неявная (implicitly) типизация int i = 10; // явная (explicitly) типизация

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

var var = 5;

Здесь первое вхождение var является ключевым словом, второе – идентификатором.

Переменная анонимного типа обязательно должна быть инициализиро-

123

вана в момент объявления. Именно по типу инициализирующего значения определяется тип объявляемой переменной:

static int Main(string[] args)

{

var v1 = 15; var v2 = 15u; var v3 = 'X'; var v4 = "АБВ";

var v5 = new float[] { 1f, 2f, 3f }; var v6 = args;

var v7; // Ошибка

Console.WriteLine(v1.GetType());

Console.WriteLine(v2.GetType());

Console.WriteLine(v3.GetType());

Console.WriteLine(v4.GetType());

Console.WriteLine(v5.GetType());

Console.WriteLine(v6.GetType()); return 0;

}

Результаты работы:

System.Int32

System.UInt32

System.Char

System.String

System.Single[]

System.String[]

Наиболее важное применение анонимная типизация имеет при создании анонимных типов объектов в выражениях запросов LINQ (LanguageIntegrated Query). Это инструмент, позволяющий писать запросы (подобно запросам SQL к базам данных) к коллекциям данных прямо в тексте программы. Справку по LINQ можно получить в справочной системе MSDN. Небольшой пример:

int[] nums = { 1, 2, 15,

13, 4, 6, 7, 1, 9, 10 };

var odd = from n

in

nums

 

where n %

2 ==

1

select

new { Description = "Найдено нечётное число: {0}",

Number = n };

 

 

 

foreach (var num

in

odd)

 

{

 

 

 

Console.WriteLine(num.Description, num.Number);

}

Результаты работы:

Найдено нечётное число: 1 Найдено нечётное число: 15 Найдено нечётное число: 13 Найдено нечётное число: 7

124

Найдено нечётное число: 1

 

Найдено нечётное число: 9

 

Пример: Samples\3.1\3_1_4_var.

 

3.1.5. Упаковка

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

 

 

System.Object

System.ValueType

System.String

System.Enum

System.Array

Объекты

Прочие объекты

пользователя…

.NET

Целые типы данных

System.TypeCode

Типы с плавающей точкой

Прочие перечисления

 

.NET

System.Boolean

 

 

Перечисления

System.Char

пользователя…

 

System.DateTime

 

Прочие структуры .NET

 

Структуры пользователя…

Типы по значению

 

Рис. 3.5 – Структура типов .NET

125

Как мы могли заметить, типы по значению проявляют дуализм в том смысле, что:

а) это значения, хранящиеся на стеке; б) это классы, для которых мы можем вызывать методы, а классы –

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

в) типы по значению, например, int, наследуются от класса Object, типа по ссылке (рис. 3.5).

Как такое возможно? Для каждого типа по значению поддерживается соответствующий «упакованный» (boxed) тип, который является классом, реализующим то же поведение и содержащим те же данные. Если требуется передать тип значения по ссылке, он автоматически упаковывается (box) в соответствующий упакованный тип, а по прибытии при необходимости распаковывается (unbox) снова в тип значения. Находясь в упакованном виде, тип может использовать все методы класса System.ValueType. Например, допустима следующая конструкция:

string s = 54.ToString();

При этом значение 54 упаковывается в класс System.Int32, наследующий от класса System.ValueType метод ToString(), который и вызывается.

Другие примеры:

Console.WriteLine(1ul.GetType());

Console.WriteLine("Hello!".Length);

В данном случае на консоли отобразится имя класса System.UInt64 и длина строки «Hello!» – 6 символов.

3.1.6. Переменные и идентификаторы

В языке C# определено несколько типов переменных: статические, экземплярные, элементы массивов, параметры по значению, параметры по ссылке, выходные параметры и локальные переменные.

С массивами мы уже познакомились, а здесь разберем лишь локальные переменные. Все остальные типы переменных будут рассмотрены позже.

3.1.6.1. Идентификаторы

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

126

этому имена также называют идентификаторами. В идентификаторе могут использоваться буквы, цифры и символ подчеркивания. Компилятором языка C# различаются прописные и строчные буквы, поэтому sysop, SySoP и SYSOP – три разных идентификатора. Первым символом идентификатора может быть буква или знак подчеркивания, но не цифра. Длина идентификатора не ограничена. Пробелы внутри имен не допускаются. В идентификаторах C# разрешается использовать, помимо латинских букв, буквы национальных алфавитов. Например:

enum Радуга { Красный, Оранжевый, Желтый, Зеленый, Голубой, Синий, Фиолетовый }

int Счётчик;

Более того, в идентификаторах можно применять даже символы

Unicode:

int \u005fid;

int _id; // Ошибка

В этом примере в первой строке объявлен идентификатор «_id», т.к. U+005F – это код символа подчеркивания. Поэтому для второй строки компилятор выдаст ошибку – дублирование идентификатора.

Имена даются элементам программы, к которым требуется обращаться: переменным, типам, константам, методам, меткам и т.д. Идентификатор создается на этапе объявления переменной (метода, типа и т.п.), после этого его можно использовать в последующих операторах программы.

При выборе идентификатора необходимо иметь в виду, что он не должен совпадать с ключевыми словами языка. Однако, сборки .NET могут включать модули, написанные на различных языках программирования. Что, если мы описали переменную, например, с именем input, которое не является ключевым словом C#, но может являться ключевым словом другого языка? Или переносим фрагмент кода, например, из программы на языке C++, где имеются переменные lock (ключевое слово в C#) и т.д., и не хотим переименовывать идентификаторы? В этих случаях достаточно идентификатор предварить символом «@». Например, правильным будет идентификатор @lock.

3.1.6.2. Локальные переменные

Локальные переменные – это переменные, объявленные в теле метода. Они не инициализируются автоматически, а время их жизни зависит от ком-

127

пилятора, но оно не больше, чем время исполнения метода. Локальные переменные могут быть определены и в управляющих блоках (for, while, foreach, if). Локальная переменная и поле класса могут иметь одинаковые идентификаторы:

class Program

{

static int i = 123;

public static int Main(string[] args)

{

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

{

Console.WriteLine(i);

}

Console.WriteLine(Program.i); return 0;

}

}

Здесь переменная «i» объявлена в классе как статическая, т.к. из статических методов класса можно получить доступ только к статическим полям (см. § 4.3). Вывод программы следующий:

0

1

2

3

4

123

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

Так называемая область действия локальной переменной, то есть область программы, где можно использовать переменную, начинается в точке ее описания и длится до конца блока, внутри которого она описана. Блок – это код, заключенный в фигурные скобки. Основное назначение блока – группировка операторов. Здесь мы говорим только о блоках внутри методов. Про поля, объявленные в классе, будем говорить позже.

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

128

охватывающем его, например:

class X

{

int А; // поле А класса X int В; // поле В класса X

void Y() // метод Y класса X

{

//локальная переменная С, область действия - метод Y int С;

//локальная переменная А (не конфликтует с полем А) int А;

{

//локальная переменная D, область действия –

//этот блок

int D;

int А; // Ошибка

}

{

//локальная переменная D, область действия –

//этот блок

int D;

}

}

}

Давайте разберемся в этом примере. В нем описан класс X, содержащий три элемента: поле A, поле B и метод Y. Непосредственно внутри метода Y заданы две локальные переменные – C и A. Внутри метода класса можно описывать переменную с именем, совпадающим с полем класса, потому что существует способ доступа к полю класса с помощью ключевого слова this (см. главу 4). Таким образом, локальная переменная A не «закрывает» поле A класса X, а вот попытка описать во вложенном блоке другую локальную переменную с тем же именем окончится неудачей. Две переменные с именем D не конфликтуют между собой, поскольку блоки, в которых они описаны, не вложены один в другой.

Отдельно остановимся на описании переменных внутри управляющих конструкций языка. В некоторых компиляторах языка C++ данный код считается правильным, в других – нет:

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

{

// тело цикла

}

int i;

Все зависит от того, на каком уровне вложенности считается объявленной переменная «i» в цикле. Если на том же уровне, что и сам цикл, компиля-

129

тор выдаст ошибку, если на уровне внутри цикла (т.е. на уровне тела цикла) – то нет. В любом случае, такой код будет компилироваться без ошибки:

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

{

int j;

// тело цикла

}

int j;

В языке C# не будут компилироваться оба варианта. Считается, что второе объявление переменной «j» сделано на один уровень выше, чем первое, и поэтому имена конфликтуют (как и в примере с классом X выше):

double[] arr = { 1, 2, 3 };

foreach (double x in arr) Console.WriteLine(x); int x; // Ошибка

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

{

int z;

}

int z; // Ошибка

Если же второе объявление переменной будет в другом блоке, ошибки не будет:

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

{

Console.WriteLine(i);

}

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

{

int z;

}

{

int z;

}

В данном примере переменные «i» и «z» объявлены дважды, но не конфликтуют, т.к. находятся в разных ответвлениях блоков кода. Некоторые компиляторы C++ для второго объявления переменной «i» выдали бы ошибку.

Как правило, переменным с большой областью действия даются более длинные и осмысленные имена (описывающие назначение переменной), а для переменных, вся «жизнь» которых – несколько строк исходного текста,

130