×
Ви дійсно бажаєте відкрити доступ до тестування за курсом C# 5.0 Стартовый на 40 днів?
ВІДЕОУРОК № 5. Логические операции в C#
Здравствуйте. Тема сегодняшнего урока – логические и побитовые операции. На этом уроке мы рассмотрим логические функции, такие как конъюнкция, дизъюнкция, исключающее или. Не пугайтесь. Это очень просто. Рассмотрим их использование в контексте побитовых логических операций. Также мы рассмотрим работу операторов сдвига, как мы можем сдвигать биты в байте. Короткозамкнутые вычисления. Немного рассмотрим теоремы де Моргана. Это достаточно важные теоремы. Также мы в с вами рассмотрим сегодня еще основы машинной математики для работы со знаковыми числами.
Давайте сперва поговорим о логике. Логика – классическая наука. Какое основное понятие, которым манипулирует логика – это высказывание. У нас бывает два типа высказываний. Это истинное высказывание или ложное высказывание. Мы понимаем, что истинное высказывание – это правильное высказывание, которое удовлетворяет истинности. Например, солнце горячее. Ложное высказывание – Земля плоская. Обратите внимание, высказывание – как утверждение. Я не спрашиваю Земля плоская? Я утверждаю – Земля плоская. Солнце горячее. Луна квадратная. Самолет медленный. Вы скажете, что самолет быстрый. Это, например, черепаха медленная. Давайте мы сейчас с вами зайдем в текстовый документик и посмотрим. Здесь у меня заготовлено уже несколько высказываний. Обратите внимание, переменная а равна лжи, потому что мы здесь использовали высказывание «Земля плоская». Мы знаем, что это ложь. Далее, переменная b равна тоже лжи, потому что мы использовали высказывание «Луна квадратная». Третья переменная с – истина – «Солнце яркое». D – самолет быстрый – истина. Обратите внимание, у нас имеются 4 логические переменные, которым присвоено некоторое значение. Вот а и b – ложь, как результат этих высказываний. Переменным с и d присвоено значение истина, как результат других двух высказываний. И теперь мы с вами поговорим о логических операциях. Которые мы можем производить над высказываниями. Смотрите, иногда мы пытаемся комплексно соврать. Я говорю – земля плоская и луна квадратная. Это комплексный обман, это комплексная ложь. Земля плоская – ложь, и луна квадратная – тоже ложь. Обратите внимание, я использую союз «И». Так вот этот союз «И» в логике называется конъюнкцией. Получается, что если мы используем несколько высказываний, даже больше двух и при этом используем логическую операцию конъюнкции, то у нас работает такое правило, что если из цепочки высказываний хотя бы одно будет ложным, то в итоге все мое длинное высказывание будет ложным. Дело в том что для логических операций, которые мы будем сегодня разбирать. Это конъюнкция, дизъюнкция, исключающее или, отрицание имеются свои таблицы истинности, которые и описывают все варианты возможного использования высказываний и работы с ними. И давайте мы сейчас подойдем к первой логической операции – конъюнкции. Перейдем сейчас на слайд и посмотрим на таблицу истинности конъюнкции. Мы еще будем возвращаться вот к этому файлику и здесь записывать свои логические формулы.
На этом слайде мы видим описание логической операции. Конъюнкция – союз «И» по-английски – AND. Обратите внимание, значок в языке программирования – два амперсанда &&. Конъюнкция – это логическая операция, по своему применению максимально приближенная к союзу «И». Смотрите, у нас здесь два операнда. Операнд – это результат высказывания. Каждый операнд может принимать одно из двух значений: либо true, либо false. C этими значениями мы ранее использовали логические переменные типа bool. Потому что был такой математик – Джордж Буль – основоположник алгебры логики. Мы с вами говорили, что каждая логическая операция конъюнкция, дизъюнкция, исключающее или, отрицание имеют свои таблицы истинности. Вот давайте посмотрим – это и есть таблица истинности для логической операции – конъюнкция. Не пугайтесь. Вот как в обычной арифметике есть знак +, -, *, /, Так же и в логике для работы с высказываниями есть тоже свои знаки. Знак && обозначает конъюнкцию. И как в обычной арифметике есть таблица сложения, та и в логике есть свои таблицы, только не сложения, а таблицы истинности для конъюнкции, для дизъюнкции, дли исключающего или, для отрицания. Давайте посмотрим на таблицу истинности конъюнкции. Если у нас Имеется два высказывания. Первое истинное и второе истинное, то в результате получаем истину. Я говорю, что луна круглая и солнце горячее. Если мы имеем два ложных высказывания. Я говорю, что Земля плоская и Солнце холодное – это ложь. Обратите внимание, на вторую половину таблицы, если первое высказывание истинно а второе ложно – Земля круглая и Солнце холодное – это ложь, false. Потому что я обманул. Если я поменяю местами и скажу, что Солнце холодное и Земля круглая – мы тоже получаем false. Обратите внимание, у нас работает переместительный закон, как при сложении, или как при умножении. От перемены мест логических операндов результат операции конъюнкции у нас не изменится. Давайте теперь разберем следующую логическую операцию – это дизъюнкция.
Дизъюнкция соответствует союзу OR – ИЛИ - || - это и есть знак обозначение дизъюнкции. Мы можем использовать операнд1 || операнд2. У нас имеются два операнда и между ними имеется знак ИЛИ. Дизъюнкция – логическая операция, по своему применению максимально приближенная к союзу «или» в смысле «или то, или это, или оба сразу». Обратите внимание, на таблицу истинности операции дизъюнкции. Два истинных высказывания. Я говорю: «Солнце горячее ИЛИ Земля круглая» - истина. Я говорю: «Солнце холодное или Луна квадратная» - false. Я говорю – «Солнце горячее или Луна квадратная» - или то, или то, в итоге – истина. Обратите внимание, здесь мы снова же поменяли местами. Я говорю, что Луна квадратная или Солнце горячее. Снова сработал переместительный закон. То есть от перемены мест логических операндов результат не изменяется, поэтому мы видим, что в логике работает переместительный закон. И поэтому мы видим, что этот двойной вертикальный слеш, мы можем заменить обычным человеческим ИЛИ. Значит еще раз смотрим. Мы разобрали Две логические операции. Конъюнкция – логические И. И это дизъюнкция – логическое ИЛИ. Давайте еще раз вспомним таблицу истинности конъюнкции. Мы помним, что у нас true в результате этой операции получится только в одном случае если у нас оба операнда этой операции будут истинными. Только лишь один операнд будет false, то результат будет ложный. Вернемся в дизъюнкцию. А здесь у нас все true и только один false. Обратите внимание, здесь наоборот, если только лишь один операнд будет истинным в высказывании, то и результат высказывания будет истинным. Поэтому часто говорят, что конъюнкция напоминает нам умножение. Если мы true представим 1, false представим 0. Мы говорим, что 1*1=1. 0*0=0. 1*0=0. 0*1=0. А вот дизъюнкция, ее еще называют логическим сложением. 1+1=1. 1+0=1. 0+1=1. 0+0=0. Две важные логические операции: конъюнкция – И, дизъюнкция ИЛИ. У каждой из этих операций имеется своя таблица истинности, запомните пожалуйста. Программист должен эти таблицы истинности знать наизусть, как каждый человек знает таблицу сложения или таблицу умножения. Зачем? Для того чтобы можно было нормально взаимодействовать с другими людьми, рассчитываться в магазине, чтобы мы не обманули продавца, так же и здесь в программировании. Программисту нужно обязательно знать эти таблицы истинности. Тем более, обратите внимание, какие они короткие и простые. А мы с вами идем дальше к рассмотрению других логических операций.
Давайте перейдем к рассмотрению следующей логической операции – исключающее ИЛИ. Она еще проще чем конъюнкция и дизъюнкция. Еще проще запомнить ее таблицу истинности. Обратите внимание, вот этот значок ^ он как раз в программировании и используется для обозначения операции исключающего ИЛИ. У нас имеется два операнда и между ними стоит этот значок. Давайте посмотрим определение, Результат выполнения операции является истинным, только при условии, что один операнд является истинным, а другой ложным. Давайте посмотрим на таблицу истинности. Обратите внимание, здесь два истинных операнда, результат – ложь. Два ложных – ложь. Обратите внимание, как здесь было написано. Один истинный второй ложный – истина. Один ложный второй истинный – истина. Мы тоже видим, что работает переместительный закон, от перемены места операндов в логическом выражении результат у нас не поменяется. Поэтому исключающее ИЛИ запомнить еще проще. Если одинаковое – ложь. Если разное – истина. Если значение операндов одинаковое – результат ложь. Если значение операндов отличается – то результатом будет истина.
И следующая, последняя логическая операция, которую мы рассмотрим – это отрицание. Обратите внимание, восклицательный знак, NOT – НЕ – мы отрицаем. Если я перед своим высказыванием поставлю НЕ. Я отрицаю свое высказывание, то есть я говорю, что солнце не холодное. Обратите внимание, на таблицу истинности. НЕ false = true. НЕ ложь – это истина. НЕ истина – ложь. Самая маленькая таблица истинности. Смотрите, мы с вами рассмотрели 4 логических операции. Это конъюнкция – логическое И, это дизъюнкция – логическое ИЛИ, это исключающее ИЛИ, и это отрицание. Обратите внимание, 4 логических операции конъюнкция, дизъюнкция, исключающее ИЛИ, отрицание. Вы спросите, а где же это используется? Подождите. Мы с вами сегодня очень подробна разберем использование логических операций на примерах, рассмотрим варианты использования их, где же программисты могут использовать логические операции. Вот например, где мы можем использовать конъюнкцию? Представьте себе, что я американский агент, который возвращается к себе в ЦРУ и для того, чтобы попасть в свое здание разведки, нужно чтобы мне просканировали сетчатку глаза, сняли отпечаток. Я подхожу и сканер глаза узнает, что глаз мой. Но отпечаток пальца не мой. Можно такого человека пропустить? Нет! Обратите внимание, конъюнкция. И глаз должен совпасть И внешность должна совпасть и отпечаток пальца должен совпасть, только в этом случае откроется дверь в секретный бункер. А кто проверяет это все? Кто управляет всеми этими сканерами? Конечно же программы. Это, конечно, фантастический пример, но мы с вами рассмотрим более подробно использование логических операций. А теперь мы с вами перейдем к рассмотрению еще одной интересной возможности – к работе с битовыми логическими операциями.
Для этого мы с вами зайдем в наш документ и поработаем немного с битами. Представьте, что у меня есть число 2 и число 4 и я хочу их сложить побитово. Берем тетраду 0010 – это число 2. Ниже записываем число 0100 – 4. Теперь подчеркиваем все это и начинаем с левой стороны складывать. 0+0=0. 0+1=1. 1+0=1. 0+0=0. Смотрите, мы взяли и в столбик сложили два двоичных числа. Здесь использовался знак сложения. И в результате мы получили какое число? 6. 2+4=6. Так вот мы здесь видим, что мы выполняли операции арифметические. Такие операции над битами мы можем выполнять, только логические. А какие мы знаем логические операции? Это конъюнкция, дизъюнкция, исключающее ИЛИ, отрицание. Давайте посмотрим, вот мы берем за основу 1 – это истина. 0 – ложь. Если мы возьмем значения байта 1010 1111 – AF. Можем взять какое-нибудь число попроще, например, код буквы А – 0100 0001 – вот это кодировка буквы А. Получается, что мы можем выполнить операцию конъюнкции, дизъюнкции, исключающего ИЛИ, отрицания. Представьте себе, что если мы будем выполнять операцию дизъюнкции. Вот у нас первый конъюнкт, а вот второй. И мы хотим так же в столбик выполнить такую операцию. 1 И 1=1. 1 И 0 = 0. 1 И 0 = 0. 0 И 0 = 0. 1 И 0 = 0. 0 И 1 = 0. 1 И 0 = 0. И в результате этой операции получаем число 0000 0001. Потому что логическое И даст в результате истину – 1. Это кстати придумал Джорж Буль, он заменил true и false на 1 и 0 в своей алгебре. Она так и называется – булева алгебра. Проще запись. Смотрите, мы берем в столбик первый операнд, второй операнд и получаем результат. А если мы возьмем дизъюнкцию. Смотрим, 1 ИЛИ 1=1. 1 ИЛИ 0 = 1. 1 ИЛИ 0 = 1. 0 ИЛИ 0 = 0. 1 ИЛИ 0 = 1. 0 ИЛИ 1 = 1. 1 ИЛИ 0 = 1. Смотрите, как у нас сработала дизъюнкция. Какая у нас еще операция была? Исключающее ИЛИ. А там у нас работало правило, если разное, то истина, если одинаковое – 0. Разное – истина. Разное – истина. Разное – истина. Одинаковое – 0. Разное – истина. Разное – истина. Разное – истина. Одинаковое – 0. Обратите внимание, что это была за операция? Исключающее ИЛИ. А как же работает отрицание? Дело в том, что операция отрицания она не работает с двумя операндами, она работает только с одним операндом. И что она делает? Она инвертирует. Она просто превращает 1 в 0, а 0 в 1. Заметьте. Вот как сработало отрицание. Оно просто инвертировало тот операнд, к которому мы применили знак отрицания. Давайте перейдем к нашей презентации и посмотрим, какими же значками представлены именно битовые логические операции. Мы видим, что у нас две разновидности логических операций в программировании: работа с true и false, с высказываниями, и работа с отдельными битами в байте. А зачем з битами вот это делать? Подождите. Еще немного теории и мы перейдем к практическим примера и посмотрим где используются такие операции, это очень важные операции. Это супер важные операции. Программист не может работать без этих операций.
Теперь переходим к нашей презентации. С чего мы начинали сначала? Конъюнкция над высказываниями. А теперь мы хотим использовать конъюнкцию с битами. Побитовое И. Если у нас в обычной конъюнкции был двойной амперсанд то при работе с битами мы будем использовать одинарный. То есть если у нас имеется два байта и у них записаны некие значения и мы хотим произвести над ними… Вот смотрите у нас имеется два байта. Одно число 255 и второй операнд – это 1 и здесь мы выполняем операцию побитовой конъюнкции. Обратите внимание, в столбик. А здесь мы записываем в десятичном виде. Я говорю: 255 И 1. Мы только на языке программирования так записываем, потому что имеется такая запись, потому что разработчики языка программирования позволяют так делать, потому что они подразумевают, что вы можете представлять себе число в двоичном виде. Обратите внимание, та же самая таблица истинности. Конъюнкция. 1 И 1 будет 1. Это напоминает логическое умножение. Во всех остальных случаях у нас будет false, переместительный закон. Следующая операция – это дизъюнкция. С дизъюнкцией мы тоже можем работать побитово. Давайте посмотрим, обратите внимание, побитовое, логическое ИЛИ. Вот ее таблица истинности. Во всех случаях будет 1, кроме того, когда оба бита ложные. 0 – ложь, 1 – истина. Так решил Джордж Буль – основоположник булевой алгебры, которая строится на Аристотелевой классической логике, ее еще называют пропозициональной. И теперь смотрите, мы здесь переменной result присваиваем возвращаемое значение операции дизъюнкции, побитового ИЛИ. Мы говорим 2 ИЛИ 1. В столбик, как в школе. И так смотрим. 0 ИЛИ 1 = 1. 1 ИЛИ 0 = 1. 0 ИЛИ 0 = 0. 0 ИЛИ 0 = 0. 0 ИЛИ 0 = 0. 0 ИЛИ 0 = 0. 0 ИЛИ 0 = 0. 0 ИЛИ 0 = 0. В результате мы получили 0000 0011 = 3. Если вы посмотрите в ту табличку, где были расписаны все комбинации вы увидите, что это число соответствует 3. Кто-то это запомнил, кто-то нет, но это желательно запомнить, хотя бы самые простейшие. Значит мы посмотрели побитовое И – конъюнкция, побитовое ИЛИ – дизъюнкция. А теперь рассмотрим исключающее ИЛИ – XOR. Есть побитовое? Давайте посмотрим, и здесь есть исключающее ИЛИ для работы с битами. Смотрите, у нас имеется два байта 3 и 1. Обратите внимание, вот мы их расписываем в столбик. 3 выглядит вот так 0000 0011 в байте, а 1 – вот так 0000 0001. А какое у нас было правило исключающего ИЛИ. Смотрим. Если операнды одинаковые, то результат 0. Если операнды разные, то результат 1. Смотрите, 1 XOR 1 = 0. 1 XOR 0 = 1. А дальше идут нули. 3 ^ 1 = 2. Разработчики языка подразумевают что вы сможете перевести или представить эти числа. Кто-то в голове, кто-то на листочке. 3 XOR 1 будет 2. Мы рассмотрели с вами три логические операции с вами, для работы с битами. Это: конъюнкция, дизъюнкция, исключающее ИЛИ. А где же отрицание? Имеется ли побитовое отрицание? Давайте посмотрим. Конечно же. Обратите внимание, значок побитового отрицания отличается от обычного значка логического отрицания. Если мы работаем с обычными высказываниями, то мы используем exclamation mark. А здесь мы используем значок, который называется тильда. Почему его так назвали я не знаю, но знаю, что в ветхославянской системе для обозначения чисел использовался тоже такой знак и он назывался титло, но мы называем этот значок тильдой. Обратите внимание, значок немножко другой, но нас это не должно смущать. Давайте посмотрим на таблицу истинности. Вот она самая простая таблица истинности для побитового отрицания. НЕ 0 = 1. НЕ 1 = 0. Чем отличается побитовое отрицание от трех предыдущих логических функций? Та потому что конъюнкция и дизъюнкция работаю з двумя операндами, а NOT работает только с одним. Смотрите что у нас получается. Мы берем отрицаем 1 и при этом в результате получаем -2. Интересно, как это могло получится. Давайте посмотрим, что у нас здесь происходит в столбик, ага у нас имеется число 1, мы применяем значок отрицания, это значит, что 1 превращается в 0. А все 0 превращаются в 1. В итоге получилось вот такое число. Но вы же скажете, что это FE. А здесь написано -2. Мы еще не знакомы с двоичной арифметикой, которая используется для работы со знаковыми числами. Мы раньше слышали, что если здесь в каком-то случае стоит единица, то это может быть отрицательным числом, если 0 то положительным числом. Возникает много вопросов. И как раз вот эту арифметику мы с вами сейчас и разберем, чтобы нам понять почему же у нас оказался вот такой эффект. Почему просто инвертировав обычную единицу мы получили в результате -2. Итак опять числа. Давайте перейдем в наш файлик и посмотрим на работу простейшей арифметики. Начнем с десятичной системы. Смотрите, я говорю 7-3 = 4. Как мы можем по-другому выполнить операцию вычитания. Мы знаем, что в математике нет операции вычитание. Это искусственная операция. Все происходит за счет сложения. Значит нам нужно смотрите что сделать. Изменить эту запись. Что нам надо прибавить к 7, чтобы получить 4? Нужно прибавить десятичное дополнение, то есть -3. 7+(-3) = 4. Особенности вычислительной машины. У компьютеров есть такая особенность, устройство внутри АЛУ, которое называется двоичным сумматором. Но там нет никакого двоичного вычитатора. Есть только двоичный сумматор. А как же он производит вычитание, например? Давайте посмотрим, как мы можем в двоичном виде представить число 7. Если не помните – смотрите в таблицу. Это 0111. Я хочу взять 0111 и из него вычесть 3. А как выглядит тройка? 0011. В итоге мы должны получить 0100. Правильно? Правильно. Смотрите, та же самая запись. Но машина не умеет вычитать на самых низких уровнях АЛУ. Потому что там есть такое устройство, которое называется двоичный сумматор. Поэтому у него не знака операции вычесть. Нет. Значит нам нужно получить некое двоичное дополнение числа 3. Как же это сделать? Откуда вообще взялось это двоичное дополнение. Как нам вообще его получить. Получается, что если мы работаем с натуральными числами… Мы уже видели состав нашего байта. Байт состоит вот с такого диапазона 0000 0000, 0000 0001, 0000 0010, 0000 0011, …, 1111 1110, 1111 1111. Обратите внимание, но если мы начнем вот что делать. Если мы хотим, чтобы мы работали со знаковыми числами, потому что этот диапазон представления без знаковых чисел. То нам нужно весь диапазон разделить на 2 части. На какие? А вот на какие. 0000 0000, 0000 0001, …, 0111 1111, 1000 0000, …, 1111 1111. И поэтому 0000 0000 – 0, 0000 0001 – 1, 0111 1111 – 127, 1111 1111 = -1. Почему здесь -1, потому что впереди идет лидирующая единица. Все что начинается с 1 – это отрицательные числа, выделим их красным, а положительные числа выделим синим. Обратите внимание, если мы интерпретируем двоичное число как знаковое, то нам необходимо учитывать этот самый старший бит. А это 1000 0000 = -128. Обратите внимание, -128. Итого получается 128 комбинаций положительных вместе с нулем, и 128 комбинаций отрицательных. Получается, что мы разделили один байт на 128 отрицательных, 127 положительных и 0. Но его не очень то хотелось относить к положительным, пусть он будет просто 0. Обратите внимание, весь диапазон чисел разделился на 2 части: на отрицательные и на положительные. И как мы видели с вами выше отрицательное число может являть чем в нашей десятичной системе? Десятичным дополнением, что бы мы смогли произвести вычитание через сложение. Отрицательное число является десятичным дополнением, а в двоичной системе отрицательное число будет являться чем? Двоичным дополнением. Для того чтобы мы через операцию сложения произвели вычитание. Потому что у нас имеется двоичный сумматор, который может только складывать числа, он их не может вычитать. Этого электронщики не сделали. Они говорят, что если вы хотите вычитать, организуйте вычитание за счет сложения с двоичным дополнением. В качестве двоичного дополнения может выступать что? Отрицательное двоичное число с лидирующей 1. Это некая искусственная математика. Если вы изучали теорию чисел, то вам будет более понятнее. Если вы ее не изучали, то какие-то моменты вам нужно просто принять и запомнить. Потому что теория чисел – это целый большой раздел в математике, который изучает именно работу числа. Мы, к сожалению, не можем рассмотреть всю теорию чисел сейчас. Но мы сейчас проверим как же у нас идет работа с двоичными дополнениями. Сейчас мы придумаем какой-нибудь пример. Мы видим, что у нас имеются вот здесь тестовые числа, который мы придумали: 7, положительная 3. К сожалению, машина не сможет выполнить такую операцию, у нее нет устройства, которое вычитает, и результат этой операции. Теперь мы хотим как-то сымитировать, мы хотим получить двоичное дополнение какого числа? 3. У нас имеется положительная тройка 0011, теперь мы хотим получить ее двоичное дополнение. Для этого имеется формула. Давайте перейдем и посмотрим на эту формулу. Обратите внимание, формула изменения знака числа. Мы берем какое-нибудь число положительное применяем к нему операцию отрицания, берем сначала нашу тройку. Применяем к ней операцию отрицания и потом прибавляем единицу. В результате мы получим -3. Сначала отрицаем ее, прибавляем единицу и получаем -3. Обратное преобразование. Смотрите, мы берем -3, отрицаем ее, прибавляем единицу и получаем положительную тройку. Обратите внимание, на вот этот пример. Здесь у нас имеются положительные единицы. Для того чтобы получить отрицательную единицу, что мы должны сделать? Мы должны применить отрицание. Инвертировали. Вы видите, единицы превратили в нули, а нули в единицы. Это первая операция – отрицание. И дальше +1. И теперь мы к этому полученному числу, которое пока еще, если его перевести будет -2, инвертированная единица нам даст просто -2. Это не правильно. И для того, чтобы скорректировать результат, мы должны взять и прибавить вот эту единичку. И у нас получилось что? Все единицы. У нас получилось -1. И теперь если мы хотим отрицательную единицу перевести в положительную, мы что делаем. Инвертируем все единицы и получаем все нули, прибавляем ко всем нулям единицу и получаем вот такую настоящую положительную единицу. Просто? Просто! Даже глубоко не понимая работу с двоичной знаковой арифметикой мы можем просто взять вот эту формулу и доверять ей. Если мы хотим перевести положительное число в отрицательное, что мы делаем? Мы его инвертируем и прибавляем единицу. А отрицательное в положительное, мы отрицательное число инвертируем, прибавляем единицу и получаем положительное. Возвращаемся к нашей формуле. Нам ее нужно как-нибудь рассчитать. Какое у нас число - +3. Это положительная тройка. Ее нужно преобразовать в отрицательную. Вы не помните, что мы там смотрели? А, первое инвертирование. Нули заменяем на единицы, а единицы на нули. Смотрите, и что это за число? Это число С. Или какое число? Конечно же – 12! Так, наша тройка превратилась вот в такое число 1100. Но это если мы его представляем без учета первого знака. Хорошо. И теперь что мы должны сделать? Прибавить единицу. Как она прибавляется? 0001. И в результате получаем что? Сначала инвертировали тройку и получили ее задом на перед. Все нули и единицы преобразовались, и прибавляем ему что? Единицу. И получается 1101. И вот эта лидирующая единица говорит нам о том, что это отрицательная трока. А этот ноль нам говорил о чем, о том что здесь была положительная тройка. А можно обратно? Давайте пробовать! Что нужно сначала сделать? Единицу прибавить? А, инвертировать, точно – 0010. Мы получили положительную тройку. И теперь что нужно сделать? Добавить единицу. У нас в результате получается вот такое число 0011. Вы видите? Мы получили обратно нашу положительную тройку. Так значит формуле мы уже верим. Давайте еще зайдем раз посмотрим на формулу изменения знака числа в двоичной арифметике. Сначала мы инвертируем любое число, то ли оно положительное, то ли оно отрицательное. Проинвертировали. Получили вообще не нужное и не адекватное число. Некорректное. И для того, чтобы его скорректировать нужно прибавить единицу. Грубо формула звучит: Инверт плюс один. А мы возвращаемся и хотим дальше посчитать нашу формулу. Мы получили отрицательную тройку. И теперь нам нужно выполнить операцию 7+(-3). Мы должны сделать так 0111 + 1101 и мы должны получить какое-то число. Пока я не знаю, что это за число. По идее здесь должна получится четверка. Положительная четверка. Для этого мы должны считать в столбик. И в результате мы будем смотреть что у нас получится. 1+1=10. Один в уме. 1+0+1=10. Один в уме. 1+1+1=11. Один в уме. 0+1+1=10. Обратите внимание, что у нас получается. У нас получилось 5 цифр. Мы их сейчас сдвинем. Получается, что 7+(-3) = 4. А что это за единица впереди? А я уберу ее. Эта единица вообще вылетела за пределы допустимого диапазона например байта и потерялась. Она зависла в регистре флагов и мы ее проигнорировали вообще. У нас получилось число 100. А 100 это у нас какое число? Это +4 – положительная четверка. Заметили? Вы видите какая хитрость? И теперь мы видим, что 0111+1101=0100 У нас получилась четверка. Сложно? Ну это пока вы не привыкли. Я бы сказал, что не очень сложно сколько немного подзапутанно. Но мы с вами понимаем, что если мы не совсем хорошо ориентируемся в этой математике, то нам помогает язык C#. Как он здесь может помочь? Очень просто! Вы помните int и uint. Смотрите 2 типа. Uint это какой? Без знаковый, а int – знаковый. Если я создаю переменную int a, то я ей смело могу присвоить -456. Какое-то отрицательное число. И получается, что переменная типа int может хранить в себе знаковые числа. Создаем переменную Uint b. Я ей не могу присваивать число со знаком, я ей могу присвоить только натуральное число. У меня имеется 22 яблока, например. И поэтому в языке C# все вот эти моменты уже предусмотрены и они контролируются чем? Вот этими волшебными переменными. Вот зачем нужны знаковые и без знаковые типы данных. Для того чтобы упростить работу. Мы должны били сами контролировать вот эту математику, сами контролировать команды ассемблера, контролировать знак числа, и сами должны были его интерпретировать. И одно и то же число, допустим, -3 я мог представить, как положительное, так и отрицательное. Я должен понимать, что это число FD. Либо это число -3. Это все зависело от интерпретации программиста и было абсолютно неочевидно другому программисту, который читал эту программу. Здесь уже четко понятно. Если это int переменная, здесь знаковое число. Если идут лидирующие единицы, то понятно, что это отрицательное число. Если лидирующие нули, то это положительное число. Ну, а если это uint, то мы работаем с натуральным рядом. Яблоки, груши, сливы и всего много вкусного. Продолжаем. А сейчас мы посмотрим программный код, и где все то, что мы с вами рассмотрели. Мы с вами рассмотрели логические операторы, логические функции конъюнкция, дизъюнкция, исключающее ИЛИ. Мы с вами рассмотрели побитовые операции. А также мы с вами рассмотрели технику изменения знака двоичного числа, двоичное дополнение. Организация вычитания за счет сложения числа с двоичным дополнением, а мы переходим в Visual Studio и смотрим первый пример.
1: using System; 2: 3: // Побитовые логические операции. 4: 5: namespace Logic 6: { 7: class Program 8: { 9: static void Main() 10: { 11: byte operand1 = 0, operand2 = 0; 12: int result; 13: 14: #region Конъюнкция 15: 16: // Таблица истинности для операций Конъюнкции (И) - & - [AND] 17: 18: // Если хоть один из операндов имеет значение 0 - вся конструкция имеет значение 0. Иначе - 1 19: 20: // 0 & 0 = 0 1 & 0 = 0 21: // 0 & 1 = 0 1 & 1 = 1 22: 23: operand1 = 0xFF; // [1111 1111 Bin] = [FF Hex] = [255 Dec] 24: operand2 = 0x01; // [0000 0001 Bin] = [01 Hex] = [01 Dec] 25: result = operand1 & operand2; // [0000 0001 Bin] = [01 Hex] = [01 Dec] 26: 27: Console.WriteLine("{0} AND {1} = {2}", operand1, operand2, result); 28: 29: #endregion 30: 31: #region Дизъюнкция 32: 33: // Таблица истинности для Дизъюнкции (ИЛИ) - | - [OR] 34: 35: // Если хоть один из операндов имеет значение 1 - вся конструкция имеет значение 1. Иначе - 0 36: 37: // 0 | 0 = 0 1 | 0 = 1 38: // 0 | 1 = 1 1 | 1 = 1 39: 40: operand1 = 0x02; // [0000 0010 Bin] = [02 Hex] = [02 Dec] 41: operand2 = 0x01; // [0000 0001 Bin] = [01 Hex] = [01 Dec] 42: result = operand1 | operand2; // [0000 0011 Bin] = [03 Hex] = [03 Dec] 43: 44: Console.WriteLine("{0} OR {1} = {2}", operand1, operand2, result); 45: 46: #endregion 47: 48: #region Исключающее ИЛИ 49: 50: // Таблица истинности для Исключающего ИЛИ - ^ - [XOR] 51: 52: // Если операнды имеют одинаковое значение - результат операции 0, 53: // Если операнды разные - 1 54: 55: // 0 ^ 0 = 0 1 ^ 0 = 1 56: // 1 ^ 1 = 0 0 ^ 1 = 1 57: 58: operand1 = 0x03; // [0000 0011 Bin] = [03 Hex] = [03 Dec] 59: operand2 = 0x01; // [0000 0001 Bin] = [01 Hex] = [01 Dec] 60: result = operand1 ^ operand2; // [0000 0010 Bin] = [02 Hex] = [02 Dec] 61: 62: Console.WriteLine("{0} XOR {1} = {2}", operand1, operand2, result); 63: 64: #endregion 65: 66: #region Отрицание 67: 68: // Таблица истинности для Отрицания (НЕТ) - ~ - [NOT] 69: 70: // ~ 0 = 1 // [0000 0001] = [01 Dec] 71: // ~ 1 = 0 // [1111 1110] = [-2 Dec] 72: 73: operand1 = 0x01; 74: result = ~operand1; 75: 76: Console.WriteLine("NOT {0} = {1}", operand1, result); 77: 78: #endregion 79: 80: #region Изменение знака числа 81: 82: // Формула изменения знака числа, с (+N) на (-N) или наоборот. 83: 84: // Для того, чтобы сменить знак числа, необходимо: 85: // выполнить его отрицание, а результат увеличить на 1. 86: 87: // ~ +N + 1 = -N 88: // ~ -N + 1 = +N 89: 90: operand1 = 0x01; // [0000 0001] 91: result = ~operand1; // [1111 1110] 92: result++; // [1111 1111] 93: 94: Console.WriteLine(" ~ {0} + 1 = {1} ", operand1, result); 95: 96: #endregion 97: 98: // Delay. 99: Console.ReadKey(); 100: } 101: } 102: }
Обратите внимание, на 11 строке мы создаем две переменные типа byte с именами operand1 и operand2 и присваиваем им значение 0 и 0. На 12 строке у нас имеется целочисленная переменная result. И теперь смотрим, здесь у нас имеются блоки: конъюнкция, дизъюнкция, исключающее ИЛИ, отрицание и изменение знака числа. Это то что мы с вами рассматривали, и посмотрим как все эти идеи реализовываются при помощи программных кодов. Так, смотрите, у нас здесь что происходит, у нас здесь имеется таблица истинности для роботы с побитовыми операциями. Обратите внимание, на 23 строке мы переменной operand1 присваиваем значение 0xFF. Давайте посмотрим в комментариях двоичное представление этого числа, а это десятичное представление этого числа. На 24 строке мы operand2 присваиваем значение 1. Обратите внимание, в двоичной системе 1, в шестнадцатеричной – 1, и в десятичной 1. И на 25 строке мы переменной result присваиваем значение возвращаемое значение операции побитовой конъюнкции. Теперь в столбик выполняем операции над каждым соответствующим битом. Смотрите, нулевой бит первого операнда равен 1. Нулевой бит второго операнда тоже равен 1. 1 и 1 результате будет 1. Первый бит первого операнда 1, второго операнда – 0. Обратите внимание, 1 И 0 = 0. И т.д. И вот мы получаем такое число. Побитовые операции – конъюнкция. Давайте посмотрим теперь дизъюнкцию. У нас имеется таблица истинности, вспоминаем ее. У нас практически везде идут 1 и ложь мы получим только в одном случае, если у нас оба операнда будут равны лжи, то есть false или в данном случае 0 – булева алгебра. На 40 строке мы operand1 присваиваем значение 2. Обратите внимание, в комментариях вот его двоичное представление, шестнадцатеричное, десятичное. Переменной operand2 присваиваем значение 1. И на 42 строке мы переменной result присваиваем значение операции побитовая дизъюнкция, которая работает с двумя операндами. И вот мы видим, что тоже в столик можем вычислить. Всегда представляйте себе в столбик. 0 ИЛИ 1 сколько будет? Давайте посмотрим, ага мы видим, что работает переместительный закон. Хорошо смотрим следующий. Исключающее ИЛИ. Здесь мы помним правило, что если операнды разные у нас что будет? 1. Если одинаковые 0. Странно, но так работает исключающее ИЛИ. На 58 строке мы переменной operand1 присваиваем значение 3. Давайте посмотрим как оно выглядит. Вот две единицы, уже знакомо выглядит. Переменной operand2 присваиваем значение 1. И на 60 строке мы переменной result присваиваем возвращаемое значение операции исключающее ИЛИ, которое представлено в языке C# таким значком - ^. Мы его сейчас сленгово называем крышкой. И теперь снова же в столбик. Еще раз напомните, одинаковое 0. Разное – 1. Хорошо. Дальше идет отрицание. Вот это – тильда. Не 0 = 1. Не 1 = 0. На 73 строке мы переменной operand1 присваиваем значение 1. А где операнд два? А точно! Вы ж подсказывайте мне, что отрицание может работать только с одним операндом. Если конъюнкция, дизъюнкция и исключающее или – это операции, которые работают с двумя операндами, то отрицание только с одним. Так и на 73 строке мы берем и инвертируем 1, то есть мы возьмём и заменим все 1 на 0, все 0 на 1. И последнее на 80 строке у нас имеется пример, показывающий работу формулы по изменению знака числа. Но мы то знаем, как звучит эта формула, сленгово – Инверт плюс 1. Какое бы у нас ни было число, положительное или отрицательное, мы его конвертируем, и потом для его корректировки мы прибавляем 1. Обратите внимание, на 90 строке мы переменной operand1 присваиваем значение 1. На 91 строке мы делаем первое преобразование – инвертирование этого числа. Давайте посмотрим, вот наша единица в двоичном формате. Инвертируем ее и увеличиваем на 1. И у нас получились все единицы – это что такое? -1. Смотрите, как просто мы в программном коде используем те понятия, те конструкции, которые мы с вами разбирали в первой половине сегодняшнего урока. А мы идем дальше. Переходим к следующему примеру.
1: using System; 2: 3: // Побитовые логические операции. (&, |) 4: 5: // Например: 6: // Мы имеем порт ввода/вывода или регистр с определенным значением в нем. 7: // Нам необходимо включить устройство управляемое первым битом, установив первый бит в 1. 8: // После нам потребуется выключить устройство, сбросив первый бит в 0. 9: // 1 1 1 1 0 0 0 0 - начальное значение порта ввода/вывода. 10: // 7 6 5 4 3 2 1 0 - нумерация битов управления устройствами. 11: 12: namespace Logic 13: { 14: class Program 15: { 16: static void Main() 17: { 18: byte port = 0xF0; // 1 1 1 1 0 0 0 0 - начальное значение порта ввода/вывода. 19: byte mask = 0x02; // 0 0 0 0 0 0 1 0 - маска включения устройства управляемого первым битом. 20: Console.WriteLine("port = {0:X}", port); // ситуация с лампочкой в комнате 21: 22: port = (byte)(port | mask); // Включить устройство управляемое первым битом. 23: Console.WriteLine("port = {0:X}", port); 24: 25: mask = 0xFD; // 1 1 1 1 1 1 0 1 - Создаем маску выключения устройства управляемого первым битом. 26: 27: port = (byte)(port & mask); // Выключить устройство управляемое первым битом. 28: Console.WriteLine("port = {0:X}", port); 29: 30: // Delay. 31: Console.ReadKey(); 32: } 33: } 34: }
Обратите внимание, как называется этот проект? Port. Работа с портами. Представьте, что у на в компьютере есть не только ОЗУ, не только процессор, который имеет регистры, но еще и есть область, которая называется областью портов ввода вывода. Вот эти порты – это специальные байты, которые напрямую завязаны на какие-то устройства, на периферийные устройства, ну и не только на периферийные. Например, чтобы нам на принтер на печать отправить определенное сообщение есть специальный порт, специальный байт. У него там имеется тоже свой номер, мы туда что-то бросаем и оно выводится. Давайте посмотрим, я вот здесь подготовил один интересный пример. Он правда шуточный, но тем не менее он хорошо показывает работу портов. Дело в том, что сегодня при программировании под операционную систему Windows мы не можем напрямую обращаться к портам ввода вывода, потому что у нас закрыты команды in и out? Мы не можем обратится к порту. Почему? Потому что Microsoft справедливо посчитала, что такие обращения являются небезопасными. А что они предоставляют взамен? Они предоставляют богатый набор API функций Windows? Если мы хотим обратится к какому-нибудь порту, то мы предварительно должны сказать: «Уважаемая операционная система не могла ли бы ты переотправить вот эту последовательность и отдать ее такому-то устройству». Там сложная каша из драйверов, портов, функций… Нас это не интересует, нас интересует легкое восприятие понятия порта ввода вывода. Вот представьте, что у нас имеется некий байт, который уже является портом. И мы пришли на роботу, а наша компания занимается тем, что сопровождает, какие-то больницы, управление сложным оборудованием. И вот мы пришли, джуниоры – начинающие программисты и нам говорят. Вот я пришел, мне 17 лет и мне говорят: «Александр, вот этот байт, он управляет множеством различного оборудования, он включает и выключает его». Что можно делать? Если бит установить в 1, то устройство включается, только его установить в 0, то он останавливается. Ага. Можно включать вентилятор, если установить первый бит в 1. Если он в нуле – остановилось. Замечательно. А это что такое? Это репродуктор он оповещает всех пациентов больницы, что нужно идти на процедуры. И поэтому если мы устанавливаем его в 1, то репродуктор сразу же включается и издает какие-то звуки. Все слышат сигнал. Я говорю, ребята – чудесно, это легко, я могу это сделать. А что вот это за биты? Их трогать нельзя. Например, 7 подключен к аппарату искусственного дыхания, 3 подключен к искусственной почке. Они сейчас как раз обслуживают пациентов. А вот эти связаны с операционной. Если мы выключим вот это, то что произойдет, то выключится аппарат искусственного дыхания. Этот включает и отключает искусственные почки. А вдруг я нечаянно что-то задену? А там сейчас работает или не работает? У нас всегда врачи работают, так что все подключено, мы не знаем, что-то работает, что-то не работает. А как я могу не трогать их? Так, а ты Александр, логику учил? Как можно включать и отключать вот эти два устройства, и включать, и отключать их так, чтобы не затрагивать вообще вот эти устройства, и даже не интересоваться что в них находится. А может бы мне можно целиком с ним работать? Нет! Нельзя! Потому что с этими битами работают другие программы. Твоя программа занимается только тем, что освежает воздух и обдувает пациентов и зовет их на процедуры, всеми остальными этими устройствами управляют другие программы. Так, что же мне делать? Вы мне можете подсказать что делать? Как мне можно включить вентилятор сейчас, чтобы не включить случайно искусственную почку, потому что я не знаю она включена или выключена, чтобы вдруг не включить какой-то аппарат, чтобы он пациента током не ударил. Или если я сейчас включу репродуктор, как мне его выключить, чтобы случайно не отключить искусственное дыхание. Он сейчас орет, зовет всех, нужно просто пикнуть на 2 секунды и отключить его. Две секунды нужно погудеть и отключить. Что мне делать? Так, вот вопрос, мы понимаем, что это шутка, что у нас в компьютере этот порт на самом деле завязан на другие устройства. Мы понимаем, что вот такой порт есть у жесткого диска. Он позволяет записать в банке памяти какую-то информацию, потом снять головку с парковки и подвести ее на нужную дорожку и записать в нужные сектора информацию. К примеру, если это жесткие диски. Если идет работа с видеокартой либо порт с клавиатуры. Там контроллер клавиатуры мне число нужно считать, либо это программируемые контроллеры прерываний, у нас здесь идет своя кухня. Конечно, хочу вас обрадовать, что мы с портами ввода-вывода напрямую работать не будем, но иногда с виртуальными аналогами таких портов мы работать будем. Мы сегодня посмотрим, что это не просто игрушка или смешной пример. Мы сегодня работаем с побитовыми операциями. И даже новые современные технологии, как например WPF – Windows Presentation Foundation, это новая презентационная подсистема, которая позволяет с использованием DirectX организовывать богатый пользовательский интерфейс, очень гибкий, с разным множеством эффектов. Так чтобы нам начать работает с этой технологией. Если мы программисты и пишем программы на WPF, чтобы они были масштабируемыми, чтобы они выполнялись на разных компьютерах мы должны определить первое это что? Уровень реализации видеокарты. Могу ли я сейчас пользователю запустить WPF со всем богатством эффектов, прозрачностями или у него компьютер начнет тормозить, дёргается. Поэтому прежде чем мы начинаем работать с той же технологией WPF мы ложны проверить, на каком компьютере будет работать наша программа. А какой уровень визуализации у пользователя? И если у него идет полное аппаратное ускорение – запускаем все возможности, а если у него программное ускорение, которое реализуется за счет работы ЦП, у него видюхи нет, или она интегрированная. Что тогда делать? Вот здесь нам эти операции и помогут. Но мы сейчас будем играться с нашей медицинской палатой с вентилятором и репродуктором, а потом посмотрим даже как определяется уровень визуализации видеокарты. Ок идем дальше. И вот я опять остался один на один с этим портом. Значит нужно зайти в программу ну и хотя бы в ней что-то поразбираться, посмотреть. Ага, на 18 строке у нас имеется порт. Ну я скажу, что это имитация этого порта. Представьте, что в нем сейчас находится вот такое значение в действительности. Но я этого не вижу. Я вижу только два последних бита. Все остальные я не вижу. Почему? Потому что здесь у нас идут знаки вопросов, но на самом деле эти все единицы, а эти в два в нулях. На это от меня скрыто. Смотрим дальше, на 19 строке программисты создают какую-то маску. Так маска и байт мы записываем 0х02. Смотрите, здесь в первом бите идет 1, а все остальные нули. Мне старшие программисты сказали, что вот этот бит завязан на вентилятор, второй на репродуктор. Я сейчас хочу запустить вентилятор, подготавливаю 1. Ага, вот этот искусственное дыхание, а вот этот искусственная почка, а те непонятно что. Смотрите, на 20 строке мы выводим на экран состояние порта. На 22 строке мы берем содержимое порта, вот его настоящее значение, и теперь хотим включить вентилятор, обратите внимание, мы подготовили специальную маску, маска – это некоторая специальная последовательность бит, которая будет применена через логическую операцию к первому операнду, ну мы знаем, что от перестановки мест операндов ничего не поменяется, но просто вот порт поставили с этой стороны, а маску с этой. То есть здесь у нас готов этот битик, чтобы включить. Так вот здесь мы выполняем операцию дизъюнкция. 0 ИЛИ 0 = 0. 0 ИЛИ 1 = 1. И получается в результате у меня произойдет установка этого первого бита порта в 1. Ура! Мы поняли, что операция дизъюнкции позволяет включать устройства завязанные на порт, а все остальные нули они никак не повлияют ни на включенный аппарат искусственного дыхания, ни на искусственную почку, ни даже на непонятные устройства, которые еще подсоединены. А если почка была выключена, то она и останется выключенной, если что-то было включено, например, искуственное дыхание, то оно и останется включенным. Но мой вентилятор сейчас загудел, потому что в результате… Давайте сейчас выполнимся и посмотрим, что у нас получилось в результате выполнения это программы. Смотрите, начальное значение порта F0, после выполнения операции, дизъюнкции, видите я включил устройство, я получил F2. Так, теперь я умею включать устройства так, чтобы не затрагивать другие, чужие, не позволенные мне к использованию биты. Давайте посмотрим, мы зажгли вот эту лампочку через использование дизъюнкции. Она включает устройство и при этом позволяет не затронуть другие устройства завязанные на порт управления устройствами. Чувствуете, у меня теперь работает вентилятор. Хорошо возвращаемся в нашу программу. Хорошо, теперь надо выключить, я замерз, но если я сейчас начну выключать, вдруг здесь что-то не то выключится, как мне безопасно это сделать? Сделать так, чтобы только вентилятор отключился, а все остальное работало. Теперь нужно применить конъюнкцию, чтобы 1, которая ярко горит, мне нужно эту единицу сделать 1 И 0. А вот это чтобы осталось работать и если оно не работает чтобы оно таким и осталось. Нужно сделать все единицы. На вентилятор поставить 0. Значит у меня получится новая маска 1111 1101. И если устройство было выключено, у меня оно и останется выключенным, если устройство было включенным оно и останется включенным. Значит на выключение у меня работает какая операция? Конъюнкция. И теперь я смогу работать с этим портом подготовив правильную маску из всех единиц кроме первого бита, который будет равен 0. 1 И 0 = 0. Давайте посмотрим, как это работает. Вот мы подготавливаем маску для выключения 1111 1101. Смотрите, только битик, который стоит в позиции, которая отвечает за включение и выключение вентилятора. Теперь на 27 строке мы берем порт в качестве первого операнда, применяем конъюнкцию с маской. И поэтому обратите внимание, здесь стоят 1 и они не повлияют на включение или выключение подключенных к порту устройств, а вот это отключит мой вентилятор. А что это за приведение к byte. Что за оператор явного преобразования значения типа? Почему? Потому что все эти логические операции, они пытаются нам вернуть int. А мне нужен просто byte. Поэтому мне приходится int преобразовывать в byte. Запомните, это такая маленькая тонкость. Значит теперь мы видим, где у нас при работе с портами вывода применяется, это у меня тест такой. Это приходит старший программист приходит и спрашивает, как у нас включаются и выключаются устройства на портах ввода вывода. Где применяются дизъюнкция? Что я должен сказать? Для включения. А конъюнкция – для выключения. Мы должны предварительно создать маску и используя логические операции дизъюнкции или конъюнкции с использованием этих масок включить или выключить нужное нам устройство. И при этом правильно созданная маска не окажет никакого влияния на соседние биты. Нам даже не нужно знать работают или не работают соседние устройства. Мы гарантируем, что мы их не включим и не выключим. Смотрите, что происходит, насколько полезными оказываются операции конъюнкции и дизъюнкции при работе с портом ввода-вывода. Как с физическими, так и с виртуальными. Потому что у нас имеются и виртуальные порты, которые управляют либо частями программ либо оказывают влияние на какие-то современные устройства, на видеокарты, настройка видеокарты происходит вот через такие маски. Хорошо. Думаю, что здесь предельно понятно. А мы идем дальше. И следующий пример покажет работу еще одного интересного логического оператора.
1: using System; 2: 3: // Побитовые логические операции. (^) 4: 5: // Например: 6: // Используя операцию XOR, мы можем зашифровать сообщение. 7: // В таком виде шифрования используется один ключ, как для шифрования, так и для расшифровки. 8: // Криптостойкость такого ключа, можно увеличить, если увеличить его длину. 9: 10: namespace Logic 11: { 12: class Program 13: { 14: static void Main(string[] args) 15: { 16: ushort secretKey = 0x0088; // Секретный ключ (длина - 16 bit). 17: char character = 'A'; // Исходный символ для шифрования. 18: 19: Console.WriteLine("Исходный символ: {0}, его код в кодовой таблице: {1:X}", character, (byte)character); 20: Console.WriteLine("Размер символа: {0} = {1} бит", character, sizeof(char) * 8); 21: 22: // Зашифровываем символ. 23: character = (char)(character ^ secretKey); 24: Console.WriteLine("Зашифрованный символ: {0}, его код в кодовой таблице: {1:X}", character, (byte)character); 25: 26: // Расшифровываем символ. 27: character = (char)(character ^ secretKey); 28: Console.WriteLine("Расшифрованный символ: {0}, его код в кодовой таблице: {1:X}", character, (byte)character); 29: 30: // Delay. 31: Console.ReadKey(); 32: } 33: } 34: }
Что крышечка обозначает? XOR – исключающее ИЛИ. А теперь представьте себе, что я снова возвращаюсь к старой шпионской деятельности, и мне теперь нужно отправить зашифрованное сообщение. Мы с вами помним, что код большого символа А равен 0х0041. И поэтому когда я напишу свое сообщение, мне хотелось перекодировать каждую букву так, чтобы когда мое шпионское сообщение перехватят и начнут его читать, то там увидят только сплошные кракозябры. И никто ничего не понял. Можно это сделать? Давайте попробуем. Но для начала нам нужен секретный ключ. 16 строка – мы формируем секретный ключ. Это число 0х0088. Интересно, как выглядит 8? 1000, поэтому секретный ключ будет выглядеть как 1000 1000. На 17 строке у нас имеется символ А. У него какая кодировка? 0х0041 – 0100 0001. Я думаю, что вы уже все помните такие простые конструкции. На 19 строке мы выводим исходный символ и его код в кодовой таблице. Давайте выполнимся и посмотрим. Код символа А в кодовой таблице 0х0041. Давайте посмотрим, какой его размер. Вот здесь мы и используем знакомый нам оператор sizeof. Передаем сюда тип char. Чему будет равен char? 2 – это же формат Юникод. Два байта умножить на 8, получим 16 бит. Размер символа – 16 бит. И я на 23 строке зашифровываю мое сообщение, представьте, в данном случае оно состоит только из одного символа, но мы просто еще не изучали циклические конструкции, чтобы красиво зашифровать большой текст. Но ничего страшного, дальше это вы сможете сделать. На 23 строке мы берем мой символ А из моего сообщения, и через использование операции исключающего ИЛИ мы берем и применяем к нему секретный ключ 88. Если вы посчитаете в столбик числа 41 XOR 88 = C9 и это будет символ Е. А как выглядит С? 1100. А Девять? 1001. Держите перед собой кодовую таблицу и подсматривайте, а я помню. И теперь вы перехватили мое секретное сообщение и что вы видите? Вы видите какие-то неправильные символы, вы его не можете прочитать. На той стороне, в ЦРУ получают мое сообщение, и что они должны сделать? Расшифровать. Я по другому каналу СМСкой агенту Смиту шлю секретный ключ, который он должен применить для расшифровки. Обратите внимание, операция XOR позволяет применяя один и тот же ключ им же зашифровывать сообщения и этим же ключом расшифровывать сообщения. Вы видите? Некий обычный симметричный ключ. Им же мы закрываем, им же мы открываем. Есть еще другие ключи, ассиметричные. Это сложные ключи. Ну конечно такого никто не будет делать, потому что вы сядете, возьмёте мое сообщение и подставляя от одного числа к другому быстренько его расшифруете. Потому для того чтобы мне сделать шифрование более надежным что мне нужно сделать? Сделать длиннее ключ. А что вы тогда сделаете. Возьмете словарь, поставите под брутфорс и збрутите, по словарю расшифруете. Что? 64 битовое? Ага. Или еще больше? То есть чем длиннее длина секретного ключа, Тем сложнее и дольше будет расшифровываться мое сообщение. Значит мы уже видим. Конъюнкция, дизъюнкция используются для работы с портами, с различными флагами, для включения и выключения устройства или для установки и сброса флажка. А исключающее ИЛИ используется, например, для шифрования и расшифровки какой-то информации. Интересно? Интересно. А мы идем с вами дальше и заходим в следующий пример.
1: using System; 2: 3: // Логические операции. 4: 5: namespace Logic 6: { 7: class Program 8: { 9: static void Main() 10: { 11: bool operand1 = false, operand2 = false, result = false; 12: 13: #region Конъюнкция 14: 15: // Таблица истинности для Конъюнкции (И) - && - [AND] 16: 17: // Если хоть одно из выражений операции операции конъюнкции имеет значение false - 18: // все выражение имеет значение false, иначе - true 19: 20: // false && false = false true && false = false 21: // false && true = false true && true = true 22: 23: operand1 = true; // true 24: operand2 = false; // false 25: result = operand1 && operand2; // false 26: 27: Console.WriteLine("{0} AND {1} = {2}", operand1, operand2, result); 28: 29: #endregion 30: 31: #region Дизъюнкция 32: 33: // Таблица истинности для Дизъюнкции (ИЛИ) - || - [OR] 34: 35: // Если хоть одно из выражений операции операции Дизъюнкции имеет значение true - 36: // все выражение имеет значение true, иначе - false 37: 38: // false || false = false true || false = true 39: // false || true = true true || true = true 40: 41: operand1 = true; // true 42: operand2 = false; // false 43: result = operand1 || operand2; // true 44: 45: Console.WriteLine("{0} OR {1} = {2}", operand1, operand2, result); 46: 47: #endregion 48: 49: #region Исключающее ИЛИ 50: 51: // Таблица истинности для Исключающего ИЛИ - ^ - [XOR] 52: 53: // Если оба выражения операции Исключающего ИЛИ имеют одинакое значение - 54: // все выражение имеет значение false, если разные - true 55: 56: 57: // false ^ false = false true ^ false = true 58: // false ^ true = true true ^ true = false 59: 60: operand1 = true; // true 61: operand2 = false; // false 62: result = operand1 ^ operand2; // true 63: 64: Console.WriteLine("{0} XOR {1} = {2}", operand1, operand2, result); 65: 66: #endregion 67: 68: #region Отрицание 69: 70: // Таблица истинности для Отрицания (НЕТ) - ! - [NOT] 71: 72: // !false = true 73: // !true = false 74: 75: operand1 = true; 76: result = !operand1; 77: 78: Console.WriteLine("NOT {0} = {1}", operand1, result); 79: 80: #endregion 81: 82: // Delay. 83: Console.ReadKey(); 84: } 85: } 86: }
На 11 строке мы создаем булевы переменные operand1, operand2 и result. Открываем блок на 13 строке и видим, как идет работа с конъюнкцией. Обратите внимание, 20 и 21 строки что здесь имеется? Таблица истинности. Мы с вами помним, что логика условно разделяется на две части в программировании: это работа с чистыми булевыми значениями и выполнение побитовых логических операций. И сейчас мы переходим к рассмотрению чистых логических булевых операций. На 23 строке мы operand1 присваиваем значение true. На 24 строке operand2 присваиваем false. И на 25 строке мы переменной result присваиваем возвращаемое значение операции конъюнкции. Мы смотрим true и false у нас дают false. Не забывайте таблицу истинности. 31 строка. На 41 строке мы operand1 присваиваем true, на 42 operand2 присваиваем false. Выполняем над ними операцию дизъюнкции. Чему будет равен result? Конечно же true. Следующий. Исключающее ИЛИ. Помните мы шифровали, но это было побитово, а здесь смотрим operand1 – true, operand2 – false. Result чему равен? Правило – если разные операнды, то это true, а если одинаковые, то это false. Обратите внимание. True, true, false. False, false, false. Смотрите как просто. И теперь нам нужно перейти и посмотреть последнее отрицание. Таблица истинности отрицания: Не false = true. Не true = false. 75 строка. Operand1 присваиваем значение true, на 76 строке result присваиваем возвращаемое значение оператора отрицания. Он преобразовывает, по сути инвертирует true в false. Вы видите, как просто и легко читаются наши коды, после того как мы уделили достаточное время для рассмотрения этой примитивной логики. Ага. А здесь у нас имеется тоже такой вспомогательный пример который нас предостережет от ошибок.
1: using System; 2: 3: // Логические операции. 4: 5: // Например: 6: // Чтобы проверить условие A < x < B, нельзя записать его в условном операторе непосредственно, 7: // так как каждая операция отношении должна иметь два операнда. 8: // Правильный способ записи: i f ( A < x && x < B). 9: 10: namespace Logic 11: { 12: class Program 13: { 14: static void Main() 15: { 16: int A = 0, B = 5, x = 3; 17: 18: 19: if (A < x && x < B) // A < x < B 20: { 21: Console.WriteLine("Число {0} находится в диапазоне чисел от {1} до {2}.", x, A, B); 22: } 23: else 24: { 25: Console.WriteLine("Число {0} не попадает в диапазон чисел от {1} до {2}.", x, A, B); 26: } 27: 28: 29: // Delay. 30: Console.ReadKey(); 31: } 32: } 33: }
Помните в математике была вот такая запись A < x < B. У нас в программировании нельзя так записать. Это невозможно. В математике на бумажке можно а как нам правильно записать на языке программирования вот такое сложное сравнение. В вот смотрите. Мы берем условную конструкцию if, например, и если A < x, к примеру, И x < B. Выполняется раз операция – математическое высказывание. Помните мы с вами говорили, мол у меня ног больше чем у сороконожки. У собаки ног меньше чем у меня. Первое высказывание A < x, второе x < B. Сначала вычисляются вот эти высказывания, получается у них значение вот этих операторов, оператора меньше. И если мы получим true и true, тогда получим true и вообще. Операцию конъюнкции выполним на возвращаемых значениях вот этих знаковых операций меньше. Давайте посмотрим, примеры. На 16 строке мы создаем три целочисленных переменных а, b и х и присваиваем значение 0, 5, 3 соответственно. На 19 строке мы создаем условную конструкцию if в условии которой указываем логическое выражение, с левой части которого у нас идет проверка, мы говорим, что A < x. Смотрим 0 меньше трех. Давайте шагать. F11. Ага замечательно. И теперь смотрим, наводим на а = 0, х = 3. Наводим на знак меньше – true. Замечательно. Теперь рассмотрим х = 3, b = 5. Результат true. Теперь наводим на амперсанды, смотрите как нам все красиво вычислилось. A < x < B – true. Смотрите, как замечательно. И куда мы сейчас пойдем? Выходим. Запомните, что мы не можем напрямую записать в условную конструкцию именно вот такое выражение, для этого на потребуется использовать операцию конъюнкции. Хорошо. А мы переходим с вами к следующему примеру. Но здесь нам тоже нужно будет вернутся к презентации. Мы с вами поговорим сейчас о сдвигах, это тоже очень интересно. Операции сдвига, для этого нам нужно опять вернутся к двоичной арифметике. Даже не к арифметике, а к двоичному представлению числа. Смотрите, вот этот знак, две этих стрелочки, он и обозначает операцию логического сдвига. Как работает этот сдвиг? На самом деле у нас имеется больше сдвигов в процессоре, но в C# используется только логический сдвиг. Давайте посмотрим, у нас имеется десятичное число 253. В двоичном представлении смотрите как оно выглядит 1111 1101. И теперь мы к этому числу применяем сдвиг, мы хотим, чтобы ты число сдвинулось. Представьте, что здесь мы все единицы друг к другу привяжем и вот здесь дальше у нас есть бесконечное число нулей, тоже связанных. И мы их на два числа так тянем. Выдвинуло две единицы и исчезли, а с этой стороны вдвинулось два нуля. То есть мы это число 253, вот эту комбинацию FD мы сдвигаем на два бита влево. Смотрите правило, справа всегда заходят нули. Биты, которые выходят за пределы диапазона теряются. Есть другие сдвиги в процессоре. У нас таких нет. Представьте, что вот здесь бы единица выдвинулась и с другого конца заскочила. А есть еще один сдвиг, у нас имеется регистр флагов и представьте, что выдвинутое число падает сюда, а то что было здесь раньше забрасывается с другой стороны числа. Как бусы. Но в C# такого нет, поэтому не волнуйтесь. У нас есть просто сдвиги. Влево и вправо. Как сдвиг влево работает мы видим. Очень просто. Давайте посмотрим как работает сдвиг влево. Если мы возьмем число -127. Смотрите как оно выглядит в двоичной системе. Мы говорим, что сдвигаем его на два. У нас выдвигаются два биты сюда. Но заметьте правило, слева если число положительное – будут заходить нули. Если бы лидирующий бит был бы равен 0. Но так как здесь стоит единица. Система расценивает его как отрицательное число, поэтому она сюда вводи что? 1. Если число положительное то заходят нули, а если число отрицательное, то заходят единицы. Shift – сдвиг. И смотрим второе здесь правило. Биты, которые выходят за пределы диапазона байта, либо машинного слова теряются, но при этом само значение байта изменяется. Получается совсем другое число. Что это за число, если его представить просто в беззнаковом формате? Это Е0. А если в знаковом то это -32. А мы переходим обратно к программным кодам и смотрим как у нас работают сдвиги в коде.
1: using System; 2: 3: // Логический Сдвиг (shift). 4: 5: namespace Logic 6: { 7: class Program 8: { 9: static void Main() 10: { 11: byte operand = 0x81; // 1000 0001 12: Console.WriteLine("Число до сдвига: {0:X}", operand); 13: 14: // Логический сдвиг числа влево. 15: 16: operand = (byte)(operand << 2); // 0000 0100 17: Console.WriteLine("Число после сдвига влево: {0:X}", operand); 18: 19: // Логический сдвиг числа вправо. 20: 21: operand = (byte)(operand >> 1); // 0000 0010 22: 23: Console.WriteLine("Число после сдвига вправо: {0:X}", operand); 24: 25: // Delay. 26: Console.ReadKey(); 27: } 28: } 29: }
На 11 строке мы создаем переменную operand типа byte и присваиваем ей 0х81. Это в двоичном виде 1000 0001. Как называются эти полубайты? Триады? Тетрады. Вы мы его создали и вывели. Давайте выполнимся сразу. Ага, вот оно число 81, число до сдвига. А теперь мы 0х81 сдвигаем на два. Как оно у нас выглядит? Два бита выдвинулись, сбда вставились нули, а эта единичка шагнула на 2 позиции. И после этого у нас получилось 0000 0100 – 4. А теперь давайте еще сдвинем вправо нашу четверку, и как только сдвинем ее на один, сразу получаем 2. Сдвиг на 1 вправо равносилен делению числа на 2. А если сдвиг влево на 2, то он равносилен умножению числа на 2. Ну с условием что не будет происходить потери части числа. Смотрите, как просто. Где работают эти сдвиги? Интересно. А сейчас мы увидим. Как называется 7 пример? Visual Level – уровень визуализации видеокарты. Вот здесь то мы сейчас и посмотрим как мы можем применить к некому виртуальному порту сдвиги. И мы заходим в 7 пример. Только не пугайтесь. Этот проект создан при помощи технологии WPF – Windows Presentation Foundation. Очень просто. Здесь имеется дизайнер форм. Здесь немножко имеется разметки. Это декларативный язык, который называется xaml. Его еще называют Зэмль. Если мы на этом языке что-то описываем. То эти изменение отражаются на форме. Но мы пока не будем сюда смотреть. Мы нажмем клавишу F7 и зайдем в code behind. Мы видим у нас есть кнопка. Можем дважды кликнуть. И теперь смотрим что у нас имеется.
1: using System.Windows; 2: using System.Windows.Media; 3: 4: // Получение информации об уровне визуализации. 5: 6: namespace VisualLevel 7: { 8: public partial class MainWindow : Window 9: { 10: public MainWindow() 11: { 12: InitializeComponent(); 13: } 14: 15: private void submit_Click(object sender, RoutedEventArgs e) 16: { 17: tbResult.Text = "УРОВЕНЬ ВИЗУАЛИЗАЦИИ:\n"; 18: 19: // RenderCapability - Позволяет приложениям WPF запрашивать текущий уровень отрисовки. 20: // RenderCapability.Tier - Получает значение, указывающее уровень отрисовки текущего потока. 21: 22: int renderingTier = RenderCapability.Tier >> 16; 23: // Значение Int32, старшее слово которого соответствует уровню отрисовки текущего потока. 24: // 16 и 17 биты. 25: 26: // Возвращаемое || Уровень || Примечания 27: // значение || отрисовки || 28: // -------------------------------------------------------------------------------------------- 29: // 0x00000000 || 0 || Для приложения на этом устройстве аппаратное ускорение недоступно. 30: // 0x00010000 || 1 || Для данной видеокарты доступно частичное графическое аппаратное ускорение. 31: // || || Это соответствует версииDirectX выше или равной 7.0, но меньше 9.0. 32: // 0x00020000 || 2 || Отрисовка уровня 2 означает, что большая часть графических 33: // || || возможностей WPF должна использовать аппаратное ускорение 34: // || || при условии, что необходимые системные ресурсы не исчерпаны. 35: // || || Это соответствует DirectX версии, которая больше или равна 9.0. 36: 37: // Подробнее о том, какие возможности WPF снабжены аппаратным ускорением на перечисленных выше уровнях, 38: // смотрите http://msdn.microsoft.com/en-gb/library/ms742196.aspx 39: 40: if (renderingTier == 0) 41: tbResult.Text += "Видеокарта не предоставляет аппаратного ускорения."; 42: 43: if (renderingTier == 1) 44: tbResult.Text += "Видеокарта предоставляет частичное аппаратное ускорение."; 45: 46: if (renderingTier == 2) 47: tbResult.Text += "Видеокарта предоставляет полное аппаратное ускорение."; 48: } 49: } 50: }
На 15 строке у нас имеется метод обработчик нажатия на кнопку. Давайте выполнимся. И теперь если я нажму на эту кнопку, программа проверила уровень визуализации моей видеокарты. А давайте теперь посмотрим небольшое описание, небольшой технический мануал. WPF – это ресурсозатратная технология. Здесь очень много различных сложных элементов. Там можно делать прозрачности и можно наделать всего чего угодно. И поэтому нам нужно прежде чем запустить наше приложение на пользовательском компьютере проверить какая у него установлена видеокарта. Это проверяется уровнем визуализации. Теперь смотрим. У нас имеется некий виртуальный порт, который называется Tier. Обратите внимание, здесь у нас имеется специальный объект, он называется RenderCapability. Он позволяет приложениям запрашивать текущий уровень визуализации. А вот это свойство Tier получает значение, указывающее уровень отрисовки текущего потока. Давайте мы с вами посмотрим вот на что. Как устроен вот этот Tier. Сколько в нем бит. В 16-ричном представлении. Всего четыре байта. Ага он типа int. Если вот здесь в третьем байте у нас хранится 1 или 2, это говорит о том что его биты что-то обозначают. Я думаю, что на придется его здесь нарисовать да? Если это четырех байтовая то рисуем так 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000. Мы смотрим, что если у нас вот эти биты. Интересно, а где же эти биты находятся. Они находятся в третьем байте. Давайте выделим первые два бита третьего байта скобками. Значит это 17 и 18 биты. Видите, четырехбайтовый порт. Хочу вас обрадовать, что дальше уже ничего нет, если до нужных нам бит еще есть какая-то служебная информация, то там ничего нет. Они просто зарезервированы для какого-то будущего использования. И получается, что эти последние два битика, которые используются в этой последовательности и обозначают уровень визуализации. Если они равны 00, то здесь ноль и для приложения на этом устройстве аппаратное ускорение недоступно. У меня все отрисовывается ЦП. А если там 1, то для данной видеокарты доступно частичное графическое аппаратное ускорение. Это равно версии DirectX 7 но меньше 9. А если здесь двойка, значит отрисовка уровня 2 означает, что большая часть графических возможностей WPF должна использовать аппаратное ускорение при условии, что необходимые системные ресурсы не исчерпаны. Это соответствует DirectX версии, которая больше или равна 9.0. Подробнее об этом вы можете почитать на msdn – это специальный ресурс для разработчиков. Но я же хочу теперь просто узнать, чему равны два этих бита. Можно это число сначала сдвинуть на 14 влево. Моя десятка окажется слева, а справа добавятся нули. А потом мне нужно на 30 сдвинуть в другую сторону. Но мы помним, что этот регистр он в старших битах ничего не использует, там всегда записаны нули. Поэтому мы можем не двигать число в эту сторону. Нам нужно сдвинуть число вправо на 16. Слева зайдут нули. У нас какое число хранится в этом регистре? 2. Смотрите как мы красиво очистили младшие биты, потому что здесь могут стоять другие биты, и не читая мануал мы не знаем, что здесь вообще. Потому нам нужно сдвинуть его и убрать. Сюда зайдут нули. На 22 строке мы создаем переменную типа int с именем renderingTier, Берем значение регистра, применяем к нему сдвиг вправо на 16. Слева у нас вводится 16 нулей. А что это за число? В данном случае это 2. Что уровень визуализации 2, как у меня на компьютере. У меня видеокарта поддерживает DirectX версии, которая больше или равна 9.0. Посмотрите у себя. Какой у вас уровень визуализации. И теперь когда мы получили сюда уже число, мы видим, что мое число здесь в десятичной системе равно? 2. И теперь смотрим на 40 строке мы сравниваем renderingTier = 0 – видеокарта не предоставляет аппаратного ускорения. Если 1, то видеокарта есть и не поддерживает девятую версию DirectX. Но если здесь оказалось число 2. То мы говорим, что видеокарта предоставляет полное аппаратное ускорение. Давайте еще раз выполнимся. Мы выводим результат. Видите, видеокарта предоставляет полное аппаратное ускорение. У нас сработал третий блок. Давайте пошагаем. Вот у меня запустилось. Жмем на кнопку. Чему равен текст? Уровень визуализации. Чему равен Tier? Видите, какое там хранится значение? Нам нужно его сдвинуть на 16. И после того как сдвинем это значение 131072 на 16. Давайте с калькулятором посмотрим тоже. Какое тут значение было? 131072 Вот мы его ввели. Теперь я хочу его представить в 16-ричном виде, а потом в двоичном. Оказывается, здесь ничего нет. Видите, как странно работают эти числа. Получается у нас используется только два бита. Интересно а как мы сдвигали? Мы сдвигали это число на 16 вправо. Давайте сдвинем. Rsh – Right Shift на 16. Получилось 2. В C# можно использовать только два сдвига, влево и вправо. Or – ИЛИ, XOR – исключающее ИЛИ. NOT – отрицание, AND – И. Смотрите, как калькулятор удобно нам помогает работать. Хорошо, а мы с вами останавливается. Мы с вами удостоверились, что действительно те темы, которые мы с вами разбирали, они достаточно актуальны. И вы как программист, как программист frontend, который разрабатывает декстопные решения, тем более с использованием технологии WPF, вам понадобилось уже использовать какие-то сдвиги, какие-то операции с битами. Поиграйтесь с этим примером. Посмотрите. А мы с вами идем дальше. У нас остались еще мелочи, мы подходим к окончанию нашего урока. Мы сейчас продолжаем.
1: using System; 2: 3: /* 4: * КОРОТКОЗАМКНУТОЕ ВЫЧИСЛЕНИЕ - техника работающая по следующему принципу: 5: * Если значение первого операнда в операции AND (&&) ложно, то второй операнд не вычисляется, 6: * потому что полное выражение в любом случае будет ложным. 7: */ 8: 9: namespace ShortLogicComputing 10: { 11: class Program 12: { 13: static void Main() 14: { 15: int MIN_VALUE = 1; 16: int denominator = 0; 17: int item = 2; 18: 19: // Условие, которое работает с использованием техники КОРОТКОЗАМКНУТОГО ВЫЧИСЛЕНИЯ. 20: // Если бы это выражение вычислялось полностью, то операция деления во втором операнде, 21: // генерировала бы ошибку деления на 0. 22: 23: if ((denominator != 0) && (item / denominator) > MIN_VALUE) // Оставьте один оператор & 24: { 25: Console.WriteLine("Мы в блоке IF"); 26: } 27: else 28: { 29: Console.WriteLine("Мы в блоке ELSE"); 30: } 31: 32: // Delay. 33: Console.ReadKey(); 34: } 35: } 36: }
У нас идут короткозамкнутые вычисления. Что это такое? Давайте рассмотрим. Обратите внимание, на 15 строке у нас идет переменная MIN_VALUE = 1, на 16 denominator = 0, на 17 item = 2. Деноминатор – делитель. Мы целое число можем разделить на 0, нет не можем. Вещественное можем, а натуральные числа не можем. А нет обратной проверки, математики не нашли обратной проверки. Есть деление, а проверка за счет умножения. А у натуральных чисел ничего не работает. Попробуйте доказать, получите премию математическую. Смотрите, на 23 строке мы создаем условную конструкцию if в условии которой указано выражение – ((denominator != 0) && (item / denominator) > MIN_VALUE) Далее мы пытаемся поделить на ноль, а этого делать нельзя. Мы делим на ноль и проверяем больше ли результат от MIN_VALUE. Давайте выполнимся. Смотрите, мы находимся в блоке else, смотрите куда мы перешли. Что здесь происходит? Обратите внимание, КОРОТКОЗАМКНУТОЕ ВЫЧИСЛЕНИЕ - техника работающая по следующему принципу: если значение первого операнда в операции AND (&&) ложно, то второй операнд не вычисляется, потому что полное выражение в любом случае будет ложным. Если мы планируем во втором операнде выполнить какие-то операции у нас ничего не произойдет, потому что эта операция должна была бы вызвать исключение деления на ноль. Выполнимся. Divide by Zero Exception. Мы не можем число поделить на ноль. Это ошибка. И что это доказывает, то что мы туда даже не дошли. Мы только поняли, что в первой части конъюнкции получилось false? Мы даже не пошли вычислять вторую часть выражения. Еще раз выполнимся. Куда мы пошли? Мы находимся в блоке else. Здесь нам советуют поставить один амперсанд. Давайте выполнимся. Divide by Zero Exception. Вы видите? Отличия двойного амперсанда от одинарного. Короткозамкнутое вычисление работает только тогда, когда у нас используется двойной амперсанд. Поэтому будьте осторожны, не путайте одинарный амперсанд и двойной. Они относятся к разным понятия, обычная логика и побитовая логика, которая работает с битами.
1: using System; 2: 3: /* 4: * КОРОТКОЗАМКНУТОЕ ВЫЧИСЛЕНИЕ - техника работающая по следующему принципу: 5: * Если значение первого операнда в операции AND (&&) ложно, то второй операнд не вычисляется, 6: * потому что полное выражение в любом случае будет ложным. 7: */ 8: 9: namespace ShortLogicComputing 10: { 11: class Program 12: { 13: static void Main() 14: { 15: int MIN_VALUE = 1; 16: int denominator = 0; 17: int item = 2; 18: 19: // Условие, которое НЕ работает с использованием техники КОРОТКОЗАМКНУТОГО ВЫЧИСЛЕНИЯ. 20: // Из-за того что, операция && (And) вычисляется слева направо, 21: // данное логически эквивалентное выражение работать не будет! 22: 23: if (((item / denominator) > MIN_VALUE) && (denominator != 0)) 24: { 25: Console.WriteLine("Мы в блоке IF"); 26: } 27: else 28: { 29: Console.WriteLine("Мы в блоке ELSE"); 30: } 31: 32: // Delay. 33: Console.ReadKey(); 34: } 35: } 36: }
Условие, которое НЕ работает с использованием техники КОРОТКОЗАМКНУТОГО ВЫЧИСЛЕНИЯ. Из-за того что, операция && (And) вычисляется слева направо, данное логически эквивалентное выражение работать не будет! Смотрите, мы поменяли местами. Сначала делим на ноль. Как вы думаете, что здесь произойдет? Сразу же попытка деления на ноль. И сразу же мы получим исключение. Давайте попробуем. Видите, да? Divide by Zero Exception. View Detail. Inner Exception – нет. Попытка деления на ноль. Этого делать нельзя, потому что натуральное число на ноль делить нельзя. Это просто маленькая техника, потому что иногда, когда у вас будут длинные условия, помните о короткозамкнутых вычислениях, помните о том что вторая часть может быть не выполнена. И мы переходим к последнему примеру. И рассмотрим теоремы де Моргана.
1: using System; 2: 3: /* 4: * Преобразования логических переменных в соответствии с теоремами Де Моргана. 5: * 6: * Для применения теорем Де Моргана к логическому оператору AND или OR и паре операндов, 7: * требуется инвертировать оба операнда, заменить (AND на OR) или (OR на AND) и 8: * инвертировать все выражение полностью. 9: * 10: * Исходное выражение Эквивалентное выражение 11: * !A & !B = !(A | B) 12: * !A & B = !(A | !B) 13: * A & !B = !(!A | B) 14: * A & B = !(!A | !B) 15: * !A | !B = !(A & B) 16: * !A | B = !(A & !B) 17: * A | !B = !(!A & B) 18: * A | B = !(!A & !B) 19: */ 20: 21: namespace DeMorganTheorems 22: { 23: class Program 24: { 25: static void Main() 26: { 27: bool A = true; 28: bool B = false; 29: 30: // Условие до применения теоремы Де Моргана. 31: if (!A || !B) 32: Console.WriteLine("!A || !B = {0}", !A || !B); 33: else 34: Console.WriteLine("!A || !B = {0}", !A || !B); 35: 36: // Условие после применения теоремы Де Моргана. 37: if (!(A && B)) 38: Console.WriteLine("!(A && B) = {0}", !(A && B)); 39: else 40: Console.WriteLine("!(A && B) = {0}", !(A && B)); 41: 42: // Delay. 43: Console.ReadKey(); 44: } 45: } 46: }
Обратите внимание, что у нас имеется некое исходное выражение. Я говорю, что НЕ А И НЕ B. Представьте, что А и B – это истина, или ложь. НЕ false – true. НE false – true. True И true даст нам true. Теперь смотрим как мы можем представить это выражение по-другому. Помните, как в школе мы выносили за скобки что-то. В логике тоже самое. Смотрите как мы можем создать эквивалентное выражение. Для применения теорем Де Моргана к логическому оператору AND или OR и паре операндов, требуется инвертировать оба операнда, заменить (AND на OR) или (OR на AND) и инвертировать все выражение полностью. НЕ А мы что сделали? Инвертировали. К отрицанию добавили еще одно отрицание. Но отрицание на отрицание, их можно убрать. Все четные отрицания в логике убираются. Это как НЕ НЕ правда, значит это что? Правда. Поэтому мы берем и убираем здесь, сокращаем, условно инвертировав. Теперь смотрим дальше. Первое, требуется инвертировать оба операнда, заменить AND на OR или OR на AND. Дальше требуется инвертировать все выражение полностью. Берем в скобки все выражение и ставим знак отрицания. Если у нас там было false и false. Смотрите, что мы делаем. НЕ false – true/ Если у нас здесь false, false ИЛИ false дает false, инвертируем и получим true. Смотрите, абсолютно эквивалентные выражения. И вот эти выражения вы можете посмотреть разобрать на бумажке. Посчитайте как мы здесь делали с вами и думаю, что в каких-то случаях теоремы де Моргана покажутся вам полезными, просто лишь для удобства представления. Просто имея такое выражение, мы можем вынести за скобки знак отрицания, поменять знак операции и сделать просто более читаемо. Но это уже дело вкуса вот эти трансформации, использование теорем де Моргана, это дело вкуса и дело интересной техники. И здесь у нас показано использование и эквивалентность тех же теорем де Моргана. Здесь мы берем какое выражение из существующих НЕ А ИЛИ НЕ B. Есть у нас такое? Есть. Давайте исправим в формулах поставим двойные амперсанды и вертикальные слеши. Иначе будут работать побитовые операции. Я думаю, что и у себя вы возьмёте и немножко исправите. Везде у нас бывают небольшие описки. Я думаю, что вы себе его исправите. Правда? Потому что если бы это были побитовые операции, то у нас бы вот этот значок восклицательного знака изменился на что? На тильду. Ну а здесь мы используем работу с булевыми переменными. На этом мы закончили рассмотрение сегодняшнего урока. Давайте еще раз быстр-быстро пройдемся по презентации и вспомним что мы сегодня изучали. Во-первых, мы сегодня рассмотрели понятие конъюнкции, побитового И, дизъюнкция, дизъюнкция с битами, исключающее ИЛИ, исключающее ИЛИ с битами, отрицание, отрицание с битами, понятие двоичного числа в знаковом представлении. Мы знали, как представить натуральное число, а теперь мы поняли, как мы можем представить в одном байте знаковое число. Знаковое, то есть положительное или отрицательное. Потому что многие делают ошибку и говорят, что знаковое число, это отрицательное число. Есть числе где мы можем применить +-. Знаковые – это и положительные, и отрицательные. Далее мы посмотрели работу логического сдвига влево и вправо и рассмотрели много интересных примеров. На этом наш урок закончен. Спасибо за внимание. До новых встреч.