C++基礎教程面向對象(學習筆記(105))

流狀態和輸入驗證

流狀態

ios_base類包含幾個狀態標誌,用於指示使用流時可能出現的各種情況:

Flag 含義
goodbit 切正常
badbit 發生了某種致命錯誤(例如程序試圖讀取文件末尾)
eofbit 流已到達文件的末尾
failbit 發生了非致命錯誤(例如,當程序期望整數時用戶輸入了字母)

雖然這些標誌位於ios_base中,因爲ios派生自ios_base,ios輸入的輸入比ios_base少,但它們通常通過ios訪問(例如,作爲std :: ios :: failbit)。

ios還提供了許多成員函數,以便於訪問這些狀態:

成員函數 含義
good() 如果設置了goodbit,則返回true(流是正常的)
bad() 如果設置了badbit,則返回true(發生致命錯誤)
eof() 如果設置了eofbit,則返回true(流位於文件的末尾)
fail() 如果設置了failbit,則返回true(發生非致命錯誤)
clear() 清除所有標誌並將流恢復到goodbit狀態
clear(state) 清除所有標誌並設置傳入的狀態標誌
rdstate() 返回當前設置的標誌
setstate(state) 設置傳入的狀態標誌

最常處理的是failbit,它是在用戶輸入無效輸入時設置的。例如,請考慮以下程序:

cout << "Enter your age: ";
int nAge;
cin >> nAge;

請注意,此程序期望用戶輸入整數。但是,如果用戶輸入非數字數據,例如“Alex”,則cin將無法向nAge提取任何內容,並且將設置failbit。

如果發生錯誤並且將流設置爲除goodbit之外的任何其他內容,則將忽略該流上的進一步流操作。可以通過調用clear()函數清除此條件。

輸入驗證

輸入驗證是檢查用戶輸入是否滿足某些條件的過程。輸入驗證通常可以分爲兩種類型:字符串和數字。

使用字符串驗證,我們接受所有用戶輸入作爲字符串,然後接受或拒絕該字符串,具體取決於它是否格式正確。例如,如果我們要求用戶輸入電話號碼,我們可能希望確保他們輸入的數據有十位數。在大多數語言中(特別是像Perl和PHP這樣的腳本語言),這是通過正則表達式完成的。但是,C ++沒有內置的正則表達式支持(它應該與C ++的下一版本一起提供),所以通常通過檢查字符串的每個字符來確保它符合某些條件。

通過數值驗證,我們通常關注確保用戶輸入的數字在特定範圍內(例如,在0到20之間)。但是,與字符串驗證不同,用戶可以輸入完全不是數字的東西,我們也需要處理這些情況。

爲了幫助我們,C ++提供了許多有用的功能,我們可以用它來確定特定字符是數字還是字母。以下函數存在於cctype頭中:

函數 含義
isalnum(int) 如果參數是字母或數字,則返回非零值
isalpha(int) 如果參數是字母,則返回非零值
iscntrl(int) 如果參數是控制字符,則返回非零值
isdigit(int) 如果參數是數字,則返回非零
isgraph(int) 如果參數是非空白的可打印字符,則返回非零值
isprint(int) 如果參數是可打印字符(包括空格),則返回非零值
ispunct(int) 如果參數既不是字母數字也不是空格,則返回非零值
isspace(int) 如果參數是空格,則返回非零值
isxdigit(int) 如果參數是十六進制數字(0-9,af,AF),則返回非零值

字符串驗證

讓我們通過要求用戶輸入他們的名字來做一個簡單的字符串驗證。我們的驗證標準是用戶只輸入字母字符或空格。如果遇到任何其他問題,輸入將被拒絕。

當涉及可變長度輸入時,驗證字符串的最佳方法(除了使用正則表達式庫之外)是逐步遍歷字符串的每個字符並確保它符合驗證標準。這正是我們在這裏要做的。

#include <cctype>
#include <string>  
#include <iostream>  
using namespace std;
 
