Задать вопрос
Ответы пользователя по тегу C++
  • В чем ошибка блок-схемы?

    @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() повторно проскочит, ни одно событие не будет отмечено, и он ничего не сделает. А ведь он может дважды проскочить, чтобы нарисовать непрямоугольную область. Или если оконный менеджер что-то попросил.
    Ответ написан
  • Возможно ли достичь аргумента с нескольким определением типа?

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

    А так здесь есть два вопроса.
    1. А что напишем в скобках вместо непонятного return?
    2. В какой код оно должно скомпилироваться?

    Пример стандартный шаблонный.
    template <class Device>
    SomeFunction getProcAddress(const Device& device, const std::string& name);
    
    template <>
    inline SomeFunction getProcAddress<VkInstance>(
            const VkInstance& device, const std::string& name)
      { return vkGetInstanceProcAddr(device, name.c_str()); }


    Это стандартное использование шаблонов Си++ с далёкого 1998 года.

    Пример через if constexpr. Си++17, значит.
    template <class Device>
    inline SomeFunction getProcAddress(const Device& device, const std::string& name)
    {
      if constexpr (std::is_same_v<Device, VkInstance>) {
        return vkGetInstanceProcAddr(device, name.c_str());
      } else {
        // false нельзя! — проверка произойдёт в любом случае, будет компилироваться ветка или нет.
        static_assert(std::always_false_v<Device>, "Unknown type");
      }
    }

    Используется в функции std::visit, чтобы исполнить разный код для разных вариантов std::variant и ничего не забыть.

    Что написано в скобках — ну, сами видите. Во что скомпилируется — в две разных функции.

    В некоторых случаях — вероятно, не в вашем — можно ещё поиграться с виртуальным полиморфизмом.
    Ответ написан
  • Почему разрешен upcast в RTTI?

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

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

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

    @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 с разнобоем кодировок — не советую.
    Ответ написан
  • Как использовать 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
    }
    Ответ написан
    Комментировать
  • Где использовать 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
    Программист на «си с крестами» и не только
    Ссылка на временный объект.

    Прямо (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 — прямыми играми с указателями.
    Ответ написан
  • Как исправить ошибку при компиляции C++ кода?

    @Mercury13
    Программист на «си с крестами» и не только
    Тут поцапались макрос Verify в dbg.h и функция Verify в cryptlib.h.
    Я бы предпочёл переименовать одно из двух — лучше макрос — а затем разобрать, где должен быть макрос, а где функция.

    Виноват определённо разработчик макроса: он назвал его простым и распространённым словом Verify. Функция-то где-то внутри объекта, а макрос заменяет идентификатор Verify на всякую дрянь независимо от того, где этот идентификатор и в каком пространстве имён. Кроме того, подобные макросы принято называть как-нибудь DBG_VERIFY — если только это не макрос совместимости, подменяющий, например, более позднее nullptr в старое NULL.
    Ответ написан
    23 комментария