Làm thế nào để xóa bộ đệm đầu vào trong C?


84

Tôi có chương trình sau:

int main(int argc, char *argv[])
{
  char ch1, ch2;
  printf("Input the first character:"); // Line 1
  scanf("%c", &ch1); 
  printf("Input the second character:"); // Line 2
  ch2 = getchar();

  printf("ch1=%c, ASCII code = %d\n", ch1, ch1);
  printf("ch2=%c, ASCII code = %d\n", ch2, ch2);

  system("PAUSE");  
  return 0;
}

Như tác giả của đoạn mã trên đã giải thích: Chương trình sẽ không hoạt động bình thường vì tại Dòng 1, khi người dùng nhấn Enter, nó sẽ để lại trong bộ đệm nhập 2 ký tự: Enter key (ASCII code 13)\n (ASCII code 10). Do đó, tại Dòng 2, nó sẽ đọc \nvà sẽ không đợi người dùng nhập một ký tự.

OK, tôi hiểu rồi. Nhưng câu hỏi đầu tiên của tôi là: Tại sao getchar()( ch2 = getchar();) thứ hai không đọc ký tự Enter key (13)chứ không phải \n?

Tiếp theo, tác giả đề xuất 2 cách để giải quyết các thăm dò đó:

  1. sử dụng fflush()

  2. viết một hàm như thế này:

    void
    clear (void)
    {    
      while ( getchar() != '\n' );
    }
    

Mã này thực sự hoạt động. Nhưng tôi không thể tự giải thích nó hoạt động như thế nào? Bởi vì trong câu lệnh while, chúng ta sử dụng getchar() != '\n', nghĩa là đọc bất kỳ ký tự đơn lẻ nào ngoại trừ '\n'? nếu vậy, trong bộ đệm đầu vào vẫn còn '\n'ký tự?

Câu trả lời:


89

Chương trình sẽ không hoạt động bình thường vì tại Dòng 1, khi người dùng nhấn Enter, nó sẽ để lại trong bộ đệm nhập 2 ký tự: Phím Enter (mã ASCII 13) và \ n (mã ASCII 10). Do đó, tại Dòng 2, nó sẽ đọc \ n và sẽ không đợi người dùng nhập ký tự.

Hành vi bạn thấy ở dòng 2 là đúng, nhưng đó không phải là lời giải thích hoàn toàn chính xác. Với các luồng ở chế độ văn bản, không quan trọng nền tảng của bạn sử dụng kết thúc dòng nào (cho dù là ký tự xuống dòng (0x0D) + dòng cấp dữ liệu (0x0A), CR trống hay LF trống). Thư viện thời gian chạy C sẽ giải quyết việc đó cho bạn: chương trình của bạn sẽ chỉ hiển thị '\n'cho các dòng mới.

Nếu bạn nhập một ký tự và nhấn enter, thì ký tự đầu vào đó sẽ được đọc theo dòng 1 và sau đó '\n'sẽ được đọc theo dòng 2. Hãy xem tôi đang sử dụng scanf %cđể đọc phản hồi Y / N, nhưng nhập sau đó bị bỏ qua. từ Câu hỏi thường gặp về comp.lang.c.

Đối với các giải pháp được đề xuất, hãy xem (một lần nữa từ Câu hỏi thường gặp về comp.lang.c):

về cơ bản nói rằng cách tiếp cận di động duy nhất là thực hiện:

int c;
while ((c = getchar()) != '\n' && c != EOF) { }

getchar() != '\n'Vòng lặp của bạn hoạt động vì khi bạn gọi getchar(), ký tự trả về đã bị xóa khỏi luồng đầu vào.

Ngoài ra, tôi cảm thấy có nghĩa vụ không khuyến khích bạn sử dụng scanfhoàn toàn: Tại sao mọi người nói không sử dụng scanf? Tôi nên sử dụng gì để thay thế?


