Кин, не дожидаясь ввода?

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

Например:
while (true)
{
    if (userInput == true)
    {
        cin >> input
    }
    //DO SOMETHING
}

Это будет означать, что //DO SOMETHING будет происходить каждый цикл, без нажатия пользователем клавиши ввода миллион раз.

Раньше моим решением было создание собственного ввода с использованием kbhit() а также getch() из conio.h, но это очень запутанно, и я не люблю использовать conio.h по причинам переносимости и т. д. Кроме того, ему не нужно использовать cin в частности, потому что есть большая вероятность, что он просто не будет работать с ним, поэтому любое хорошее решение, которое не требует от меня внесения собственного вклада с "не очень хорошей" библиотекой, будет высоко цениться.

4 ответа

Решение

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

Если вы не хотите полагаться на сторонние библиотеки управления событиями, и если многопоточность будет излишней, одной из альтернатив может быть ваша собственная версия kbhit()с условной компиляцией для сред, которые вы хотите поддерживать:

  • если ваш conio.h поддерживает kbhit(), просто используйте его.
  • для окон вы можете обратиться к _kbhit()
  • для linux и posix вы можете использовать ответ Матье или посмотреть здесь код Моргана Мэтьюса

Это не самый академический ответ, но он прагматичный.

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

#include <atomic>
#include <chrono>
#include <iostream>
#include <thread>

int main() {
  std::atomic<bool> interrupted;
  int x;
  int i = 0;

  do {
    interrupted.store(false);

    // create a new thread that does stuff in the background
    std::thread th([&]() {
        while(!interrupted) {
          // do stuff. Just as an example:
          std::cout << i << std::flush;
          std::this_thread::sleep_for(std::chrono::milliseconds(100));
        }
      });

    std::cin >> x;

    // when input is complete, interrupt thread and wait for it to finish
    interrupted.store(true);
    th.join();

    // apply x, then loop to make new thread to do stuff
    i = x;
  } while(x != -1); // or some other exit condition
}

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

Отказ от ответственности: похоже, что следующее работает с gcc в Linux, однако по некоторым причинам он не работает с VC++ в Windows. Спецификации, кажется, дают много возможностей для реализации здесь, и VC++ определенно использует это в своих интересах...

Есть несколько функций, доступных на любой std::basic_istream или его основа std::basic_streambuf,

Чтобы узнать, есть ли какой-либо символ, доступный для ввода, вы можете позвонить in_avail на std::basic_streambuf:

if (std::cin.rdbuf() and std::cin.rdbuf()->in_avail() >= 0) {
}

in_avail дает количество символов, доступных без блокировки, он возвращает -1 если нет такого персонажа. После этого вы можете использовать обычные "отформатированные" операции чтения, такие как std::cin >> input,

В противном случае, для неформатированных чтений, вы можете использовать readsome от std::basic_istream, который возвращает до N символов, доступных без блокировки:

size_t const BufferSize = 512;
char buffer[BufferSize];

if (std::cin.readsome(buffer, BufferSize) >= 1) {
}

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

Примечание: как уже упоминалось в комментарии, in_avail подход может быть пятнистым. Я подтверждаю, что это может работать, однако сначала вы должны использовать скрытую функцию потоков ввода-вывода C++: std::ios_base::sync_with_stdio(false) который позволяет потокам C++ буферизовать ввод (и таким образом украсть его из буферов stdio C).

Может быть, вы можете попробовать:

      while (true)
{
    if (userInput == true)
    {
        if(cin >> input){
        }else{
            std::cout << "Invalid input!" << std::endl;
            cin.clear();
        }
    }
    //DO SOMETHING
}
Другие вопросы по тегам