Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
C# Лекция_3 Выражения и операции.docx
Скачиваний:
28
Добавлен:
18.12.2018
Размер:
667.21 Кб
Скачать
      1. Логические операции над булевскими операндами и целыми числами. Работа со шкалами

Рассмотрим логические операции, которые могут выполняться не только над булевскими значениями, но и над целыми числами. Высший приоритет среди этих операций имеет унарная операция отрицания (~x). Заметьте: есть две операции отрицания, одна из них (!x) определена только над операндами булевского типа, другая (~x) - только над целочисленными операндами.

Говоря о логических операциях над целыми числами, следует понимать, что целые числа можно рассматривать как последовательность битов (разрядов). Каждый бит, имеющий значение 0 или 1, можно интерпретировать как логическое значение обычным образом: 0 соответствует false, 1 - true. Логическая операция, применяемая к операндам одного и того же целочисленного типа, выполняется над соответствующими парами битов, создавая результат в виде последовательности битов и интерпретируемый как целое число. По этой причине такие логические операции называются побитовыми или поразрядными операциями.

Бинарных побитовых логических операций три - & , ^ , |. В порядке следования приоритетов это конъюнкция (операция "И"), исключающее ИЛИ, дизъюнкция (операция "ИЛИ"). Они определены как над целыми типами выше int, так и над булевыми типами. В первом случае они используются как побитовые операции, во втором - как обычные логические операции. Когда эти операции выполняются над булевскими операндами, то оба операнда вычисляются в любом случае, и если хотя бы один из операндов не определен, то и результат операции будет не определен. Когда необходима такая семантика логических операций, тогда без этих операций не обойтись.

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

        1. Шкалы

Побитовые логические операции широко применяются в реальном программировании при работе с так называемыми шкалами. Будем называть шкалой последовательность из n битов (n разрядов). Рассмотрим объект с n свойствами, каждым из которых объект может обладать или не обладать. Шкала позволяет однозначно задать, какими свойствами объект обладает, а какими нет. Пронумеруем свойства и будем записывать единицу в разряд с номером i, если объект обладает i-м свойством, и нуль - в противном случае.

Шкала позволяет экономно задавать информацию об объекте, а побитовые операции дают возможность весьма эффективно эту информацию обрабатывать. Поскольку эти операции определены над типами int, uint, long, ulong, C# может работать со шкалами длины 32 и 64.

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

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

// <summary>

/// Свойства претендентов на должность программиста,

/// описывающие знание технологий и языков программирования

/// </summary>

public enum Prog_Properties

{

VB = 1, C_sharp = 2, C_plus_plus = 4,

Web = 8, Prog_1C = 16

}

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

Свойства каждого претендента на должность характеризуются своей шкалой, которую можно рассматривать как переменную типа Prog_Properties. Задать шкалу претендента можно целым числом в интервале от 0 до , приведя значение к нужному типу. Например, так:

Prog_Properties candidate1 = (Prog_Properties)18;

Согласно шкале, этот кандидат знает язык C# и умеет работать в среде 1С. Более естественно шкалу кандидатов задавать с использованием логических операций над данными перечисления. Например, так:

Prog_Properties candidate2 = Prog_Properties.C_sharp |

Prog_Properties.C_plus_plus | Prog_Properties.Web;

Логические операции над шкалами позволяют эффективно реализовывать различные запросы, отбирая из массива кандидатов тех, кто соответствует заданным требованиям. Пусть, например, cand[i] - шкала i-го кандидата, а pattern - шкала, которая задает набор требований, предъявляемых к кандидатам. Рассмотрим условие:

(cand[i] & pattern) == pattern

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

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

Я написал отдельный класс Scales для работы со шкалами и перечислением Prog_Properties. Приведу несколько методов этого класса, позволяющих выполнять различные запросы к кандидатам.

/// <summary>

/// Список кандидатов, которые обладают

/// свойствами, заданными образцом.

/// </summary>

public ArrayList CandsHavePat()

{

ArrayList temp = new ArrayList();

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

if ((cand[i] & pattern) == pattern)

temp.Add("cand[" + i + "]");

return temp;

}

/// <summary>

/// Список кандидатов, которые не обладают

/// всеми свойствами, заданными образцом.

/// </summary>

public ArrayList CandsHaveNotAllPat()

{

ArrayList temp = new ArrayList();

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

if ((~cand[i] & pattern) == pattern)

temp.Add("cand[" + i + "]");

return temp;

}

/// <summary>

/// Список кандидатов, которые обладают

/// некоторыми свойствами, заданными образцом.

/// </summary>

public ArrayList CandsHaveSomePat()

{

ArrayList temp = new ArrayList();

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

{

currentScale = cand[i] & pattern;

if (currentScale > 0 && currentScale < pattern)

temp.Add("cand[" + i + "]");

}

return temp;

}

/// <summary>

/// Список кандидатов, которые обладают

/// только свойствами, заданными образцом.

/// </summary>

public ArrayList CandsHaveOnlyPat()

{

ArrayList temp = new ArrayList();

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

if (((cand[i] & pattern) == pattern) &&

((cand[i] & ~pattern) == 0))

temp.Add("cand[" + i + "]");

return temp;

}

Все эти методы устроены одинаково. Они отличаются условием отбора в операторе if, которое включает побитовые логические операции, выполняемые над шкалами cand и pattern, объявленными как массив переменных, и простой переменной перечислимого типа Prog_Properties. В качестве результата выполнения запроса возвращается массив типа ArrayList, который содержит список кандидатов, удовлетворяющих условиям запроса. На рис. 3.4 показаны результаты работы консольного приложения, в котором используется созданный класс и вызываются приведенные выше методы этого класса.

Рис. 3.4. Результаты запросов над шкалами