簡體   English   中英

在 C++ 中使用 cin 進行輸入驗證的最佳方法是什么?

[英]What is the best way to do input validation in C++ with cin?

我哥哥最近開始學習 C++。 他告訴我他在嘗試驗證一個簡單程序中的輸入時遇到的一個問題。 他有一個文本菜單,用戶在其中輸入了一個整數choice ,如果他們輸入了一個無效的選項,他們將被要求再次輸入(執行 while 循環)。 但是,如果用戶輸入的是字符串而不是整數,代碼就會中斷。 我閱讀了有關 stackoverflow 的各種問題,並告訴他按照以下方式重寫他的代碼:

#include<iostream>
using namespace std;

int main()
{
    int a;
    do
    {
    cout<<"\nEnter a number:"
    cin>>a;
        if(cin.fail())
        {
            //Clear the fail state.
            cin.clear();
            //Ignore the rest of the wrong user input, till the end of the line.
            cin.ignore(std::numeric_limits<std::streamsize>::max(),\
                                                    '\n');
        }
    }while(true);
    return 0;
}

雖然這行得通,但我還嘗試了其他一些想法:
1. 使用 try catch 塊。 它沒有用。 我認為這是因為輸入錯誤不會引發異常。 2. 我試過if(! cin){//Do Something}也沒有用。 我還沒有想通這一點。
3.第三,我嘗試輸入一個固定長度的字符串,然后解析它。 我會使用atoi()。 該標准是否符合標准和便攜性? 我應該編寫自己的解析函數嗎?
4.如果寫一個使用cin的類,但是動態的做這種錯誤檢測,也許是在運行時判斷輸入變量的類型,會不會有太多的開銷? 甚至有可能嗎?

我想知道進行此類檢查的最佳方法是什么,最佳做法是什么?

我想補充一點,雖然我對編寫 C++ 代碼並不陌生,但我對編寫符合標准的良好代碼並不陌生。 我正在努力摒棄不良做法並學習正確的做法。 如果回答者給出詳細的解釋,我將非常感激。

編輯:我看到 litb 已經回答了我之前的編輯之一。 我將在此處發布該代碼以供參考。

#include<iostream>
using namespace std;

int main()
{
    int a;
    bool inputCompletionFlag = true;
    do
    {
    cout<<"\nEnter a number:"
    cin>>a;
        if(cin.fail())
        {
            //Clear the fail state.
            cin.clear();
            //Ignore the rest of the wrong user input, till the end of the line.
            cin.ignore(std::numeric_limits<std::streamsize>::max(),\
                                                    '\n');
        }
        else
        {
            inputCompletionFlag = false;
        }
    }while(!inputCompletionFlag);
    return 0;
}

此代碼在輸入(如“1asdsdf”)時失敗。 我不知道如何修復它,但 litb 發布了一個很好的答案。 :)

這是您可以使用的代碼,以確保您也拒絕諸如

42crap

數字后面是非數字字符。 如果您閱讀整行,然后解析它並適當地執行操作,則可能需要您更改程序的工作方式。 如果您的程序到現在為止從不同的地方讀取您的號碼,那么您必須放置一個中央位置來解析一行輸入並決定操作。 但也許這是一件好事-這樣你就可以增加代碼的可讀性,通過把東西一路之隔:NPUT - P rocessing -頻

無論如何,這是您如何拒絕上述數字非數字的方法。 將一行讀入一個字符串,然后用stringstream解析它:

std::string getline() {
  std::string str;
  std::getline(std::cin, str);
  return str;
}

int choice;
std::istringstream iss(getline());
iss >> choice >> std::ws;
if(iss.fail() || !iss.eof()) {
  // handle failure
}

它吃掉所有尾隨空格。 當它在讀取整數或尾隨空格時遇到字符串流的文件末尾時,它會設置 eof 位,我們會檢查它。 如果它首先未能讀取任何整數,則將設置失敗或壞位。

這個答案的早期版本直接使用了std::cin - 但是std::ws不能與連接到終端的std::cin一起工作(它會阻塞而不是等待用戶輸入一些東西),所以我們使用stringstream用於讀取整數。


回答你的一些問題:

問題: 1. 使用 try catch 塊。 它沒有用。 我認為這是因為輸入錯誤不會引發異常。

答:嗯,當你讀到一些東西時,你可以告訴流拋出異常。 你使用istream::exceptions函數,你可以告訴你想要拋出異常的錯誤類型:

iss.exceptions(ios_base::failbit);

我從來沒有用過。 如果你在std::cin上這樣做,你將不得不記住為依賴它的其他讀者恢復標志而不是拋出。 發現只使用函數fail更容易,詢問流的狀態不好

