Ответы пользователя по тегу C++
  • Как быть с реестром (пишет в WOW64)?

    @Mercury13
    Программист на «си с крестами» и не только
    Так надо, у W32 и W64 разные места в реестре. И в 90% случаев ничего не надо делать.
    Но иногда всё-таки приходится, например:
    • Есть проги для W32 и W64, и хотелось бы иметь общие настройки.
    • Прога для W32 читает чужие настройки от проги для W64, и наоборот.
    • У вас программа, оперирующая реестром: редактор, чистильщик, архиватор…
    Тогда вот дока от M$, объясняющая всё это.
    https://msdn.microsoft.com/en-us/library/windows/d...

    P.S. «Работает с реестром» — это пишет свои настройки в реестр или действительно оперирует реестром, типа редактора или чистильщика реестра?
    Ответ написан
    2 комментария
  • Стоит ли вынести объявление типов в отдельный файл?

    @Mercury13
    Программист на «си с крестами» и не только
    Стоит!
    Назовите этот хедер как-нибудь defines.h
    Ответ написан
    Комментировать
  • Как взять данные stringstream без копирования?

    @Mercury13
    Программист на «си с крестами» и не только
    stringstream не обязан хранить данные в строке. Так что функция str() просто собирает информацию в строку.

    https://gcc.gnu.org/onlinedocs/libstdc++/libstdc++...

    Судя по исходникам G++, их sstream хранит информацию в длинном буфере, но не в строке. Но и это не обязательно; буферов может быть много.

    Так что, к сожалению, для экономии памяти придётся делать свой велосипед. Правда, как говорит MiiNiPaa, некоторые задачи удастся выполнить и на stringstream’е, воспользовавшись, например, итератором.
    Ответ написан
    Комментировать
  • Для чего нужны замыкания в C++ и как вы их используете?

    @Mercury13
    Программист на «си с крестами» и не только
    Замыкание — это способ передать в callback, из какого контекста он запустился.
    struct Row {
      int data[10];
    };
    struct Table {
      Row rows[10];
    }

    Нам нужно отсортировать таблицу по j-му столбцу. Номер j заранее неизвестен.

    int sortJ;
    
    int myCompare(const void* a,const void* b) {
      int ia = reinterpret_cast<Row*>(a)->data[sortJ];
      int ib = reinterpret_cast<Row*>(b)->data[sortJ];
      if (ia < ib) return -1;
      if (ia == ib) return 0;
      return 1;
    }
    
    int someJ = 5;
    sortJ = someJ;
    qsort(table.rows, 10, sizeof(Row), myCompare);

    Вот эта переменная sortJ — по какому столбцу сортировать — это и есть замыкание. Но, как известно, «избегай незнакомых женщин и глобальных переменных». Поэтому на STL мы делаем функтор (объект-функцию) и эту информацию кидаем в него.

    class MyCompare {
    public:
      MyCompare(int aJ) : j(aJ) {}
      bool operator () (const Row& a, const Row& b) const
        { return (a.data[j] < b.data[j]); }
    private:
      const int j;
    }
    
    int someJ = 5;
    std::sort(table.rows, table.rows + 10, MyCompare(someJ));

    Вот мы и избавились от глобальной переменной, закинув наше замыкание в private-поля объекта.

    Что плохо? Не будем говорить про технические тонкости. С точки зрения красоты и лаконичности кода: код слишком разлапистый. И тут пришёл C++11.
    int someJ = 5;
    std::sort(table.rows, table.rows + 10,
      [someJ](const Row& a, const Row& b) -> bool { return (a.data[someJ] < b.data[someJ]); } );

    Корявовато, но таков синтаксис Си++. Автоматически создаётся объект-функтор, и someJ становится его полем. Вот оно, замыкание — [someJ] — то есть те вещи, которые надо протащить внутрь функтора.

    Из реального проекта. Отбегал поток автоматического поиска нового регистрационного ключа; если что-то получилось — синхронно вызываем лямбду через Qt’шный механизм «сигнал-слот». Чтобы всё было синхронно, нужен объект, живущий в главном потоке (и он в интерфейсе MainControl — управление главной формой — тоже есть). Но тогда придётся вызывать не слот, а лямбду. Этой лямбде нужны два поля: fReregKey (новый ключ защиты от копирования) и fMainControl. Оба они в this, его и замыкаем.
    connect(this, &RenewThread::needUpdate, fMainControl.maincQobj(),
                [this]() {
            drm::createFile(fReregKey);
            fMainControl.maincLayoutOnRegister();
        });


    А теперь посмотрим в WinApi. Первая попавшаяся функция из DirectInput.
    HRESULT EnumObjects(
             LPDIENUMDEVICEOBJECTSCALLBACK lpCallback,
             LPVOID pvRef,
             DWORD dwFlags
    );
    
    BOOL DIEnumDeviceObjectsCallback(
             LPCDIDEVICEOBJECTINSTANCE lpddoi,
             LPVOID pvRef
    );

    Про pvRef говорится: функции EnumObjects он ни на что не нужен; что функция приняла, то и даст в callback. Тоже форма замыкания: можно передать указатель на любые данные, которые нужны callback’у.
    Ответ написан
    4 комментария
  • Почему ошибка в netbeans c++?

    @Mercury13
    Программист на «си с крестами» и не только
    В одном проекте два CPP-файла: int_float.cpp и hello.cpp. В обоих по main’у.
    Я не знаю, как NB объединяет файлы в проект, но если всё, что в каталоге и подкаталогах — значит, не держи где-то в подкаталогах другой проект.

    Второй вариант — очистить временный каталог с .o-файлами; там, возможно, завалялось что-то старое.
    Ответ написан
    Комментировать
  • Как разнести класс по файлам?

    @Mercury13
    Программист на «си с крестами» и не только
    Принцип прост. В .h можно ставить только то, что не производит кода. Как только в проекте появится второй CPP и задействует этот хедер, код будет произведён дважды, и компоновщик (cl/ld/ilink) будет ругаться, что переменная или функция в двух экземплярах. Что именно не производит кода…
    • Определения макросов. Они в принципе кода не производят.
    • Объявление любого типа. Оно лишь говорит об устройстве этого самого типа; код же производят те, кто этим типом пользуются.
    • Шаблоны. Код производит не сам шаблон, а факт расшаблонивания. Разумеется, шаблон может расшаблониться в двух единицах компиляции, но с этим автоматически бороться научились.
    • inline—  код производит не сам inline, а факт включения. inline бывает как явный ключевым словом, так и неявный — в теле класса.
    • Прототипы и extern — они говорят: код есть, но где-то не здесь.
    • Constexpr C++11. Они подставляют значение.
    • Некоторые const в зависимости от компилятора. Например, на Borland const double производит код, а const int — нет.

    Производят код и в хедерах запрещены.
    • Переменная без extern, даже const.
    • Функция, которая не inline.
    • Полностью специализированный шаблон, в котором не осталось шаблонных параметров (template<>).

    Не производят кода, но и лучше закинуть в CPP.
    • Некоторые скрытые (private) inline и шаблоны, если они не используются из хедера.
    Ответ написан
    3 комментария
  • Как правильно организовать структуру в классе?

    @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 комментария
  • Как объяснить кусок кода C++?

    @Mercury13
    Программист на «си с крестами» и не только
    Весь этот код (за исключением Close) — автогенерируемый.

    ///// Защита от повторного включения
    #ifndef Unit1H
    #define Unit1H
    
    ///// Хедеры VCL. Причём всё это сделано так, чтобы упростить написание ценой удлинения
    ///// компиляции. Более громоздкий, но и более удачный вариант.
    ///// В H:
    /////   namespace Controls { class TLabel; }
    /////   using namespace Controls;
    ///// В CPP:
    /////   #include <Controls.hpp>
    ///// Вот таким образом можно (было) избавиться от каскадного подключения
    ///// хедера Controls. А то каждый, кто использует главной форму,
    ///// автоматически подключает эти хедеры.
    #include <Classes.hpp>
    #include <Controls.hpp>
    #include <StdCtrls.hpp>
    ///// Только от Forms.hpp избавиться таким макаром нельзя:
    ///// мы наследуемся от TForm.
    #include <Forms.hpp>
    
    ///// Класс формы. Все формы наследуются от TForm.
    class TForm1 : public TForm
    {
       ///// Особое право доступа Borland, для совместимости с Delphi.
       ///// Поля и свойства published не просто public, но включаются
       ///// в структуру рефлексии (aka reflection или introspection)
       ///// и программа о них знает при выполнении.
       ///// Применительно к формам — published-поля доступны
       ///// загрузчику.
    __published: // IDE-managed Components
       ///// Компоненты, которые мы установили на форме редактором.
    TLabel *Label1;
    TButton *Button1;
       ///// События, которые мы прописали в редакторе.
       ///// __fastcall — модель вызова, аналогичная Delphi.
       ///// Именно такая модель вызова принята в обработчиках
       ///// событий.
    void __fastcall Button1Click(TObject *Sender);
       ///// Пользователь пока не прописал никаких своих
       ///// полей и функций.
    private: // User declarations
    public: // User declarations
       ///// Конструктор. Раз уж у формы нетривиальный конструктор —
       ///// по правилам Си++ его надо повторить в подклассе.
       ///// Снова-таки, модель вызова __fastcall: в формах Delphi
       ///// используются т.н. виртуальные конструкторы, 
       ///// когда по имени класса можно создать объект этого класса.
       ///// Фабричный метод, только немного лучше.
       ///// Но это значит: у всех подчинённых классов
       ///// должен быть один и тот же набор параметров
       ///// и модель вызова.
    __fastcall TForm1(TComponent* Owner);
    };
    //---------------------------------------------------------------------------
    ///// Как известно, переменная объявляется один раз.
    ///// Поскольку хедер может подключаться к огромному числу CPP,
    ///// её объявляют как extern (она есть, но в другом месте).
    ///// Макрос PACKAGE раскрывается в __declspec(package),
    ///// чтобы эту штуку можно было собрать как пакет.
    extern PACKAGE TForm1 *Form1;
    //---------------------------------------------------------------------------
    #endif

    Модель вызова — это как технически мы вызываем подпрограмму. Какая память и какие регистры на это используются, и кто подчищает стек. Ищи в Википедии.
    Ответ написан
    Комментировать
  • Как в Linux через raw sockets подключиться к tcp-серверу, используя Qt?

    @Mercury13
    Программист на «си с крестами» и не только
    ::connect
    Ответ написан
    Комментировать
  • Почему не компилируется helloworld с использованием wxWidgets?

    @Mercury13
    Программист на «си с крестами» и не только
    Project → Build Options → Search Directories → Linker
    Ответ написан
  • Как посчитать сумму чисел в файле?

    @Mercury13
    Программист на «си с крестами» и не только
    Пока вижу вот что.
    1. a += buff[i]; Вы суммируете коды символов, а не их числовые значения. Правильно a += buff[i] - '0';

    2. Эти строки дублируют друг друга.
    fin >> buff;
      fin.getline(buff, 11);

    Сначала получаем 0123456789, потом файл кончился и на место 0 записываем нулевой символ (NUL).

    3. for (int i = 0; i < 11; i++) — неуниверсальная конструкция. Плюс цифр в строчке всего 10, поэтому надо i < 10.

    4. Так сумму чисел или сумму цифр?
    Ответ написан
  • Как реализовывается функцию(main), которая будет производить проверку?

    @Mercury13
    Программист на «си с крестами» и не только
    Мы пишем модульный тест собственными силами, без фреймворка. Это значит, что нам надо каким-то образом сымитировать работу фреймворка, но собственными силами и минимумом строчек.

    Для удобства нам потребуется одна функция — и немного препроцессорной магии.
    #include <iostream>
    #include <cstring>
    
    void doAssert(bool condition, int line)
    {
        if (condition) {
            std::cout << "Line " << line << " ok" << std::endl;
        } else {
            std::cout << "Line " << line << " FAILED" << std::endl;
        }
    }
    
    #define ASSERT(x) doAssert(x, __LINE__)
    #define ASSERT_STREQ(x, y) doAssert((std::strcmp(x, y) == 0), __LINE__)


    А теперь что-нибудь протестируем. Например, создание строки. Для простоты я проверю не вашу строку, а std::string.
    void testStringCreation()
    {
        std::string s;
        ASSERT(s.length() == 0);
        ASSERT(s.empty());
        ASSERT_STREQ("", s.c_str());
    }

    Проверим ещё операцию +=.
    void testPlusEq()
    {
        std::string s1 = "Alpha";
        std::string s2 = "Bravo";
        s1 += s2;
        ASSERT_STREQ("AlphaBravo", s1.c_str());
    }
    
    int main()
    {
        testStringCreation();
        testPlusEq();
        return 0;
    }

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

    А теперь немного о том, какие должны быть модульные тесты.
    1. Изолированные. Если уничтожить часть тестов или запустить их в другом порядке, ничего не должно меняться — при условии, конечно, что никто не обращается к чужой памяти.
    2. Каждый модульный тест проверяет одну концепцию, которая указана в его названии. Например: строка создаётся, работает операция +=, и т.д. Будет очень много дублей кода, просто смиритесь с этим.
    3. Но нет ничего зазорного, что какой-то тест полагается на концепции, испытанные в других тестах. Если мы испытываем +=, то полагаем, что конструктор копирования работает, и не будем испытывать это.
    4. Модульным тестам иногда приходится иметь дело с внутренним поведением объекта. В таком случае объект должен выдавать наружу некоторую информацию — например, соответствующие функции обычно private, но по какой-то препроцессорной команде они становятся public.
    5. Модульные тесты обычно имеют дело с простейшим поведением объекта. Это не нагрузочные тесты, надо с этим просто смириться.
    6. Принцип работы теста таков. Создать исходные объекты, убедиться в том, что их ВНУТРЕННЕЕ (неспецифицированное, зависящее от реализации) состояние верное, провести некое действие, убедиться, что действие проведено правильно. Например, пусть строка выделяет память с запасом, и надо проверить, как она расширяется — тогда при присваивании "Alpha" мы убеждаемся, что выделено менее 10 символов, затем проводим s1 += "Bravo", затем убеждаемся, что выделено, например, 16 символов.
    7. Но заранее не нужно проверять, верно ли ВНЕШНЕЕ (заспецифицированное) состояние. Если мы пишем s1 = "Alpha", значит, строка равна "Alpha", и точка. Все случаи, возможные в операции string = char*, разумеется, покрыты другими тестами.
    8. Если вдруг в «боевом» коде обнаружится ошибка, надо сделать юнит-тест, её повторяющий, затем исправить ошибку и констатировать, что тест проходит и остальные тесты не «упали».
    9. То же самое, если ошибка случилась, когда проверяли другую концепцию. Проверяем +=, а глюк в конструкторе копирования — заводи новый тест, покрывающий эту ошибку.
    10. В проверках на равенство принят порядок ASSERT_EQ(что_должно_быть, что_реально_есть).
    Ответ написан
    Комментировать
  • На какой версии C++ Builder(максим. доступной) можно запустить проект C++ Builder 6?

    @Mercury13
    Программист на «си с крестами» и не только
    Переписывать придётся.

    1. Потому что в паскале — string и есть string, и раньше он был Ansi-, а теперь Unicode-. А на Си — именно что AnsiString/UnicodeString.
    2. То же самое касательно char. В Паскале-то можно объявить, что отныне char двухбайтовый, а на Си — есть стандарт, и по стандарту Си char — это один байт.
    3. Алгоритмы-то какие используются? Возможны какие-то алгоритмические уловки на манер алгоритма поиска подстроки Бойера-Мура — там тоже придётся переписывать.
    4. Если программа работает с файлами или сетью — надо указывать, как юникодные строки сериализовать. На Windows распространены UTF-16 Intel и UTF-8. На этот счёт AnsiString почти не используется в истинно юникодных программах из-за того, что хранит кодировку; если нужна цепочка байтов — есть RawByteString или std::string.
    5. Иногда старые программы работают с Юникодом через WideString (обёртку над BSTR). Сейчас этот WideString придётся массово менять на Unicode-.
    6. По-другому пишутся стандартные хедеры Delphi/VCL. Ранее #include <Forms.hpp>, сейчас — #include <Vcl.Forms.hpp>.

    Версия Builder — ну, любая, какую найдёшь. Не забудь только, что из C++11 есть enum class (не совсем стандартный, правда) и кое-что ещё. Я не проверял, как работает новый clang под XE10, но это только вопрос эффективности, «въедливости» и C++11 — миграцию на Юникод придётся проводить, как и раньше.

    Насчёт «въедливости». Я всегда считал, что G++ — самый «въедливый» компилятор. Но по статическому анализатору clang (встроен в Qt Creator, мой сейчас рабочий инструмент) кажется, что он переплюнул и этот рекорд. И сделать, чтобы один и тот же файл компилировался под древним Borland и суперсовременным clang — тяжеловато, по-моему. А именно это требовал Embarcadero от XE3 до XE8, если нужны версии и под x86, и под x64. Так что пусть BCC32 идёт туда, куда ему и дорога!

    Да, приходилось мигрировать на Юникод, когда появился Builder XE. Как я был рад!

    Знаю, что подхватит и сконвертирует XE2. Про более новые — не знаю ничего. Но даже если не сконвертирует — ничего не стоит построить новый проект с нуля и добавить туда все модули и формы. Но сконвертировать — это лишь половина вопроса; дальше придётся добиться компилируемости, а потом — и Юникода.
    Ответ написан
    Комментировать
  • Как решить проблему вывода из потока?

    @Mercury13
    Программист на «си с крестами» и не только
    #include <iostream>;
    Это не синтаксис Си, а директива препроцессора. Убери точку с запятой.

    #include <conio.h>;
    conio.h — это не стандартный хедер, а Windows-специфичный. Разумеется, вместо _getch() придётся использовать что-то другое.
    Ответ написан
    Комментировать
  • Корректно ли такое сравнение вещественного числа с нулём?

    @Mercury13
    Программист на «си с крестами» и не только
    Так сравнивать безопасно. Единственный вопрос — может ли появиться ноль во время вычислений и не сломает ли это логику программы?
    Лучше, конечно, для этого использовать не 0.0, а 0.0f.
    Ответ написан
    2 комментария
  • В чем ошибка в операторе присвоения?

    @Mercury13
    Программист на «си с крестами» и не только
    Здесь нужны две операции: «присвоить» (возможно, автоматически сгенерированная) и конструктор копирования.
    class MP3_Format {
       MP3_Format(const char* c_string);
       MP3_Format& operator=(const MP3_Format&);   // велика вероятность, что компилятор её сгенерирует автоматически
    };


    Если пишете на C++11 — вместо операции «присвоить» нужна операция «присвоить из временного объекта». Опять-таки, годится автоматически сгенерированная.

    Семантика языка C++ в этой строчке такова. Мы создаём временный объект MP3_Format("Summertime"), а затем переносим его в наш mp3a. Скорее всего, оптимизатор уберёт этот перенос и инициализирует mp3a на месте, но такова уж семантика…
    Ответ написан
    1 комментарий
  • Почему появляется ошибка компилятора '?

    @Mercury13
    Программист на «си с крестами» и не только
    Ошибся тут не компилятор, а линкер. Этот вопрос идёт со времён Си и происходит из технологии компиляции: несколько единиц компиляции компилируются независимо, а потом собираются воедино компоновщиком (линкером).

    Статический член класса — это глобальная переменная, и если объявлять её в каждой единице компиляции, то какую потом линкеру брать? Вот мы и пришли к выводу: static int counter; в хедере — это тот же extern. Другими словами, мы не объявляем переменную, а говорим: не беспокойся, компилятор, в какой-то единице компиляции она будет.

    И решается этот вопрос так же, как для любого extern’а: в одной единице компиляции (CPP) объявляем
    int Skript::counter;
    Можно также статически инициализировать этот счётчик:
    int Skript::counter = 0;

    Ах да. А технология эта с единицами компиляции откуда? Из ассемблера. Если программа у нас почти на всю память в 16 килобайт, то ассемблерный текст намного, намного превышает память ПК. Вот и приходится ассемблировать по частям, а потом собирать куски полуготового машинного кода в полную программу. Наконец, чтобы язык заработал, КиР пришлось написать только компилятор Си, а линкер — имеющийся.
    Ответ написан
    Комментировать
  • Как указать путь создаваемого файла в c++?

    @Mercury13
    Программист на «си с крестами» и не только
    1. Нам придётся работать с юникодными (wchar_t* / wstring) именами файлов. Использовать функции wfopen или CreateFile.
    2. Чтобы заполучить путь к рабочему столу, надо исполнить вот что.
    std::wstring desktopPath;
    wchar_t tmp[MAX_PATH + 1];
    if (SHGetSpecialFolderPathW(HWND_DESKTOP, tmp, CSIDL_DESKTOPDIRECTORY, FALSE))
        desktopPath = tmp;

    Не забудь подключить к программе соответствующие хедер и библиотеку, загугли уж сам, какие нужны.
    Ответ написан
  • Как вывести цифры находящиеся в тексте в файле?

    @Mercury13
    Программист на «си с крестами» и не только
    Вводить по символу, проверяя функцией isdigit, цифра это или нет.
    Ответ написан
    Комментировать
  • Как создать обьект с std::initializer_list?

    @Mercury13
    Программист на «си с крестами» и не только
    Это нельзя, initializer_list — это тонкая прослойка, позволяющая инициализировать вектор из линейного массива. Ни на что большее она не нужна.

    У вас есть какая-то странность — SearchVektor<unsigned int> мы инициализируем из вектора vector<int>. Это вообще ни в какие ворота не пишется. Для данного случая вам нужно…

    1. vector<int> liste превратить в vector<unsigned int>.
    2. Написать конструктор, работающий из временного вектора: SearchVektor(std::vector<T> && t ). Такой конструктор будет очень быстрым, ибо дерёт всю информацию из t, оставляя в нём пустоту.
    3. Объявить liste временным —
    return  SearchVektor< unsigned  int> (std::move(liste));
    Ответ написан
    4 комментария