• Для чего нужны замыкания в 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 комментария
  • Как составлять header файл для статической библиотеки?

    wataru
    @wataru Куратор тега C++
    Разработчик на С++, экс-олимпиадник.
    А зачем это делать? Чем вас 8 хедеров не устраивают? Если уж так хочется сэкономить строчки в коде пользователей библиотеки, то можно воспользоваться рекурсивностью препроцессора и сделать хедер с 8 инклудами. Копипастить код точно не надо.
    Ответ написан
    1 комментарий
  • Как исправить ошибку "Нарушение прав доступа при записи по адресу"?

    wataru
    @wataru Куратор тега C++
    Разработчик на С++, экс-олимпиадник.
    Эта ошибка означает, что вы пишите в какую-то память, которая вам не принадлежит.

    Или у вас там неинициализированный указатель, или выход за границы массива, или что-то подобное. Надо присмотреться ко всем указателям в программе.

    Можно во время отладки нажать на "продолжить" и тогда дебаггер остановится именно на той инструкции, которая вызвала ошибку. Дальше уже можно смотреть, в какую переменную вы там пишите и откуда она взялась.

    Падает оно потому, что нельзя string читать и писать в файл вот так, просто интенрпретируя память объекта как char*. Потому что string содержит в себе указатели на динамически выделенную память.

    Поэтому, когда вы его (в составе AutoBase) пишите в файл а потом читаете, вы получаете указатель на адрес, который был жив вместе со старым экземпляром класса. Однако, после удаления этого старого экземпляра, этот адрес уже вам не принадлежит.

    Вы можете так писать в файл только структуры из простых типов, и то не всегда (там всякое выравнивание может сыграть с вами злую шутку). Надо писать собственные методы сериализации и десериализации. Строку можно сохранять, например, как длину и потом столько байт, сколько нужно. Тогда при считывании вы сначала чистаете сколько-то байт размера, а потом нужное количество байт в саму строку.
    Ответ написан
    1 комментарий
  • Как и на чем написать андроид приложение?

    Adamos
    @Adamos
    Qt
    Ответ написан
    Комментировать
  • Почему lambda не захватывает this?

    gbg
    @gbg Куратор тега C++
    Любые ответы на любые вопросы
    Банальная порча всех итераторов, ссылок и указателей в тот момент, когда вы вызываете emplace у вашей свалки потоков второй раз. Лечение - избавиться от переаллокации при вызове emplace, путем вызова reserve(), или использовать контейнер, который не передвигает свое содержимое туда-сюда, например, std::list
    Ответ написан
    Комментировать
  • Для каких примерно целей программисту нужен computer science?

    @Arlekcangp
    Разработчик, Лид, Архитектор ПО
    Для любой задачи которую без CS не решить. В предыдущих ответах часть задач уже перечислили. Но это всё касается специализированных задач. Никто не гарантирует вам, что в какой то момент у вас подобных задач не встретится на рядовом до сего времени проекте. Обычно это происходит когда проект вырастает за рамки какого-либо фреймворка, который до того покрывал все 100% таких задач. Банальный пример: было однопоточное приложение. Оно перестало справляться с нагрузкой. По совету с Хабр QA (ну или стэковерфлоу - не важно )) приняли решение переписать на параллельные вычисления. А не у кого нет даже базовых знаний какие существуют "грабли" (опять банальный пример - "состояние гонки" может и маститого профессора CS свести с ума, а, как в том анекдоте про каплю никотина, лошадь и хомяка, "голову вайтишника разрывает на куски") Поэтому пока у вас в команде есть кто-то со знаниями может пусть не всего CS, а хотя бы каких то базовых вещей, а вы просто кодите, то вам оно и не нужно. (как выше замечено - на "галерах" это не актуально. Хотя даже там вообще то такие люди обычно есть и получают они вдвое больше. Иначе какой им смысл там оставаться)
    Ответ написан
    Комментировать
  • Assignment operator VS Destructor + Placement new, где аргумент placement new - prvalue?

    @dolusMalus
    С++ programmer from gamedev
    Мне кажется, что данный вопрос стоит рассмотреть с точки зрения нескольких позиций:
    1. Безопасность относительно исключений. Какой уровень гарантий должен предоставлять данный метод?
    2. Производительность. Критично ли получить максимально эффективный код или на данном этапе этим можно пожертвовать?
    3. Оценить проектное решение в свете известных и проверенных практик, например, принципы SOLID


    Для начала определяемся используются ли вообще исключения в Вашем проекте и если нет, то можно переходить сразу к вопросу о производительности. В противном случае, рассмотрим каждый случай:
    int32_t index = sizeof(T) * head;
            (reinterpret_cast<T *>(&buffer[index]))->~T();
            new (&buffer[index]) T(std::forward<Args>(args)...);
            head = (head + 1) % maxSize;

    данный вариант не предоставляет даже базовых гарантий (basic exception guarantee), т.к. если конструктор сгенерирует исключение, то мы уже закончили время жизни объекта вызвав деструктор, но не изменятся size и head; а значит мы пришли в невалидное состояние.

    int32_t index = sizeof(T) * head;
            (reinterpret_cast<T *>(&buffer[index]))->~T();
            new (&buffer[index]) T(std::forward<Args>(args)...);
            head = (head + 1) % maxSize;

    Можем сделать развилку по noexcept для конструктора с помощью SFINAE или if constexpr и в случае, если конструктор является noexcept; то оставить данный код. В противном случае придется уже действовать в зависимости от необходимого уровня гарантий, однако решения будут достаточно громоздкие: в добавок к развилке надо будет ловить исключение, пробрасывать его дальше, при этом восстанавливать объект или уменьшать счетчики и т.п. Более того можно вообще не обеспечить сильную гарантию при определенных условиях. Как видите, это уже сильно осложнило решение.
    Теперь к другому варианту:
    // Assignment operator
    //        (*(reinterpret_cast<T *>(&buffer[sizeof(T) * head]))) = T(std::forward<Args>(args)...);

    Здесь ситуация относительно лучше, т.к. проблемы могут быть только на уровне оператора присваивания, что как минимум перекладывает часть ответственности в сторону автора типа T. Однако воспользовавшись решением от Евгений Шатунов можно относительно легко получить достаточно понятный и "чистый" код на уровне сильных гарантий. Также стоит посмотреть в сторону copy and swap идиомы, как близкой к данной проблеме.
    По итогу при необходимости строгих гарантий стоит отдать предпочтение варианту с временным объектом.

    С точки зрения производительности, если нет необходимости в максимальной оптимизации, то стоит отдать предпочтение более понятному коду. Данный момент уже прекрасно освещен в ответе Евгений Шатунов, поэтому не вижу смысла повторяться. Однако формально если отбросить предположения об оптимизациях, то вариант с реконструированием по месту (деструктор -> конструктор) оптимальней, т.к. гарантировано не требует выделения дополнительных ресурсов от временного объекта и только две операции + нет необходимости в относительно сложном анализе на перемешаемость/копируемость. В случае со swap, мы можем таки попасть на копирование в зависимости от перемещаемости типа T.

    И часто забываемый, но крайне важный пункт про проектирование. Здесь нарушен Single responsibility принцип, что возможно и породило этот вопрос. Т.е. у Вас метод по добавлению элемента может удалять/заменять элементы, что должно вызвать вопросы. Более того, вы решили за клиента вашего API (даже если это и Вы сами) как нужно обрабатывать исключительную ситуацию по переполнению. Потом например вы решите, что в одном месте стоит сложить старые элементы в отдельную очередь в другом залогировать или удалять не по одному, а сразу половину буффера. Все это потребует переписывания метода add, а зачем и почему? Попробуйте убрать эту часть кода заменив на выбрасывание исключения или вариант с возвращением успешности операции (менее грамотное решение, но это уже из области субъективной оценки) и посмотреть как увеличится прозрачность и простота написания клиентского кода для этого метода. Еще стоит посмотреть на сходное проектное решение в std::vector и его методе pop_back. Подумайте почему он не возвращает удаленный элемент?

    Итого, если важна производительность и не важна работа с исключениями; то разумно выбрать вариант с реконструированием; иначе обмен с временным объектом. Но не стоит забывать всегда про анализ проектного решения и правильную ли Вы проблему вообще решаете.
    Ответ написан
    2 комментария
  • В чем смысл определения const int &ref=1;?

    @MarkusD Куратор тега C++
    все время мелю чепуху :)
    Для начала стоит немного пробежаться по категориям выражений в современном C++.
    Из статьи будет видно что 1 имеет категорию prvalue. 1 является литералом и не является строковым литералом. Это - литерал с типом int по умолчанию.

    Если снова обратиться к статье и посмотреть свойства prvalue, то будет видно, в частности, что prvalue:
    • имеют всегда полный тип;
    • не имеют размещения, а следовательно не имеют адреса в памяти;
    • могут использоваться для инициализации cvq-lvalue-ref и rvalue-ref.

    Последний пункт говорит о том, что код const int& ref = 1; или int&& ref = 1; является полностью стандартным.
    ref в этом случае будет ссылаться на переданный литерал и отсутствие размещения литерала этому не помеха.

    Смысл подобных выражений в том чтобы позволить написать такой код.
    void foo( const int& ref );
    
    void bar()
    {
      foo( 1 );
    }


    Смыл конкретно кода const int &ref=1; можно найти в том, чтобы не писать магические константы в коде. ref - очень плохое имя. Но голая 1 в коде еще хуже.
    Инженер ПО свой код пишет не для транслятора, а для своих сотрудников. Транслятор разберется в любом коде и по любому синтаксически верному коду всегда произведет работающий исполняемый код. Но другие люди в коде смогут разобраться только тогда, когда этот код написан доступным для понимания образом.

    Чтобы код было легче понять, его документируют. Документация бывает разная. Это могут быть комментарии, это может быть отдельный документ. Но лучше всего код понимается тогда, когда он "самодоукментируется" [1], [2], т.е. когда сам код однозначно поясняет цели своего существования и принципы своей работы.
    Чтобы код сам себя пояснял, имена функций, переменных и констант, имена типов и прочих рукописных конструкций должны как можно более ясно отражать цели своего существования. В частности, чтобы код не пестрил безликими магическими числами [?], эти числа принято обличать в т.н. поясняющие константы.
    Такую константу можно определить как cvq-lvalue-ref, назначить понятное в контексте кода имя и присвоить ей требуемый литерал.
    Ответ написан
    Комментировать
  • Как написать простой калькулятор?

    @majstar_Zubr
    C++, C#, gamedev
    Я отвечу для случая работы с простым форматом ввода:
    12345 + 6789

    1) вы не модифицируете входную строку, поэтому, если имеем дело со стандартом от c++17, то лучше в функцию принимать явно string_view или const string_view&, если ниже, то принимать надо const string&.

    2) если уж принимаем что-то строкоподобное, то смело пользуемся функцией-членом ::find, которая есть и в строке и в вью. С её помощью можно найти сразу позицию арифметического символа. Для простого случая, даже не нужен обход от найденной позиции к началу строки и к концу строки для проверок std::isspace, является ли символ пробелом, поскольку мы можем сразу слать в atoi для левого числа например string_view::substr(0, opPos - 1). И, получится, только один if/switch, который будет сопоставлять символ операции, например, с указателем на функцию.
    ...
    int plus(int left, int right) { return left + right; }
    
    using CalculatorFunction = int (*)(int, int);
    ...
    CalculatorFunction operation;
    ...
    switch (opChar):
    case '+': operation = plus;
    ...


    3) настоятельно рекомендую после этого реализовать нормальный простой калькулятор, а потом третьим заходом добавить поддержку скобок, hex oct бинарную и с мантиссой нотации записи чисел, операцию степени и корня.
    Нормальный - имеется ввиду, что в калькулятор входит строка, которая является корректным арифметическим выражением. В этом случае нужно ещё иметь дело с разделителями. По-хорошему, решение состоит из нескольких этапов:

    - на первом логическом этапе функция парсит выражение и сохраняет в контексте или возвращает контейнер с токенами, (или прерывает весь процесс из-за плохой строки). В вашем варианте, задача однопоточная, поэтому можно воспользоваться функцией strtok, чтобы удобно извлечь строковое значение токена по входной строке от разделителя до разделителя.

    - как вариант, токены могут быть структурами с членом string_view на строковые представления и членом на тип токена (число, операция).

    - на втором логическом этапе мы считываем последовательность токенов и делаем какое-то действие:
    • считали число - ожидаем за ним токен операции
    • если оказался не токен операции - бросаем исключение о некорректном арифметическом выражении, иначе - подготавливаем аргументы и кормим функцию evaluate<T>( Token::kOperation op, T arg1, T arg2) , для случая, если операции нужно два аргумента, то первый уже считан, а второй будет в последовательности токенов за операцией. Результат evalaute записываем в переменную результата, которая, кстати говоря, типа double.
    • продолжаем движение по последовательности токенов до конца, контейнером может быть std::list, когда добрались до конца, возвращаем результат.
    Ответ написан
    Комментировать
  • Почему не вызывается конструктор копирования?

    hePPer
    @hePPer
    похоже на пропуск конструктора при оптимизации
    Copy elision
    Ответ написан
    4 комментария
  • Как изменять что-любо в программных проектах?

    saboteur_kiev
    @saboteur_kiev Куратор тега Программирование
    software engineer
    Большинство книг, которые выпущены вчера, базируются на знаниях, которые кто-то получал в 2004, поэтому даже в старых книгах всегда полно полезной информации.

    Что же касается изменений в проекте - так если вы умеете программировать - вы должны знать что менять. Если не умеете - не меняйте.

    Или уточните вопрос.
    Ответ написан
    Комментировать
  • Как вывести несколько MessageBox на C++?

    jcmvbkbc
    @jcmvbkbc
    http://dilbert.com/strip/1998-08-24
    MessageBox -- модальный. Невозможно одновременно вывести несколько MessageBox из одного потока. Можно создать несколько потоков и в каждом из них вызвать MessageBox.
    Ответ написан
    Комментировать
  • Насколько перспективна смена профессии на разработчика С++?

    tsarevfs
    @tsarevfs
    C++ developer
    Если у человека хорошая математика, физика то учить C++ достаточно перспективно. Сложный код про моделирование физических систем, робототехнику часто пишут на C++. Знание языка и программирования в таких задачах бывает вторично.
    Писать сайты, особенно фронт с таким бекграундом может быстро стать достаточно скучно.
    Можно посмотреть на Python с ML. Тоже очень близко по интересам, а спрос огромный.
    Ответ написан
    Комментировать
  • Как называются разработчики, которые пишут не качественный код, но делают продукты?

    gbg
    @gbg
    Любые ответы на любые вопросы
    Быдлокодер. Индусокодер. За примерами - govnokod.ru

    Проблема плохо написанного кода главным образом в том, что он удорожает дальнейшую разработку и поиск ошибок экспоненциально.

    В какой-то момент, разработка буквально "встает" - перестают выпускаться новые фичи, а разработчики всем колхозом рефакторят на протяжении 6-8 месяцев (менеджмент при этом бегает и орет. Впрочем, они всегда бегают и орут).

    Даже квалифицированные разработчики штампуют помойку, когда решают новую для них задачу или применяют новые для них инструменты. У хороших разработчиков при этом растет бэклог и ЧСВ, у плохих - только ЧСВ.

    Хороший код до выхода на рынок должен быть переписан хотя бы дважды, пройдя experimental -> staging -> release, а не вот это вот все.

    Говнокод же получается от работы в режме expelsease (фигак-фигак - и в продакшн).
    Ответ написан
    2 комментария
  • Как перестать говнокодить и принимать неверные архитектурные решения?

    miraage
    @miraage
    Старый прогер
    как писать поддерживаемый код?

    Если уж очень коротко, то соблюдать SOLID/GRASP. Мне понравился твит одного из авторов React Router:
    https://twitter.com/mjackson/status/1171524189850701825

    Most common mistake software developers make: putting stuff in the wrong place. Coupling responsibilities and concepts that should be kept separate.
    For me, this is 95% of software development. Just figuring out *where* things belong.


    Что гуглить, что учить?

    Фундаментальные знания, вроде вышеупомянутых SOLID/GRASP, паттерны (не только классические паттерны, но и вообще, общеизвестные решения определённых задач), базовые структуры данных. Фреймворки/библиотеки всегда будут приходить/уходить, что-то будет забываться. А фундаментальные знания всегда актуальны.

    Может литературу какую почитать посоветуете?

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

    Можно ли себя называть миддлом, если твой код говно?

    Не пытайтесь себя оценить. В каждой компании свои понятия миддла. А если кто-то 35 лет на лиспе кодил, а потом прыгнет на Angular - кто он, джун или сеньор?
    И, да, все мы в какой-то степени пишем говнокод. Если кто-то Вам доказывает, что он пишет супер чистый код - не слушайте.

    И ответ на главный вопрос.
    Как перестать говнокодить и принимать неверные архитектурные решения?

    Это невозможно. Все проекты, которые чуток сложнее CRUD-ов, рано или поздно обрастают говнокодом. Никто не пишет идеальный код. Код должен работать и решать проблемы бизнеса.
    Ответ написан
    5 комментариев
  • Почему константы должны быть static?

    jcmvbkbc
    @jcmvbkbc
    http://dilbert.com/strip/1998-08-24
    Но ведь static StorageClass для другого

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

    samodum
    @samodum
    Какой вопрос - такой и ответ
    Фотон движется со скоростью света и времени для него нет, не существует. Все процессы, которые могли бы происходить внутри фотона, остановлены. Даже для фотона, родившегося 13 миллиардов лет назад, времени не существовало. Он родился давно и умер - для него это мгновение, хотя и прошли миллиарды лет. Он ничего не видел.
    Так же и мы будем как фотон - для нас время остановится, мы просто замрём на месте, видеть ничего не будем и делать ничего не сможем.
    Ответ написан
    3 комментария
  • Как вы проектируете классы в ООП и их взаимодействие?

    @xfg
    В PHP сообществе вообще не развиты вопросы проектирования и архитектуры. Большинство лепит, что придумает. PHP изначально родился для примитивных homepage, вобрал в себя всю несерьезность, низкий порог входа и как следствие довольно слабое комьюнити, что часто становится объектом для шуток.

    Искать ответы на вопросы проектирования и архитектуры нужно в Java. Например там почти каждый с самых азов слышал о де факто ставшей стандартом слоистой архитектуре, она же layered architecture, она же n-tier architecture и видел изображение похожее на это
    main-qimg-91d7188a63a833488f92239028d0ae
    Из которой нужно понять, что всю программу можно разделить на несколько слоев и зависимость между слоями должна быть направлена сверху вниз, но не наоборот. Таким образом, например фреймворк может быть инкапсулирован в presentation слой и в любой момент безболезненно заменен на другой, так как другие слои ничего о нем не знают. Вся бизнес-логика инкапсулирована в domain слой в виде plain old java object, который не зависит вообще не от чего, а также предоставляет интерфейсы (репозиториев например) для инфраструктурного слоя и только в этом слое фактически и будет тот самый настоящий ООП, который все так упорно пытаются найти. Никакого стороннего кода в бизнес-логике нет, а соответственно весь сторонний код можно в любой момент поменять, не трогая бизнес-логику вообще.

    Для этого нужно открыть какую-нибудь книгу, где за руку проведут с нуля до конкретного приложения построенного с использованием этой архитектуры. Например Implementing domain-driven design, хоть эта книга и о DDD, но я уже говорил, что слоистая архитектура это де факто. С опытом, будет понятно, что в более простых приложениях количество слоев можно уменьшить, понимая на какой компромисс придется пойти, что иногда можно объединить domain и часть infrastructure и получить всем известный шаблон Active Record или вообще выбросить эти слои и получить шаблон transaction script, когда для решения просто не требуется что-то более сложное. Придет понимание, как можно начать с transaction script и в итоге постепенно катиться в сторону domain layer, через active record или не через active record если это когда-нибудь понадобится и тому подобное. Cтанет понятно, зачем, как и когда использовать патерны о которых написал Мартин Фаулер в своей книге Patterns of Enterprise Application Architecture.

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

    vt4a2h
    @vt4a2h Куратор тега C++
    Senior software engineer (C++/Qt/boost)
    Вы или тег "C++" уберите, или C++ используйте, а не C.
    using Map = std::vector<std::vector<char>>;
    Map map = {
       {1, 1, 1},
       {0, 1, 0},
       {0, 1, 0},
    };
    
    // ...
    void scaleMap(Map &map, int coefficient)
    {
       // ...
       Map newMap(newSize, std::vector<char>(newSize));
       // ...
       map.swap(newMap);
    }

    Всё просто, быстро и безопасно. Главное за границы массива не выходите.
    Ответ написан
    Комментировать
  • Как сервер находит запрашиваемый файл?

    @EvgeniiR
    https://github.com/EvgeniiR
    При запросе, например html файла, сервер или ОС ищет файл циклом

    Зачем, если есть путь?

    С массивами та же история? Например чтобы добраться до 11 элемента, просматриваются все предыдущие?

    В связном списке - да, перебором. В хэш-таблице обычно без перебора, т.е. за O(1) в среднем (если нет коллизий). Зависит от алгоритма.
    Ответ написан
    1 комментарий