Преговор - УПП, Седмица 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, това е защото има някаква програмистка грешка.