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

ооп теория

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

если первый операнд равен значению true, то второй операнд не вычисляется и результат операции равен true. Ценность условных логических операций заключается не в их эффективности по времени выполнения. Часто они позволяют вычислить логическое выражение, имеющее смысл, но в котором второй операнд не определен. Приведу в качестве примера классическую задачу поиска по образцу в массиве, когда разыскивается элемент с заданным значением (образец). Такой элемент в массиве может быть, а может и не быть. Вот типичное решение этой задачи в виде упрощенном, но передающем суть дела:

//Условное And - && int[] ar= {1,2,3};

int search = 7; int i=0;

while ((i < ar.Length) && (ar[i]!= search)) i++; if(i<ar.Length) Console.WriteLine("Образец найден"); else Console.WriteLine("Образец не найден");

Если значение переменной search (образца) не совпадает ни с одним из значений элементов массива ar, то последняя проверка условия цикла while

будет выполняться при значении i, равном ar.Length. В этом случае первый операнд получит значение false, и, хотя второй операнд при этом не определен, цикл нормально завершит свою работу. Второй операнд не определен в последней проверке, поскольку индекс элемента массива выходит за допустимые пределы (в C# индексация элементов начинается с нуля). Заметьте, что "нормальная" конъюнкция требует вычисления обеих операндов, поэтому ее применение в данной программе приводило бы к выбросу исключения в случае, когда образца нет в массиве.

Три бинарные побитовые операции - "& - AND " , "| - OR ", "^ - XOR"

используются двояко. Они определены как над целыми типами выше int, так и над булевыми типами. В первом случае они используются как побитовые операции, во втором - как обычные логические операции. Иногда

111

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

операций не обойтись. Вот пример первого их использования:

//Логические побитовые

операции

And, Or, XOR (&,|,^)

int k2 = 7, k3 =

5, k4,

k5,

k6;

 

k4 = k2 & k3; k5

= k2|

k3; k6 =

k2^k3;

Console.WriteLine("k4 =

" +

k4 + " k5 = " + k5 + " k6 = " + k6);

Приведу результаты вывода:

k4 = 5 k5 = 7 k6 =2

Приведу пример поиска по образцу с использованием логического AND:

i=0; search = ar[ar.Length - 1];

while ((i < ar.Length) & (ar[i]!= search)) i++; if(i<ar.Length) Console.WriteLine("Образец найден"); else Console.WriteLine("Образец не найден");

Вданном фрагменте гарантируется наличие образца поиска в массиве,

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

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

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

//Условное выражение int a = 7, b= 9, max; max= (a>b) ? a:b;

Console.WriteLine("a = " + a + "; b= " + b + "; max(a,b) = " + max);

Условное выражение начинается с условия, заключенного в круглые скобки, после которого следует знак вопроса и пара выражений, разделенных двоеточием " : ". Условием является выражение типа bool. Если оно истинно,

то из пары выражений выбирается первое, в противном случае результатом

112

является значение второго выражения. В данном примере переменная max

получит значение 9.

Операция приведения к типу

Осталось рассмотреть еще одну операцию - приведение к типу. Эта операция первого приоритета имеет следующий синтаксис:

(type) <унарное выражение>

Она задает явное преобразование типа, определенного выражением, к

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

чтобы такое явное преобразование существовало. Напомню, существуют явные преобразования внутри арифметического типа, но не существует,

например, явного преобразования арифметического типа в тип bool. При определении пользовательских типов для них могут быть заданы явные преобразования в другие, в том числе встроенные, типы. О явных преобразованиях говорилось достаточно много, приводились и примеры.

Поэтому ограничусь совсем простым примером:

//cast int p;

double x=3,14159; p = (int)x;

//b = (bool)x;

В данном примере явное преобразование из типа double в тип int

выполняется, а преобразование double в тип bool приводит к ошибке, потому и закомментировано.

113

Тема 7. ПРИСВАИВАНИЕ И ВСТРОЕННЫЕ ФУНКЦИИ

СОДЕРЖАНИЕ ЛЕКЦИИ

ПРИСВАИВАНИЕ

o СПЕЦИАЛЬНЫЕ СЛУЧАИ ПРИСВАИВАНИЯ o ОПРЕДЕЛЕННОЕ ПРИСВАИВАНИЕ

o ЕЩЕ РАЗ О СЕМАНТИКЕ ПРИСВАИВАНИЯ o РАССМОТРИМ ОБЪЯВЛЕНИЯ:

КЛАСС MATH И ЕГО ФУНКЦИИ КЛАСС RANDOM И ЕГО ФУНКЦИИ

114

ПРИСВАИВАНИЕ

В большинстве языков программирования присваивание - это оператор, а не операция. В языке C# присваивание унаследовало многие особенности присваивания языка C++. В C# оно толкуется как операция, используемая в выражениях. Однако в большинстве случаев присваивание следует рассматривать и использовать как обычный оператор.

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

///<summary>

///анализ присваивания

///</summary>

public void Assign()

{

double x,y,z,w =1, u =7, v= 5; x = y = z = w =(u+v+w)/(u-v-w);

}//Assign

По мере изложения в метод Assign будут добавляться фрагменты кода,

связанные с рассматриваемой темой присваивания.

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

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

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

115

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

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

несмотря на то, что переменная w первой получит значение, а выражение в правой части зависит от w, все переменные будут иметь значение 13.0.

Рассмотрим еще один фрагмент кода:

bool b; x=5; y=6; //b= x=y;

//if (x=y) z=1;else z=-1;

В программе на языке C++ можно было снять комментарии с операторов, и

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

операция присваивания "x=y" написана по ошибке и ее следует заменить операцией эквивалентности "x==y". В языке C# оба закомментированных оператора, к счастью, приведут к ошибке трансляции, поскольку результат присваивания имеет тип double, для которого нет неявного преобразования в тип bool. На C# такая программа будет выполняться, только если x и y будут иметь тип bool, но в этом случае, возможно, применение операции присваивания имеет смысл. С типами double корректная программа на C#

может быть такой:

x =y;

b= (y!=0);

if(y!=0) z=1; else z = -1;

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

порождаемая операцией присваивания.

116

СПЕЦИАЛЬНЫЕ СЛУЧАИ ПРИСВАИВАНИЯ

В языке C++ для двух частных случаев присваивания предложен отдельный синтаксис. Язык C# наследовал эти полезные свойства. Для присваиваний вида "x=x+1", в которых переменная увеличивается или уменьшается на единицу, используются специальные префиксные и постфиксные операции

"++" и "--". Другой важный частный случай - это присваивания вида:

X = X <operator> (expression)

Для таких присваиваний используется краткая форма записи:

X <operator>= expression

В качестве операции разрешается использовать арифметические, логические (побитовые)

операции и операции сдвига языка C#. Семантика такого присваивания достаточно

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

x += u+v; y /=(u-v); b &= (x<y);

Однако и здесь есть один подводный камень, когда x= x+a не эквивалентно x +=a. Рассмотрим следующий пример:

byte b3 = 21;

b3 +=1; //Это допустимо

//b3 = b3+1; //А это недопустимо:результат типа int

Закомментированный оператор приведет к ошибке компиляции, поскольку правая часть имеет тип int, а неявное преобразование к типу byte отсутствует.

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

117

ОПРЕДЕЛЕННОЕ ПРИСВАИВАНИЕ

Присваивание в языке C# называется определенным присваиванием (definite assignment). В этом термине отражен тот уже обсуждавшийся факт, что все используемые в выражениях переменные должны быть ранее инициализированы и иметь определенные значения. Единственное, за чем компилятор не следит, так это за инициализацией переменных массива. Для них используется инициализация элементов, задаваемая по умолчанию.

Приведу пример:

//определенное присваивание

int an =0 ; //переменные должны быть инициализированы for (int i= 0;i<5;i++)

{an =i+1;}

x+=an; z+=an; y = an; string[] ars = new string[3]; double[] ard = new double[3]; for (int i= 0;i<3;i++)

{

//массивы могут быть без инициализации ard[i] += i+1;

ars[i] += i.ToString()+1; Console.WriteLine("ard[" +i + "]=" +ard[i] +

"; ars[" +i + "]=" +ars[i]);

}

Заметьте, в этом фрагменте переменная an обязана быть инициализированной, а массивы ard и ars не инициализируются и спокойно участвуют в вычислениях.

ЕЩЕ РАЗ О СЕМАНТИКЕ ПРИСВАИВАНИЯ

Подводя итоги рассмотрения присваивания x=e, следует отметить, что семантика присваивания далеко не столь проста, как может показаться с первого взгляда. Напомню, что деление типов на значимые и ссылочные приводит к двум семантикам присваивания. Будет ли семантика значимой или ссылочной - определяется типом левой части присваивания. Переменные значимых типов являются единоличными владельцами памяти, в которой хранятся их значения. При значимом присваивании память для хранения

118

значений остается той же - меняются лишь сами значения, хранимые в ней.

Переменные ссылочных типов (объекты) являются ссылками на реальные объекты динамической памяти. Ссылки могут разделять одну и ту же область памяти - ссылаться на один и тот же объект. Ссылочное присваивание - это операция над ссылками. В результате ссылочного присваивания ссылка начинает указывать на другой объект.

РАССМОТРИМ ОБЪЯВЛЕНИЯ:

int x=3, y=5; object obj1, obj2;

Здесь объявлены четыре сущности: две переменные значимого типа и две -

объектного. Значимые переменные x и y проинициализированы и имеют значения, объектные переменные obj1 и obj2 являются пустыми ссылками со значением void. Рассмотрим присваивания:

obj1 = x; obj2 = y;

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

операции boxing - в динамической памяти создаются два объекта,

обертывающие соответственно значения переменных x и y. Сущности obj1 и obj2 получают значения ссылок на эти объекты.

КЛАСС MATH И ЕГО ФУНКЦИИ

Кроме переменных и констант, первичным материалом для построения выражений являются функции. Большинство их в проекте будут созданы самим программистом, но не обойтись и без встроенных функций. Умение работать в среде Visual Studio .Net предполагает знание встроенных возможностей этой среды, знание возможностей каркаса Framework .Net,

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

119

знакомство с возможностями, предоставляемыми пространством имен

System. Мы уже познакомились с классом Convert этого пространства и частично с классом Console. Давайте рассмотрим еще один класс - класс

Math, содержащий стандартные математические функции, без которых трудно обойтись при построении многих выражений. Этот класс содержит два статических поля, задающих константы E и PI, а также 23 статических метода. Методы задают:

тригонометрические функции - Sin, Cos, Tan;

обратные тригонометрические функции - ASin, ACos, ATan, ATan2 (sinx, cosx);

гиперболические функции - Tanh, Sinh, Cosh;

экспоненту и логарифмические функции - Exp, Log, Log10;

модуль, корень, знак - Abs, Sqrt, Sign;

функции округления - Ceiling, Floor, Round;

минимум, максимум, степень, остаток - Min, Max, Pow, IEEERemainder.

В особых пояснениях эти функции не нуждаются. Приведу пример:

///<summary>

///работа с функциями класса Math

///</summary>

public void MathFunctions()

{

double a, b,t,t0,dt,y; string NameFunction;

Console.WriteLine("Введите имя F(t)исследуемой функции a*F(b*t)" + " (sin, cos, tan, cotan)");

NameFunction = Console.ReadLine(); Console.WriteLine("Введите параметр a (double)"); a= double.Parse(Console.ReadLine()); Console.WriteLine("Введите параметр b (double)"); b= double.Parse(Console.ReadLine());

Console.WriteLine("Введите начальное время t0(double)"); t0= double.Parse(Console.ReadLine());

const int points = 10; dt = 0.2;

for(int i = 1; i<=points; i++)

{

t = t0 + (i-1)* dt; switch (NameFunction)

{

case ("sin"):

120