1
Thao tác này sẽ bị treo khi người dùng nhấn Enter. Nếu bạn muốn xóa stdin đơn giản, hãy sử dụng trực tiếp termios. stackoverflow.com/a/23885008/544099
ishmael

@ishmael Nhận xét của bạn không có ý nghĩa vì không cần nhấn Enter để xóa stdin; nó bắt buộc phải có đầu vào ngay từ đầu. (Và đó là chỉ tiêu chuẩn cách để đọc đầu vào trong C. Nếu bạn muốn để có thể đọc các phím ngay lập tức, sau đó bạn sẽ phải sử dụng các thư viện không chuẩn.)
jamesdlin

@jamesdlin Trên Linux, getchar () sẽ đợi người dùng nhấn Enter. Chỉ khi đó họ mới thoát ra khỏi vòng lặp while. Theo như tôi biết, termios là một thư viện tiêu chuẩn.
ishmael

@ishmael Không, bạn sai cả hai điểm. Câu hỏi này là về cách xóa dữ liệu đầu vào còn lại khỏi stdin sau khi đọc đầu vào và trong tiêu chuẩn C, việc cung cấp đầu vào đó trước tiên yêu cầu nhập một dòng và nhấn Enter. Sau khi dòng đó đã được nhập, getchar()sẽ đọc và trả về các ký tự đó; người dùng sẽ không cần nhấn Enter lần khác. Hơn nữa, trong khi termios có thể được phổ biến trên Linux , nó không phải là một phần của thư viện chuẩn C .
jamesdlin

Lý do scanf(" %c", &char_var)làm việc và bỏ qua dòng mới chỉ bằng cách thêm dấu cách là gì?
Cătălina Sîrbu

44

Bạn có thể làm điều đó (cũng) theo cách này:

fseek(stdin,0,SEEK_END);

Wow ... btw, tiêu chuẩn nói fseekcó sẵn cho stdin?
Ikh

3
Tôi không biết, tôi nghĩ nó không đề cập đến nó, nhưng stdin là FILE * và fseek chấp nhận một tham số FILE *. Tôi đã thử nghiệm nó và nó hoạt động trên Mac OS X nhưng không hoạt động trên Linux.
Ramy Al Zuhouri

Vui lòng giải thích. Sẽ là một câu trả lời tuyệt vời nếu tác giả viết ra những hàm ý của phương pháp này. Có vấn đề gì không?
EvAlex

7
@EvAlex: có nhiều vấn đề với điều này. Nếu nó hoạt động trên hệ thống của bạn, thật tuyệt; nếu không, thì không có gì đáng ngạc nhiên vì không có gì đảm bảo rằng nó sẽ hoạt động khi đầu vào tiêu chuẩn là một thiết bị tương tác (hoặc một thiết bị không thể tìm kiếm như ống hoặc ổ cắm hoặc FIFO, để đặt tên nhưng một số cách khác mà nó có thể Thất bại).
Jonathan Leffler

Tại sao nó phải là SEEK_END? Thay vào đó, chúng ta có thể sử dụng SEEK_SET không? FP stdin hoạt động như thế nào?
Rajesh ngày

11

Một cách di động để xóa đến cuối dòng mà bạn đã cố gắng đọc một phần là:

int c;

while ( (c = getchar()) != '\n' && c != EOF ) { }

Thao tác này đọc và loại bỏ các ký tự cho đến khi nhận được ký tự \nbáo hiệu kết thúc tệp. Nó cũng kiểm tra EOFtrong trường hợp dòng đầu vào bị đóng trước khi kết thúc dòng. Loại cphải int(hoặc lớn hơn) để có thể giữ giá trị EOF.

Không có cách nào di động để tìm xem có thêm dòng nào sau dòng hiện tại hay không (nếu không có, thì getcharsẽ chặn đầu vào).


tại sao while ((c = getchar ())! = EOF); không hoạt động? Đó là vòng lặp vô tận. Không thể hiểu vị trí của EOF trong stdin.
Rajesh

