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

ооп теория

.pdf
Скачиваний:
19
Добавлен:
14.02.2015
Размер:
3.58 Mб
Скачать

В процедурный блок вложены два блока, порожденные оператором if. В

каждом из них объявлены переменные с одинаковыми именами u и v. Это корректные объявления, поскольку время существования и области видимости этих переменных не пересекаются. Итак, для невложенных блоков разрешено объявление локальных переменных с одинаковыми именами.

Заметьте также, что переменные u и v перестают существовать после выхода из блока, так что операторы печати, расположенные внутри блока, работают корректно, а оператор печати вне блока приводит к ошибке, - u и v здесь не видимы, кончилось время их жизни. По этой причине оператор закомментирован.

Выражение, проверяемое в операторе if, зависит от значения поля name.

Значение поля глобально для метода и доступно всюду, если только не перекрывается именем аргумента (как в случае с полем x) или локальной переменной (как в случае с полем y).

Во многих языках программирования разрешено иметь локальные статические переменные, у которых область видимости определяется блоком,

но время их жизни совпадает со временем жизни проекта. При каждом повторном входе в блок такие переменные восстанавливают значение,

полученное при предыдущем выходе из блока. В языке C# статическими могут быть только поля, но не локальные переменные. Незаконная попытка объявления static переменной в процедуре ScopeVar закомментирована.

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

ГЛОБАЛЬНЫЕ ПЕРЕМЕННЫЕ УРОВНЯ ПРОЦЕДУРЫ. СУЩЕСТВУЮТ ЛИ?

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

91

Переменная, объявленная во внешнем блоке, рассматривается как глобальная по отношению к внутренним блокам. Во всех известных мне языках программирования во внутренних блоках разрешается объявлять переменные с именем, совпадающим с именем глобальной переменной. Конфликт имен снимается за счет того, что локальное внутреннее определение сильнее внешнего. Поэтому область видимости внешней глобальной переменной сужается и не распространяется на те внутренние блоки, где объявлена переменная с подобным именем. Внутри блока действует локальное объявление этого блока, при выходе восстанавливается область действия внешнего имени. В языке C# этот гордиев узел конфликтующих имен разрублен, - во внутренних блоках запрещено использование имен,

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

Обратите внимание, что подобные решения, принятые создателями языка C#,

не только упрощают жизнь разработчикам транслятора. Они способствуют повышению эффективности программ, а самое главное, повышают надежность программирования на C#.

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

92

КОНСТАНТЫ

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

y = 7.7f;

Значение константы "7.7f" является одновременно ее именем, оно же позволяет однозначно определить тип константы. Заметьте, иногда, как в данном случае, приходится добавлять к значению специальные символы для точного указания типа. Я не буду останавливаться на этих подробностях.

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

C++.

Всюду, где можно объявить переменную, можно объявить и именованную константу. Синтаксис объявления схож. В объявление добавляется модификатор const, инициализация констант обязательна и не может быть отложена. Инициализирующее выражение может быть сложным, важно,

чтобы оно было вычислимым в момент его определения. Вот пример объявления констант:

///<summary>

///Константы

///</summary>

public void Constants()

{

const int SmallSize = 38, LargeSize =58;

const int MidSize = (SmallSize + LargeSize)/2; const double pi = 3.141593;

//LargeSize = 60; //Значение константы нельзя изменить.

Console.WriteLine("MidSize= {0}; pi={1}", MidSize, pi);

}//Constants

93

Тема 6. ВЫРАЖЕНИЯ. ОПЕРАЦИИ В ВЫРАЖЕНИЯХ СОДЕРЖАНИЕ ЛЕКЦИИ:

ВЫРАЖЕНИЯ

o ПРИОРИТЕТ И ПОРЯДОК ВЫПОЛНЕНИЯ ОПЕРАЦИЙ o ПЕРЕГРУЗКА ОПЕРАЦИЙ

o С ЧЕГО НАЧИНАЕТСЯ ВЫПОЛНЕНИЕ ВЫРАЖЕНИЯ

o ОПЕРАЦИИ "УВЕЛИЧИТЬ" И "УМЕНЬШИТЬ" (INCREMENT,

DECREMENT)

o ОПЕРАЦИИ SIZEOF И TYPEOF

o КАК ПОЛУЧИТЬ ПОДРОБНУЮ ИНФОРМАЦИЮ О КЛАССЕ? o СТАТИЧЕСКИЕ ПОЛЯ И МЕТОДЫ АРИФМЕТИЧЕСКИХ

КЛАССОВ

o ОПЕРАЦИЯ NEW

o АРИФМЕТИЧЕСКИЕ ОПЕРАЦИИ o ОПЕРАЦИИ ОТНОШЕНИЯ

