Lập trình C: Bài 3 – Nhập xuất trong c

Ở các bài trước, chúng ta đã làm quen với một số chương trình mà in lên màn hình một số thông tin. Bài này chúng ta sẽ tìm hiểu kỹ hơn về cách nhập xuất trong C.

1. Chuỗi định dạng dữ liệu

Trước khi đến với phần nhập, xuất dữ liệu cho các biến mình sẽ nói về một số định dạng để nhập và xuất. Sau đây là các dấu mô tả định dạng:

  • %c : Ký tự đơn
  • %s : Chuỗi
  • %d : Số nguyên hệ 10 có dấu
  • %f : Số chấm động (VD 5.54 khi in sẽ ra 5.540000)
  • %e : Số chấm động (ký hiệu có số mũ)
  • %g : Số chấm động (VD 5.54 khi in sẽ in ra 5.54)
  • %x : Số nguyên hex không dấu (hệ 16)
  • %o : Số nguyên bát phân không dấu (hệ 8)
  • l : Tiền tố dùng kèm với %d, %x, %o để chỉ số nguyên dài (ví dụ%ld)

2. Xuất dữ liệu dùng printf

Chúng ta sử dụng hàm printf để xuất dữ liệu ra màn hình console (từ print có nghĩa là in). Kết hợp với các chuỗi định dạng ở trên, chúng ta xét một ví dụ đơn giản sau.

Ví dụ 1:

#include <stdio.h>

int main()
{
    int a = 12;
    float b = 13.5;
    char c = 'Q';
    long d = 3454;
    char* s = "nguyenvanquan7826"; // khai bao kieu chuoi

    printf("Vi du ve su dung lenh printf\n");
    printf("Tong cua %d va %f la %f \n", a, b, a+b);
    printf("Tich cua %d va %ld la %ld \n", a, d, a*d);
    printf("Ky tu c la: %c \n", c);
    printf("Chuoi s la: %s \n", s);
    printf("Dinh dang so mu cua b la %e \n", b);
    printf("So he 16 va he 8 cua %d la %x va %o \n", a, a, a);
    printf("Ma ASCII cua %c la %d", c, c);

    return 0;
}

Ở trên mình có dùng một số ký tự điều khiển (\n – xuống dòng) đã được đề cập ở bài trươc, các bạn có thể xem lại nếu không nhớ. Các bạn chạy chương trình và rút ra nhận xét cho riêng mình.

Mình giải thích 1 câu lệnh để làm rõ hơn việc xuất của chúng ta.

Xuất dữ liệu

Câu lệnh printf để in các biến tương ứng

Chuỗi định dạng được đặt trong ngoặc kép: “”. Tương ứng với mỗi định dạng là một biến có kiểu tương ứng, nếu khác kiểu sẽ dẫn đến sai sót.

Các bạn cũng chú ý là đối với số nguyên và ký tự có sự qua lại với nhau thông qua mã ASCII nên chúng ta có thể in mã của ký tự bằng định dạng %d và cũng có thể in ký tự có mã là số nào đó thông qua định dạng %c. Tuy nhiên bản chất của biến không thay đổi. Ở Vd trên câu lệnh in mã ASCII của c sẽ cho số nguyên nhưng bản chất c vẫn là một biến kiểu char.

Ví dụ 2:

#include <stdio.h>

int main()
{
    int a = 12;
    float b = 13.5;
    char c = 'Q';
    long d = 3454;
    char s[] = "nguyenvanquan7826"; // khai bao kieu chuoi

    printf("%6d %5.3f %.3f \n", a, b, a+b);
    printf("%-5d %5ld %5ld \n", a, d, a*d);
    printf("%5c \n", c);
    printf("%30s \n", s);

    return 0;
}

Các bạn thấy ví dụ này chúng ta dùng các định dạng %d, %f nhưng lại chèn thêm các số ở giữa thành %6d, %5.3f. Ý nghĩa của chúng như sau:

  • %5c : Xuất ký tự có bề rộng 5
  • %5d : Số nguyên có bề rộng 5
  • %20s : Xuất chuỗi có bề rộng 20
  • %5.3f : Xuất số thực có bề rộng 5 trong đó có 3 số sau dấu phẩy
  • %-5d : Số nguyên có bề rộng 5 nhưng căn lề trái

Các bạn chạy chương trình, xem kết quả để thấy rõ hơn. Trong TH nếu bề rộng của chúng ta có kích thước bé hơn độ dài của số thì sao? Ví dụ chúng ta có số int a = 1234 nhưng chỉ in là %2d, rõ ràng dành 2 phần trống là ít hơn số a có 4 chữ số nhưng kết quả là số vẫn được in đầy đủ. Các bạn có thể thử.

Ví dụ 3:

Đặt bài toán là giả sử chúng ta có các biến thể hiện ngày tháng năm sinh bất kỳ (ví dụ ngay = 12, thang = 8, nam = 1992). Hãy in ra theo định dạng là dd/mm/yyyy. Nghĩa là chúng ta phải in ra kết quả 12/08/1992.