@Rajesh Điều đó sẽ rõ ràng cho đến khi dòng đầu vào bị đóng, điều này sẽ không xảy ra nếu có thêm đầu vào sau này
MM

@Rajesh Đúng, bạn nói đúng. Nếu không có gì trên stdin để tuôn ra khi nó được gọi, nó sẽ chặn (treo) do nó bị mắc vào một vòng lặp vô hạn.
Jason Enochs

11

Những dòng kẻ:

int ch;
while ((ch = getchar()) != '\n' && ch != EOF)
    ;

không chỉ đọc các ký tự trước dòng cấp dữ liệu ( '\n'). Nó đọc tất cả các ký tự trong luồng (và loại bỏ chúng) cho đến và bao gồm cả dòng cấp dữ liệu tiếp theo (hoặc gặp phải EOF). Để kiểm tra là đúng, nó phải đọc dòng cấp dữ liệu trước; vì vậy khi vòng lặp dừng lại, dòng cấp dữ liệu là ký tự cuối cùng được đọc, nhưng nó đã được đọc.

Về lý do tại sao nó đọc dòng cấp dữ liệu thay vì ký tự xuống dòng, đó là bởi vì hệ thống đã dịch trả về thành nguồn cấp dữ liệu dòng. Khi nhấn enter, điều đó báo hiệu sự kết thúc của dòng ... nhưng thay vào đó, luồng chứa nguồn cấp dữ liệu dòng vì đó là điểm đánh dấu cuối dòng bình thường cho hệ thống. Điều đó có thể phụ thuộc vào nền tảng.

Ngoài ra, việc sử dụng fflush()trên một luồng đầu vào không hoạt động trên tất cả các nền tảng; ví dụ như nó thường không hoạt động trên Linux.


Lưu ý: while ( getchar() != '\n' );là một vòng lặp vô hạn nên getchar()trả về EOFdo end-of-file.
Chux - Khôi phục Monica

Để kiểm tra EOF cũng int ch; while ((ch = getchar()) != '\n' && ch != EOF);có thể được sử dụng
Dmitri

8

Nhưng tôi không thể tự giải thích nó hoạt động như thế nào? Bởi vì trong câu lệnh while, chúng ta sử dụng getchar() != '\n', nghĩa là đọc bất kỳ ký tự đơn lẻ nào ngoại trừ '\n'?? nếu vậy, trong bộ đệm đầu vào vẫn còn '\n'ký tự ??? Tôi đang hiểu lầm điều gì đó sao ??

Điều bạn có thể không nhận ra là sự so sánh xảy ra sau khi getchar() xóa ký tự khỏi bộ đệm đầu vào. Vì vậy, khi bạn đạt đến '\n', nó được tiêu thụ và sau đó bạn thoát ra khỏi vòng lặp.


6

bạn co thể thử

scanf("%c%*c", &ch1);

trong đó% * c chấp nhận và bỏ qua dòng mới

một phương thức khác thay vì fflush (stdin) gọi hành vi không xác định mà bạn có thể viết

while((getchar())!='\n');

đừng quên dấu chấm phẩy sau vòng lặp while


2
1) "%*c"quét bất kỳ ký tự nào (và không lưu nó), có thể là một dòng mới hoặc một cái gì đó khác. Mã dựa trên rằng ký tự thứ 2 là một dòng mới. 2) while((getchar())!='\n');là một vòng lặp vô hạn nên getchar()trả về EOFdo end-of-file.
Chux - Khôi phục Monica

1
phương pháp thứ hai phụ thuộc vào điều kiện như xuống dòng, kí tự null, EOF vv (ở trên nó là newline)
Kapil

1

Tôi gặp sự cố khi cố gắng triển khai giải pháp

while ((c = getchar()) != '\n' && c != EOF) { }

Tôi đăng một chút điều chỉnh 'Mã B' cho bất kỳ ai có thể gặp vấn đề tương tự.

Vấn đề là chương trình đã giữ cho tôi bắt ký tự '\ n', độc lập với ký tự enter, đây là mã đã gây ra sự cố cho tôi.