o ОПЕРАЦИИ ПРОВЕРКИ ТИПОВ o ОПЕРАЦИИ СДВИГА

o ЛОГИЧЕСКИЕ ОПЕРАЦИИ o УСЛОВНОЕ ВЫРАЖЕНИЕ

o ОПЕРАЦИЯ ПРИВЕДЕНИЯ К ТИПУ

94

ВЫРАЖЕНИЯ

Выражения строятся из операндов - констант, переменных, функций, -

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

приоритет операций;

для операций одного приоритета порядок применения - слева направо или справа налево;

преобразование типов операндов и выбор реализации для перегруженных операций;

тип и значение результата выполнения операции над заданными значениями операндов определенного типа.

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

как это делается в C#.

ПРИОРИТЕТ И ПОРЯДОК ВЫПОЛНЕНИЯ ОПЕРАЦИЙ

Большинство операций в языке C#, их приоритет и порядок наследованы из языка C++. Однако имеются и различия: например, нет операции " , ", позволяющей вычислять список выражений; добавлены уже упоминавшиеся операции checked и unchecked, применимые к выражениям.

Как это обычно делается, приведем таблицу приоритетов операций, в

каждой строке которой собраны операции одного приоритета, а строки следуют в порядке приоритетов, от высшего к низшему.

95

 

При

 

Категория

 

Операции

 

 

Пор

 

 

 

 

 

 

 

 

 

 

 

 

оритет

 

 

 

 

ядок

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

0

 

Первичные

 

 

(expr)

x.y f(x) a[x]

 

 

 

 

 

 

 

 

 

 

 

 

x++ x-- new sizeof(t)

 

 

 

 

 

 

 

 

 

 

 

 

typeof(t)

 

 

 

 

 

 

 

checked(expr)

unchecked(expr)

 

 

 

 

 

 

 

 

 

 

Слев а направо

 

 

 

 

 

 

 

 

 

 

1

 

Унарные

 

+ - ! ~ ++x --x (T)x

 

Слев

 

 

 

 

 

 

 

 

а направо

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

2

 

Мультипликативные

 

* / %

 

Слев

 

 

 

 

(Умножение)

 

 

 

а направо

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

3

 

Аддитивные

 

+ -

 

Слев

 

 

 

 

(Сложение)

 

 

 

а направо

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

4

 

Сдвиг

 

<< >>

 

Слев

 

 

 

 

 

 

 

 

а направо

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

5

 

Отношения, проверка

 

< > <= >= is as

 

Слев

 

 

 

 

типов

 

 

 

а направо

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

6

 

Эквивалентность

 

== !=

 

Слев

 

 

 

 

 

 

 

 

а направо

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

7

 

Логическое И

 

&

 

Слев

 

 

 

 

 

 

 

 

а направо

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

8

 

Логическое

 

^

 

Слев

 

 

 

 

исключающее ИЛИ (XOR)

 

 

 

а направо

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

9

 

Логическое ИЛИ (OR)

 

|

 

Слев

 

 

 

 

 

 

 

 

а направо

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

10

 

Условное И

 

&&

 

Слев

 

 

 

 

 

 

 

 

а направо

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

11

 

Условное ИЛИ

 

||

 

Слев

 

 

 

 

 

 

 

 

а направо

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

12

 

Условное выражение

 

? :

 

Спра

 

 

 

 

 

 

 

 

ва налево

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

13

 

Присваивание

 

= *= /= %= += -= <<= >>=

 

Спра

 

 

 

 

 

 

&= ^= |=

 

ва налево

 

 

 

 

 

 

 

 

 

 

ПЕРЕГРУЗКА ОПЕРАЦИЙ

Под перегрузкой операции понимается существование нескольких реализаций одной и той же операции. Большинство операций языка C#

перегружены - одна и та же операция может применяться к операндам различных типов. Поэтому перед выполнением операции идет поиск реализации, подходящей для данных типов операндов. Замечу, что операции,

как правило, выполняются над операндами одного типа. Если же операнды разных типов, то предварительно происходит неявное преобразование типа

96

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

///<summary>

///Анализ выражений

///</summary>

public void Express()

{

//перегрузка операций byte b1 =1, b2 =2, b3; short sh1;

int in1;

//b3 = b1 + b2; //ошибка: результат типа int

b3 = (byte)(b1+b2);

 

//sh1

= b1 +

b2;

//ошибка: результат типа int

sh1

=

(short)(b1+b2);

 

in1

=

b1+ b2

+ sh1;

 

Console.WriteLine("b3= " + b3 + " sh1= "+ sh1 +" in1= " + in1); }//Express

