Tôi muốn cung cấp một viễn cảnh trừu tượng, cấp cao.
Đồng thời và tính đồng thời
Hoạt động I / O tương tác với môi trường. Môi trường không phải là một phần của chương trình của bạn và không thuộc quyền kiểm soát của bạn. Môi trường thực sự tồn tại "đồng thời" với chương trình của bạn. Như với tất cả mọi thứ đồng thời, các câu hỏi về "trạng thái hiện tại" không có ý nghĩa: Không có khái niệm về "tính đồng thời" trong các sự kiện đồng thời. Nhiều thuộc tính của nhà nước đơn giản là không tồn tại đồng thời.
Hãy để tôi nói điều này chính xác hơn: Giả sử bạn muốn hỏi, "bạn có nhiều dữ liệu hơn không". Bạn có thể yêu cầu điều này của một container đồng thời hoặc hệ thống I / O của bạn. Nhưng câu trả lời nói chung là không thể thực hiện được, và do đó vô nghĩa. Vậy điều gì sẽ xảy ra nếu container nói "có" - vào thời điểm bạn thử đọc, nó có thể không còn dữ liệu nữa. Tương tự, nếu câu trả lời là "không", vào thời điểm bạn thử đọc, dữ liệu có thể đã đến. Kết luận là đơn giản là cókhông có tài sản nào như "Tôi có dữ liệu", vì bạn không thể hành động có ý nghĩa để đáp lại bất kỳ câu trả lời nào có thể. (Tình hình tốt hơn một chút với đầu vào được đệm, trong đó bạn có thể hình dung được "có, tôi có dữ liệu" cấu thành một loại bảo đảm nào đó, nhưng bạn vẫn phải có thể xử lý trường hợp ngược lại. chắc chắn là tệ như tôi đã mô tả: bạn không bao giờ biết nếu đĩa đó hoặc bộ đệm mạng đó đã đầy.)
Vì vậy, chúng tôi kết luận rằng không thể, và trên thực tế là không hợp lý , để hỏi một hệ thống I / O liệu nó có thể thực hiện thao tác I / O không. Cách khả thi duy nhất chúng ta có thể tương tác với nó (giống như với một thùng chứa đồng thời) là thử hoạt động và kiểm tra xem nó đã thành công hay thất bại. Tại thời điểm đó, nơi bạn tương tác với môi trường, sau đó và chỉ sau đó bạn mới có thể biết liệu tương tác có thực sự khả thi hay không, và tại thời điểm đó, bạn phải cam kết thực hiện tương tác. (Đây là "điểm đồng bộ hóa", nếu bạn muốn.)
EOF
Bây giờ chúng ta đến EOF. EOF là phản hồi bạn nhận được từ thao tác I / O đã thử . Điều đó có nghĩa là bạn đã cố đọc hoặc viết một cái gì đó, nhưng khi làm như vậy bạn không thể đọc hoặc ghi bất kỳ dữ liệu nào, và thay vào đó, kết thúc đầu vào hoặc đầu ra đã gặp phải. Điều này đúng cho tất cả các API I / O, cho dù đó là thư viện chuẩn C, iostreams C ++ hoặc các thư viện khác. Miễn là các hoạt động I / O thành công, bạn chỉ đơn giản là không thể biết liệu các hoạt động trong tương lai có thành công hay không. Trước tiên, bạn phải luôn luôn thử hoạt động và sau đó đáp ứng thành công hay thất bại.
Ví dụ
Trong mỗi ví dụ, lưu ý cẩn thận rằng trước tiên chúng tôi thử thao tác I / O và sau đó sử dụng kết quả nếu nó hợp lệ. Lưu ý thêm rằng chúng ta luôn phải sử dụng kết quả của thao tác I / O, mặc dù kết quả có các hình dạng và hình thức khác nhau trong mỗi ví dụ.
C stdio, đọc từ một tập tin:
for (;;) {
size_t n = fread(buf, 1, bufsize, infile);
consume(buf, n);
if (n < bufsize) { break; }
}
Kết quả chúng ta phải sử dụng là n
, số lượng phần tử đã được đọc (có thể chỉ bằng 0).
C stdio, scanf
:
for (int a, b, c; scanf("%d %d %d", &a, &b, &c) == 3; ) {
consume(a, b, c);
}
Kết quả chúng ta phải sử dụng là giá trị trả về của scanf
, số phần tử được chuyển đổi.
C ++, iostreams định dạng trích xuất:
for (int n; std::cin >> n; ) {
consume(n);
}
Kết quả chúng ta phải sử dụng là std::cin
chính nó, có thể được đánh giá trong bối cảnh boolean và cho chúng ta biết liệu luồng có còn ở good()
trạng thái không.
C ++, iostreams getline:
for (std::string line; std::getline(std::cin, line); ) {
consume(line);
}
Kết quả chúng ta phải sử dụng là một lần nữa std::cin
, giống như trước đây.
POSIX, write(2)
để xóa bộ đệm:
char const * p = buf;
ssize_t n = bufsize;
for (ssize_t k = bufsize; (k = write(fd, p, n)) > 0; p += k, n -= k) {}
if (n != 0) { /* error, failed to write complete buffer */ }
Kết quả chúng ta sử dụng ở đây là k
, số byte được ghi. Vấn đề ở đây là chúng ta chỉ có thể biết có bao nhiêu byte được ghi sau thao tác ghi.
POSIX getline()
char *buffer = NULL;
size_t bufsiz = 0;
ssize_t nbytes;
while ((nbytes = getline(&buffer, &bufsiz, fp)) != -1)
{
/* Use nbytes of data in buffer */
}
free(buffer);
Kết quả chúng ta phải sử dụng là nbytes
, số lượng byte lên đến và bao gồm cả dòng mới (hoặc EOF nếu tệp không kết thúc bằng một dòng mới).
Lưu ý rằng hàm trả về một cách rõ ràng -1
(và không phải EOF!) Khi xảy ra lỗi hoặc nó đạt đến EOF.
Bạn có thể nhận thấy rằng chúng tôi rất hiếm khi đánh vần từ "EOF" thực tế. Chúng tôi thường phát hiện tình trạng lỗi theo một số cách khác thú vị hơn ngay lập tức (ví dụ: không thực hiện được nhiều I / O như chúng tôi mong muốn). Trong mọi ví dụ, có một số tính năng API có thể cho chúng ta biết rõ ràng rằng trạng thái EOF đã gặp phải, nhưng thực tế đây không phải là một thông tin hữu ích khủng khiếp. Đó là nhiều chi tiết hơn chúng ta thường quan tâm. Điều đáng quan tâm là liệu I / O đã thành công, hơn-hơn so với nó như thế nào thất bại.
Một ví dụ cuối cùng thực sự truy vấn trạng thái EOF: Giả sử bạn có một chuỗi và muốn kiểm tra xem nó có đại diện cho một số nguyên không, không có bit thừa ở cuối trừ khoảng trắng. Sử dụng iostreams C ++, nó diễn ra như sau:
std::string input = " 123 "; // example
std::istringstream iss(input);
int value;
if (iss >> value >> std::ws && iss.get() == EOF) {
consume(value);
} else {
// error, "input" is not parsable as an integer
}
Chúng tôi sử dụng hai kết quả ở đây. Đầu tiên là iss
, chính đối tượng luồng, để kiểm tra xem quá trình trích xuất được định dạng value
thành công. Nhưng sau đó, sau khi sử dụng khoảng trắng, chúng tôi thực hiện một thao tác I / O / khác iss.get()
và hy vọng nó sẽ thất bại dưới dạng EOF, đó là trường hợp nếu toàn bộ chuỗi đã được sử dụng bởi trích xuất được định dạng.
Trong thư viện chuẩn C, bạn có thể đạt được một cái gì đó tương tự với các strto*l
hàm bằng cách kiểm tra xem con trỏ cuối đã đến cuối chuỗi đầu vào chưa.
Câu trả lời
while(!feof)
là sai bởi vì nó kiểm tra một cái gì đó không liên quan và không kiểm tra cho một cái gì đó mà bạn cần biết. Kết quả là bạn đang thực thi mã sai, giả sử rằng nó đang truy cập dữ liệu được đọc thành công, trong khi thực tế điều này không bao giờ xảy ra.
feof()
để kiểm soát một vòng lặp