Ответы пользователя по тегу Структуры данных
  • Как хранится c++ struct в памяти и как определить размер вручную?

    @Mercury13
    Программист на «си с крестами» и не только
    Выяснилось, что на компьютерах с разрядностью 16 и более бит минимальной единицей памяти стоит делать всё равно 8-битный байт: это сильно упрощает работу с узкими типами данных, как в памяти, так и на вводе-выводе. Например, канал RGB8 или кодовая единица UTF-8 — байт. Считаем для простоты, что разрядность 16, порядок Intel.

    Чтобы загрузить 2-байтовое число в 2-байтовый регистр, есть ДВА варианта.
    1. ОЗУ, кэш и другие устройства хранения передают нижний байт данных на нижний байт шины, верхний, соответственно, на верхний.
    2. Байты, чьи номера кратны двум, соединяются только с нижним байтом шины, не кратные — только с верхним. А уж процессор как-то разбирает, что делать, если двухбайтовое число сидит по нечётному адресу и считывается за два обращения к шине.

    Более простым оказался второй подход: он упрощает схемотехнику всех сидящих на шине, кроме процессора. В частности, дешифраторы становятся на 1 бит меньше, и упрощается топология. А процессор — в нём и так микропрограммное управление, и в нём и так до хрена соединений, и если обратился к байту (!) по адресу условно 4, вероятно, скоро потребуется адрес 5 и его один хрен стоит закэшировать.

    Но это значит, что доступ к 2-байтовым числам по нечётному адресу (1-2, 3-4, 5-6) будет медленнее, чем по чётному (0-1, 2-3, 4-5). И потому по умолчанию двухбайтовые числа располагаются именно по ЧЁТНЫМ адресам, и структура { char a; short b; char c; } будет иметь длину 6 байт (0 — a, 1 — выравнивание, 2,3 — b, 4 — c, 5 — выравнивание), а структура { short b; char a; char c; } — всего 4 байта (всё упаковано плотно).

    Но это приводит к двум вещам.
    1. Если нужно сочетать скорость и компактность, надо чётко выравнивать структуры данных. Обычно подобная ручная оптимизация делается для мелких частых структур.
    2. Если кусок памяти подготавливается в памяти, а потом отправляется на устройство, надо принудительно отключить выравнивание. Если в предыдущем примере формат файла так устроен, что байт 0 — a, байты 1 и 2 — b, байт 3 — c, то можно написать
    #include <iostream>
    
    struct Q1 { char a; short b; char c; };
    struct __attribute__ ((packed)) Q2 { char a; short b; char c; };
    
    int main()
    {
        std::cout << sizeof(Q1) << ' ' << sizeof(Q2) << '\n';   // 6 4
    }

    Q1 (или более оптимизированную short-char-char) использовать для хранения в памяти, а Q2 — для записи в файл, раз уж у него такой формат.

    Да, почему компилятор сам этого не делает? Это нарушает совместимость компиляторов. В Си++ до 20 включительно он имел право кое-что переставить, если в struct/class были поля с разными правами доступа (но никто по факту не делал), в 23+ больше не имеет права.

    Ну а алгоритм прост.
    1. Длина := 0, выравнивание := 1
    2. Для каждого поля: позиция := длина, округлённая по выравниванию[i]; длина := позиция+длина[i]; выравнивание := max{выравнивание; выравнивание[i]}
    3. Общая длина := длина, округлённая по общему выравниванию

    Существует очень простой способ уменьшить выравнивание: отсортировать поля по убыванию выравнивания.

    Для того же Q1:
    0. Длина = 0, выравнивание = 1
    1. char a: длина округляется до 0, затем 1, выравнивание = 1
    2. short b: длина округляется до 2, затем 4, выравнивание = 2
    3. char c: длина округляется до 4, затем 5, выравнивание = 2
    4. Общая длина округляется до 6. Ну а выравнивание = 2
    Ответ написан
    Комментировать
  • Где и как используют деревья в программировании?

    @Mercury13
    Программист на «си с крестами» и не только
    1. Нечто, действительно имеющее древовидную форму — например, деревья каталогов на дисках, деревья сцен в 3D, деревья принятия решений.
    2. Деревья поиска — структуры данных, позволяющие добавлять-убирать объекты и позволяющие быстрый поиск по ключу. Например, словари всякие, индексы БД.
    3. Так называемая куча — структура данных, позволяющая добавлять-убирать объекты и поддерживающая минимальный элемент в этом множестве. Используется как вспомогательная в каких-нибудь алгоритмах.
    4. Двоичное разбиение пространства в 3D — известный способ сортировки от дальних к ближним.

    Кроме того, деревья могут не существовать в памяти, а только подразумеваться — в синтаксических разборах, теоретико-игровых обсчётах. Хотя один из вариантов промежуточного хранения разобранной формулы, чтобы потом по ней проводить многократные расчёты — дерево (но чаще используют обратную польскую запись).
    Ответ написан
    Комментировать
  • Существует ли структура данных «расширяемая 2D-таблица»?

    @Mercury13 Автор вопроса
    Программист на «си с крестами» и не только
    Пока остановился на таком механизме.
    Есть два одномерных массива — оглавление строк и оглавление столбцов, два одномерных массива — задействованность строк/столбцов, а также двухмерный массив — буфер.

    a[i,j] := buffer[rowIndex[i−i0], colIndex[j−j0]]

    Если в rowIndex[i−i0] замечен маркер незадействованности, находим незадействованную строку и прописываем её в оглавлении.
    Чтобы меньше перевыделять памяти, перевыделение идёт импульсами и с запасом. Например, хотим 20 строк, а выделяем сразу на 30.
    Такой массив способен только расширяться, но этого мне хватает.
    Ответ написан
    Комментировать
  • В чем разница связаного списка от хеш-таблицы?

    @Mercury13
    Программист на «си с крестами» и не только
    Связанный список решает такую задачу: как хранить коллекцию объектов, добавляя и удаляя туда объекты. (Простите, что я не пишу характеристики того и другого, почитайте это в умных книгах)

    В чистом виде связанный список используется крайне редко из-за ограничений, но представьте себе объектный пул (кучу готовых к использованию объектов), и надо хранить список свободных — очень удобно использовать связанный. Также связанным иногда хранят содержимое гнезда в хэш-таблице.

    Хэш-таблица решает другую задачу: наладить отображение ключ→значение. Например, «осёл → иа, петух → кукареку», и так далее. Массив, только индексом будет не цифра, а что-то другое: x[«осёл»] = «иа». Так называемый ассоциативный массив.

    Если индексом массива может быть только цифра, поступим так: превратим нашего осла в цифру — например, о+с+ё+л = 4363 (в Юникоде), и пусть 63 — это номер гнезда. В 63-м элементе массива пусть и лежит наше «осёл → иа».

    Если у другого животного значением хэша будет 63 — это хэш-коллизия, и в разных реализациях решается по-разному. Я знаю такое: в гнезде лежит не просто один элемент, но связанный список. Главное, что такое слегка снижает производительность, но допустимо.
    Ответ написан
    Комментировать
  • Hash талица это асоциативный массив индексов и адрессов в памяти?

    @Mercury13
    Программист на «си с крестами» и не только
    Производительность хэш-таблиц — O(1) в среднем. Усреднённое по всем возможным наборам данных, если хэш-функция хорошо перемешивает.

    Хэш-таблица действует так. Допустим, у нас выпало значение хэш-функции 1234 и у нас в хэш-таблице 100 гнёзд. Тогда наш элемент где-то в гнезде 34. Когда расширим таблицу до 200 гнёзд, элемент останется в гнезде 34, а когда расширим до 1000 — он переедет в гнездо 234.

    Как разрешаются коллизии (два элемента в одном гнезде) — зависит от реализации: например, в гнезде могут быть связанные списки элементов.

    Для словарей, если тот строится раз и до конца своей (не)долгой жизни, можно применить и другой способ, совершенно неубиенный и дешёвый по памяти. Взять массив, построить и отсортировать. Бинарный поиск — O(log n).
    Ответ написан
    2 комментария
  • Что выполняют эти функции в коде?

    @Mercury13
    Программист на «си с крестами» и не только
    1. Найти «в лоб» НОД и похѣрить его.
    2. Найти «в лоб» НОК и отправить его туда же.
    Повторяю, обе функции крайне неэффективны, а их результат идёт в никуда. Как только управление пересечёт точку с запятой, переменная исчезнет.
    Ответ написан
    Комментировать
  • Какой хэшкод является идейно верным?

    @Mercury13
    Программист на «си с крестами» и не только
    Существуют так называемые криптографические хэши, задача которых — сделать, чтобы генерация двух документов с одинаковым хэшем была вычислительно невозможна. Если хэш удлиняется на два бита, эта задача усложняется, насколько мне известно, не вчетверо, а вдвое.

    Потому криптографические хэши огромные (устаревший MD5 — 16 байтов, большинство современных — 32 или 64). Как его записать в БД, если и тип такой не всегда есть, а BLOB’ы — пальба из пушки по муравьям? Как-то закодировать в строчку. К тому же в вебе есть места, где двоичные данные не катят и без строчного кодирования никак (например, URL’ы).

    Автор, очевидно, мэн из лагеря Perl/PHP и в дополнение к вычислению хэша взял и закодировал его. Вероятно, методом BASE64 (8 символов BASE64 соответствуют 6-байтовому хэшу). Для чего так кодировать обычные, не криптографические хэши — я вообще не знаю!
    Ответ написан
    Комментировать
  • Нету вывода из массива с указателями из структуры с++?

    @Mercury13
    Программист на «си с крестами» и не только
    Первое и главное. Удивительно, что у вас программа заработала, ведь text.sentences не инициализировано. У меня вылет.

    Ну и извечная ошибка начинающего «плюсовика»: память выделяется, непонятно, кто чем владеет, и, разумеется, память «течёт». Для чего, извините, в Си++ сделали инкапсуляцию, конструкторы и деструкторы?
    Ответ написан
    Комментировать
  • Структура данных типа очереди, позволяющая быстро определить позицию элемента. Есть?

    @Mercury13
    Программист на «си с крестами» и не только
    Очередь, физически не перемещающая элементы (кольцевая или некое подобие std::deque) и позволяющая по указателю быстро определить позицию.

    Плюс любой индекс (хэш- или деревянный), элементы которого — указатели на элементы очереди. Если элементы очереди unique_ptr, то указатели будут именно на unique_ptr! Для «мусорных» языков (C#, Java) вместо указателей можно придумать какие-то идентификационные коды — например, сочетание из номера в пуле массивов и номера в массиве.

    Enqueue — вписываем элемент в индекс. Dequeue — удаляем из индекса. Обмен позициями — корректируем эти два элемента индекса.
    Ответ написан
    Комментировать
  • Как правильно организовать структуру в классе?

    @Mercury13
    Программист на «си с крестами» и не только
    Помимо того, что я написал…

    1. Как устроить данные?
    struct CheckLine {
    public:
       int itemCode;    // код товара
       int qty;         // количество
       int price;       // цена, по которой всё это продано в копейках
       const CheckLine* next() const { return _next; }
    private:
       friend class Check;   // я тут ошибся с const-корректностью и заconst’ив всё, что можно, не дал Check’у писать
       CheckLine* _next;
    }
    
    class Check {
    public:
        Check() : _firstLine(NULL), _lastLine(NULL) {}
        void addLine(int itemCode, int qty, int price);   // пиши реализацию сам.
        const CheckLine* firstLine() const { return _firstLine; }
        ~Check();  // не забудь про деструктор…
        Check(const Check&)  // …конструктор копирования…
        Check& operator = (const Check&);  // и операцию «присвоить».
    private:
        CheckLine *_firstLine, *_lastLine;
    }

    В общем, я тут для простоты (мы же пока не знаем, что такое vector) наладил хранение данных связанным списком.

    2. Во внутренней кухне класса ты обращаешься к консоли. Зачем? Обычно это делают как можно ближе к main.

    Вернусь — буду писать дальше.
    Ответ написан
    3 комментария