• Кроссплатформенное программирование на C?

    @Mercury13
    Программист на «си с крестами» и не только
    Что тогда делают? Есть три варианта.
    1. Перейти на кроссплатформенную библиотеку (Qt, например).
    2. Наладить свою небольшую библиотеку, которая когда-нибудь станет кроссплатформенной.
    3. Наладить механизм псевдонимов.
    Эти варианты можно объединять: что-то перевести на другую библиотеку, что-то написать своё.

    Примеры из личного кода.

    ОДИН. Ну, например, собственные механизмы работы с путями к файлам потихоньку уходят в сторону std::filesystem::path.

    ДВА. Ну, допустим, сделал свою библиотеку доступа к файлам — бонуса три. 1) Есть функции вроде writeIW (Intel word). 2) Проще писать свои абстрактные потоки, чем с std. 3) Феноменальная скорость под Windows (использует нечастый, но быстрый overlapped API с двойной буферизацией). На остальных ОС — обычная обёртка над FILE*.

    ТРИ. std::random_device из MinGW 9 давал детерминированную последовательность. Потом попытался бросить эту обходную ветку, но кто-то из программистов пожаловался — на его машине r_d просто не инициализировался.
    #if defined(__MINGW32__) && defined(__GNUC__) && !defined(__clang__)
    
        #define MINGW_RANDOM_DEVICE_WORKAROUND
    
        class MingwRandomDevice {};  // куча WinApi, опустим
    #else //defined(__MINGW32__) && defined(__GNUC__) && !defined(__clang__)
        #include <random>
        using MingwRandomDevice = std::random_device;
    #endif //defined(__MINGW32__) && defined(__GNUC__) && !defined(__clang__)
    Ответ написан
    1 комментарий
  • Почему разрешен upcast в RTTI?

    @Mercury13
    Программист на «си с крестами» и не только
    1. Принцип подстановки Лисков — один из важных принципов организации интерфейса объекта. Смысл: само устройство абстракции должно допускать такое, что у нас сын C, но мы безопасно могли работать с отцовскими типами A и B.
    Пример: Отец — прямоугольник с setWidth/setHeight. Сын — квадрат. Значит, уже при проектировании абстракции надо допустить, что setWidth/setHeight или изменит заодно и высоту, или откажется работать, или…
    2. Принцип достаточности. Зачем работать с типом C, если достаточно с B?

    UPD. А для чего этот dynamic_cast вообще? ООП без него проживёт, и часто dynamic_cast — это расписка в плохой конструкции объектов. Но в первую очередь он нужен на API настолько общие, что не совсем понятно, как сделать просто и эффективно. Или требование бинарной совместимости, что важно для всяких там VCL и Qt.
    Ответ написан
    Комментировать
  • Как в Qt сделать DecorationRole, зависящую от DPI экрана?

    @Mercury13 Автор вопроса
    Программист на «си с крестами» и не только
    Опять приходится отвечать самому.

    1. Нашему QListBox’у задаём setIconSize в дипелях.
    2. В data(Qt::DecorationRole) использовать конструктор QIcon(QIconEngine*). Система переводит дипели в пиксели, и работает QIconEngine — объект, содержащий кучу кода и немножко данных, призванный синтезировать иконку нужного размера в аппаратных пикселях.

    Если нужны ещё и разные размеры иконы — патчить QStyledItemDelegate.initStyleOption. Вероятно, тоже в дипелях (не проверял).
    Ответ написан
    Комментировать
  • Выдаст ли ошибку при аллоцировании памяти?

    @Mercury13
    Программист на «си с крестами» и не только
    Нормально.
    Аварии в деструкторе и конструкторе — дела сложные, но возможные.
    Но тут ни того, ни другого не будет. До вызова конструктора просто не дойдёт.
    } catch (const std::bad_alloc&) {}
    Ответ написан
    Комментировать
  • Как сделать элемент поверх другого в QT?

    @Mercury13
    Программист на «си с крестами» и не только
    Самый обычный QWidget
    #wiRound {
      background-color: yellow;
      border-radius: 20;
    }

    6390801eab617817237396.png
    Сам QWidget брошен куда попало, кнопки же расставлены через стандартный vertical layout.
    Ответ написан
  • Как исправить ошибку в коде из-за сравнения с кириллицей/русским алфавитом?

    @Mercury13
    Программист на «си с крестами» и не только
    Итак, нам нужна обработка русского текста, портабельно и поменьше геморроя. Если константа 'Д' многосимвольная — значит, кодировка исполнения UTF-8 и на string забивай, тяжело будет. Работаем в строчке пошире: wstring, u16string или u32string. Главное, разобраться, как правильно выводить всё это в консоль — например, через wcout. То есть:
    wchar_t a;
    if (a == L'Д') {}


    Иногда можно работать и в UTF-8: std::string s; if (s.starts_with("Д")) {} (Си++20!!) Но инструментарий поуже будет, а под Windows с разнобоем кодировок — не советую.
    Ответ написан
  • Будет ли работать бинарный поиск, если в массиве есть пробелы?

    @Mercury13
    Программист на «си с крестами» и не только
    Что такое вообще «пробелы»?
    1. Пробелы в исходном коде: за пределами закавыченных строк это только оформление и в исполнении не участвует. Ну и слова, разумеется, нужно разделять пробелами — как в паскалевском «packed array» или сишном «int main».
    2. Пропуски: не 1,2,3, а 1,4,5. Да, для этого бинарный поиск и предназначен: массив отсортирован, никаких других правил нет, найти или сам элемент, или место, куда вставить его.
    Например, ищем 7: 6 → больше, 13 → меньше, 12 → меньше. Не нашли; если нужно вставить — то после 6-ки.
    Ищем 1: 6 → меньше, 4 → меньше, 1 → попали.
    Ответ написан
    1 комментарий
  • Генерировать числа с заданной вероятностью?

    @Mercury13
    Программист на «си с крестами» и не только
    Алгоритм простой, подготовка O(n), расчёт O(log n). Вычислить функцию распределения (она кусочно-постоянная). Сгенерировать равномерно распределённое число, определить, в какой кусок оно попадает.
    Для целых «шансов» — если сумма этих «шансов» 123, то генерируем число от 0 до 122, а дальше понятно.

    Алгоритм многопамятный, подготовка O(sum ai), расчёт O(1). Годится только для небольших целых шансов.
    Его предложил Сергей Соколов.

    Алгоритм палочный, подготовка O(n log n), расчёт O(1).
    https://elementy.ru/problems/2263/Razdelyay_i_uravnivay
    Алгоритм легко переделывается на ситуацию, когда все палки имеют целую длину, резать-клеить их можно только по целым длинам, при этом последняя палка может оказаться короче. В общем, простым делением определяем, в какую палку попали, а потом доступом к массиву и сравнением — в какой компонент данной палки.

    Например, у нас есть четыре шанса — 100, 20, 2 и 1. Палочный алгоритм даст, например, такие палки (у всех длина 31, и у последней — 30).
    Палка [0..31): 1 (x<1); 100 (x>=1)
    Палка [31..62): 2 (x<33); 100 (x>=33)
    Палка [62..93): 20 (x<82); 100 (x>=82)
    Палка [93..123): всегда 100
    Разыграв 32, получаем: [32/31] = 1, первая палка. 32<33 — та шмотка, у которой два шанса.
    Ответ написан
  • Какя разница в формулах теоремы Байеса?

    @Mercury13
    Программист на «си с крестами» и не только
    В знаменателе — формула полной вероятности. Вот и всё.
    p(B) = p(B|A)·p(A) + p(B|¬A)·p(¬A)

    Для чего? Да просто p(B) в большинстве случаев хрен поймёшь, и его приходится вычислять непрямо. Например:
    A — письмо является спамом
    B — в письме есть слово «sex»
    Видим в письме слов «sex» — спам ли оно?
    Мы можем собрать базу спама со словом «sex», и базу обычной переписки с этим словом, и вычислить p(B|A) и p(B|¬A). А p(A) и p(¬A) вычисляются уже на компе конечного пользователя в зависимости от того, насколько жёстко его спамят.

    Пример второй. Каждый тысячный водитель — пьяный. Алкотестер чётко видит алкаша, но останавливает каждого сотого трезвого. Какой процент из приехавших в больницу действительно пьянствуют за рулём?
    U — проехавшие через пост водители
    A — пьяный
    B — алкотестер сработал
    Аналогично, p(B) заранее неизвестен, но приходится вычислять по полной вероятности. И вроде бы при таких цифрах один из одиннадцати попавшихся реально пьяный. И это затрудняет антитеррористические меры: если по городу-миллионнику ходит сотня террористов, какая должна быть точность, чтобы не ломать невинные жизни!
    UPD: чуть меньше 1/11: p(B|A)=1, p(A)=1/1000, p(B|¬A)=1/100, p(¬A)=999/1000,
    итого с сокращением на 1000 будет 1/(1+999/100)=100/1099.
    Ответ написан
    Комментировать
  • В каких случаях логичней чтобы получить половину умножать на 1/2, а в каких делить на 2?

    @Mercury13
    Программист на «си с крестами» и не только
    ЦЕЛЫЕ ЧИСЛА / ФИКСИРОВАННАЯ ЗАПЯТАЯ
    ·0,5 в фиксированной запятой нет никакого смысла. /2 и арифметический сдвиг >>1 ведут себя немного по-разному на отрицательных числах, и как сейчас оптимизируют /2 с сохранением точности до бита — я не в курсе (Godbolt показывает пекло какое-то на пять команд — и оно быстрее, чем div?).

    ДРОБНЫЕ ЧИСЛА
    С /2 и ·0,5 никакой разницы, но второе быстрее. Если коэффициент не умещается в компьютерное дробное (⅓, например) — если нет никакого сакрального смысла в точности до бита, можно множить.
    Ответ написан
    Комментировать
  • Как использовать std::begin и std::end в функциях для массивов?

    @Mercury13
    Программист на «си с крестами» и не только
    Я вообще не в курсе, как взаимодействуют Сишный int[] и Си++-ный &arr.
    Но получается, это под капотом превращается в банальный const int*, для которого нет std::begin/end.

    Я бы предложил ещё два распространённых механизма. Первый желательно делать лёгким инлайном, который под капотом превращается в data/size или std::span (если Си++20).

    template <size_t N>
    void myA(const int(&arra)[N], const int(&arrb)[N])
    {
      auto arra_begin = std::begin(arra);
    
      std::cout << *arra_begin << std::endl; // 1
    }
    
    // Си++20!!!!!!!!!!
    void myB(std::span<int> arra, std::span<int> arrb)
    {
      auto arra_begin = std::begin(arra);
    
      std::cout << *arra_begin << std::endl; // 1
    }
    Ответ написан
    Комментировать
  • Как сравнить два списка с помощью хеш-кода?

    @Mercury13
    Программист на «си с крестами» и не только
    1. Реализация из Java8:
    public int hashCode() {
        int hashCode = 1;
        for (E e : this)
            hashCode = 31*hashCode + (e==null ? 0 : e.hashCode());
        return hashCode;
    }

    Из неё видно, что не совпало — у одинаковых списков хэши одинаковые. Но вспомни комбинаторику: если хэши одинаковы, объекты, СКОРЕЕ ВСЕГО, одинаковые, и один хрен нужно глубокое сравнение. Если разные — точно разные.

    2. Если просто сравнить два списка — сравнивай обычным equals, ничего ты не выиграешь от хэшей. Один хрен для вычисления хэша придётся пройти по всем данным. Хэши используй, если нужно сравнить, например, 100 объектов попарно — я так сжимал WAD’ы для Doom без потерь и рассинхронизаций демо-роликов. Сначала находил множества потенциально равных блоков, потом вёл глубокое сравнение.
    Ответ написан
    5 комментариев
  • Что лучше window.write(object) или object.write(window)?

    @Mercury13
    Программист на «си с крестами» и не только
    Да, относится — к MVC.
    В большинстве случаев лучше window.write(object).
    Внутренние структуры данных не должны зависеть от интерфейса, а интерфейс — может зависеть от внутренних структур данных.
    Если интерфейс сложный, могут быть какие-то промежуточные околоинтерфейсные объекты — например, чтобы изолировать или повторно использовать какую-нибудь логику. И вот в этих-то околоинтерфейсных структурах, каком-нибудь ComboBuilder, может быть writeTo(comboBox).

    Кроме того, в вебе, которому привычно видеть картину в момент T, а не в динамике, MVC если и есть, то сильно модифицированный.
    Ответ написан
    3 комментария
  • Зачем нужна фабрика классов?

    @Mercury13
    Программист на «си с крестами» и не только
    Преимущества второго над первым три.
    1. Разделение ответственности: класс отвечает за организацию памяти, а сериализатор — за сохранение-запись.
    2. Продуманный наружный интерфейс: сложно сделать «класс в себе», который позволяет тянуть данные из JSON, но такую же функциональность для XML сделать уже невозможно. Как вы понимаете, программист всегда лавирует между трёх огней: одна крайность — позволить программисту «напортачить» и сделать так, чтобы song.records[] и record.tracks[] были не согласованы. Вторая — сделать «класс в себе», где шаг влево, шаг вправо — шаблон «Public Морозов». Третья — не обеспечить производительность: без record.tracks[] никуды, а вот без song.records[] можно и пожить, но как искать в коллекции пластинок нужную песню — ХЗ.
    3. Когда, помимо песен, на пластинках могут быть лекции и танцы, но мы коллекционируем Битлов, мы не обязаны тащить в EXE-файл то, на что не подписывались. Или большинство текстовых форматов сериализации тяжелы — в Питоне-то они сидят прямо в библиотеке и на это забить, она всегда есть, а вот в Си++ обычно где-то в EXE-файле.

    Главный недостаток второго над первым: когда, помимо песен, на пластинках могут быть лекции и танцы, виртуальный полиморфизм проще и ошибкобезопаснее.

    И ещё: это НЕ фабрика классов. Фабрика классов начинается, когда…
    1. Эту штуку нужно ДЕсериализовать.
    2. И не просто десериализовать, а на пластинках могут быть песни, лекции, танцы — например, при тэге «song» мы делаем песню, при тэге «lecture» лекцию…
    Ответ написан
    Комментировать
  • AUX и Jack 3,5 - это одно и то же?

    @Mercury13
    Программист на «си с крестами» и не только
    AUX ≠ Jack 3,5 ≠ наушники.
    1. AUX (неусиленный линейный выход) может быть и другими разъёмами — чаще всего DIN-5 или два RCA. Допустим, в моём самодельном переключателе колонки-наушники на колонки идут два RCA из-за сверхнадёжности, а на наушники — ну, так и быть, джек.
    2. На миниджеке 3,5 мм может быть и микрофон. У микрофонов отдельный механизм работы, и ухи от микрофона отличают омметром — наушник имеет сопротивление около 30 Ом, а микрофон — около 1к. И даже ухи с микрофоном, если джек 4-контактный.
    3. В советской аппаратуре существуют разъёмы для высокоомных наушников, и низкоомные могут плохо работать. (Сомневаюсь, что погорят — для проверки полярности часто берут и подключают к ним батарейку, хотя на AUX меньше вольта.) Впрочем, не видел ни одного устройства, где этот разъём — джек.

    Но в целом разъёмы для современных низкоомных наушников и внешней аудиоаппаратуры электрически сходны, подключай свободно. Могут быть проблемы с громкостью и/или помехами (как у меня в машине — если заряжать смартфон и выводить звук с него на магнитофон, будут помехи, зависящие от оборотов двигателя), но работать будет.
    Ответ написан
    2 комментария
  • Где использовать const?

    @Mercury13
    Программист на «си с крестами» и не только
    Это стандартный студенческий код. Можно маленькую ревизию?
    1. Класс хранит строки в виде char*, ссылаясь на чужую память, и ничего хорошего не сделано с этой памятью. Подобные ссылки на чужую память хороши как оптимизация, когда практически всегда передаём строковые литералы (например, названия тэгов XML). А так стандартный способ хранить строку — std::string. Ну или char[], если не учили этого.
    2. Продолжение 1. С одной стороны, setMarka (например) имеет дело с внешней памятью, которой объект не владеет и которую не надо освобождать. С другой — Input передаёт во владение объекту буфер памяти, который освобождать НАДО.
    3. С возвратом true/false — он хорошо сказал. Сам класс должен решать, корректны ли данные.
    4. Деструктор — если std::string разрешён, лучше возьми его вместо const char* и забей на деструктор. Если запрещён — либо используй char[], деструктор не нужен. Либо сделай свой string, способный только управлять памятью неизменяемой строки, и деструктор нужен только ему. В промышленном программировании деструкторы редки: либо это реально какая-то необычная структура данных, либо просто компилятор заглючил и нужно хоть пустое, но тело в CPP-файле. Либо идиома pimpl (pointer to implementation, указатель на реализацию), призванная снизить количество каскадных include’ов — а значит, укоротить неполную сборку.
    5. char* car_name = "Toyota"; — работает только на очень старых компиляторах. Во всех известных мне строковый литерал имеет тип const char*.
    6. Input() должен быть чем-то внешним по отношению к машине: объект «машина» предназначен для хранения данных, и нечего притягивать к нему консоль, которой в программе может и не быть (например, она GUI).
    Ответ написан
  • На каком ЯП стоит писать программу для управления компьютерным клубом (десктоп)?

    @Mercury13
    Программист на «си с крестами» и не только
    Главное — сделать клиент, который сложно будет хакнуть и/или выбить. И тут что угодно, компилирующее в машинный код или что-то близкое: Delphi, C++, .NET. И придётся поспрашивать у опытных, как правильно защитить.

    Рабочее место админа — что угодно, хоть сайт в браузере.

    Сервер — машина, которая суткам работает в уголке и к которой даже админ не имеет доступа. Что угодно, хоть PHP.
    Ответ написан
    5 комментариев
  • Для чего нужна двойная ссылка &&?

    @Mercury13
    Программист на «си с крестами» и не только
    Ссылка на временный объект.

    Прямо (int&& d = 5) используется крайне редко, чтобы продлить время жизни временному объекту.

    Куда чаще оно используется в типах возврата (string&& Container::give()), и означает: это ссылка на объект, который спокойно можно выпотрошить. Или в типах параметров — Container& Container::operator = (string&& x): то есть принимает объект, который больше не нужен.

    Для чего потрошить объекты? Вот представьте себе код.
    std::vector<int> doSmth() {
      std::vector<int> r;
      r.push_back(42);
      return r;
    }
    ...
    std::vector<int> a = doSmth();


    Забьём на необязательные оптимизации — как эта штука будет действовать по семантике Крестов?
    std::vector<int> r; — создадим в стековом фрейме переменную r.
    return r; — vector(const vector&) в какое-то место, предложенное вызывающей функцией.
    std::vector<int> a = doSmth(); — vector(const vector&) из этого места в a.

    Си++17 ТРЕБУЕТ, чтобы подобный возврат гарантированно вызывал ОДИН конструктор копирования (а если конструировать на месте вроде return vector<int>{ 42 }; — то ни одного), но у нас-то Си++03. Не много конструкторов? А ведь зная, что объект больше не нужен, можем просто перенести указатель из одного вектора в другой, точка — даже впрямую, без оптимизаций, никаких перевыделений памяти. Вот этот прямой перенос указателей я и назвал словом «выпотрошить объект».

    Пример 1: container = string("alpha") + "bravo"; Тут в контейнер перемещается временная строка.
    Пример 2: string a = "alpha"; container = a; Не компилируется: строка a не временная.
    Пример 3. container1 = container2.give(); Перенос строки из контейнера в контейнер без выделения памяти.
    Пример 4: string a = "alpha"; container = std::move(a); Компилируется (мы функцией move сказали: с обычным объектом обращаться как с временным), но в результате в строке a может быть что угодно и не стоит закладываться на дальнейшее её значение. Но объект после потрошения в любом случае должен быть корректным, ведь ему ещё вызывать деструктор :)

    Последний вопрос: кто потрошит? Функция, которая принимает временную ссылку как параметр. В данном случае Container::op=. Чем потрошит? — ну, например, кодом myString=std::move(x) или std::swap(myString, x). А они, в свою очередь — внутренняя string::op= и дружественная swap — прямыми играми с указателями.
    Ответ написан
  • Как поставляются игры на компьютеры?

    @Mercury13
    Программист на «си с крестами» и не только
    Касательно читов. Я простым анализом памяти смог узнать в Microsoft Train Simulator состав поезда игрока, его скорость, положение контроллеров, пройденный путь, давление в различных элементах пневматического тормоза (оказывается, в PSI), боксует ли он. Жаль, я не смог обнаружить привязку всего этого к путям.

    Как я это делал. Через ArtMoney получал некие базовые адреса. Если адрес статический — ну, всё в порядке. Если нет — писал утилиту, в которую изначально вводился найденный адрес. По цифрам я прикидывал, где мог начинаться объект, и снова поиск через ArtMoney… Ну и так далее, пока не дойду до статического адреса. Вот так оно у меня выглядело.

    TmstsLocalTrain = packed record
          _mem0000 : array [$0000..$0061] of byte;
          // 0062
          HeadWagon : dword;   // Головная единица ПС
          // 0066
          TailWagon : dword;   // Хвостовая единица ПС
          // 006A
          LocWagon : dword;    // Управляемая игроком единица ПС
          // 006E
          _mem006E : dword;
          // 0072
          Caps : dword;
          // 0076
          _mem0076 : array [$0076..$0091] of byte;
          // 0092
          Speed : single;       // Скорость по скоростемеру, м/с
          // 0096
          Acceleration : single;  // Ускорение, м/с2
          // 009A
          _mem009A : array [$009A..$00D5] of byte;
          // 00D6
          TimeSec : single;
          // 00DA
          ReversingOdometer : single;
          // 00DE
        end;


    ПС = подвижной состав. Одометр реверсивный, потому что при осаживании (заднем ходе) считает назад. Байтовый массив _mem0000 — это память, которую я не смог опознать. HeadWagon, TailWagon, LocWagon, Caps — на самом деле указатели, но поскольку они не имеют смысла в адресном пространстве лентописателя (задачей было сделать аналог скоростемерной ленты), они Dword, а не указатели.

    Вспоминал, что значат сокращения П, ПТЭ и прочее. Оказалось: паровоз, тепловоз, электровоз. Естественно, регулятор пара есть только у паровозов.
    Ответ написан
    Комментировать
  • Как отличить векторный графический редактор от растрового?

    @Mercury13
    Программист на «си с крестами» и не только
    Векторный
    Растровый с векторными элементами (как и Шоп)
    Растровый
    Растровый
    Векторный

    Собственно, как отличить.
    У растрового — пиксельное поле. Если близко приблизишь, увидишь эти пиксели.
    У векторного — отрезки, кривые и тексты, которые в любой момент можно взять и отредактировать.

    Векторная графика из-за лёгкой редактируемости уже везде в хороших растровых редакторах, типа Шопа и Гимпа. Но они остаются растровыми, потому что основа — всё равно пиксельный холст.
    Ответ написан