2009-02-13 70 views
7

Anh tôi gần đây đã bắt đầu học C++. Ông nói với tôi một vấn đề ông gặp phải trong khi cố gắng để xác nhận đầu vào trong một chương trình đơn giản. Anh ta có một menu văn bản nơi người dùng nhập một số nguyên choice, nếu họ đã nhập một lựa chọn không hợp lệ, họ sẽ được yêu cầu nhập lại nó (làm trong khi vòng lặp). Tuy nhiên, nếu người dùng nhập một chuỗi thay vì một int, mã sẽ phá vỡ. tôi đọc câu hỏi khác nhau trên stackoverflow và nói với ông phải viết lại mã của mình dọc theo dòng:Cách tốt nhất để thực hiện xác thực đầu vào trong C++ bằng cin là gì?

#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; 
} 

Trong khi điều này làm việc ok, tôi cũng thử một vài ý tưởng khác:
1. Sử dụng một khối try catch. Nó không hoạt động. Tôi nghĩ rằng điều này là bởi vì một ngoại lệ không được nâng lên do đầu vào xấu. 2. Tôi đã thử if(! cin){//Do Something} không hoạt động. Tôi chưa tìm ra cái này.
3. Thứ ba, tôi đã thử nhập một chuỗi có độ dài cố định và sau đó phân tích nó. Tôi sẽ sử dụng atoi(). Tiêu chuẩn này có tuân thủ và di động không? Tôi có nên viết chức năng phân tích cú pháp của riêng mình không?
4. Nếu viết một lớp sử dụng cin, nhưng tự động thực hiện loại phát hiện lỗi này, có lẽ bằng cách xác định loại biến đầu vào khi chạy, nó có quá nhiều chi phí không? Thậm chí có thể không?

Tôi muốn biết cách tốt nhất để thực hiện loại kiểm tra này, phương pháp hay nhất là gì?

Tôi muốn thêm rằng trong khi tôi không phải là người mới viết mã C++, tôi mới viết mã tuân thủ tiêu chuẩn tốt. Tôi đang cố gắng để không học những thực hành xấu và học những điều đúng. Tôi sẽ có nhiều nghĩa vụ nếu người trả lời đưa ra một lời giải thích chi tiết.

EDIT: Tôi thấy rằng litb đã trả lời một trong các chỉnh sửa trước đó của tôi. Tôi sẽ đăng mã đó ở đây để tham khảo.

#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; 
} 

Mã này không thành công như "1asdsdf". Tôi không biết làm thế nào để sửa chữa nó nhưng litb đã đăng một câu trả lời tuyệt vời. :)

Trả lời

14

Đây là mã bạn có thể sử dụng để chắc chắn rằng bạn cũng bác bỏ những thứ như

42crap 

đâu ký tự không phải số lượng theo số lượng. Nếu bạn đọc toàn bộ dòng và sau đó phân tích nó và thực hiện các hành động thích hợp, nó có thể sẽ yêu cầu bạn thay đổi cách chương trình của bạn hoạt động. Nếu chương trình của bạn đọc số của bạn từ những nơi khác nhau cho đến bây giờ, thì bạn phải đặt một vị trí trung tâm phân tích một dòng đầu vào và quyết định hành động. Nhưng có lẽ đó là một điều tốt quá - vì vậy bạn có thể làm tăng khả năng đọc của mã theo cách đó bởi có những thứ tách ra: tôi nput - P rocessing - O utput

Dù sao, đây là cách bạn có thể từ chối số không-số ở trên. Đọc một dòng vào một chuỗi, sau đó phân tích nó với một 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 
} 

Nó ăn tất cả các khoảng trắng đuôi. Khi nó chạm vào cuối của tập tin của chuỗi trong khi đọc số nguyên hoặc dấu cách khoảng trắng, sau đó nó đặt eof-bit và chúng tôi kiểm tra điều đó. Nếu nó không đọc được bất kỳ số nguyên nào ở vị trí đầu tiên, thì bit thất bại hoặc bit xấu sẽ được thiết lập.

phiên bản trước của câu trả lời này sử dụng std::cin trực tiếp - nhưng std::ws sẽ không làm việc tốt với nhau với std::cin kết nối với một thiết bị đầu cuối (nó sẽ chặn thay vì chờ đợi cho người dùng một cái gì đó đầu vào), vì vậy chúng tôi sử dụng một stringstream cho việc đọc các số nguyên .


trả lời một số câu hỏi của bạn:

Câu hỏi: 1. Sử dụng một khối try catch. Nó không hoạt động. Tôi nghĩ rằng điều này là bởi vì một ngoại lệ không được nâng lên do đầu vào xấu.

Trả lời: Vâng, bạn có thể yêu cầu luồng phát hành ngoại lệ khi bạn đọc nội dung nào đó. Bạn sử dụng istream::exceptions chức năng, mà bạn nói mà loại lỗi mà bạn muốn có một ngoại lệ ném:

iss.exceptions(ios_base::failbit); 

