## Лабораторная работа № 4 #### 1. Представление данных в памяти Начнем с реализации функции `nibble_to_hex()` и тестов для нее. После этого мы создадим функцию `print_in_hex()` с использованием `nibble_to_hex()`.
``` #include #include using namespace std; // Функция для представления nibble (4 бит) в шестнадцатеричной системе char nibble_to_hex(uint8_t i) { // Массив цифр в шестнадцатеричной системе char digits[] = "0123456789abcdef"; // Проверка на корректность аргумента assert(0x0 <= i && i <= 0xf); // Возвращаем символ для nibble из массива digits return digits[i]; } ``` Напишем реализацию функции `print_in_hex()` для печати одного байта в шестнадцатеричной и двоичной форме:
``` // Функция для печати одного байта в шестнадцатеричной и двоичной форме void print_in_hex(uint8_t byte) { // Печать в шестнадцатеричной форме cout << "Hex: " << nibble_to_hex(byte >> 4) << nibble_to_hex(byte & 0xf) << endl; // Печать в двоичной форме cout << "Binary: "; for (int i = 7; i >= 0; --i) { cout << ((byte >> i) & 1); } cout << std::endl; } ``` Итак, выполним проверку работы `nibble_to_hex()`, добавим в программу функцию-тест, вызываемую в начале main(): ``` int main() { // Тестирование функции nibble_to_hex assert(nibble_to_hex(0x0) == '0'); assert(nibble_to_hex(0x1) == '1'); assert(nibble_to_hex(0x2) == '2'); assert(nibble_to_hex(0x3) == '3'); assert(nibble_to_hex(0x4) == '4'); assert(nibble_to_hex(0x5) == '5'); assert(nibble_to_hex(0x6) == '6'); assert(nibble_to_hex(0x7) == '7'); assert(nibble_to_hex(0x8) == '8'); assert(nibble_to_hex(0x9) == '9'); assert(nibble_to_hex(0xa) == 'a'); assert(nibble_to_hex(0xb) == 'b'); assert(nibble_to_hex(0xc) == 'c'); assert(nibble_to_hex(0xd) == 'd'); assert(nibble_to_hex(0xe) == 'e'); assert(nibble_to_hex(0xf) == 'f'); ``` Также проверку можно выполнить в цикле чтобы код не был таким громоздким: ``` for (int i = 0; i <= 0xf; ++i) { assert(nibble_to_hex(i) == nibble_to_hex(i)); } ``` Теперь напечатаем байт в шестнадцатеричной и двоичной форме:<>br ``` using namespace std; // Функция для печати одного байта в шестнадцатеричной и двоичной форме void print_in_hex(uint8_t byte) { // Печать в шестнадцатеричной форме cout << "Hex: " << nibble_to_hex(byte >> 4) << nibble_to_hex(byte & 0xf) << endl; // Печать в двоичной форме cout << "Binary: "; for (int i = 7; i >= 0; --i) { cout << ((byte >> i) & 1); } cout << endl; } ``` Теперь заключим преобразование типов в функцию `const uint8_t* as_bytes(const void* data)`: ``` // Функция для преобразования типов const uint8_t* as_bytes(const void* data) { return reinterpret_cast(data); } ``` Также напишем функцию для печати массива байтов: ``` // Функция для печати массива байтов void print_in_hex(const void* data, size_t size) { const uint8_t* bytes = as_bytes(data); for (size_t i = 0; i < size; i++) { print_in_hex(bytes[i]); // Для удобства чтения: пробелы между байтами, по 16 байт на строку. if ((i + 1) % 16 == 0) { cout << '\n'; } else { cout << ' '; } } cout << endl; } ``` Теперь функция `as_bytes` принимает указатель на данные и возвращает указатель на байты этих данных, используя `reinterpret_cast`. Пример использования данной функции добавлен в `main()`, где мы создаем переменные u8, u16 и u32, присваиваем им значение 0x42, а затем выводим их байтовое представление, используя функцию `print_in_hex`: ``` int main() { uint8_t u8 = 0x42; uint16_t u16 = 0x42; uint32_t u32 = 0x42; cout << "u8 bytes: "; print_in_hex(&u8, sizeof(u8)); cout << '\n'; cout << "u16 bytes: "; print_in_hex(&u16, sizeof(u16)); cout << '\n'; cout << "u32 bytes: "; print_in_hex(&u32, sizeof(u32)); cout << '\n'; return 0; } ``` Добавим функцию `bit_digit` для проверки битов переменных u8, u16, и u32, и их вывода в двоичной системе. Обновим функцию `print_in_binary`. Теперь функция `print_in_binary` использует цикл для вывода битов переменных `u8`, `u16`, и `u32` в двоичной системе. ``` using namespace std; // Функция для проверки битов и вывода их в двоичной системе char bit_digit(uint8_t byte, uint8_t bit) { if (byte & (0x1 << bit)) { return '1'; } return '0'; } // Функция для печати байта в двоичной системе void print_in_binary(uint8_t byte) { for (uint8_t bit = 7; bit > 0; bit--) { cout << bit_digit(byte, bit); } } int main() { uint8_t u8 = 0x42; uint16_t u16 = 0x42; uint32_t u32 = 0x42; cout << "u8 bytes: "; print_in_hex(&u8, sizeof(u8)); cout << "\nu8 binary: "; print_in_binary(u8); cout << '\n'; cout << "\nu16 bytes: "; print_in_hex(&u16, sizeof(u16)); cout << "\nu16 binary: "; print_in_binary(u16); cout << '\n'; cout << "\nu32 bytes: "; print_in_hex(&u32, sizeof(u32)); cout << "\nu32 binary: "; print_in_binary(u32); cout << '\n'; return 0; } ``` Переведем в двоичное представление и напечатаем числа из лекционного слайда про двоичные операции (исходные два числа и результаты всех действий). ``` int main() { // Два числа для примера uint8_t num1 = 0b11011011; // 219 в десятичной системе uint8_t num2 = 0b00101101; // 45 в десятичной системе // Вывод двоичного представления чисел cout << "num1 в двоичной системе: "; print_in_binary(num1); cout << " (" << static_cast(num1) << " в десятичной системе)\n"; cout << "num2 в двоичной системе: "; print_in_binary(num2); cout << " (" << static_cast(num2) << " в десятичной системе)\n"; return 0; } ``` Напишем функцию `print_in_binary` для вывода массива байтов в двоичной системе по аналогии с `print_in_hex()`: ``` using namespace std; void print_in_binary(const void* data, size_t size) { const uint8_t* bytes = as_bytes(data); for (size_t i = 0; i < size; i++) { print_in_binary(bytes[i]); // Для удобства чтения: пробелы между байтами, по 4 байта на строку. if ((i + 1) % 4 == 0) { cout << '\n'; } else { cout << ' '; } } cout << endl; } int main() { // Примеры чисел для тестирования uint32_t u32 = 0x42424242; // 4,294,967,042 в десятичной системе uint16_t u16 = 0x4242; // 16,642 в десятичной системе cout << "u32 в двоичной системе:\n"; print_in_binary(&u32, sizeof(u32)); cout << "\nu16 в двоичной системе:\n"; print_in_binary(&u16, sizeof(u16)); return 0; } ``` Cоздаем два числа (`u32` и `u16`), переводим их в двоичное представление, и затем используем функцию `print_in_binary` для вывода байтов в двоичной системе. #### 2. Битовый калькулятор. Напишем программу-калькулятор для побитовых операций. ``` #include #include #include using namespace std; // Функция для проверки битов и вывода их в двоичной системе char bit_digit(uint16_t num, uint8_t bit) { return (num & (0x1 << bit)) ? '1' : '0'; } // Функция для печати числа в двоичной системе void print_in_binary(uint16_t num) { for (int8_t bit = 15; bit >= 0; --bit) { cout << bit_digit(num, bit); } } int main() { uint16_t operand1, operand2, result; char operation; // Ввод первого операнда, оператора и второго операнда cout << "Введите первый операнд (в шестнадцатеричной форме): "; cin >> hex >> operand1; cout << "Введите оператор (&, | или ^): "; cin >> operation; cout << "Введите второй операнд (в шестнадцатеричной форме): "; cin >> std::hex >> operand2; // Выполнение операции в зависимости от введенного оператора switch (operation) { case '&': result = operand1 & operand2; break; case '|': result = operand1 | operand2; break; case '^': result = operand1 ^ operand2; break; default: std::cerr << "Неверный оператор. Допустимые операторы: &, |, ^.\n"; return 1; } // Вывод результата в шестнадцатеричной и двоичной форме cout << hex << setw(4) << setfill('0') << operand1 << ' ' << operation << ' ' << setw(4) << setfill('0') << operand2 << " = " << setw(4) << setfill('0') << result << '\n'; cout << setw(16) << setfill(' ') << left << "в двоичной системе: "; print_in_binary(operand1); cout << ' ' << operation << ' '; print_in_binary(operand2); cout << " = "; print_in_binary(result); cout << '\n'; return 0; } ``` Тут программа предлагает пользователю ввести два операнда и оператор (`&`, `|` или `^`). Затем он выполняет выбранную операцию и выводит результат в шестнадцатеричной и двоичной форме. #### 3. Исследование представления и размещения данных в памяти ``` #include #include #include #include using namespace std; struct Student { char name[17]; uint16_t admissionYear; float averageGrade; bool isMale : 1; uint8_t completedCourses; Student* groupLeader; }; int main() { const int numberOfStudents = 3; Student students[numberOfStudents]; students[0].admissionYear = 2023; students[0].averageGrade = 4.5; students[0].isMale = true; students[0].completedCourses = 5; students[0].groupLeader = nullptr; students[1].admissionYear = 2022; students[1].averageGrade = 3.8; students[1].isMale = false; students[1].completedCourses = 6; students[1].groupLeader = &students[0]; students[2].admissionYear = 2021; students[2].averageGrade = 3.2; students[2].isMale = true; students[2].completedCourses = 7; students[2].groupLeader = nullptr; for (int i = 0; i < numberOfStudents; ++i) { cout << "\nИнформация о студенте " << i + 1 << ":\n"; cout << "Адрес поля admissionYear: " << static_cast(&students[i].admissionYear) << ", Смещение: " << offsetof(Student, admissionYear) << ", Размер: " << sizeof(students[i].admissionYear) << " байт, " << "Шестнадцатеричное: " << hex << students[i].admissionYear << ", Двоичное: " << bitset<16>(students[i].admissionYear) << '\n'; cout << "Адрес поля averageGrade: " << static_cast(&students[i].averageGrade) << ", Смещение: " << offsetof(Student, averageGrade) << ", Размер: " << sizeof(students[i].averageGrade) << " байт, " << "Значение: " << students[i].averageGrade << '\n'; cout << "Адрес и битовое поле isMale: " << static_cast(&students[i].isMale) << ", Смещение: " << offsetof(Student, isMale) << ", Размер: " << sizeof(students[i].isMale) << " бит, " << "Значение: " << (students[i].isMale ? "мужской" : "женский") << '\n'; cout << "Адрес поля completedCourses: " << static_cast(&students[i].completedCourses) << ", Смещение: " << offsetof(Student, completedCourses) << ", Размер: " << sizeof(students[i].completedCourses) << " байт, " << "Значение: " << static_cast(students[i].completedCourses) << '\n'; cout << "Адрес поля groupLeader: " << static_cast(&students[i].groupLeader) << ", Смещение: " << offsetof(Student, groupLeader) << ", Размер: " << sizeof(students[i].groupLeader) << " байт, " << "Шестнадцатеричное: " << hex << reinterpret_cast(students[i].groupLeader) << '\n'; // Выводим представление структуры в памяти cout << "Представление в памяти (в шестнадцатеричной системе):\n"; const uint8_t* bytes = reinterpret_cast(&students[i]); for (size_t j = 0; j < sizeof(Student); ++j) { cout << hex << setfill('0') << setw(2) << static_cast(bytes[j]) << ' '; } cout << '\n'; } return 0; } ``` Тут мы создаем структуру `Student`, представляющую информацию о студенте, и инициализирует массив из трех студентов. Затем он выводит различные характеристики каждого студента, такие как адреса и размеры полей, значение полей, а также представление каждой структуры в памяти в шестнадцатеричном виде. Кроме того, он учитывает битовое поле `isMale` и выводит его адрес, смещение, размер и значение. #### 4. Работа со строками C ``` #include #include #include #include const int MAX_FILENAME_LENGTH = 256; const int MAX_INPUT_STRING_LENGTH = 256; bool isValidFilename(const char* filename) { // Проверка наличия запрещенных символов const char* forbiddenChars = "*\"<>?|"; if (strpbrk(filename, forbiddenChars) != nullptr) { return false; } // Проверка наличия двоеточия const char* colonPosition = strchr(filename, ':'); if (colonPosition != nullptr) { if (colonPosition == filename + 1 && isalpha(filename[0])) { // Проверка двоеточия только вторым символом const char* backslashPosition = strchr(colonPosition + 1, '\\'); if (backslashPosition == nullptr) { return false; } } else { return false; } } // Проверка расширения файла const char* extension = strrchr(filename, '.'); if (extension != nullptr) { if (strcmp(extension, ".txt") != 0 && strcmp(extension, ".TXT") != 0) { return false; } } return true; } int main() { // Запрос имени файла char filename[MAX_FILENAME_LENGTH]; std::cout << "Введите имя файла: "; std::cin.getline(filename, MAX_FILENAME_LENGTH); // Проверка корректности имени файла if (!isValidFilename(filename)) { std::cerr << "Некорректное имя файла.\n"; return 1; } // Добавление расширения .txt, если его нет if (strrchr(filename, '.') == nullptr) { strcat(filename, ".txt"); } // Открытие файла std::ifstream file(filename, std::ios::binary); if (!file.is_open()) { std::cerr << "Ошибка открытия файла.\n"; return 1; } // Определение размера файла file.seekg(0, std::ios::end); std::streampos fileSize = file.tellg(); file.seekg(0, std::ios::beg); // Выделение памяти под содержимое файла char* fileContent = new char[fileSize]; // Чтение содержимого файла в память file.read(fileContent, fileSize); file.close(); // Запрос строки от пользователя char inputString[MAX_INPUT_STRING_LENGTH]; std::cout << "Введите строку для поиска: "; std::cin.getline(inputString, MAX_INPUT_STRING_LENGTH); // Подсчет вхождений строки в файл int occurrences = 0; char* position = fileContent; while ((position = strstr(position, inputString)) != nullptr) { occurrences++; position += strlen(inputString); } // Вывод результата std::cout << "Число вхождений строки в файл: " << occurrences << std::endl; // Освобождение выделенной памяти delete[] fileContent; return 0; } ``` Данный код выполняет все шаги, описанные в задании: запрос имени файла, проверка корректности имени, добавление расширения .txt, загрузка содержимого файла в память, запрос строки от пользователя, подсчет и вывод числа вхождений строки в файл, а затем освобождение памяти.