Mã A

int y;

printf("\nGive any alphabetic character in lowercase: ");
while( (y = getchar()) != '\n' && y != EOF){
   continue;
}
printf("\n%c\n", toupper(y));

và điều chỉnh là để 'bắt' ký tự (n-1) ngay trước khi điều kiện trong vòng lặp while được đánh giá, đây là mã:

Mã B

int y, x;

printf("\nGive any alphabetic character in lowercase: ");
while( (y = getchar()) != '\n' && y != EOF){
   x = y;
}
printf("\n%c\n", toupper(x));

Có thể giải thích là để vòng lặp while bị phá vỡ, nó phải gán giá trị '\ n' cho biến y, vì vậy nó sẽ là giá trị được gán cuối cùng.

Nếu tôi bỏ lỡ điều gì đó với lời giải thích, mã A hoặc mã B, vui lòng cho tôi biết, tôi hầu như là người mới trong c.

hy vọng nó sẽ giúp ai đó


Bạn nên xử lý (các) ký tự " mong đợi " của mình bên trong whilevòng lặp. Sau vòng lặp, bạn có thể coi stdinsạch / rõ ràngysẽ giữ ' \n' hoặc EOF. EOFđược trả về khi không có dòng mới và bộ đệm đã hết (nếu đầu vào làm tràn bộ đệm trước khi [ENTER]được nhấn). - Bạn sử dụng hiệu quả while((ch=getchar())!='\n'&&ch!=EOF);để nhai từng ký tự trong stdinbộ đệm đầu vào.
veganaiZe,

0
unsigned char a=0;
if(kbhit()){
    a=getch();
    while(kbhit())
        getch();
}
cout<<hex<<(static_cast<unsigned int:->(a) & 0xFF)<<endl;

-hoặc là-

sử dụng có thể sử dụng _getch_nolock().. ???


Câu hỏi là về C, không phải C ++.
Spikatrix

1
Lưu ý rằng kbhit()getch()là các hàm được khai báo trong <conio.h>và chỉ có sẵn dưới dạng tiêu chuẩn trên Windows. Chúng có thể được mô phỏng trên các máy giống Unix, nhưng làm như vậy cần phải cẩn thận.
Jonathan Leffler

0

Một giải pháp khác chưa được đề cập là sử dụng: rewind (stdin);


2
Điều đó sẽ hoàn toàn phá vỡ chương trình nếu stdin được chuyển hướng đến một tệp. Và đối với đầu vào tương tác, nó không được đảm bảo để làm bất cứ điều gì.
melpomene,

0

Tôi ngạc nhiên là không ai đề cập đến điều này:

scanf("%*[^\n]");

-1

Ngắn gọn, di động và được khai báo trong stdio.h

stdin = freopen(NULL,"r",stdin);

Không bị treo trong một vòng lặp vô hạn khi không có gì trên stdin để tuôn ra như dòng biết rõ sau:

while ((c = getchar()) != '\n' && c != EOF) { }

Hơi tốn kém nên không sử dụng nó trong một chương trình cần xóa bộ đệm nhiều lần.

Đánh cắp từ đồng nghiệp :)


Không hoạt động trên Linux. Sử dụng các thuật ngữ trực tiếp để thay thế. stackoverflow.com/a/23885008/544099
ishmael

2
Bạn có thể giải thích tại sao dòng nổi tiếng là một vòng lặp vô hạn?
Cacahuete Frito

Toàn bộ lý do freopentồn tại là bạn không thể chuyển nhượng cho stdin. Đó là một macro.
melpomene

1
ishmael: Tất cả các máy tính của chúng tôi ở đây trong Cyber ​​Command đều là Linux và nó hoạt động tốt trên chúng. Tôi đã thử nghiệm nó trên cả Debian và Fedora.
Jason Enochs

Cái mà bạn gọi là "vòng lặp vô hạn" là chương trình đang chờ đầu vào. Đó không phải là điều tương tự.
jamesdlin

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.