Một cách nhập xâu kí tự trong C - giải quyết vấn đề của fflush cho Linux, Mac OS

Khi lập trình C trên Windows, bạn có thể sử dụng fflush() để làm sạch bộ đệm trước khi nhập một xâu, tránh tình trạng "lạc trôi" của lệnh nhập xâu. Tuy nhiên khi làm việc với các hệ thống khác như Linux, hay Mac OS, mọi chuyện không đơn giản nữa: nhiều khi fflush() sẽ không làm việc. Bài viết này sẽ cho bạn một cách giải quyết đơn giản có thể chạy được trên cả Linux, Mac OS và cả Windows.

Vấn đề gì khi không sử dụng fflush() và cả khi sử dụng nó?

Không sử dụng fflush() (và các phương pháp thay thế):

Hãy xem xét đoạn chương trình sau:

 1#include <stdio.h>
 2#include <string.h>
 3
 4int main(int argc, char const *argv[])
 5{
 6    int c;
 7    char s[100];
 8
 9    printf("c = "); scanf("%d", &c);
10    printf("s = ");
11    fgets(s, 100, stdin);
12    if (strlen(s) > 0) // xoá kí tự '\n' ở cuối khi nhập bằng fgets
13        if (s[strlen(s) - 1] == '\n')
14            s[strlen(s) - 1] = 0;
15
16    printf("c = %d\n", c);
17    printf("s = %s\n", s);
18    
19    return 0;
20}

Khi chạy chương trình ta thu được kết quả sau:

Trôi lệnh trong C - không có fflush

Rõ ràng xâu s đã không được nhập vào. Vấn đề này xảy ra do khi nhập c bằng scanf() thì kí tự \n vẫn còn lưu trong bộ đệm bàn phím. Khi lệnh nhập s (fgets) chạy, nó gặp kí tự \n trong bộ đệm do vậy trả luôn về một xâu chỉ chứa \n.

Vậy khi sử dụng fflush() để làm sạch bộ đệm thì có vấn đề gì?

Bạn có thể giải quyết vấn đề trôi lệnh trên Windows bằng cách thêm một lệnh fflush() vào trước fgets(). Tuy vậy khi chuyển chương trình sang Linux hoặc Mac OS thì nhiều khi lệnh này sẽ không còn hoạt động.

Phương pháp nhập xâu trong C

Google một thời gian tôi cũng tìm được câu trả lời cho phương pháp nhập xâu trong các hệ thống ngoài Windows. Đơn giản là bạn thêm đoạn lệnh sau khi trong bộ đệm vẫn còn kí tự \n.

1int ch;
2do { ch = getchar(); } while (ch != '\n' && ch != EOF);

Sử dụng trong chương trình bên trên thì có vẻ khá ổn. Tuy nhiên nếu trước đó không có số nguyên c được nhập thì sao? Có vẻ không ổn rồi! - Chương trình sẽ dừng lại, nhận vào một kí tự rồi mới tiếp tục chạy.

Vậy cách giải quyết thế nào? Tôi xin đưa ra cách tôi đã dùng:

 1#include <string.h>
 2#include <stdio.h>
 3
 4void removeLastEnter(char *s) {
 5    if (strlen(s) != 0)
 6        if (s[strlen(s) - 1] == '\n')
 7            s[strlen(s) - 1] = '\0';
 8}
 9
10char* readLn(FILE * fin, char *s, int max_len) {
11    char* p;
12    p = fgets(s, max_len * sizeof(char), fin);
13
14    if (p != NULL) { // already read sth
15        // Re-read line if it contain only enter character
16        removeLastEnter(s);
17        if (strlen(s) == 0) {
18            p = readLn(fin, s, max_len);
19        }
20    }
21
22    return p;
23}
24
25int main(int argc, char const *argv[]) {
26    int c;
27    char s[100];
28
29    printf("c = "); scanf("%d", &c);
30    printf("s = "); readLn(stdin, s, 100);
31    
32    printf("c = %d\n", c);
33    printf("s = %s\n", s);
34    
35    return 0;
36}

Hàm readLn() sẽ giúp nhập vào một xâu (s) từ bàn phím (độ dài lớn nhất là 100). Quá trình nhập xâu không bị ảnh hưởng nếu có sẵn một kí tự \n trong bộ đệm bàn phím từ trước và sẽ chỉ dừng lại khi nhận vào một xâu có độ dài > 0.

Đây là cách làm tôi sử dụng trong chương trình của mình. Nếu bạn có cách làm khác hoặc góp ý gì với cách làm này, hãy để lại comment bên dưới!

Related Posts

Cấu hình Emacs để lập trình C

Link hướng dẫn cài đặt và sử dụng các chức năng: http://tuhdo.github.io/c-ide.html Sau đây là vài thứ cơ bản ko cần đọc cái bên trên cài và dùng luôn đã >>

Read more

Cấu trúc rẽ nhánh trong C

Cấu trúc rẽ nhánh trong mỗi ngôn ngữ lập trình luôn luôn là một thành phần quan trọng. Đó là thành phần cơ bản tạo nên hầu hết các thuật toán hiện đại.

Read more

Biến, hằng và nhập xuất dữ liệu trong C

Biến, hằng giống như những chiếc hộp có tên riêng mà chúng ta có thể để dữ liệu vào và mang ra mỗi khi chúng ta cần sử dụng.

Read more