enum и наложение масок

      Комментарии к записи enum и наложение масок отключены

В этой теме 0 ответов, 1 участник, последнее обновление  Васильев Владимир Сергеевич 1 неделя, 5 дн. назад.

  • Автор
    Сообщения
  • #3899

    Достаточно часто встречается ситуация, когда в функцию надо передать дополнительные указания о том, как эта работа должна выполняться. Например, функция открытия файла из стандартной библиотеки (ifstream::open) принимает режим открытия файла:

    std::ifstream ifs;
    ifs.open("test.txt", std::ifstream::out | std::ifstream::trunc | std::ifstream::binary);
    

    В данном случает, с помощью комбинирования флажков мы требуем открыть бинарный файл для записи, при этом файл будет очищен.

    Возможны и другие ситуации, где оказывается удобным использование комбинаций таких флагов.

    В этом случае флаги представляют собой константы — степени двойки (1, 2, 4, 8, ...), В двоичном коде каждое такое число содержит единственную единицу, поэтому при выполнении операции логического ИЛИ с ними получится результат, в котором единицы будут установлены в позиции, соответствующие позициям единиц в операндах:

    Подобным образом можно наложить маску, выполнив операцию логического И. Далее показано как проверить что в числе, представленном переменной value установлены второй и третий разряды:

    bool flag = value & 6;
    // xxxx xxxx - value
    // 0000 0110 - 6

    Выполнение логических операций гораздо эффективнее, чем проверка режимов другими способами. Например, в старой части стандартной библиотеки <cstdio> есть функцию fopen, также предназначена для открытия файла:

    FILE *file;
    file = fopen ("myfile.txt","w+b");

    В данном случае файл открывается для добавления в конец в двоичном режиме. Причем допустим также вариант "wb+". Очевидно, проверка флагов, связанное со сравнением строк будет выполняться значительно дольше чем логическое И.

    Нашим константам (флагам) надо дать понятные имена (чтобы программисту не приходилось помнить кучу констант). При этом группа констант будет тесно связана, поэтому целесообразно использовать enum. В заметке «enum и константы» описано почему это хорошо, а в статье «Зачем нужен enum class» описана конструкция enum class введенная в стандарте С++11 и позволяющая без сооружения велосипеда задавать в перечислениях пространства имен.

    Итак, мы могли бы написать следующий код для задания состояния активности

    enum class SomeFlags {
      delay = 1,
      animate = 2, 
      random = 4
      // ...
    };

    Однако, заметку я начал писать, т.к. нашел более красивый вариант в коде нашего читателя — Daniil159x. Дальше опишу, предложенный им пример. Стояла задача генерации пароля, при этом функция генерации получает в виде комбинации флагов режим (наборы используемых символов). Флажки заданы следующим образом:

        enum flags {
            sym     = 1 << 1,
            num     = 1 << 2,
            alf_low = 1 << 3,
            alf_up  = 1 << 4
        };

    каждый элемент перечисления при этом представляет собой единицу, сдвинутую на заданное число разрядов. Вычисление значений элементов, естественно, выполняется на этапе компиляции, поэтому это эффективно.

    Дальше выкладываю исходник целиком — он мне понравился, но в рамках этой темы стоит заглянуть в функции generate, где выполняется наложение масок:

    #include <iostream>
    #include <vector>
    #include <string>
     
    #include <random>
    #include <chrono>
     
    #include <fstream>
     
    class Password {
    public:
     
        Password() : m_gen(std::chrono::steady_clock::now().time_since_epoch().count()) {}
     
        enum flags {
            sym     = 1 << 1,
            num     = 1 << 2,
            alf_low = 1 << 3,
            alf_up  = 1 << 4
        };
     
     
        std::string generate(size_t len, int args = sym | num | alf_low | alf_up) {
     
            std::vector<std::uniform_int_distribution<>> dist;
     
            if(args & sym){
                dist.push_back(std::uniform_int_distribution<>{'!', '/'});
            }
     
            if(args & num){
                dist.push_back(std::uniform_int_distribution<>{'0', '9'});
            }
     
            if(args & alf_low){
                dist.push_back(std::uniform_int_distribution<>{'a', 'z'});
            }
     
            if(args & alf_up){
                dist.push_back(std::uniform_int_distribution<>{'A', 'Z'});
            }
     
            std::string res;
            res.resize(len);
     
            for(auto &i : res){
                i = dist[ this->index(dist.size()) ](m_gen);
            }
     
            return res;
        }
     
    private:
        size_t index(size_t size) {
            return std::uniform_int_distribution<size_t>(0, size-1)(m_gen);
        }
     
        std::mt19937 m_gen; // стандартное семя, его нужно менять
     
    };
     
    int main() {
     
        std::ofstream fout("passwords.txt");
     
        Password pass;
     
        int get = 1;
        for(int q = 0; q <= 1E7; ++q){
            fout << q << ": " << pass.generate(20) << std::endl;
            if(q == 10*get){
                get *= 10;
                std::cout << q << std::endl;
            }
        }
     
        return 0;
    }

    Пароль должен генерироваться случайным образом, поэтому должен использоваться генератор случайных чисел (ГСЧ) — если в <cstdlib> для этого использовалась функция rand(), то в современном С++ используется объект генератора (std::mt19937). Генератор, на самом деле, выдает не случайные числа, но числа распределены определенным образом (обычно равномерно), каждое следующее число вычисляется однозначным образом из предыдущего. Чтобы при каждом следующем запуске программы (или вызове функции) числа были новыми ГСЧ задают первое число (называемое затравкой, seed), зависящее от текущего момента времени. В <cstdlib> это выполнялось с помощью функции srand(), однако сейчас соответствующий параметр передается конструктору генератора. ГСЧ неразрывно связан с генератором паролей, время жизни этих объектов совпадают, поэтому объект ГСЧ вложен в объект генератора паролей и инициализируется в конструкторе. При инициализации используется библиотека <chrono> — более хороший и современный аналог библиотеки <time.h>, поверхностно почитать про него можно в с статье «Работа с датой и временем на С++«.

    В функции генерации на аргумент, задающий режим, накладывается ряд флагов. Если в режиме был установлен бит, соответствующий маске, то в вектор добавляет тот или иной диапазон генерирумых символов. Диапазоны представляются с помощью стандартного класса uniform_int_distribution из модуля <random>, например:
    auto distance = std::uniform_int_distribution<>{'0', '9'}
    задает равномерное распределение чисел от 0 до 9. Для генерации очередного числа достаточно выполнить distance(m_gen), где m_gen — объект ГСЧ.

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

Для ответа в этой теме необходимо авторизоваться.