tôi đã không bao giờ sử dụng nó. Nếu bạn làm điều đó trên std::cin, bạn sẽ phải nhớ để khôi phục lại các cờ cho người đọc khác dựa vào nó không ném. Tìm cách dễ dàng hơn để chỉ sử dụng các chức năng không thành công, xấu để yêu cầu trạng thái của luồng.

Câu hỏi: 2. Tôi đã thử if(!cin){ //Do Something } không hoạt động. Tôi chưa tìm ra cái này.

Trả lời: Điều đó có thể đến từ thực tế là bạn đã cho nó một cái gì đó giống như "42crap". Đối với luồng, đó là đầu vào hoàn toàn hợp lệ khi thực hiện trích xuất thành một số nguyên.

Câu hỏi: 3. Thứ ba, tôi đã thử nhập chuỗi có độ dài cố định và sau đó phân tích cú pháp. Tôi sẽ sử dụng atoi(). Tiêu chuẩn này có tuân thủ và di động không? Tôi có nên viết chức năng phân tích cú pháp của riêng mình không?

Trả lời: atoi là Chuẩn tuân thủ. Nhưng nó không tốt khi bạn muốn kiểm tra lỗi. Không có kiểm tra lỗi, được thực hiện bởi nó như trái ngược với các chức năng khác. Nếu bạn có một chuỗi và muốn kiểm tra xem nó có chứa một số hay không, thì hãy làm như trong mã ban đầu ở trên.

Có các hàm giống như C có thể đọc trực tiếp từ chuỗi C. Chúng tồn tại để cho phép tương tác với mã cũ, cũ và viết mã hoạt động nhanh. Người ta nên tránh chúng trong các chương trình vì chúng hoạt động khá thấp và yêu cầu sử dụng con trỏ khỏa thân thô. Bởi bản chất của chúng, chúng không thể được nâng cao để làm việc với các kiểu do người dùng định nghĩa. Cụ thể, điều này nói về chức năng "strtol" (chuỗi-to-long) mà về cơ bản là atoi với kiểm tra lỗi và khả năng làm việc với các cơ sở khác (ví dụ hex).

Câu hỏi: 4. Nếu tôi viết một lớp sử dụng cin, nhưng tự động thực hiện loại phát hiện lỗi này, có lẽ bằng cách xác định loại biến đầu vào khi chạy, nó có quá nhiều chi phí không? Thậm chí có thể không?

Trả lời: Nói chung, bạn không cần phải quan tâm quá nhiều về chi phí ở đây (nếu bạn có nghĩa là thời gian chạy trên đầu). Nhưng nó phụ thuộc cụ thể vào nơi bạn sử dụng lớp đó. Câu hỏi đó sẽ rất quan trọng nếu bạn đang viết một hệ thống hiệu năng cao xử lý đầu vào và cần phải có cao trong suốt. Nhưng nếu bạn cần đọc đầu vào từ thiết bị đầu cuối hoặc tệp, bạn đã thấy điều này xảy ra: Chờ người dùng nhập nội dung nào đó quá lâu, bạn không cần phải xem chi phí thời gian chạy tại thời điểm này nữa tỉ lệ.

Nếu bạn có nghĩa là mã trên đầu - cũng phụ thuộc vào cách mã được triển khai. Bạn sẽ cần phải quét chuỗi của bạn mà bạn đọc - cho dù nó có chứa một số hay không, cho dù một số chuỗi tùy ý. Tùy thuộc vào những gì bạn muốn quét (có thể bạn có một "ngày" đầu vào, hoặc một "thời gian" định dạng đầu vào quá. Nhìn vào boost.date_time cho rằng), mã của bạn có thể trở thành tùy tiện phức tạp. Đối với những thứ đơn giản như phân loại giữa số hay không, tôi nghĩ bạn có thể lấy đi với một lượng nhỏ mã.

+0

Cảm ơn lời giải thích chi tiết, nhưng tôi đã tra cứu và thấy rằng Boost không phải là thư viện chuẩn. Trong ánh sáng đó, sẽ tốt hơn nếu tôi cuộn mã của riêng mình nếu tôi có thể hoặc tôi nên gắn bó với tăng cường? – batbrat

+0

Tôi chỉ nhận thấy rằng nó sẽ được chuẩn hóa ở mức độ lớn trong C++ 0x. Vì vậy, tôi sẽ sử dụng Boost. Tôi sẽ rất vui nếu bạn xác nhận. Cảm ơn. – batbrat

+0

vâng, tiếp theo C++ sẽ bao gồm một số thư viện được thiết kế sau khi tăng cường (shared_ptr, thread, system (error_code, ...), array, bind, function). nhưng không phải date_time. Tuy nhiên, khuyến khích được khuyến khích. nó tốt hơn là lăn của riêng bạn thực sự –

3

Điều tôi sẽ làm là gấp đôi: Trước tiên, hãy thử xác thực đầu vào và trích xuất dữ liệu, sử dụng cụm từ thông dụng, nếu đầu vào hơi phần nhỏ. Nó có thể rất hữu ích ngay cả khi đầu vào chỉ là một dãy số.

Sau đó, tôi thích sử dụng boost::lexical_ cast, có thể tăng ngoại lệ bad_ lexical_ nếu không thể chuyển đổi dữ liệu nhập.

Trong ví dụ của bạn:

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); 
12

