Ответы пользователя по тегу C++
  • Как сделать чтоб перснаж не мог проходить через стены?

    @Mercury13
    Программист на «си с крестами» и не только
    Если отбросить в сторону общую упоротость кода (облом делать ревизию), проблема вот в чём.
    Работая с коллайдером (фигурой, которая участвует в проверке столкновений, она же хитбокс), вы выбрали диапазон клеток
    [y / 16; (y + h) / 16).

    Например, y = 1, h = 16, (y+h)/16 = 1, диапозон [0,1), и проверяется только клетка 0. А надо 0 и 1.

    Так что надо [y / 16; (y + h - 1) / 16]. ВКЛЮЧАЯ СПРАВА.
    Ответ написан
    Комментировать
  • Как сделать чтоб одна вункция роботала всегда а другая паралельно каждую секунду?

    @Mercury13
    Программист на «си с крестами» и не только
    Итак.
    1. В играх очень хорош объектный подход — что-то вроде
    class World {
    public:
      bool wasStarPlaced = false;
      bool wasStaircasePlaced = false;
      std::vector<std::vector<char>> map;
      unsigned long long tick = 0;  // пригодится ещё
      void update();  // стандартное название для функции «исполнить игровой такт»
    };

    Тут даже не нужны все каноны ООП, достаточно что-то вроде «поле X принадлежит объекту Y».

    2. Многопоточка чревата гонками по данным, и если в многопоточку не стоит — пожалуйста, не делайте! В данном случае вы хотите что-то вроде: каждую секунду выполнять дополнительную ветку.

    Как я понял, игра динамичная, и расчёты в ней достаточно простые, чтобы вести их с постоянной частотой (например, 60 тактов/с).
    constexpr int TICKS_PER_SEC = 60;
    
    void World::update()
    {
      ++tick;
      if (tick % TICKS_PER_SEC == 0) {
         // происходит каждые 60 тактов
      }
    }
    Ответ написан
    Комментировать
  • Как обеспечивается совместимость динамических библиотек при ликовке в рантайме?

    @Mercury13
    Программист на «си с крестами» и не только
    Устроено просто.
    1. Имена всё равно стараются не козявить. Те библиотеки, где объектный интерфейс выставлен наружу, менее любимы. А уж шаблонных специализаций вообще сторонятся.
    2. А если козявить — есть модель Windows (MSVC, Borland) и модель Linux (MinGW, CLang).
    3. В модели Linux подключение DLL устроено через файл *.a, смысл которого для DLL’ки — сопоставить покозявленное компилятором имя функции и таковое же, выставленное DLL’кой наружу. И для любой DLL’ки можно создать этот *.a с любыми именами. MSVC CL и MinGW LD могут подключать DLL и напрямую, CLang LLD — когда проверял, ещё нет.
    4. Да, а как заменить в скомпилированной программе. Обычно новую версию DLL’ки компилируют тем же компилятором — вот и вся совместимость. В x64 с этим делом проще, чем в x86 — меньше соглашений вызова.
    5. Часто в ABI приходится переименовывать функции из-за того, что сменилась сигнатура. То есть тащат и старую, и новую с разными именами.

    Когда программа собирается одним компилятором, а DLL’ка другим — это всегда большая сложность, и вопроса два: соглашения вызова и правило, по которым козявятся имена.
    Ответ написан
    Комментировать
  • В какого типа переменных хранить адреса?

    @Mercury13
    Программист на «си с крестами» и не только
    Вариантов много.
    1. Непрозрачные указатели, которые нельзя разыменовывать.
    struct OpaqueAddress;
    using Address = OpaqueAddress*;

    2. void*, const void*.
    3. uintptr_t.
    4. Жёсткие int’ы, если работаем с конкретной посторонней прогой под конкретную архитектуру (например, пишем чит к игре).
    using Address = uint32_t;
    5. Enum class, основанный на соответствующем int’е.
    enum class Address : uintptr_t { NUL = 0 };
    Ответ написан
    Комментировать
  • Что стоит учить с или c++ или c#?

    @Mercury13
    Программист на «си с крестами» и не только
    Если ты умеешь программировать на чём угодно, хоть на Скрэтче — можно любой по желанию. Разберёшься.
    Если с нуля — только C#, на нём меньше шансов напортачить. Главная проблема Си, плохо решённая в Си++,— для простых вещей приходится работать со сложными концепциями вроде указателей для scanf.
    Ответ написан
    Комментировать
  • Почему '\xDA', '\xc4', и другие управляющие последовательности не работают?

    @Mercury13
    Программист на «си с крестами» и не только
    Потому что вы перевели консоль в кодировку Win-1251 и и ждёте в ней псевдографику.
    Лучше всего работать в каком-то из вариантов Юникода.
    Ответ написан
    Комментировать
  • Как нормализовать массив значений в цветовое представление?

    @Mercury13
    Программист на «си с крестами» и не только
    Уровень 1. Чёрно-белое.
    R, G, B = round((x−min)·255 / (max − min))

    Уровень 2. Градиент между цветом X и цветом Y.
    t = (x−min) / (max − min)
    R = round(R1·t + R2·(1−t))
    G и B аналогично.

    Уровень 3. Учёт гамма-кривой монитора. Тут работаем сразу в двух цветовых пространствах: линейном от 0 до 1, и sRGB от 0 до 255.
    gamma = 2,2 — sRGB состоит из линейного и степенного участка, но неплохо приближается real_brightness = channel_%^gamma
    invGamma = 1/gamma
    функция toLinear(v) := (v/255)^gamma
    функция toSrgb(q) := (q^invGamma)·255 — в общем, обратная
    linR1 = toLinear(R1)
    linR2 = toLinear(R2)
    t = (x−min) / (max − min)
    R = round(toSrgb(linR1·t + linR2·(1−t)))

    Уровень 3.1. 16-битная аппроксимация (если важна скорость). В общем, линейное цветовое пространство — не дробные от 0 до 1, а целые от 0 до 65535.
    функция toLinear(i) := round(((i/255)^gamma)·65535)
    массив toSrgb(q) := ((q/65535)^invGamma)·255 — записывается в виде массива на 65536 величин
    linR1 = toLinear(R1)
    linR2 = toLinear(R2)
    K = (65535 / (max − min)
    t = round((x−min) * K)
    R = toSrgb(((linR1·t) + linR2·(65535−t) + 127) >> 16)

    Уровень 4. Сложный градиент из нескольких цветов.
    Для этого например, t=0 — синий, t=⅓ — зелёный, t=⅔ — жёлтый, t=1 — красный.
    Тогда прикидываем, в какой промежуток попадает t, получаем, например, t1=(t−⅓)·3, и дальше по уровню 3.
    Ответ написан
    Комментировать
  • Мне надо иметь две .lib для debug и release?

    @Mercury13
    Программист на «си с крестами» и не только
    Debug-версия библиотеки может помочь отлаживать какую-то дичь на границе вашего и библиотечного кода. Я лично не пользовался ни разу, всюду подключал release-версию.
    UPD. Я не работал с MSVC, но этот самый MSVC имеет разные runtime-библиотеки для отладки и выпуска. И они не хотят соседствовать друг с другом. Это уже внутренние разборки MSVC; две библиотеки MinGW, статическую и динамическую, но обе release, вполне себе позволяет и для некоторых целей (упрощение отладки на той самой границе) очень даже помогает. SDL2, как и большинство открытых библиотек, имеют минимальные зависимости и ему просто не нужна библиотека MSVC.
    Ответ написан
    Комментировать
  • В чем ошибка блок-схемы?

    @Mercury13
    Программист на «си с крестами» и не только
    Если считать, что прога верная, а блок-схема нет.

    1. Непонятно, почему ввода нет, вывод есть.
    2. Начальное присваивание smallest опущено.
    3. Очень креативно изображён двойной цикл. Не знаю, верно ли это — но пару вложенных циклов, если рамки внутреннего не зависят от того, что творится во внешнем и не нужно прерывать один внутренний, я бы написал оба в одном шестиугольнике.
    4. Условие внутреннего цикла я бы написал i=[0..n)
    5. В программе сначала выводим сообщение, потом матрицу. На БС наоборот.

    PS. Чёртов препод, ну не годится тут блок-схема, и точка. Может, она больше годилась, когда не было структурного программирования…
    Ответ написан
    Комментировать
  • Как правильно округлять числа с плавающей точкой с заданной точностью?

    @Mercury13
    Программист на «си с крестами» и не только
    Гуглите GRISU2 — и, возможно, переделайте этот код под свои нужды.
    Он работает на целой арифметике и делает вот что: находит самое круглое десятичное число, переводимое в компьютерное дробное x.

    Допустим, мне приходилось добавлять такое.
    1. В случае не очень большого числа, большего 1/относительная_погрешность — поднять точность до единиц.
    Например, переходить в экспоненциальный формат, когда у нас больше 5 цифр, а точность 3 цифры — то 0,00123, 0,0123, 0,123, 1,23, 12,3, 123, 1234, 12345, 1,23e5.

    2. Большее из абсолютной и относительной погрешности.
    Если у нас ещё две цифры абсолютной погрешности — то 0, 0,01, 0,12, 1,23, 12,3…

    3. Формат с фиксированной длиной дробной части по принципу «всё или ничего».
    С теми же двумя цифрами абсолютной погрешности — 0, 0,01, 0,12, 1,23, 12,34, 123…

    4. Работу с локалями.
    Ответ написан
    Комментировать
  • Почему множественный вызов конструктора создаёт только один объект?

    @Mercury13
    Программист на «си с крестами» и не только
    Прочитайте про избавление от копирования, которое сделали обязательной функциональностью Си+17.
    https://en.cppreference.com/w/cpp/language/copy_elision
    Ответ написан
    Комментировать
  • Как хранится 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. Преобразовать в двоично-длинный формат (264 не входит в long long), сдвинуть, вернуть.
    2. Получить 232 в десятично-длинном формате, и перемножить.
    3. В зависимости от величины сдвига поступить так или этак: например, сдвиги на 1/2/3 вторым способом, а на 4 и больше — первым.
    Думайте сами, что лучше.
    Ответ написан
  • Как использовать потоки в современном C++ в приложении на основе цикла событий?

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

    Я считаю, что лучше использовать механизмы наподобие почтовых ящиков, при этом для передачи информации из потока в интерфейс использовать PostMessage и обёртки над ним вроде Qt QueuedConnection, возможно, с дополнительной структурой данных наподобие atomic<unique_ptr<SomeData>>).

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

    А future нужно использовать только при взаимодействии потоков-работников, чтобы один подождал, если ему позарез нужен результат второго.
    Ответ написан
    Комментировать
  • В чём ошибка вычисления бесконечно убывающей прогрессии с точностью до эпсилон?

    @Mercury13
    Программист на «си с крестами» и не только
    У вас две ошибки.
    1. Целочисленное переполнение в знаменателе в prog += (double)1/(i*(i+1));. Вычисляйте знаменатель в double, его длины более чем хватит.
    2. Нельзя брать в счёт аналитический результат, надо высчитывать погрешность по имеющимся данным. Поскольку ещё и ряд сходится ХУдожественно — придётся помучиться, 1/(i(i+1)) < k·eps не катит (катит, если элемент ряда убывает экспоненциально, а у нас всего лишь квадратично).
    Ответ написан
    Комментировать
  • Тонкости Компиляторов. Почему в классах с++ не требуется объявление функции до вызова?

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

    Проходов компиляции может быть много — например, сначала мы говорим, что f(x) — это вызов, а вторым проходом определяем, с какой функцией f имеем дело. К тому же конкретно в Си++ любой код в теле класса — автоматический inline, то есть не создаёт кода, пока кто-то снаружи не вызовет.

    Умножение, приведение типа или ещё какая-то дрянь — это особенность метода разбора. Во многих языках, в том числе в Паскале, используется LL-разбор — сканирование слева направо, опознание слева направо. Ошибки наподобие «ожидалась точка с запятой» — это артефакт примитивных LL-анализаторов. В Си используется LR-разбор — сканирование слева направо, опознание справа налево, а ожидающие опознания лексемы хранятся в стеке.

    И вообще, если пишете собственный крайне специализированный язык, делайте его LL(1), это очень упростит разбор.

    Кстати, в Паскале принят обратный порядок видимости: private → protected → public → published. Последнее — поле доступно в рефлексии времени выполнения.
    Ответ написан
    Комментировать
  • Стоит ли использовать типы данных из cstdint?

    @Mercury13
    Программист на «си с крестами» и не только
    Для олимпиадного программирования.
    Если результат до 109 — то int.
    Если до 1018 — то long long!
    Long’ом лучше не пользоваться.
    Можно также использовать int_fast32_t и int_fast64_t.
    Но всё это ТОЛЬКО В СЛУЧАЕ, ЕСЛИ МЫ НЕ ЗАКЛАДЫВАЕМСЯ НА ПЕРЕПОЛНЕНИЕ.
    Если закладываемся — тогда чёткие int32_t и int64_t, без разговоров.
    Ответ написан
    Комментировать
  • Как понять rvalue ссылки? Когда использовать std::move, а когда std::forward?

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

    Функция move предназначена для превращения ссылки (именованной или временной) во временную. Например, чтобы сказать: это именованный объект, но спокойно потрошите его, мне он не нужен.

    У Си++ есть одна фишка:
    void bar(std::string&& x) {}
    
    void foo(std::string&& x) {
      bar(std::move(x));
    }
    
    int main() {
      std::string x = "Test";
      foo (std::move(x));
      return 0;
    }

    То есть снаружи foo требует временную ссылку. Но внутри эта ссылка для ошибкобезопасности обычная именованная! В данном случае, когда функция нешаблонная, мы можем поставить move, но что делать в шаблоне?

    Автоопределение типов тут пасует, потому приходится так.
    template <class T>
    void foo(T&& x) {
      bar(std::forward<T>(x));
    }

    Да, я написал «ошибкобезопасность», хотя функция типа усложняет код. Дело в том, что потеря данных всегда хуже, чем неоптимальный код.

    Таким образом, move — это превращение ссылки (обычной или временной) во временную. Что-то вроде: «Потрошите этот объект спокойно, он мне не нужен».

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

    Вот моё описание всего этого.
    https://mercury13-kiev.livejournal.com/96961.html
    Ответ написан
    Комментировать
  • Как сделать чтобы программа не запускала процес на каждый аргумент, а запускала один процес на все аргументы?

    @Mercury13
    Программист на «си с крестами» и не только
    Под Windows это делается через DDE.
    Штука сильно устаревшая, однако осталась именно на это — поддержку нескольких файлов через файловый менеджер.
    Ответ написан
    2 комментария
  • Работа с рисованием в Qt. Почему не получается рисовать после оператора if?

    @Mercury13
    Программист на «си с крестами» и не только
    Дело тут вот в чём. События рисования могут проскакивать когда угодно по любому чиху, и потому механизмы перерисовки надо делать такие, что когда угодно этот paintEvent() проскочит — изображение в окне будет корректно восстановлено.

    Например:
    enum class Shape { NONE, LINE, ELLIPSE, RECTANGLE };
    
    ... в форме...
    Shape shareToDraw = Shape::NONE;

    …соответственно в коде нажатия кнопок устанавливаем эту shapeToDraw,
    а paintEvent реагирует на неё.

    У вас же, как только paintEvent() повторно проскочит, ни одно событие не будет отмечено, и он ничего не сделает. А ведь он может дважды проскочить, чтобы нарисовать непрямоугольную область. Или если оконный менеджер что-то попросил.
    Ответ написан