while (1)
{
    // 獲取用戶名
    cout << "Enter your name: ";
    string strName;
    getline(cin, strName); // 得到整行,包括空格
 
    bool bRejected=false; // strName被拒絕了嗎?
 
    // 逐步瀏覽字符串中的每個字符,直到我們點擊
    // 字符串的結尾,或者我們拒絕了一個字符
    for (unsigned int nIndex=0; nIndex < strName.length() && !bRejected; nIndex++)
    {
        // 如果當前字符是字母字符,那很好
        if (isalpha(strName[nIndex]))
            continue;
 
        // 如果它是一個空間,那也沒關係
        if (strName[nIndex]==' ')
            continue;
 
        // O否則我們拒絕這個輸入
        bRejected = true;
    }
 
    // 如果已接受輸入,則退出while循環
    // 否則我們繼續循環
    if (!bRejected)
        break;
}

請注意,這段代碼並不完美:用戶可以說他們的名字是“asf w jweo s di we ao”或者其他一些亂碼,或者更糟糕的是,只是一堆空格。我們可以通過改進我們的驗證標準來僅接受包含至少一個字符和至多一個空格的字符串來解決這個問題。

現在讓我們看看另一個例子,我們將要求用戶輸入他們的電話號碼。與用戶的名稱(可變長度,並且每個字符的驗證標準相同)不同,電話號碼是固定長度,但驗證標準根據角色的位置而不同。因此,我們將採用不同的方法來驗證我們的電話號碼輸入。在這種情況下,我們將編寫一個函數,它將根據預定的模板檢查用戶的輸入,以查看它是否匹配。該模板將按如下方式工作:

A #將匹配用戶輸入中的任何數字。
A @將匹配用戶輸入中的任何字母字符。
A _將匹配任何空格。
A ?會匹配任何東西。
否則,用戶輸入中的字符和模板必須完全匹配。

因此,如果我們要求函數匹配模板“(###)### - ####”,這意味着我們希望用戶輸入’(‘character,three numbers,a’)'字符,一個空格,三個數字,一個短劃線和另外四個數字。如果這些事情中的任何一個不匹配,則輸入將被拒絕。

這是代碼:

bool InputMatches(string strUserInput, string strTemplate)
{
    if (strTemplate.length() != strUserInput.length())
        return false;
 
    // 逐步執行用戶輸入以查看是否匹配
    for (unsigned int nIndex=0; nIndex < strTemplate.length(); nIndex++)
    {
        switch (strTemplate[nIndex])
        {
            case '#': //數字匹配
                if (!isdigit(strUserInput[nIndex]))
                    return false;
                break;
            case '_': // 空白匹配
                if (!isspace(strUserInput[nIndex]))
                    return false;
                break;
            case '@': // 字幕匹配
                if (!isalpha(strUserInput[nIndex]))
                    return false;
                break;
            case '?': // anything匹配
                break;
            default: //準確的字符匹配
                if (strUserInput[nIndex] != strTemplate[nIndex])
                    return false;
        }
    }
 
    return true;
}
 
int main()
{
    string strValue;
 
    while (1)
    {
        cout << "Enter a phone number (###) ###-####: ";
        getline(cin, strValue); // 獲得所有,包括空格
        if (InputMatches(strValue, "(###) ###-####"))
            break;
    }
 
    cout << "You entered: " << strValue << endl;
}

使用此函數,我們可以強制用戶完全匹配我們的特定格式。但是,此函數仍然受到幾個限制:如果是#,@,_和?在用戶輸入中是有效字符,此函數不起作用,因爲這些符號具有特殊含義。此外,與正則表達式不同,沒有模板符號表示“可以輸入可變數量的字符”。因此,這樣的模板不能用於確保用戶輸入由空格分隔的兩個單詞,因爲它不能處理單詞具有可變長度的事實。對於這些問題,非模板方法通常更合適。

數字驗證

處理數字輸入時,顯而易見的方法是使用提取運算符將輸入提取到數字類型。通過檢查failbit,我們可以判斷用戶是否輸入了數字。

讓我們嘗試這種方法:

int main()
{
    int nAge;
 
    while (1)
    {
        cout << "Enter your age: ";
        cin >> nAge;
 
        if (cin.fail()) // 沒有提取
        {
            cin.clear(); // 狀態位重置爲goodbit所以我們可以使用ignore()
            cin.ignore(32767, '\n'); // 清除流中的錯誤輸入
            continue; // t再試一次
        }
 
        if (nAge <= 0) // 保nAge是正數
            continue;
 
	break;
    }
 
    cout << "You entered: " << nAge << endl;
}

如果用戶輸入一個數字,cin.fail()將爲false,我們將點擊break語句,退出循環。如果用戶輸入以字母開頭的輸入,則cin.fail()將爲true,我們將進入條件。

然而,還有一個我們還沒有測試過的情況,那就是用戶輸入一個以數字開頭但後面包含字母的字符串(例如“34abcd56”)。在這種情況下,起始數字(34)將被提取到nAge中,字符串的其餘部分(“abcd56”)將保留在輸入流中,並且不會設置failbit。這會導致兩個潛在的問題:

1)如果您希望這是有效輸入,您現在在流中有垃圾。
2)如果您不希望這是有效輸入,則不會拒絕它(並且您的流中有垃圾)。

