Ответы пользователя по тегу C++
  • Что означает эта строка(модификатор в макросе)?

    @Mercury13
    Программист на «си с крестами» и не только
    Тут есть два модификатора.

    1. extern "C" — говорит, что функцию НЕ надо «козявить» на манер Си++, кодируя в имени её параметры наподобие h(int) → _Z1hi. Используется для доступа из Си++ к функциям, которые скомпилированы другим компилятором (Паскалем, Си…), а также для всяких там функций, которым надо дать фиксированное имя — например, функций DLL.

    2. __declspec(dllimport). Это нужно для доступа к функциям DLL: линкер будет брать функции прямо из DLL, без создания *.lib или *.a (как минимум MinGW так работает). Языки, сильно привязанные к определённой ОС (например, Delphi) могут создавать свои ключевые слова для доступа, например, к DLL или COM.
    procedure DoSomething(x : integer); cdecl;  external 'something.dll';

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

    @Mercury13
    Программист на «си с крестами» и не только
    void* используется как указатель на сырые байтовые данные, не имеющие конкретного типа.
    Обычно это используется…
    1. В чтении-записи в файлы и на устройства, когда мы можем писать туда абсолютно любые типы.
    2. В «многоликих» функциях, которые могут принимать данные разных типов (malloc/calloc, часть функций WinAPI и ODBC).
    3. Как дескриптор — указатель, который запрещается разыменовывать. В Си для этого также часто используют указатель на недоопределённый тип, в Паскале с другими правилами эквивалентности типов — на пустой record. Но только пока не появится очередная многоликая функция вроде CloseHandle.
    4. Для обеспечения т.н. замыкания — передачи callback’у контекста, откуда была вызвана функция, вызвавшая callback.
    BOOL WINAPI EnumWindows(
      _In_ WNDENUMPROC lpEnumFunc,
      _In_ LPARAM      lParam
    );
    
    BOOL CALLBACK EnumWindowsProc(
      _In_ HWND   hwnd,
      _In_ LPARAM lParam
    );

    Вот этот LPARAM, который обычно определяется как какой-то указатель, и есть замыкание. Функция EnumWindows обещает передать его в функцию lpEnumFunc без изменений.
    (В Си++ для этого также используют виртуальные интерфейсы, но такой метод, сами понимаете, языкозависим и не годится для межъязыкового API.)

    Что происходит на стороне функции? Одно из двух (считаем, функция написана на ЯВУ).
    1. Либо вызывается некая функция устройства, которая говорит: «записать 100 байт», и дальше уже работает железо.
    2. Либо мы преобразуем void* в нужный нам тип и работаем с ним.

    Типы указателям дают по трём причинам.
    1. Вы забыли про операцию «разыменовать указатель». Чтобы его разыменовать, он должен иметь тип!
    2. Чтобы не ошибаться и не переприсвоить несовместимые указатели.
    3. Для полиморфизма — в Си++, давая delete x, мы даже можем не хранить, сколько байтов в блоке, поскольку мы знаем длину типа. (Есть ещё и виртуальные классы, но это другой вопрос.)
    Ответ написан
    Комментировать
  • Что за WINAPI, CALLBACK перед названиями функций?

    @Mercury13
    Программист на «си с крестами» и не только
    На x86 оба они — макроопределения для нестандартного соглашения вызова __stdcall.
    На ARM они ничего не делают.

    Соглашение вызова — это…
    • как на уровне регистров мы вызываем функцию;
    • кто подчищает стек за вызывающим;
    • кто отвечает за восстановление регистров, если они менялись (или есть риск, что они менялись).

    stdcall — вызов через стек, справа налево, за очистку стека отвечает функция, результат в eax (rax), функция отвечает за восстановление сегментных регистров, esp и ebp, программа за остальные.

    На ARM используется соглашение cdecl. То же самое, но за очистку стека отвечает программа (что там на ARM с регистрами, я не в курсе).
    Ответ написан
    Комментировать
  • Зачем в абстрактном базовом классе создавать конструктор?

    @Mercury13
    Программист на «си с крестами» и не только
    Абстрактные классы делят на интерфейсы и частично реализованные. Грань между ними такова:
    • Интерфейс не имеет данных.
    • У интерфейса все неабстрактные виртуальные методы представляют собой или эталонное поведение, или самую частую реализацию. В обоих случаях, если что, их надо не расширять, а переписывать с нуля.

    Так вот, для интерфейсов таких конструкторов, разумеется, не нужно.

    Например, между абстрактным потоком и файлом Win32 может быть такая иерархия: Stream → HandleStream → File. Stream — интерфейс, даже если там есть что-то типа
    // virtual
    unsigned long long Stream::remainder() const { return size() - pos(); }


    HandleStream содержит уже данные (дескриптор Win32), и это уже частично реализованный класс, который крутится вокруг этого дескриптора: в деструкторе вызов CloseHandle, конструктор может принимать дескриптор, полученный каким-то «левым» образом.
    HandleStream::HandleStream(HANDLE aHandle) : fHandle(aHandle) {}
    HandleStream::~HandleStream() { close(); }
    
    void HandleStream::close()
    {
      if (Handle != INVALID_HANDLE)  { // не помню, как там эта константа в Win32
        CloseHandle(fHandle);
        fHandle = INVALID_HANDLE;
      }
    }

    Вот в таких полуреализованных классах, разумеется, конструктор может инициализировать те данные, которые там есть.
    Ответ написан
  • Как вернуть массив строк C++?

    @Mercury13
    Программист на «си с крестами» и не только
    ВНИМАНИЕ: У вас есть пара тонких мест.
    РАЗ. не забудьте, что строковые литералы в Си — это нуль-терминированные строки, и "\0" по факту будет пустой строкой. Строку из одного нулевого символа можно загнать в string — например, конструктором string(begin, end) или s += '\0', но не конструктором string(const char*).
    ДВА. Этот static будет инициализирован при первом вызове. Второй вызов вернёт тот же массив.
    ТРИ. Строчку лучше передавать как string *func(const string& s) {}

    К делу. Массив — это довольно сложный объект, и возникает вопрос: кто этим массивом будет владеть после того, как он покинет пределы функции?
    1. Владеет система времени выполнения. Это отлично ответил Роман, дам только ключевую строчку.
    string* arr = func(exp);
    НЕЛЬЗЯ ИСПОЛЬЗОВАТЬ: если массив используется в чьих-то «конских» конструкторах-деструкторах статических объектов (из-за отсутствия модулей Си++ не позволяет установить на уровне языка порядок создания/уничтожения статических объектов).

    2. Владеет какой-то объект.
    string *func(string s) {
        static string* ar = nullptr;
        if (ar) delete[] ar;
        ar = new ar[3];
        ar[0] = "qwe";
        ar[1] = s;
        ar[2].clear();
        return ar;
    }

    НЕЛЬЗЯ ИСПОЛЬЗОВАТЬ: ну, это уж разбирайтесь сами с этим объектом, сколько он будет жить и насколько долго он будет держать массив. В данном случае каждый новый вызов уничтожает старый массив. (Код корявый, потому что последний массив не уничтожается.)

    3. Владеет тот, кто вызывает. Лучше для таких целей использовать какой-нибудь std::vector.
    vector<string> func(string s) {
        vector<string> r;
        r.reserve(3);
        r.push_back("qwe");
        r.push_back(s);
        r.push_back(std::string());
        return r;
    }
    Ответ написан
    Комментировать
  • Объясните пожалуйста смысл строк(указатели)?

    @Mercury13
    Программист на «си с крестами» и не только
    Перед нами структура данных под названием «односвязный список». У каждого элемента ссылка на следующий, у всей очереди ссылка на голову (front) и иногда на хвост (rear).

    Для чего нужна проверка на заполненность — непонятно, ведь ёмкость списка не ограничена и единственный способ убедиться, что очередь полна — завести память под новый элемент. Выпадает авария std::bad_alloc — значит, памяти не хватило. Если только для каких-то прикладных нужд: так, в StarCraft очередь на строительство пять юнитов, и точка. (Есть другой тип очереди, т.н. циклическая очередь — вон там ограничено.)

    Как работает enqueue: создаём новый элемент, следующий за ним — nullptr. Если очередь пуста, направляем на него front и rear. В противном случае пристраиваем его за тем, на который «смотрит» rear, и перенаправляем rear. Таким образом, «направляем rear» можно вынести за скобки, а остальные два исполнить в зависимости от пустоты очереди.
    Ответ написан
    3 комментария
  • Расскажите в каких ситуациях используют класс quiloader в QT?

    @Mercury13
    Программист на «си с крестами» и не только
    В крайне-крайне редких случаях, когда вы пишете редактируемый пользовательский интерфейс. Например, в ERP.
    Разумеется, QUiLiader под капотом есть всегда, но среднему программисту он ни к чему.
    Ответ написан
    Комментировать
  • Где ошибка в самодельном strcmp?

    @Mercury13
    Программист на «си с крестами» и не только
    Ваш код коряв (и пишет за пределы буфера, если в потоке действительно окажется 2048 байт) но нуль-терминированные строки отрабатывает правильно.

    TCP работает сплошным потоком, и если вы послали в сокет нечто ещё, кроме «123123», он считает весь поток до 2048 байт и, разумеется, строки не совпадут: с одной строны «123123», с другой, например, «123123qwe».

    Вам надо своими силами разбивать TCP-поток на сообщения — например, тем самым нулевым символом, CR или двумя/четырьмя байтами длины пакета.
    Ответ написан
  • Какова очередность присваивания нового объекта?

    @Mercury13
    Программист на «си с крестами» и не только
    А это значит, что манипулируя членами b я буду редактировать память a?

    Нет, конечно.

    Type& Type::operator=(const Type& x);
    Задача операции присваивания — сделать, чтобы объект слева (он же *this) стал копией объекта справа (он же x).

    Почему операция присваивания берёт Type& по ссылке? Да потому, что взять по копии — это вызвать конструктор копирования. Вполне можно написать
    BigInt& BigInt::operator = (int x);
    когда слева «большой» тип, а справа — «маленький», и его конструктор копирования незначителен (а то и равняется накладным расходам на ссылку, как у того же int). Но когда с обеих сторон один и тот же тип — это часто бездумный вызов конструктора копирования (собственные конструктор копирования и операцию = обычно пишут, когда нужна хитрая логика, а не тривиальное копирование).

    Почему операция присваивания возвращает Type&? Просто чтобы работали конструкции типа a = b = c. Может и void возвращать, если хочешь.

    class C
    {
    public:
        C() {}
        C (const C&) { std::cout << "Copy ctor" << std::endl; }
        C & operator = (const C&) { std::cout << "Assign" << std::endl; return *this; }
    };
    
    int main()
    {
        C a;
        C b = a;

    Выведет «Copy ctor». То есть ответ на первый вопрос — конструктор копирования.

    В литературе что я читаю это называется присваиванием

    Не присваивание, а инициализация. Присваиванием было бы…
    Type b;
    b = a;


    Type b = Type(a);

    В идеале тут два конструктора копирования и один деструктор, однако компилятору даётся воля уменьшать их количество. Потому только «copy ctor».

    Код
    int a = 10;
    C b = C(a);

    также даёт один вызов C(int). А C(const C&) не вызывается ни разу.

    UPD. Одно из нововведений Си++11 — чтобы даже без этих негарантированных оптимизаций, когда один объект исчезает, а второй появляется, можно было вместо копирования проводить нечто более простое, не требующее отвода-возврата памяти. Я-то экспериментировал в режиме Си++03, но в Си++11 уже были бы конструктор копирования, конструктор перемещения и деструктор.
    Ответ написан
    Комментировать
  • В чем здесь ошибка (перегрузка операторов)?

    @Mercury13
    Программист на «си с крестами» и не только
    Вы наладили операцию String << ostream → ostream. Надо наоборот, ostream << string → ostream.

    Такую операцию можно наладить только за пределами класса (возможно, как friend).
    Ответ написан
    1 комментарий
  • Почему sizeof показывает фактический размер массива хотя по сути имя массива это указатель на первый элемент?

    @Mercury13
    Программист на «си с крестами» и не только
    Несмотря на то, что массив часто отождествляют с указателем, массив — это НЕ указатель. У него, например, другие операции приводят к неопределённому поведению.

    И самое противное в Си — это то, что в коде
    void sort(int x[5]);
    x — это не массив, а именно что указатель. Чтобы был массив, надо Си++
    void sort(int (&x)[5]);
    И компилятор даже подсвечивает, что параметр функции, отмеченный как массив — всегда указатель.
    typedef int Arr[SIZE];
    int sum(Arr arr, int n)
    // warning: 'sizeof' on array function parameter 'arr' will return size of 'int *' [-Wsizeof-array-argument]|
    Ответ написан
    Комментировать
  • Что значит эта строчка?

    @Mercury13
    Программист на «си с крестами» и не только
    If the most significant bit is set, the key is down, and if the least significant bit is set, the key was pressed after the previous call to GetAsyncKeyState.

    Проверка на младший бит (сокращённая запись if ((GetAsyncKeyState(VK_F1) & 1) != 0). Он означает: с предыдущего вызова GetAsyncKeyState клавиша хоть раз нажималась. С одной стороны, этот бит нерекомендуемый (работает, когда нет других программ, вызывающих GetAsyncKeyState). С другой, возможно, младший бит GetAsyncKeyState() в современной реализации Windows работает именно так, как надо (если программа неактивна, GAKS возвращает 0).

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

    @Mercury13
    Программист на «си с крестами» и не только
    1. Будьте осторожны, перед нами динамический массив, сделанный расширением стека. Не на всех компиляторах есть, плюс стек ограничен парой мегабайт.
    Нормализацию размера лучше сделать shift %= size;
    Подпрограмма работает с локальной переменной и всё, что она делает, идёт на экран и больше никуда.
    (вообще стоит разделять обработку и вывод!)
    Время работы size.

    2. Сдвигаем на один элемент нужное количество раз. Время работы shift·size.

    3. Как за size «на месте»…
    квоПрох := НОД(shift, size)
    для i = [0..квоПрох)
      i1 := i
      tmp := a[i]
      вечный цикл
        i2 := (i1 + shift) % size
        если i2 = i
          прервать вечный цикл
        a[i1] := a[i2]    
        i1 := i2
      a[i1] := tmp
    Ответ написан
    Комментировать
  • Как создать "библиотеку" для NPC на языке C++?

    @Mercury13
    Программист на «си с крестами» и не только
    Из такого вопроса видно, что человек всё ещё нетвёрдо знает программирование, а уже пытается программить игру.
    И тут есть два варианта.

    1. Движок с нуля. Тонкая графическая прослойка наподобие SDL, оставляющая 100%-ю свободу движкописателям — это тоже «с нуля» и всячески рекомендуется даже продвинутым (не нужно писать всякие там Alt-Tab’ы и прочую дрянь, далёкую от игры). Тогда пусть начнёт с «питончика» (https://ru.wikipedia.org/wiki/Snake_(игра) ) или текстового квеста.
    А потом, когда доберётся до платформера или CRPG, он поймёт, каким образом привязать к монстру характеристики.

    Первое приближение (не слишком удобное для скриптования и подходящее для платформеров/action-RPG, но негодное в настоящих RPG) — сделать каждому компьютерному персонажу «класс» (не путать с классом ООП), включающий кол-во HP, внешний вид и прочее. А также «набор скриптов» — для поведения в диалогах, запрограммированного передвижения, превращения из «болванчика» в компьютерного врага, и т.д. Без набора скриптов он просто компьютерный враг, тупо нападающий на ПиСя.

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

    2. На чужом движке. Это уж смотрите, что движок может, какие возможности в нём «в коробке», а какие придётся дописывать своим кодом и чужими модулями. Не знаком ни с одним. Чужой движок — довольно противное дело для начинающего программиста: он автоматически формирует какую-то структуру программы, и программисту приходится не столько творить, сколько разбираться с этой структурой. С одной стороны, можно быстро написать довольно сложную игру, с другой — не так много радости «а у меня заработало».
    Ответ написан
    Комментировать
  • Возникает ошибка Ошибка C4700 использована неинициализированная локальная переменная как исправить?

    @Mercury13
    Программист на «си с крестами» и не только
    #include "stdio.h"
    #include "stdlib.h"

    Лучше это писать в угловых скобках.

    double dr(double zb)
    {
        return zb = (rand() % 100) / (100 * 1.0);
    }

    Присваиваем параметру zb что-то, чтобы его похѣрить? Параметр будет жить до конца функции и НЕ ВЕРНЁТСЯ в вызвавшую программу (параметры передаются по копии).
    Кстати, параметр zb ни на что не используется: что бы вы ни передали туда снаружи, подпрограмма в этот параметр не посмотрит.

    printf("%lf", dr(double zb));
    На G++ не скомпилировалось. Ну, написал dr(0), благо параметр не используется ни на что.

    if (d == e)
    {
        printf("Win! your cash:%lf", winWay(a, b));

    C:\TestApps\ErrUninitLocal\main.cpp|30|warning: 'd' may be used uninitialized in this function [-Wmaybe-uninitialized]|
    А действительно, переменная d нигде никому не присваивается.

    И ещё одно. Я не знаю, как действует Visual C, но модификатор l для формата %f нужен для scanf, но не нужен для printf. Вот так-то всё это дело устроено.

    double a = winWay(a, b);
    }

    Что мы делаем? Присваиваем локальной переменной, чьё имя пересекается с более ранней переменной, чтобы её сразу же похѣрить? Она доживёт до ближайшей закрытой скобки.

    C:\TestApps\ErrUninitLocal\main.cpp|33|warning: 'a' may be used uninitialized in this function [-Wmaybe-uninitialized]|
    Тут вообще забавно работает. double a — считаем, что a определилась. = winWay(a, b) — считаем, что используется ОНА ЖЕ, а не более ранняя a. Естественно, неинициализированная.

    (аналогично с loseway)
    C:\TestApps\ErrUninitLocal\main.cpp|38|warning: 'a' may be used uninitialized in this function [-Wmaybe-uninitialized]|
    Ответ написан
    Комментировать
  • Как вернуть ссылку/указатель на элемент вектора?

    @Mercury13
    Программист на «си с крестами» и не только
    1.
    bool FindElement(const string& search, CElement*& result);

    2. CElement* FindElement(const string& search);
    Ответ написан
    1 комментарий
  • Можно ли организовать связь между QT, mySQL и Excel?

    @Mercury13
    Программист на «си с крестами» и не только
    Qt и MySQL работают отлично и очень быстро. Маленькая подсказка: помимо DLL драйвера, который надо бросить в подкаталог plugins\sqldrivers, надо положить в РАБОЧИЙ каталог программы DLL MySQL или MariaDB (зависит от сборки Qt).

    С Excel’ем сложнее, и приходится искать любую Excel-библиотеку, имеющуюся на Си++.
    Из открытых — XLNT (кроссплатформенный) и QtXlsx (привязан к системе классов Qt).
    Мы используем частично LibXL (платный, тормозной, огромный расход памяти, но QtXlsx ещё хуже, насколько я проверял), частично свой велосипед (минимум функциональности, оптимизирован под огромные XLSX — вплоть до того, что Excel берёт большую таблицу за 10 секунд, а мы за три, а LibreOffice вообще над ней размышляет минутами).

    Но это уже собственно вопрос: а что есть для Excel’я на Си++. Может, сырой XLNT довели до ума. Может, у вас нет огромных таблиц, и того, что есть, вам хватает…

    UPD. Есть разные сборки DLL MySQL, так что придётся экспрериментировать, чтобы работало не только на разработческой машине, но и у потенциального юзверя.
    Ответ написан
    Комментировать
  • Как удалить часть строки в c++?

    @Mercury13
    Программист на «си с крестами» и не только
    Так и надо, но не хватает только одного: каким-то образом обрезать строку после всего этого.
    for (…)
      s1[i] = s1[i + s2len]
    s1.resize(s1.length() - s2len);

    Не забывайте: если длина вашей строки — это strlen, то length() в цикле вызывать запрещено!

    Да, ещё вопрос: убрать ПЕРВОЕ вхождение или ВСЕ вхождения?
    Ответ написан
  • Как нарисовать на курсоре в RunTime?

    @Mercury13
    Программист на «си с крестами» и не только
    Попробуйте Brush.Color := $FF000000 or clBlue;. Велика вероятность, что механизмы работы с 32-битными изображениями тут слабоваты.
    Ответ написан
    1 комментарий
  • Как получить нужную точность при умножении и делении чисел типа double?

    @Mercury13
    Программист на «си с крестами» и не только
    Дело в том, что 4,2 и 4,3 невозможно представить в виде double. И система, например, сохраняет 4,3 в виде 4,2999999, которое при умножении на 10 станет 42,999999.
    Нужно убедиться, что при преобразовании double → int происходит round, а не усечение.
    Ответ написан
    2 комментария