Ответы пользователя по тегу C++
  • Как затереть ссылку возврата?

    @Mercury13
    Программист на «си с крестами» и не только
    Для чего это нужно? А для того, чтобы вы знали, как хакеры вас ломают. Простейший способ для этого — устроить переполнение буфера.
    void foo()
    {
      void* a[1];
      a[1] = (void*)&bar;
    }

    Может быть, придётся подменить не a[1], а a[2] или a[3].
    Если так поступить, bar-то будет вызван, но по выходу из него, скорее всего, программа упадёт. Как сделать, чтобы не упала — оставлю домашним заданием. Высший пилотаж — завести в main() пару переменных, а потом через std::cout показать: вот они, целые и невредимые (компилируйте без оптимизации, макс. оптимизация может их просто заменить константами). Но тут уже придётся знать соглашение вызова и читать дизассемблерный листинг: от этого зависит, как вернуть стек в подходящее состояние.

    UPD. Простите за моё незнание синтаксиса Си.
    Ответ написан
    5 комментариев
  • Как подключить libcurl к Qt?

    @Mercury13
    Программист на «си с крестами» и не только
    Подключать надо libcurldll.a в таком виде.
    -LC:\Qt\curl-7.49.0-win32-mingw\lib -lcurldll

    Вот кусок, взятый из реального проекта.
    win32|win64: LIBS += -lws2_32 -lshlwapi -lodbc32 -lpsapi -lcomdlg32 \
                         -L$$PWD/../../Common/cURL/lib/ -lcurldll -lole32 \
                         -loleaut32


    Кроме того, тебе не хватает OpenSSL — найди libeay32.dll и ssleay32.dll, лучше скомпилированные старой версией MSVC и не требующие новых runtime’ов, которые есть не везде. Ах да, есть версии libcurl, которые скомпилированы без SSL — им этого, разумеется, не нужно.

    Подожди, сейчас посмотрю, что будет, если вкомпилировать в программу библиотеку cURL статически, без DLL.

    UPD. Попробовал. Почти работает, но надо разобраться, как подключить winsock и OpenSSL — раньше-то ими занимался тот, кто компилирует DLL, а сейчас это будет моя забота.
    Ответ написан
  • Как поместить "нетривиальный" объект в стуктуру?

    @Mercury13
    Программист на «си с крестами» и не только
    Одно из полей (в данном случае data в реализации item<Edge>) не имеет конструктора по умолчанию. Есть три пути.

    РАЗ. Придумать, как сделать, чтобы конструктор всё-таки был.
    class Edge : public EdgeBase
    {
    public:
      Edge (Point* firstPoint, Point* secondPoint) { init(firstPoint, secondPoint); }
      Edge () { init(NULL, NULL); }
      void init (Point* firstPoint, Point* secondPoint)
      {
        this->firstPoint = firstPoint;
        this->secondPoint = secondPoint;
      }
    private:
      Point* firstPoint;
      Point* secondPoint;
    };


    ДВА. Если объект копируется/переносится, можно воспользоваться такой штукой. С вашего позволения, упрощу задачу и вместо Edge заведу другой класс — ImmutableInt. Правда, этот код — попытка совместить ежа с ужом (непонятно, какую концепцию должен поддерживать Payload), но, тем не менее, работает.
    #include <iostream>
    
    class ImmutableInt
    {
    public:
        explicit ImmutableInt(int x) : fData(x) {}
        int data() const { return fData; }
    private:
        int fData;
    };
    
    template <class Payload>
    class ListItem
    {
    public:
        Payload payload;
        ListItem* next;
    
        ListItem() : next(NULL) {}
        ListItem(const Payload& x) : payload(x), next(NULL) {}
    };
    
    // Так писать нельзя — эта конструкция расшаблонивает все функции,
    // а конструктора по умолчанию как не было, так и нет!
    //template class ListItem<ImmutableInt>;
    
    int main()
    {
        ListItem<ImmutableInt> li(ImmutableInt(42));
        std::cout << li.payload.data() << std::endl;
        return 0;
    }


    ТРИ. Воспользоваться переменными шаблонами (variadic templates) C++11.
    #include <iostream>
    
    class ImmutableInt
    {
    public:
        explicit ImmutableInt(int x) : fData(x) {}
        int data() const { return fData; }
    private:
        int fData;
    };
    
    template <class Payload>
    class ListItem
    {
    public:
        Payload payload;
        ListItem* next = nullptr;
    
        template<class... Args>ListItem(Args... args) : payload(args...) {}
    };
    
    int main()
    {
        ListItem<ImmutableInt> li(42);
        std::cout << li.payload.data() << std::endl;
        return 0;
    }


    P.S. Хотя ImmutableInt — семантически тот же int и теоретически explicit не надо, всё-таки отметил — просто чтобы показать, что во втором случае мы передаём параметром ImmutableInt<42>, а в третьем — 42.

    P.P.S. Я упомянул слово «концепция». Это набор требований к типу. Что-то типа интерфейса — но, во-первых, никак не связано с ООП и его динамическим полиморфизмом, и, во-вторых, не столь жёсткое: как ты интерфейсом из Java наладишь концепцию «есть конструктор копирования» или «может делить себя на число и что-то выходит»? Синтаксическая поддержка концепций откладывается на C++17, но уже в C++03 было несколько концепций: InputIterator, DefaultConstructible, CopyAssignable и другие. А пока… концепция не поддерживается — ошибка компиляции где-то в страшенном стандартном хедере.

    P.P.P.S. Написав код
    template<typename type_t>
    struct item
    {
      item* next;
      type_t data;
    };

    вы автоматически потребовали от type_t концепцию DefaultConstructible (есть конструктор по умолчанию)
    Ответ написан
    1 комментарий
  • Как программно скачать несколько файлов с сайта?

    @Mercury13
    Программист на «си с крестами» и не только
    1. Заполучить принцип, по которому строятся ссылки. Возможно, для этого придётся разбирать код самого сайта.
    2. Использовать любую библиотеку HTTP: Indy (Delphi), cURL (DLL с большим количеством привязок) или что-нибудь другое. С другими я, к сожалению, не работал. Ну или просто запускать подходящий EXE-загрузчик (wget, curl…).
    3. Обойти анти-DDoS’овские меры. Многие сети раздачи контента проверяют, похож ли клиент на легитимный браузер — наш бот должен все эти меры обойти.

    Знаю, что слабость последних версий Indy — ориентация на .NET и вытекающие оттуда ограничения; также Indy «слишком вумный» и сложные REST-службы не работают, так как строишь тело, вычисляешь имитовставку — а служба говорит, что они друг другу не соответствуют; видимо, Indy что-то подстроил. Кстати, когда я понял, что запросы без тела работают, а с телом — ни в какую, первым тестом стал curl.exe, и только потом подключил libcurl через собственную Delphi-привязку.

    Слабость cURL — тонкости протоколов (скажем, заголовки и кодировки) остаются за прикладными программистом; ориентирован на FILE* и на всех языках, кроме Си, запись в файл слегка затруднена; ориентирован на vararg и если работать без типобезопасной обёртки, надо быть предельно осторожным.
    Ответ написан
    Комментировать
  • Как изменять значения переменных другого класса в C++?

    @Mercury13
    Программист на «си с крестами» и не только
    Не буду говорить о качестве кода, const-корректности и прочей бяке. Конкретная ваша ошибка
    int& operator [](int i) // именно ссылка!

    У вас возвращается значение — временный объект, которому, разумеется, невозможно присвоить что бы то ни было.
    Ответ написан
  • Как удалить элемент из вектора, а затем добавить несколько новых?

    @Mercury13
    Программист на «си с крестами» и не только
    У вас могут быть две проблемы.
    1. Неверно написана операция «присвоить» или «переместить».
    2. Забыл, что в std::vector при операции «добавить» или «удалить» возможно физическое перемещение объекта и ссылки на него больше недействительны.

    Ну и IsEnded лучше писать вот так.
    bool IsEnded(const Query &aVar) { return (aVar.id == NO_ID); }
    Ответ написан
    2 комментария
  • Что будет содержать абстрактный класс?

    @Mercury13
    Программист на «си с крестами» и не только
    Вариант один вы предложили. Всё пишу на C++03, без шаблонов. C++11 и шаблоны, разумеется, дадут больше вариантов.
    class Object {
    public:
        virtual ~Object() {}
    };
    
    class List {    // interface
    protected:
        virtual Object& getAt(size_t i) = 0;
    public:
        virtual size_t size() const = 0;
        inline Object& at(size_t i) { return getAt(i); }
        inline const Object& at(size_t i) const
            { return const_cast<List*>(this)->getAt(i); }
        virtual ~List() {}
    };

    Вариант 2. С ним пользователю не так просто, но если к нему ещё добавить лямбда-функции C++11 — вообще бомба будет!
    class ListCallback {    // interface
    public:
        virtual void act(size_t index, Object& object) = 0;
        virtual ~ListCallback() {}
    };
    
    class ListConstCallback {   // interface
    public:
        virtual void act(size_t index, const Object& object) = 0;
        virtual ~ListConstCallback() {}
    };
    
    class List2 {   // interface
    public:
        virtual size_t size() const = 0;
        virtual void enumerate(ListCallback& body) = 0;
        virtual void enumerate(ListConstCallback& body) const = 0;
    };

    Например, для динамического массива, у которого быстрый доступ по номеру, будет такое тело
    void DynArray::enumerate(ListConstCallback& body) const {
       size_t sz = size();
       for (size_t i = 0; i < sz; ++i)
           body(i, operator[](i));
    }

    Разумеется, если вы нагрузкой сделаете не абстрактный Object, а что-то окончательное, dynamic_cast не нужен будет.

    Вариант 3 принят в Java. Один недостаток — мого кода писать для const-корректности, так что с вашего позволения опущу.
    class VirtualListIterator { // interface
    public:
        virtual bool next() = 0;
        virtual Object& value() = 0;
        virtual ~VirtualListIterator() {}
    };
    
    class ListIterator {   // Назван так для красоты, по сути это умный указатель
    private:
        VirtualListIterator* ptr;
        // Запретим копирование и op=, но при желании можно реализовать и их
        ListIterator(ListIterator&) {}
        void operator=(const ListIterator&) {}
    public:
        ListIterator() : ptr(NULL) {}
        ListIterator(VirtualListIterator* value) : ptr(value) {}
        ~ListIterator() { delete ptr; }
        bool next() { return ptr->next(); }
        Object* operator->() const { return &ptr->value(); }
        Object& operator*() const { return ptr->value(); }
    };
    
    class List3 {   // interface
    public:
        virtual size_t size() const = 0;
        virtual VirtualListIterator* enumerate() = 0;
    };
    
    class Number : public Object {
    public:
        int value;
    };
    
    // И пользоваться этим так...
    void doSmth(List3& aList) {
        ListIterator it(aList.enumerate());
        while (it.next()) {
            std::cout << dynamic_cast<Number&>(*it).value << std::endl;
        }
    }


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

    Да, и из вас будет хороший инженер, если вы сразу же задумываетесь о производительности.
    Ответ написан
    Комментировать
  • Как создать динамический массив из объектов класса?

    @Mercury13
    Программист на «си с крестами» и не только
    Операция присваивания или перемещения есть? Пускай даже созданная автоматически? Если есть — вот, смотрите.
    #include <iostream>
    #include <vector>
    
    class Test {
      int k;
    public:
      Test(int _i) {k = _i; }
    
      void put_k(int i) {k = i; }
      int get_k() {return k; }
    };
    
    
    struct TestWrapper {
        Test payload;
        TestWrapper() : payload(0) {}
        TestWrapper(int i) : payload(i) {}
        TestWrapper(const Test& i) : payload(i) {}
    };
    
    int main()
    {
        Test test(10);
        std::vector<TestWrapper> v;
        v.push_back(test);
        std::cout << v[0].payload.get_k() << std::endl;
        return 0;
    }


    Если нет — тогда динамический массив так просто работать не может (фишка динамического массива — заводить новый массив и копировать информацию из старого). Только использованием умных указателей.
    #include <iostream>
    #include <vector>
    #include <memory>
    
    
    class Test {
      int k;
    public:
      Test(int _i) {k = _i; }
      Test& operator=(const Test&) = delete;
      Test& operator=(Test&&) = delete;
    
      void put_k(int i) {k = i; }
      int get_k() {return k; }
    };
    
    
    int main()
    {
        std::vector<std::unique_ptr<Test>> v;
        v.push_back(std::unique_ptr<Test>(new Test(10)));
        std::cout << v[0]->get_k() << std::endl;
        return 0;
    }
    Ответ написан
  • Передача узла дерева?

    @Mercury13
    Программист на «си с крестами» и не только
    По сути. Вы забыли порядок исполнения операций.
    (*root)->next.push_back(node);

    Чем ещё можно улучшить код…
    Зачем вам указатель на указатель? Как массив указателей? Как «либо ссылка на указатель, либо ничего»? По видимому, ни то, ни другое, указатель, по сути передаётся по значению — так что стоило бы функцию переделать как…
    bool Syntax::On_BG(Node * root)
    или даже
    bool Syntax::On_BG(Node & root)

    PS. И да, указатель передаётся по значению, но то, на что он указывает, не исчезнет с выходом из функции.
    Ответ написан
  • Qt, Время собирать "камни" или как получить Release?

    @Mercury13
    Программист на «си с крестами» и не только
    «can't find -lQSerialPort» — это значит, не в инклудах дело, а в .lib или в .a (зависит от компилятора). Ищи, как указать путь к этому файлу. И почему в отладке работало? — там что, этот путь указан?
    Ответ написан
    4 комментария
  • Как лечить char warning overflow при считывании с файла?

    @Mercury13
    Программист на «си с крестами» и не только
    fin.getline(pRun->sBooks.chBook, 99, static_cast<char>('»'));


    Всё у вас правильно. А в Си char почему-то (обычно) знаковый.
    Ответ написан
  • Приведение обьекта производного класса к базовому?

    @Mercury13
    Программист на «си с крестами» и не только
    Через указатели и ссылки — останется.
    По значению (как у тебя сейчас в векторе) — преобразуется в базовый.

    Главная проблема работы с указателями и ссылками — где хранить объекты и как их уничтожать. Хранят обычно в «куче», а для уничтожения используют умные указатели или что-то самописное под задачу. Вот, например, решение на умных указателях C++11:
    #include <iostream>
    #include <memory>
    #include <vector>
    
    class Base{
    public:
        // Нужен виртуальный деструктор, ведь мы будем уничтожать детей
        // через родительский ук-ль. Да и dynamic_cast требует RTTI,
        // а значит, хоть одного виртуального.
        virtual ~Base() = default;
    };
    
    class Exp: public Base
    {
    int i=0;
    public:
           int Get() const {  return i; }
           void Set(const int &num) { i=num; }
    };
    
    std::vector<std::shared_ptr<Base>> MyStack;
    Base &GetRef() { return **MyStack.begin();  }
    
    int main() {
        std::shared_ptr<Exp> a = std::make_shared<Exp>();
        a->Set(4);
        MyStack.push_back(a);
        int res=dynamic_cast<Exp&>(GetRef()).Get(); // Теперь работает
        std::cout << res << std::endl;;
    }
    Ответ написан
    Комментировать
  • Что такое normaliz.dll?

    @Mercury13
    Программист на «си с крестами» и не только
    Нормализация Юникода.
    Обычно находится в Windows\System32 (Windows\SysWOW64).
    Официальный сайт не держит библиотек cURL, он только даёт ссылки на чужие сборки. cURL (или OpenSSL) использует VC2008; вы явно скачали версию НЕ для XP. Ничего, VS2008 Redistributable можно скачать тут.
    https://www.microsoft.com/en-us/download/details.a...

    Я специально искал и нашёл сборку cURL/OpenSSL, которая не требует ничего необычного. Мы программисты, ставим кучу софта — а у одного бета-тестера не прокатило, т.к. на его рабочем ноутбуке не было VS2013, приблудил с OpenSSL.
    Ответ написан
    Комментировать
  • Как заставить сработать исключение std::bad_alloc?

    @Mercury13
    Программист на «си с крестами» и не только
    #include <iostream>
    #include <conio.h>
    
    int main()
    {
      setlocale(LC_ALL, "Russian");
      const size_t SIZE = 3000000000ul;
      char* ptr = NULL;
      try
      {
        ptr = new char[SIZE];
        std::cout << "Память используется без сбоев.\n";
      }
      catch (std::bad_alloc&)
      {
        std::cout << "Исключение bad_alloc: невозможно разместить данные в памяти.\n";
      }
      delete[] ptr;
      getch();
      return 0;
    }


    Столько кода — и столько ошибок! По пунктам.
    1. Ответ на ваш вопрос. Сделать константу побольше. Кстати, эта константа — size_t. В 64-битном коде надо ещё больше.
    2. Не проработано поведение delete[], если случится ошибка. Покатит инициализация NULL.
    3. Если случится ошибка, будут выведены оба сообщения.
    4. getch — функция из платформозависимого заголовка conio.h. Только DOS/Windows.
    5. Обработку аварий обычно делают по ссылке.
    6. Для первого параметра setlocale надо указывать ненулевую маску, на какие части ставить локаль. LC_ALL — везде. Что писать вторым параметром, зависит от библиотеки времени выполнения.
    Ответ написан
    7 комментариев
  • Undefined reference to?

    @Mercury13
    Программист на «си с крестами» и не только
    В проект не включили файл methods.cpp. Потому линкер говорит, что нет этих функций.

    Язык Си (как, впрочем, и большинство ассемблеров) собирает cpp-файлы воедино неязыковыми средствами: makefile’ами и файлами проектов. Сначала компилятор обрабатывает все единицы компиляции отдельно друг от друга, а потом линкер собирает из того, что получилось, исполняемый файл. Разумеется, если где-то какой-то функции не нашлось, это можно опознать только при линковке.
    Ответ написан
    4 комментария
  • Почему при достаточно малых значениях вещественного числа кол-во итераций при нахождении машинного эпсилон резко убывает?

    @Mercury13
    Программист на «си с крестами» и не только
    Машинный эпсилон — это минимальное число, что 1 + ε ≠ 1. Так что, в принципе, ты вычислил его правильно, хоть код и студенческий. Но есть один нюанс.

    Дело в том, что float и double бывают нормализованные и денормализованные. Что это такое?
    Любое число в двоичной системе счисления начинается с единицы. Поэтому головная единица подразумевается и не хранится — т.н. «нормализованное число». НО: когда порядок 00…00, считается, что в голове НОЛЬ, а относительная погрешность сменяется абсолютной — это денормализованное число.

    0 1010…00 00000001 = +0,11012×2−127 — нормализованное число
    0 1010…00 00000000 = +0,01012×2−127 — денормализованное
    0 0000…00 00000000 = +0,00002×2−127. Ноль — тоже денормализованное число.

    10−38 — минимальное нормализованное число. 10−45 — минимальное денормализованное, с мантиссой 0,00…001. Помнишь, я говорил, что в денормализованных числах относительная погрешность сменяется абсолютной в эти самые 10−45 — потому чем меньше число, тем больше «типа-эпсилон».

    10-байтовый extended, он же long double, насколько мне известно, не нормализован, т.е. головная единица там хранится явно. Но такая точность редко нужна, появляется перерасход памяти (2 или 6 байтов, в зависимости от процессора и его настроек), да и не слишком оптимизируют сопроцессоры под такие числа.
    Ответ написан
    Комментировать
  • Использование функций из Dll бибилеотеки?

    @Mercury13
    Программист на «си с крестами» и не только
    Возникает вопрос. Это простой DLL с торчащими наружу функциями или COM?
    • QLibrary — для DLL.
    • regsvr32 — это для COM.
    • Первый раз не загрузилось и второй раз загрузилось, потому что 32-битные программы со стандартным манифестом принимают каталог «SysWOW64» за «System32». Когда DLL не найден, программа отыскивает его в некоторых стандартных местах и таковыми, возможно, являются и активные COM-серверы.
    • OpenConnection мог не найтись по адовой куче причин. Может оказаться, что имя OpenConnection «покозявлено» и надо что-то типа «OpenConnection@4». Может оказаться, что библиотека — действительно COM-сервер и QLibrary тут бесполезен.
    Ответ написан
    9 комментариев
  • Почему не отрабатывает перегруженный конструктор класса?

    @Mercury13
    Программист на «си с крестами» и не только
    Попробую рассказать об этом же более простыми словами.

    Текст String s3 = "Нас обманули, расходимся."; означает вот что. Мы создаёт временную строку «Нас обманули, расходимся», а затем присваиваем её нашему s3. Конечно, компилятор это потом заоптимизирует, но это семантика языка, и ей надо следовать.

    Есть два способа передать временный объект в функцию. Любую: хоть простую, хоть конструктор, хоть операцию =.
    Первый — константная ссылка: String(const String &S).
    Второй способ из C++11 — временная ссылка: String(String &&S).

    Из-за этих временных объектов конструктор копирования и операция «присвоить», по-хорошему, должны брать const-ссылку. Вот вам аналогичный пример с операцией «присвоить».

    class String
    {
    public:
        String() {}
        String& operator= (String& s) { return *this; }
    };
    String operator+ (String a, String b) { return String(); }
    
    int main()
    {
        String s1;
        String s2 = s1;
        String s4;
        s4 = s1 + s2;
    }


    Решается String& operator= (const String& s).
    Ответ написан
    Комментировать
  • Как хранить записи со стены ВК?

    @Mercury13
    Программист на «си с крестами» и не только
    Не громоздко и не ресурсоёмко (особенно в C++11 — кстати, скоро Qt вообще откажется от 03).
    Однако я бы в таких нагромождениях делал struct’ы из одного элемента. Что-то типа
    struct Comment {
      QMap<QString, QString> objects;
    };
    
    struct Post {
       QList<Comment> comments;
    };

    Так код проще будет разобрать.

    Только одно непонятно. Что собой представляют эти objects и на что тебе эта «строкотипизированность»?
    Ответ написан
    Комментировать
  • Как конвертировать list в string[]?

    @Mercury13
    Программист на «си с крестами» и не только
    Итератор — это объект с семантикой указателя, который может указывать на N+1 точку в объекте.
    5f40aef227644b9ca57224ffeb571a97.png
    Раз он с семантикой указателя, у него есть операции «унарная звезда» и −> (разыменование и разыменование+взятие поля). Также у итератора есть операция ++ (сдвинуться на следующую позицию). Если это т.н. «однонаправленный итератор» — всё, больше ничего.

    Также бывают т.н. двунаправленные итераторы (есть операция −−), и итераторы произвольного доступа (их можно свободно складывать с числами — ну совсем как указатели). В частности, у std::list итераторы двунаправленные.

    У итераторов неопределённое поведение…
    • при попытке выйти за начало или конец;
    • при попытке разыменовать, если он смотрит на последнюю позицию (отмеченную как «конец»).

    Конкретно о задаче.
    1. std::vector предпочтительнее std::list.
    2. Не нужно возвращать string*, хватает какого-нибудь контейнера (std::vector<std::string> или std::list<std::string>).
    3. Если функциональности и скорости istringstream хватает, флаг в руки! Я бы написал по хардкору, с нуля. Вот мой код, выдранный из моего проекта, наверно, будет несложно переделать его в учебный.

    void parseCommaList(
            const char *aStart,   // указатель на начало
            const char *aEnd,    // указатель на символ за концом
            char aComma,        // символ-разделитель
            bool aSkipEmpty,   // true, если пустые подстроки пропускать
            ProcParseCommaList aCallback,   // функция-нагрузка
            void *aData)   // этот параметр нужен, чтобы передавать какие хочешь данные в функцию-нагрузку, удаляй его смело!
    {
        str::trim(aStart, aEnd);    // моя функция; пододвигает aStart вперёд и aEnd назад, убирая пробелы.
                                    // Если удаление пробелов не нужно — удаляй! Если нужно — пиши сам.
        if (aStart == aEnd) return;
        const char *sstart = aStart;
        for (const char *p = aStart; p != aEnd; ++p)
        {
            if (*p != aComma) continue;
            const char *send = p;
            str::trim(sstart, send);   // то же самое, можно убрать
            if (p != sstart || !aSkipEmpty)
                aCallback(sstart, send, aData);    // замени на боевую нагрузку
            sstart = p + 1;
        }
        str::trim(sstart, aEnd);   // то же самое, можно убрать
        if (sstart != aEnd || !aSkipEmpty)
            aCallback(sstart, aEnd, aData);    // замени на боевую нагрузку
    }


    И, соответственно, версия для std::string.

    inline void parseCommaList(
            const std::string &aIn,
            char aComma,
            bool aSkipEmpty,
            ProcParseCommaList aCallback,
            void *aData)
    {
        parseCommaList(aIn.data(), aIn.data() + aIn.length(), aComma, aSkipEmpty,
                aCallback, aData);
    }
    Ответ написан
    Комментировать