讓我們解決第一個問題。這很簡單:

int main()
{
    int nAge;
 
    while (1)
    {
        cout << "Enter your age: ";
        cin >> nAge;
 
        if (cin.fail()) //有提取
        {
            cin.clear(); // 將狀態位重置爲goodbit所以我們可以使用ignore()
            cin.ignore(32767, '\n'); // 清除流中的錯誤輸入
            continue; // try again
        }
 
        cin.ignore(32767, '\n'); // 清除流中的任何其他輸入
 
        if (nAge <= 0) // 確保nAge是正數
            continue;
 
	break;
    }
 
    cout << "You entered: " << nAge << endl;
}

如果您不希望這樣的輸入有效,我們將不得不做一些額外的工作。幸運的是,之前的解決方案讓我們走了一半。我們可以使用gcount()函數來確定忽略了多少個字符。如果我們的輸入有效,gcount()應該返回1(被丟棄的換行符)。如果返回的值超過1,則用戶輸入的內容未正確提取,我們應該要求他們輸入新內容。這是一個例子:

int main()
{
    int nAge;
 
    while (1)
    {
        cout << "Enter your age: ";
        cin >> nAge;
 
        if (cin.fail()) // 沒有提取
        {
            cin.clear(); // 將狀態位重置爲goodbit所以我們可以使用ignore()
            cin.ignore(32767, '\n'); // 清除流中的錯誤輸入
            continue; // try again
        }
 
        cin.ignore(32767, '\n'); // 除流中的任何其他輸入
        if (cin.gcount() > 1) // 如果我們清除了多個額外的character
            continue; // 我們會認爲此輸入無效
 
        if (nAge <= 0) // 確保nAge是正數
            continue;
 
	break;
    }
 
    cout << "You entered: " << nAge << endl;
}

數字驗證爲字符串

上面的例子只是爲了得到一個簡單的值而做了相當多的工作!處理數字輸入的另​​一種方法是將其作爲字符串讀取,將其作爲字符串處理,如果它通過驗證,則將其轉換爲數字類型。以下程序使用該方法:

int main()
{
	int nAge;
 
    while (1)
    {
        cout << "Enter your age: ";
        string strAge;
        cin >> strAge;
 
        // 檢查以確保每個字符都是數字
        bool bValid = true;
        for (unsigned int nIndex=0; nIndex < strAge.length(); nIndex++)
            if (!isdigit(strAge[nIndex]))
            {
                bValid = false;
                break;
            }
        if (!bValid)
            continue;
 
        //此時,我們可以將某些內容轉換爲數字
        // 所以我們將使用stringstream進行轉換
        stringstream strStream;
        strStream << strAge;
        strStream >> nAge;
 
        if (nAge <= 0) // 確保nAge是正數
            continue;
		
	break;
    }
 
    cout << "You entered: " << nAge << endl;
}

這種方法是否比直接數字提取更多或更少取決於您的驗證參數和限制。

如您所見,在C ++中進行輸入驗證是很多工作。幸運的是,許多此類任務(例如,將數字驗證作爲字符串)可以很容易地轉換爲可以在各種情況下重用的函數。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章