#include <stdio.h>

int main()
{
    int ngay = 12;
    int thang = 8;
    int nam = 1992;

    printf("KQ: %02d/%02d/%04d\n", ngay, thang, nam);

    return 0;
}

Ở trên, các bạn chú ý chúng ta dùng %02d nghĩa là sao? Nhĩa là số in ra sẽ có 2 chữ số, nếu ban đầu số có ít hơn 2 thì sẽ thêm số 0 đằng trước để đủ 2 chữ số. Ví dụ số 8 sẽ thành 08. Nếu dùng %05d thì sẽ là 00008.

3. Nhập dữ liệu dùng scanf

Trong các ví dụ trước, chúng ta chỉ khai báo biến và gán giá trị cho biến nhưng như thế làm chương trình không linh hoạt vì chỉ hoạt động với những giá trị đã định trước, muốn hoạt động với các dữ liệu khác nhau chúng ta lại phải sửa lại code, như vậy thì không phải một chương trình tốt. Một chương trình phần mềm làm ra là phải dành cho nhiều bộ giá trị khác nhau có thể dùng được.

Chính vì vậy dữ liệu sẽ được đưa vào trong quá trình phần mềm được dùng và cụ thể đơn giản là nhập dữ liệu vào từ bàn phím. Chúng ta sử dụng lệnh scanf để nhập dữ liệu từ bàn phím (từ scan có nghĩa là quét và chúng ta dùng để quét dữ lệu từ bàn phím).

Ví dụ 1:

#include <stdio.h>

int main()
{
    int a;
    float b;

    printf("Nhap so nguyen a = ");
    scanf("%d", &a);

    printf("Nhap so thuc b = ");
    scanf("%f", &b);

    printf("a = %d \t b = %.3f", a, b);

    return 0;
}

Việc sử dụng lệnh scanf kết hợp với chuỗi định dạng để nhập dữ liệu sẽ tương tự như khi xuất dữ liệu bằng lệnh printf.

Chú ý đừng quên ký tự & trước mỗi biến khi nhập. Nếu không sẽ sai.

Ví dụ 2:

/* Tinh dien tich hinh chu nhat */
#include <stdio.h>

int main()
{
    int a, b;
    printf("Nhap chieu dai, chieu rong: \n");
    scanf("%d%d", &a, &b);

    printf("Dien tich HCN: %d\n", a * b);

    return 0;
}

Ở ví dụ trên, các bạn thấy chúng ta có thể dùng 1 lệnh scanf để nhập dữ liệu cho nhiều biến, mỗi biến tương ứng với 1 định dạng nhất định.

4. Nhập chuỗi trong C

4.1 Lỗi khi nhập chuỗi bằng scanf

Nếu các bạn dùng hàm scanf để nhập chuỗi thì bạn sẽ thấy rằng không thể nhập được chuỗi có dấu cách hoặc nếu trước đó bạn nhập số thì sau đó không nhập được chuỗi nữa. Nếu không tin bạn có thể thử chạy với chương trình sau:

#include <stdio.h>

int main()
{
    int tuoi = 0;
    // khai bao chuoi co toi da 30 ky tu
    char ten[30], tenNguoiYeu[30];

    printf("Ho va ten cua ban ten la gi? ");
    scanf("%s", ten); // nhap chuoi khong can dau &

    printf("Ban bao nhieu tuoi roi? ");
    scanf("%d", &tuoi);

    printf("Nguoi yeu cua ban ten la gi? ");
    scanf("%s", tenNguoiYeu);

    printf("\n====\n");
    printf("Ten: %s \nTuoi:%d \nNY:%s \n", ten, tuoi, tenNguoiYeu);

    return 0;
}

Kết quả là bạn sẽ không nhập được tuổi và tên người yêu như hình sau.

Troi Lenh Trong C

Kết quả khi dùng scanf để nhập chuỗi

Lý do là scanf chỉ đọc được dữ liệu không có khoảng trắng (đấu cách, dấu tab, enter, …) và các khoảng cách này sẽ được lưu vào bộ đệm bàn phím do đó bạn chỉ nhận được chuỗi đầu tiên trước đấu cách mà thôi (chữ Nguyen), sau mỗi dấu cách, các giá trị tiếp theo nếu phù hợp với kiểu dữ liệu của các biến tiếp theo thì nó sẽ gán luôn cho chúng và bạn sẽ không được nhập nữa. Do tuoi kiểu nguyên nên không nhận được, tenNguoiYeu sẽ nhận giá trị tiếp theo trong các giá trị nhận được là chữ Van.

4.2 Hiện tượng trôi lệnh

