Дайте відповіді на запитання:
- Коротко опишіть поширені представлення знакових цілих чисел у пам’яті. Яке з них використовується у C++?
- На прикладах поясніть різницю між арифметичним, логічним і циклічним зсувом. Який з них відповідає оператору >>?
- Яку дію виконують оператори # та ## у макросах?
- Чому в макросах весь код інколи загортають у конструкцію 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, або всі три літери виводяться, або ні.
Добавить комментарий