Đây là những gì tôi làm với C nhưng đây có thể áp dụng cho C++ là tốt.

Nhập mọi thứ dưới dạng chuỗi.

Sau đó, và chỉ sau đó, phân tích cú pháp chuỗi thành những gì bạn cần. Đôi khi tốt hơn là viết mã của riêng bạn hơn là cố gắng uốn cong người khác theo ý muốn của bạn.

+0

Cảm ơn bạn đã trả lời Pax. Tôi đã nghĩ đến việc làm những gì bạn nói, nhưng tự hỏi liệu đó có phải là cách tiếp cận đúng hay không. Cảm ơn câu trả lời. – batbrat

2

Quên về việc sử dụng định dạng đầu vào (toán tử >>) trực tiếp trong mã thực. Bạn sẽ luôn luôn cần phải đọc văn bản thô với std :: getline hoặc tương tự và sau đó sử dụng các thủ tục phân tích cú pháp đầu vào của riêng bạn (có thể sử dụng toán tử >>) để phân tích đầu vào.

4
  • Để nhận được exceptions with iostreams, bạn cần đặt cờ ngoại lệ phù hợp cho luồng.
  • Và tôi sẽ sử dụng get_line để có được toàn bộ dòng đầu vào và sau đó xử lý nó cho phù hợp - sử dụng lexical_cast, biểu thức thông thường (ví dụ Boost Regex hoặc Boost Xpressive, phân tích nó với Boost Spirit, hoặc chỉ sử dụng một số loại logic thích hợp
+0

Cảm ơn câu trả lời. Tôi muốn biết nếu có một thư viện chuẩn tương đương với các phương án tăng cường được đề cập ở đây. – batbrat

2

Làm thế nào về một sự kết hợp của các phương pháp khác nhau:

  1. Snag đầu vào từ std::cin sử dụng std::getline(std::cin, strObj) nơi strObj là 0.123.đối tượng.

  2. Sử dụng boost::lexical_cast để thực hiện một bản dịch từ vựng từ strObj hoặc là một số nguyên ký kết hoặc unsigned chiều rộng lớn nhất (ví dụ, unsigned long long hoặc một cái gì đó tương tự)

  3. Sử dụng boost::numeric_cast để cast số nguyên xuống phạm vi dự kiến.

Bạn chỉ có thể lấy đầu vào với std::getline và sau đó gọi boost::lexical_cast để kiểu số nguyên một cách thích hợp hẹp cũng tùy thuộc vào nơi bạn muốn bắt lỗi. Cách tiếp cận ba bước có lợi ích của việc chấp nhận bất kỳ dữ liệu số nguyên nào và sau đó bắt các lỗi thu hẹp một cách riêng biệt.

1

Tôi đồng ý với Pax, cách đơn giản nhất để làm điều này là đọc mọi thứ dưới dạng chuỗi, sau đó sử dụng TryParse để xác minh đầu vào. Nếu nó ở định dạng đúng, sau đó tiếp tục, otherwhise chỉ thông báo cho người dùng và sử dụng tiếp tục trên vòng lặp.

+0

TryParse là thành ngữ cụ thể của .NET. C++ dựa trên chuẩn, di động không thể sử dụng nó. –

+0

Tôi đã tự hỏi về điều đó. cảm ơn vì đã làm rõ. Cảm ơn bạn đã trả lời Rekreativc – batbrat

1

Một điều chưa được đề cập đến là điều quan trọng là bạn phải kiểm tra xem hoạt động cin >> đã hoạt động chưa trước khi sử dụng biến được cho là có thứ gì đó từ luồng.

Ví dụ này tương tự như ví dụ của bạn, nhưng thực hiện thử nghiệm đó.

#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; 
} 

Điều này sẽ sử dụng toán tử thông thường >> ngữ nghĩa; nó sẽ bỏ qua khoảng trống đầu tiên, sau đó cố gắng đọc càng nhiều chữ số càng tốt và sau đó dừng lại. Vì vậy, "42crap" sẽ cung cấp cho bạn 42 sau đó bỏ qua "crap". Nếu đó không phải là những gì bạn muốn, thì tôi đồng ý với các câu trả lời trước, bạn nên đọc nó thành chuỗi và sau đó xác nhận nó (có thể sử dụng cụm từ thông dụng - nhưng có thể quá mức cho một chuỗi số đơn giản).

+0

Đó là những gì tôi muốn. Cảm ơn. tuy nhiên, các câu trả lời trên đã khiến tôi suy nghĩ lại cách tôi đang làm việc và tôi có một ý tưởng tốt hơn về cách viết mã tuân thủ các tiêu chuẩn tốt. Tôi sẽ sử dụng cả hai đề xuất của bạn và những người khác trong tương lai. – batbrat

Các vấn đề liên quan