Hiện tượng như trên được gọi là hiện tượng trôi lệnh. Nếu bây giờ bạn thực hiện cho nhập số trước và chuỗi ngay sau đó thì hiện tượng này cũng xảy ra vì scanf chỉ đọc số theo đúng định dạng mà không đọc được phím enter khi bạn nhấn lúc nhập xong số (enter là ký tự hoặc cũng có thể coi là chuỗi), nó được lưu vào bộ đệm và khi đọc giá trị nhập cho chuỗi nó tìm trong bộ đệm thấy ký tự enter là kiểu chuỗi nên nó gán luôn cho chuỗi đó.

Để nhập được chuỗi có khoảng trắng (dấu cách) chúng ta sử dụng hàm gets.

Để không bị trôi lệnh khi nhập số trước và chuỗi sau ta cần xóa bộ đệm bàn phím bằng lệnh fflush(stdin); ngay sau khi nhập số.

#include <stdio.h>

int main()
{
    int tuoi = 0;
    // khai bao chuoi co toi da 30 ky tu
    char ten[30], tenNguoiYeu[30];

    printf("Ho va ten cua ban ten la gi? ");
    gets(ten); // nhap chuoi khong can dau &

    printf("Ban bao nhieu tuoi roi? ");
    scanf("%d", &tuoi);
    fflush(stdin);

    printf("Nguoi yeu cua ban ten la gi? ");
    gets(tenNguoiYeu);

    printf("\n====\n");
    printf("Ten: %s \nTuoi:%d \nNY:%s \n", ten, tuoi, tenNguoiYeu);

    return 0;
}

Nếu bạn dùng Linux thì fflush(stdin); sẽ không hoạt động, bạn có thể khắc phục bằng cách nhập vào 1 chuỗi tạm ngay sau khi nhập số. Việc nhập chuỗi tạm này chỉ để xóa bỏ các ký tự, chuỗi ký tự thừa có trong bộ đệm như sau (cách này cũng dùng được khi bạn làm trên windows).

#include <stdio.h>

int main()
{
    int tuoi = 0;
    // khai bao chuoi co toi da 30 ky tu
    char ten[30], tenNguoiYeu[30], temp[255];

    printf("Ho va ten cua ban ten la gi? ");
    gets(ten); // nhap chuoi khong can dau &

    printf("Ban bao nhieu tuoi roi? ");
    scanf("%d", &tuoi);
    //fflush(stdin);
    gets(temp);

    printf("Nguoi yeu cua ban ten la gi? ");
    gets(tenNguoiYeu);

    printf("\n====\n");
    printf("Ten: %s \nTuoi:%d \nNY:%s \n", ten, tuoi, tenNguoiYeu);

    return 0;
}

5. Giải thích một chút về printf, scanf và stdio.h

5.1 Chữ f trong printf và scanf

Như các bạn đã biết print nghĩa là in, scanquét hay ta gọi trong này là để nhập. Vậy tại sao chúng lại có chữ f đằng sau để thành printfscanf ?

Chữ f này có nghĩa là format (định dạng). Như các bạn thấy chúng ta nhập hoặc xuất các giá trị của các biến đều có định dạng % gì đó ví dụ %d là số nguyên, %f là số thực,… và chữ f này chính là có ý nghĩa như thế.

5.2 Thư viện stdio.h ?

Trong tất cả các chương trình từ trước tới giờ, chúng ta luôn có #include <stdio.h>, vậy nó là gì?

#include nghĩa là bao gồm tức là chương trình của chúng ta sẽ khai báo sử dụng một cái gì đó, mà ở đây là sử dụng thư viện stdio.h

Vậy stdio.h là gì? std viết tắt của standard , i viết tắt của input, o viết tắt của output, h viết tắt của header (header – đầu, trên đầu – vì nó được khai báo ở trên đầu của các chương trình). Vậy nó có nghĩa là standard input output – nhập xuất chuẩn. Chúng ta hiểu nó là thư viện phục vụ cho việc nhập xuất chuẩn của chương trình. Nhập xuất chuẩn chính là nhập từ bàn phím và xuất ra màn hình. Có còn rất nhiều nguồn nhập xuất như nhập từ file, nhập từ con trỏ chuột,… xuất ra máy in, xuất ra file,… nhưng họ coi bàn phím và màn hình là hệ thống nhập xuất chuẩn.

Bài tập

  1. Viết chương trình nhập vào một số a bất kỳ và in ra giá trị a2, a3, a4
  2. Viết chương trình đọc từ bàn phím 3 số nguyên biểu diễn ngày, tháng, năm và xuất ra màn hình dưới dạng “dd/mm/yyyy”.
  3. Viết chương trình đọc và 2 số nguyên và in ra kết quả của phép (+), phép trừ (-), phép nhân (*), phép chia (/). Nhận xét kết quả chia 2 số nguyên.
  4. Viết chương trình nhập vào bán kính hình cầu, tính và in ra diện tích, thể tích của hình cầu đó.
    Hướng dẫn: S = 4πR^2 và V = (4/3)πR^3.
  5. Nhập vào một số là số giây, đổi số giây này ra giờ phút giây và xuất theo dạng gio:phut:giay, mỗi thành phần có 2 chữ số. Ví dụ 3661 = 01:01:01.