問題: 2. 我試過if(!cin){ //Do Something }也沒有用。 我還沒有想通這一點。

回答:這可能是因為你給了它類似“42crap”的東西。 對於流,當提取到整數時,這是完全有效的輸入。

問題: 3. 第三,我嘗試輸入一個固定長度的字符串,然后解析它。 我會使用atoi()。 該標准是否符合標准和便攜性? 我應該編寫自己的解析函數嗎?

答: atoi 符合標准。 但是當你想檢查錯誤時,這並不好。 與其他功能相反,它沒有錯誤檢查。 如果你有一個字符串並想檢查它是否包含一個數字,那么就像上面的初始代碼一樣。

有一些類似 C 的函數可以直接從 C 字符串中讀取。 它們的存在是為了允許與舊的遺留代碼進行交互並編寫快速執行的代碼。 人們應該在程序中避免使用它們,因為它們的工作級別相當低,並且需要使用原始裸指針。 就其本質而言,它們也無法增強以使用用戶定義的類型。 具體來說,這討論了函數“strtol”(字符串到長),它基本上是具有錯誤檢查和與其他基數(例如十六進制)一起工作的能力的 atoi。

問題: 4.如果我寫了一個使用cin的類,但是動態的做這種錯誤檢測,也許是通過在運行時判斷輸入變量的類型,會不會有太多的開銷? 甚至有可能嗎?

答:一般來說,你不需要太在意這里的開銷(如果你的意思是運行時開銷)。 但這具體取決於您在哪里使用該類。 如果您正在編寫一個處理輸入並需要高吞吐量的高性能系統,那么這個問題將非常重要。 但是,如果您需要從終端或文件讀取輸入,您已經看到了這歸結為:等待用戶輸入內容確實需要很長時間,此時您不再需要在此時刻關注運行時成本規模。

如果您的意思是代碼開銷 - 那么這取決於代碼的實現方式。 您需要掃描您讀取的字符串 - 是否包含數字,是否為任意字符串。 根據您要掃描的內容(也許您有一個“日期”輸入格式,或者也有一個“時間”輸入格式。查看boost.date_time ),您的代碼可以變得任意復雜。 對於像對數字進行分類這樣的簡單事情,我認為您可以使用少量代碼。

這就是我用 C 所做的,但它可能也適用於 C++。

將所有內容作為字符串輸入。

然后,只有這樣,才能將字符串解析為您需要的內容。 有時,編寫自己的代碼比試圖將別人的代碼屈從於您的意願更好。

我會做兩件事:首先,嘗試驗證輸入,並使用正則表達式提取數據,如果輸入有點不重要。 即使輸入只是一系列數字,它也非常有用。

然后,我喜歡使用boost::lexical_ cast ,如果輸入無法轉換,它會引發 bad_lexical_ cast 異常。

在你的例子中:

std::string in_str;
cin >> in_str;

// optionally, test if it conforms to a regular expression, in case the input is complex

// Convert to int? this will throw bad_lexical_cast if cannot be converted.
int my_int = boost::lexical_cast<int>(in_str);

忘記在實際代碼中直接使用格式化輸入(>> 運算符)。 您將始終需要使用 std::getline 或類似方法讀取原始文本,然后使用您自己的輸入解析例程(可能使用 >> 運算符)來解析輸入。

各種方法的組合如何:

  1. 使用std::getline(std::cin, strObj)std::cin獲取輸入std::getline(std::cin, strObj)其中strObjstd::string對象。

  2. 使用boost::lexical_cast執行從strObj到最大寬度的有符號或無符號整數的詞法轉換(例如, unsigned long long或類似的東西)

  3. 使用boost::numeric_cast將整數向下轉換到預期范圍。

您可以使用std::getline獲取輸入,然后根據您想要捕獲錯誤的位置,將boost::lexical_cast調用為適當的窄整數類型。 三步法的好處是接受任何整數數據,然后分別捕獲縮小錯誤。

我同意 Pax,最簡單的方法是將所有內容作為字符串讀取,然后使用 TryParse 驗證輸入。 如果格式正確,則繼續,否則只需通知用戶並在循環中使用 continue。

尚未提及的一件事是,在使用據稱從流中獲取某些內容的變量之前,測試以查看 cin >> 操作是否有效通常很重要。

此示例與您的示例相似,但進行了該測試。

#include <iostream>
#include <limits>
using namespace std;
int main()
{
   while (true)
   {
      cout << "Enter a number: " << flush;
      int n;
      if (cin >> n)
      {
         // do something with n
         cout << "Got " << n << endl;
      }
      else
      {
         cout << "Error! Ignoring..." << endl;
         cin.clear();
         cin.ignore(numeric_limits<streamsize>::max(), '\n');
      }
   }
   return 0;
}

這將使用通常的運算符 >> 語義; 它將首先跳過空格,然后嘗試讀取盡可能多的數字,然后停止。 所以“42crap”會給你42然后跳過“crap”。 如果這不是您想要的,那么我同意之前的答案,您應該將其讀入一個字符串然后進行驗證(也許使用正則表達式 - 但對於簡單的數字序列來說這可能是過度的)。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM