Преговор - УПП, Седмица 3, 20.10.2023
Цикли, assert
Бележки от миналия път
#Видях две груби грешки в предадените решения, първо:
bool b; if (condition) b = true;
Грешно е, понеже
condition
сам по себе си е от тип bool, с тозиif
буквално не правите нищо. Правилния вариант е:bool b = condition;
Аналогично за:
bool b; if (!condition) b = true;
правилното е:
bool b = !condition;
второ:
a >= 48 && a <= 57
Всяка буква действително съответства на определено число, дефинирано от ASCII стандарта. Наистина ‘0’ е 48 и ‘9’ е 57, обаче от вас не се очаква да помните ASCII таблицата! Също, ако не знаеш наизуст кои буква са тези числа, този израз е много неясен.
Правилния вариант е просто да направите сравнения с буквите, все пак C++ ги “конвертира” (много грубо казано) към съответните ASCII стойности:
a >= '0' && a <= '9'
С/без загуба на данни
#Получи се малко разногласие между това, което Ивана ви е казала и това което аз ви казах. В края на деня, конвертиране от int към unsigned int не губи информация!
Цитирайки 4то издание на “The C++ Programming Language” глава 10.5:
A conversion is value-preserving if you can convert a value and then convert the result back to it’s original type and get the original value
Тоест, конвертирания при които не губиш битове, нямаш загуба на данни.
2s compliment
#Интуитивно обяснение на 2s compliment. Да приемем, че работим с тип данни, при който имаме само 3 бита.
Как бихме ги използвали, за да представим отрицателните числа?
Най-лесния начин е да погледнем най-левия (старшия) бит, ако е 0 значи е положително, ако е 1 значи е отрицателно, тоест 010
е 2, докато 110
е -2.
Ако разпишем всичките ни числа:
111 -3
110 -2
101 -1
100 -0
011 3
010 2
001 1
000 0
Това работи, обаче имаме един гигантски проблем: събирането не работи! От математика знаем, че “2 - 1 = 2 + (-1)”, тоест би трябвало да можем да съберем побитово 2 и -1 и да получим 1. Ама,
2 + -1 = 010 + 101 = 111 = -3
Също, знаем, че “(-1) + (-1) = -2”, обаче
-1 + -1 = 101 + 101 = 1010 имаме overflow, това се съкращава на 010 = 2
Излиза, че с тази схема не можем да събираме отрицателни числа както събираме положителни (или както събираме побитово).
Да не говорим, че ние имаме -0, което не ни трябва много.
Нека да помислим пак, да вземем само положителните:
011 3
010 2
001 1
Сега, искаме -1 да бъде такова число, различно от досега дефинираните (тоест което има единица в старшия си бит) така че “1 + -1 = 0”. Лесно се досещаме, че ако добавим към “001” числото “111”, тогава тяхната сума ще предизвика overflow и резултата ще бъде 0:
001 + 111 = 1000 overflow, става 000
Нека тогава “-1 = 111”.
Правейки същото за “2 + -2”, намираме че
010 + 110 = 1000 overflow, става 000
Нека тогава “-2 = 110”. За “3 + -3” се досещаме, че “-3 = 101”. За сега имаме:
111 -1
110 -2
101 -3
100 x
011 3
010 2
001 1
000 0
100 не сме го използвали, обаче намерихме отрицателното число за всяко положително. Да видим как работим с него, побитово “100 + 001 = 101”, тоест “x + 1 = -3”, следователно x ще бъде -4.
Това е идеята на “complements”, дефинираме отрицателни числа, така че да използваме едно и също събиране между положителни и отрицателни.
Още от много старите “калкулатори” (сумиращи машини), изваждането се е свеждало до събиране на положително и отрицателно число.
И това е причината защо ~ 2
връща -3
:
~ 00000000000000000000000000000010
──────────────────────────────────
11111111111111111111111111111101
И затова при >>
със знаков тип имаме аритметическо отместване:
11111111111111111111111111111101 >> 1
─────────────────────────────────────
11111111111111111111111111111110
Цикли
#while
цикълwhile (условие) { тяло }
“условие” е булева стойност, като ако е
false
цикъла спира, докато ако еtrue
“тяло” се изпълнява и после пак се проверява условие
Може да си го представите като
if (условие) { тяло } else { спри } if (условие) { тяло } else { спри } ...
do-while
цикъл, катоwhile
, но “тяло” се изпълнява поне един пътdo { тяло } while (условие);
Нашият аналог е:
тяло if (условие) { тяло } else { спри } if (условие) { ...
for
цикълfor (инициализация; условие; инкрементация) { тяло }
“инициализация” се изпълнява само веднъж, преди всичко останало в цикъла
"условие” се изпълнява преди всяка итерация, като ако еfalse
цикъла спира, докато ако еtrue
продължава
"инкрементация” се изпълнява след края на всяка итерация
Може да си представяте, че това на практика извършва:
инициализация if (условие) { тяло } else { спри } инкрементация if (условие) { тяло } else { спри } ...
или използвайки
while
цикъл:инициализация while (условие) { тяло инкрементация }
Ключови думи
#break
: моментално прекратява изпълнението на цикълаПри
for
цикли инкрементацията не се изпълнява следbreak
continue
: продължи след края на сегашната итерация; тоест изпълнението на тяло се прекратява и започваме нова итерацияПри
for
цикли директно отива на инкрементация
Assert
#Понякога е полезно да тестваме нашия код вътре в самия код. Тоест, искаме на конкретно място в кода да проверим нещо.
assert(condition);
Ако condition
е true
, нищо не се случва, обаче ако е false
програмата моментално прекратява изпълнение.
Използва се повече като програмистки уред, тоест ако програмата спре заради assert
, това е защото има някаква програмистка грешка.