Разберем этот фрагмент. Начнем с первого закомментированного оператора присваивания b3 = b1+b2;. Выражение здесь простейшее, включает одну бинарную операцию сложения. Оба операнда имеют тип byte, казалось бы, и результат должен быть типа byte и без помех присвоен переменной b3.

Однако это не так. Для данных типа byte нет перегруженной реализации сложения. Ближайшей операцией является сложение целых типа int. Поэтому оба операнда преобразуются к типу int, выполняется операция сложения,

результат имеет тип int и не может быть неявно преобразован в тип byte, -

возникает ошибка еще на этапе компиляции. Корректная запись показана в следующем операторе. Аналогичная ситуация возникает, когда в левой части оператора стоит переменная типа short, - и здесь необходимо явное приведение к типу. Этого приведения не требуется, когда в левой части стоит переменная типа int.

97

Давайте разберем, как в данном примере организован вывод в методе

WriteLine. До сих пор при вызове метода задавалось несколько параметров и использовалась форма вывода данных с подстановкой значений параметров в строку, заданную первым параметром. Здесь же есть только один параметр -

это строка, заданная сложным выражением. Операция, многократно применяемая в этом выражении, это сложение " + ". Операнды сложения имеют разный тип: левый операнд имеет тип string, правый - арифметический

(byte, short, int). В этом случае арифметический тип преобразуется к типу string и выполняется сложение строк (конкатенация). Напомню, при преобразовании арифметического типа к типу string вызывается метод

ToString(), определенный для всех встроенных типов. Результатом этого выражения является строка, она и будет результатом вывода метода

WriteLine.

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

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

С чего начинается выполнение выражения

Вычисление выражения начинается с выполнения операций высшего приоритета. Первым делом вычисляются выражения в круглых скобках -

(expr), определяются значения полей объекта - x.y, вычисляются функции - f(x), переменные с индексами - a[i]. Выполнение этих операций достаточно понятно и не нуждается в комментировании. Операции checked и unchecked

включают и выключают проверку преобразований арифметического типа в

98

выражениях, которым они предшествуют. О других операциях этой

категории скажу чуть подробнее.

Операции "увеличить" и "уменьшить" (increment, decrement)

Операции "увеличить на единицу" и "уменьшить на единицу" могут быть префиксными и постфиксными. К высшему приоритету относятся постфиксные операции x++ и x--. Префиксные операции имеют на единицу меньший приоритет. Главной особенностью как префиксных, так и постфиксных операций является побочный эффект, в результате которого значение x увеличивается (++) или уменьшается (--) на единицу. Для префиксных (++x, --x) операций результатом их выполнения является измененное значение x, постфиксные операции возвращают в качестве результата значение x до изменения. Приведу пример применения этих операций, дополнив метод Express новым фрагментом:

//операции increment и decrement

//Следующее выражение допустимо, но писать подобное никогда не следует

in1 = ++in1 +in1+ in1++; //in2 = ++in1 + in1++ + in1;

Console.WriteLine(" in1= " + in1);

Обратите внимание, что хотя у постфиксной операции высший приоритет, это вовсе не означает, что при вычислении выражения вначале выполнится операция in1++, затем ++in1, и только потом будет проводиться сложение. Нет, вычисления проводятся в том порядке, в котором они написаны. Поскольку на входе значение in1 было равно 6, то выражение будет вычисляться следующим образом:

7(7) + 7 + 7(8),

где в скобках записан побочный эффект операции. Так что консольный вывод даст следующий результат:

99

in1 = 21

Операциями "увеличить" и "уменьшить" не следует злоупотреблять.

Уже оператор, приведенный в нашем фрагменте, сложен для понимания из-за побочного эффекта. Понимаете ли вы, что при изменении порядка записи слагаемых, как это сделано в закомментированном операторе, результат вычисления выражения будет уже не 21, а 22?

Разный приоритет префиксных и постфиксных операций носит условный характер. Эти операции применимы только к переменным, свойствам и индексаторам класса, то есть к выражениям, которым отведена область памяти. В языках C++ и C# такие выражения называются l-value, поскольку они могут встречаться в левых частях оператора присваивания. Как следствие, запись в C# выражения < --x++ > приведет к ошибке. Едва лишь к x слева или справа приписана одна из операций, выражение перестает принадлежать к классу l-value выражений, и вторую операцию приписать уже нельзя.

ОПЕРАЦИИ SIZEOF И TYPEOF

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

котором она может использоваться, должен быть скомпилирован с включенным свойством /unsafe. На рис. 6.1 показано, как на странице свойств проекта можно включить это свойство:

Далее необходимо создать небезопасный блок, например, метод класса,

помеченный как unsafe, в котором уже можно вызывать эту функцию

(операцию). Приведу пример такого метода, созданного в классе Testing:

100