Завдання 10 Дедлайн 11 грудня

в

Дайте відповіді на запитання:

  1. Коротко опишіть поширені представлення знакових цілих чисел у пам’яті. Яке з них використовується у C++?
  2. На прикладах поясніть різницю між арифметичним, логічним і циклічним зсувом. Який з них відповідає оператору >>?
  3. Яку дію виконують оператори # та ## у макросах?
  4. Чому в макросах весь код інколи загортають у конструкцію do-while?

Відповідь:

1. Коротко опишіть поширені представлення знакових цілих чисел у пам’яті. Яке з них використовується у C++?

У всіх представленнях знакових цілих старший біт це знак - 0 (невід'ємне) або 1 (від'ємне), а решта - саме значення. Різниця полягає в тому, як інтерпретувати біти від'ємних значень, як отримати відповідне від'ємне значення з додатного, і в діапазоні значень.
Прямий код - sign and magnitude, "знак і модуль". Зміна знаку на протилежний - просто зміна найстаршого біта:
2: 00010
-2: 10010

Діапазон значень симетричний: від -2n-1 — 1 до 2n-1 — 1, але має додатний і від’ємний нуль, тому всього є 2n — 1 унікальних значень. Обернений код — ones’ complement, «доповнення до одиниць». Зміна знаку на протилежний — побітове заперечення.

10: 01010
-12: ~01100 + 00001 = 10011 + 00001 = 10100

Особливість: Якщо побітово додати два протилежних n-бітних числа, отримаємо 2n+1: 01100 + 10100 = 100000. Діапазон значень асиметричний: від -2n-1 до 2n-1 — 1, але має 2n унікальних значень, тобто кожен бітовий патерн унікальний. Внутрішньо C++ використовує доповняльний код (так було по факту завжди, і зафіксовано в стандарті C++20), оскільки він ефективно використовує ресурси комп’ютера і спрощує реалізацію на фізичному рівні.

2. На прикладах поясніть різницю між арифметичним, логічним і циклічним зсувом. Який з них відповідає оператору >>?

Приклади на 6-бітних значеннях (two's complement).

Логічний зсув заповнює всі біти нулями, як для знакових, так і для беззнакових.
111011 << 1 = 110110
111111 >> 3 = 000111

Циклічний зсув ставить відкинуті біти на місце звільнених, як для знакових, так і для беззнакових:

001101 >> 2 = 010011
001101 << 2 = 110100

Арифметичний зсув на n біт відповідає множенню (вліво) чи діленню з заокругленням до меншого (вправо) на 2 в степені n. Беззнакові та додатні знакові загалом мають ту ж поведінку, що і логічний зсув:

000101 (5) << 2 = 010100 (20)
000101 (5) >> 2 = 000001 (1)

Знакові, проте, можуть стати від’ємними внаслідок зсуву вліво: 000101 (5) << 3 = 010100 (-24 для знакових) І лише для негативних знакових чисел при зсуві вправо знаковий біт зберігається:

111011 (-5)  << 1 = 110110 (-10)
111011 (-5)  >> 1 = 111110 (-3)
111111 (-1)  >> 3 = 111111 (-1)

Оператор >> в C++ виконує арифметичний зсув.

3. Що роблять оператори # та ## у макросах?

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

Оператор # - це перетворення параметра макроса-функції у рядок (const char[]), прямо так, як він переданий. Використовується здебільшого для логування. Нижче приклад макроса, що виводить не лише результат виразу, а і сам вираз.
#include <iostream>

#define PRINT(expr) std::cout << #expr << " = " << expr << std::endl

int main() {
    int a = 5;
    int b = 10;
    PRINT(a);      // Виведе: a = 5
    PRINT(b);      // Виведе: b = 10
    PRINT(a + b);  // Виведе: a + b = 15
    return 0;
}

Оператор ## — це конкатенація. Використовується здебільшого для генерації коду, особливо в тестуванні для створення ідентифікаторів. Нижче приклад оголошення цілих чисел, де сам тип додається до імені змінної:

#include <iostream>

#define INT(type, name, value) type type##_##name = value

int main() {
   INT(signed, one, 1);
   INT(unsigned, two, 2);
   INT(char, three, '3');
   std::cout << signed_one << unsigned_two << char_three << std::endl; // Виведе: 123
   return 0;
}

4. Чому в макросах весь код інколи загортають у конструкцію do-while?

#define MACRO             \
    do {                  \
        std::cout << "A"; \
        std::cout << "B"; \
        std::cout << "C"; \
    } while (0)

Це робиться з кількох різних причин:
— Щоб змусити поставити крапку з комою після використання макроса.
— Щоб заховати тимчасові змінні і не засмічувати простір імен.
— Щоб дозволити використовувати макрос з кількома окремими діями в однорядковій формі запису розгалуження. Без do-while, наступний вираз би виводив «А», якщо умова виконується, і після цього «BC», але завжди, незалежно від умови:

if (/* умова */) MACRO;

Загорнуті в do-while, або всі три літери виводяться, або ні.


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *

Максимальный размер загружаемого файла: 1 ГБ. Вы можете загрузить: изображение, аудио, видео, документ, таблица, интерактив, текст, архив, код, другое. Ссылки на YouTube, Facebook, Twitter и другие сервисы, вставленные в текст комментария, будут автоматически встроены. Перетащите файл сюда