Преговор - УПП, Седмица 2, 13.10.2023




Конвертиране на данни, побитови оператори, условни оператори

Конвертиране на данни

  • Неявно (скрито, експлицитно): компилаторът сам се досеща да промени типа
      int a = 5;
      double b = a;
    
  • Явно (изрично, експлицитно): казваме как типът да се конвертира
      double a = 5.7, b = 3.42;
      int m = (int)a + (int)b;
    
  • Със загуба на информация (от по-голям тип в по-малък)
      double a = 5.8371;
      int b = a;
    
  • Без загуба на информация (от по-малък тип в по-голям)
      int a = 1000;
      long b = a;
    

Побитови оператори

Първо ще ги погледнем чисто теоретично.

Внимавайте, те са различни от логическите оператори!

Означавам побитовите числа с водеща буква "b"! Тоест, числото b1001 е двоичното число 1001, което е десетичното число 9.

  • Отрицание/негация, ~: обръща 0 в 1 и 1 в 0
      ~ b0010 = b1101
    
  • Побитово "и", &: за два бита, ако и двете са 1, връща 1, иначе връща 0
      3 & 5 = 1
      b0011 & b0101 = b0001
    
  • Побитово "или", |: за два бита, ако и двете са 0, връща 0, иначе връща 1
      3 | 5 = 7
      b0011 | b0101 = b0111
    
  • Изключващо "или", ^: ако само един от двата бита е 1, връща 1, иначе връща 0
      3 ^ 5 = 6
      b0011 ^ b0101 = b0110
    
  • Побитово отместване на ляво, <<: отмества всеки бит с n позиции на ляво
    Винаги "запълва" с нули (няма разлика между логическо и аритметично ляво отместване)
      b1010 << 1 = b0100
      b0001 << 3 = b1000
    
    (приемаме, че типа данни съхранява само 4 бита данни)
  • Побитово отместване на дясно, >>: отмества всеки бит с n позиции на дясно
    При аритметичното отместване, дупките се запълват с предходната стойност на старшия бит
    При логическото отместване, винаги запълваме с 0
    В C++ има един оператор, дали е аритметическо или логическо се определя от типа
      b1000 >> 1 = b0100 или пък b1100
      b1010 >> 3 = b0001 или пък b1111
    
Примери с код:
int a = 3, b = 5;

int n1 = ~ a; // -4
int n2 = ~ (unsigned int)a; // 4294967292

int i1 = 3 & 5; // 1
int i2 = 3 | 5; // 7
int i3 = 3 ^ 5; // 6

int l1 = b << 1; // 10
int l2 = b << 3; // 40

int r1 =  b >> 1; // 2
int r2 =  b >> 2; // 1
int r3 = -b >> 1; // -3
int r4 = (unsigned int)b  >> 1; // 2
int r5 = (unsigned int)-b >> 1; // 2147483645

Логически оператори

Много приличат на побитовите, обаче побитовите работят върху битове, докато логическите работят върху bool.

  • !: обръща true във false и обратно
       ! true  // false
       ! false // true
    
  • &&: "логическо и", ако и двете стойности са true, тогава и резултатът е true
      true  && true  // true
      false && true  // false
      false && false // false
    
  • ||: "логическо или", само ако и двете са false, тогава е false
      true  || true  // true
      false || true  // true
      false || false // false
    

Условни конструкции

  • if: приема булева стойност, ако е true, изпълнява кода в къдравите скоби, ако е false ги пропуска
      if (expression) {
          dosomething;
      }
      dootherthing;
    
    expression се конвертира към типа bool
    Ако в къдравите скоби имаме един израз (тоест е само един ред код, завършващ на ;) можем да ги пропуснем.
      if (expression) dosomething;
      dootherthing;
    
  • if-else: работи като if, обаче след къдравите скоби добавяме else { ... }.
      if (expression) {
          dothing1;
      }
      else {
          dothing2;
      }
      dootherthing;
    

    Ако expression е истина, dothing1 се изпълнява и dothing2 се пропуска изцяло. Аналогично, ако expression е лъжа, dothing1 се изпуска и dothing2 се изпълнява.

    Както с if, ако dothing2 е само един израз, можем да пропуснем къдравите скоби

      if (expression) {
          dothing1;
      }
      else dothing2;
      dootherthing;
    
  • благодарение на изпускането на къдрави скоби, можем да пишем
      if (expression1) {
          doSomething1;
      }
      else if (expression2) {
          doSometing2;
      }
      ...
      else {
          doSomethingN;
      }
    
    Това е еквивалентно на:
      if (expression1) doSomething1;
      else {
          if (expression2) doSomething2;
          else {
              ...
                  else doSomethingN;
          }
      }
    
  • expression винаги се конвертира до тип bool (ако е нужно)
    Никога не правете такива неща:
      bool a;
      if (expression) a = true;
      else a = false;
    
      bool a = false;
      if (expression) a = true;
    
    Просто правите bool a = expression;
      bool a;
      if (expression) a = false;
      else a = true;
    
    Просто правите bool a = ! expression;
  • Тернарен оператор: ако искате да имате такава условна конструкция като част от израз (който взема стойност) използвате
      (expression) ? valueA : valueB
    
    Ако expression е истина, връща се valueA, иначе се връща valueB
      int grade;
      std::cin >> grade;
      double a = (grade < 300) ? 2.0 : grade / 100.0;
    
    Има много нисък приоритет, в повечето случаи ще ви се наложи да го оградите в скоби.
      int grade;
      std::cin >> grade;
      std::cout << ((grade < 300) ? 2.0 : grade / 100.0);
    
  • switch: приема стойност и изпълнява случая, при който стойността съвпада с определената
      switch (value) {
          case valueA: doSomething1; break;
          case valueB: doSomething2; break;
          case valueC: doSomething3; break;
      }
    
    Служи като улеснение на синтаксиса:
      if (value == valueA) doSomething1;
      else if (value == valueB) doSomething2;
      else if (value == valueC) doSomething3;
    

    otherwise е като else, това е случая когато не съвпада с никоя стойност. Може да бъде изпуснат.

    Без break изпълнението продължава към следващия case

char characterInput;
std::cin >> characterInput;
switch(characterInput) {
    case 'h':
    case '?': std::cout << "This is a helpful message."; break;
    case 'w': std::cout << "Hello World"; break;
    default: std::cout << "Error"; break;
}