Thanh navigation

Thứ Bảy, 8 tháng 12, 2018

Một số vấn đề khi nhập dữ liệu trong C

Mục này sẽ nói về một số vấn đề và cách giải quyết chúng khi gặp các bài tập có:
-  nhập chuỗi ký tự chứa dấu cách.
-  kết hợp lệnh scanfgets trong một chương trình (các bài tập ở phần cấu trúc - struct).

I. Nhập chuỗi

Khi làm bài tập, có một số bài yêu cầu dữ liệu vào là chuỗi ký tự. Khi đó, ta có thể dùng hàm scanf hoặc gets để nhập.

Ví dụ 1:  Dùng hàm gets nhập vào một chuỗi ký tự. In chuỗi vừa nhập ra màn hình.

#include<stdio.h>
main()
{
     char   a[100];

     printf("Nhap chuoi:  ");
     gets(a);

     printf("Chuoi vua nhap la:  %s ", a);
}


Ví dụ 2: Dùng hàm scanf nhập vào một chuỗi ký tự. In chuỗi vừa nhập ra màn hình.

#include<stdio.h>
main()
{
     char   a[100];

     printf("Nhap chuoi:  ");
     scanf("%s", a);

     printf("Chuoi vua nhap la:  %s ", a);
}


Lưu ý

1) Trong ví dụ 1(gets), a lưu được cả chuỗi có dấu cách.
2) Trong ví dụ 2(scanf), nếu chuỗi ký tự nhập vào có dấu cách (khoảng trống) thì từ dấu cách trở về sau sẽ không được lưu vào biến a. Chẳng hạn, nếu nhập vào chuỗi kí tự  "Tran Hung Dao"  thì a chỉ lưu được chữ "Tran" còn phần sau của chuỗi sẽ không được lưu vào a.

Vậy làm thế nào để hàm scanf có thể nhận được cả chuỗi có khoảng trống?. Ta sẽ sửa dòng scanf trong ví dụ 2 lại như sau:

scanf("%[^\n]", a);

II. Nhập cả số và chuỗi

Nếu trong chương trình có sử dụng lệnh scanfgets điều gì sẽ xảy ra ? Xem ví dụ sau đây.


Ví dụ 3: Nhập vào một số nguyên và nhập vào một chuỗi ký tự. In kết quả ra màn hình.

#include<stdio.h>
main()
{
     int   N;
     char  a[100];

     printf("Nhap vao so N  ");
     scanf("%d", &N);

     printf("Nhap chuoi: ");
     gets(a);

     printf("So vua nhap la: %d \n",N) ;
     printf("Chuoi vua nhap la: %s \n", a);
}

Khi biên dịch và chạy ví dụ 3 chúng ta thấy rằng, lệnh gets bị bỏ qua ! Điều gì đã xảy ra ?

Trong lập trình C có một khái niệm là stdin (standard input - bàn phím). Nó được gọi là dòng vào chuẩn, dữ liệu nhận từ stdin sẽ được lưu vào bộ nhớ đệm (input buffer). Tất cả dữ liệu từ bàn phím khi đưa vào cho máy tính xử lý, sẽ được lưu ở bộ  nhớ đệm trước.

Khi các hàm nhận dữ liệu từ bàn phím (gets, scanf, fgets, ...) được thực hiện thì ngoài dữ liệu được gõ vào, ta phải xác nhận kết thúc các lệnh đó bằng cách gõ Enter (\n).

Hàm gets sẽ nhận dữ liệu từ stdin và LOẠI bỏ ký tự \n khỏi bộ nhớ đệm của stdin.
Hàm scanf sẽ nhận dữ liệu từ stdin nhưng KHÔNG LOẠI bỏ được ký tự \n ra khỏi bộ đệm của stdin.

Nếu trong bufffer của stdin vẫn tồn tại \n thì sẽ làm TRÔI lệnh gets đứng ở phía sau. Như vậy ở trong ví dụ 3, phía trước hàm gets có sử dụng hàm scanf. Chính hàm scanf này sẽ để lại \n trong buffer của stdin và làm TRÔI lệnh gets.

Vậy làm thế nào để loại bỏ ký tự \n ra khỏi buffer stdin nếu trong bài tập của chúng ta sử dụng cả scanf và gets ?  Có hai cách.


Cách 1. Dùng hàm fflush để làm sạch stdin ngay sau hàm scanf. Ví dụ 3 sửa lại như sau.

#include<stdio.h>
main()
{
    int N;
    char a[100];
    printf("Nhap vao so N:  ");
    scanf("%d",&N);
    fflush(stdin);

    printf("Nhap chuoi: ");
    gets(a);
 
    printf("So vua nhap la: %d \n",N);
    printf("Chuoi vua nhap la: %s \n",a);
}

Cách 2. Thêm đặc tả %*c và trong lệnh scanf. Ví dụ 3 sửa lại như sau.

#include<stdio.h>
main()
{
    int N;
    char a[100];

    printf("Nhap vao so N:  ");
    scanf("%d%*c",&N);

    printf("Nhap chuoi: ");
    gets(a);
 
    printf("So vua nhap la: %d \n",N);
    printf("Chuoi vua nhap la: %s \n",a);
}


Hàm gets nó sẽ nhận ký tự từ bàn phím cho đến khi gặp được ký tự \n (bấm Enter) thì ngừng nhận. Như vậy nó không quan tâm đến giới hạn của biến mảng dùng để chứa ký tự. Trường hợp nhập vào số ký tự vượt quá kích thước của mảng, có thể gây lỗi. Để tránh trường hợp này có thể thay hàm gets bằng hàm fgets. Có thể đọc thêm ở đây. https://www.geeksforgeeks.org/fgets-gets-c-language/

Vì không có chỉ định đối với việc dùng fflush làm sạch stdin. Do đó, không nên dùng fflush để làm sạch stdin. Tuy nhiên, thực tế fflush vẫn làm sạch stdin trong một số trường hợp. Việc sử dụng fflush để làm sạch stdin có thể xem ở đây.

Cẩn thận ta nên xài cách 2 để loại bỏ \n trong stdin.

Không có nhận xét nào:

Đăng nhận xét