printf
Sau bài này bạn sẽ làm được
- Giải thích giá trị trả về của printf và dùng được nó
- Dùng đúng các escape sequence, kể cả \r cho thanh tiến trình
- Giải thích ba chế độ đệm và vì sao stderr không bị đệm
- Biết khi nào cần gọi fflush
printf là hàm bạn gõ nhiều nhất trong suốt khóa học. Bài này nói về ba thứ ít ai để ý: nó trả về giá trị gì, các ký tự thoát, và cơ chế đệm quyết định khi nào chữ thật sự hiện ra màn hình. Riêng phần định dạng sẽ dành cả bài sau.
#Cách dùng cơ bản
int printf(const char *format, ...);Tham số đầu là chuỗi định dạng. Các tham số sau là giá trị cần in, số lượng tùy ý.
#include <stdio.h>
int main(void)
{
printf("Chuỗi thường\n");
printf("Số: %d, Thực: %.2f, Ký tự: %c, Chuỗi: %s\n",
42, 3.14159, 'A', "xin chào");
return 0;
}#Giá trị trả về
printf trả về số ký tự đã in ra, hoặc một số âm nếu gặp lỗi. Rất ít người dùng giá trị này, nhưng nó có ích trong vài tình huống.
#include <stdio.h>
int main(void)
{
int n = printf("Xin chào\n"); /* in 9 ký tự: 8 chữ và 1 xuống dòng */
printf("Vừa in %d ký tự\n", n);
/* Ứng dụng thật: căn cột khi không biết trước độ dài */
int rong = printf("Tên: %s", "Nguyễn Văn A");
for (int i = rong; i < 40; ++i) {
putchar('.');
}
printf(" xong\n");
return 0;
}#Escape sequence
Một số ký tự không gõ thẳng vào chuỗi được, ví dụ dấu nháy kép vì nó đánh dấu hết chuỗi. Chúng được viết bằng dấu gạch chéo ngược kèm một chữ cái.
| Ký hiệu | Ý nghĩa | Ghi chú |
|---|---|---|
| \n | Xuống dòng | Là MỘT ký tự, không phải hai |
| \t | Tab ngang | Thường nhảy tới cột chia hết cho 8 |
| \\ | Dấu gạch chéo ngược | Cần khi viết đường dẫn Windows |
| \" | Dấu nháy kép | Dùng trong chuỗi |
| \' | Dấu nháy đơn | Dùng trong hằng ký tự |
| \0 | Ký tự null | Đánh dấu kết thúc chuỗi |
| \r | Về đầu dòng | Không xuống dòng, dùng làm thanh tiến trình |
| \a | Tiếng chuông | Nhiều terminal đã tắt |
| %% | Dấu phần trăm | Không phải escape, là quy tắc của printf |
Ứng dụng của ký tự về đầu dòng
Ký tự \r đưa con trỏ về đầu dòng hiện tại mà không xuống dòng mới. Kết hợp với fflush, nó tạo ra thanh tiến trình cập nhật tại chỗ.
#include <stdio.h>
#include <unistd.h>
int main(void)
{
for (int i = 0; i <= 100; i += 5) {
printf("\rTiến độ: [%-20.*s] %3d%%",
i / 5, "####################", i);
fflush(stdout); /* bắt buộc, vì chưa có ký tự xuống dòng */
usleep(100000);
}
printf("\nXong\n");
return 0;
}#Ba luồng chuẩn
Mọi chương trình C khi khởi động đều được cấp sẵn ba luồng. Bạn không cần mở chúng.
| Luồng | Số hiệu | Dùng để |
|---|---|---|
| stdin | 0 | Đọc dữ liệu vào, mặc định là bàn phím |
| stdout | 1 | In kết quả bình thường, mặc định là màn hình |
| stderr | 2 | In thông báo lỗi và chẩn đoán |
printf("...") chính là cách viết tắt của fprintf(stdout, "...").
/* Mẫu đúng khi báo lỗi */
if (!f) {
fprintf(stderr, "Không mở được file %s\n", ten_file);
return 1;
}#Cơ chế đệm
Ghi ra màn hình là một lời gọi hệ thống, mà lời gọi hệ thống thì tốn thời gian. Nếu mỗi printf đều gọi thẳng xuống nhân hệ điều hành, chương trình sẽ chậm hàng chục lần. Vì vậy thư viện chuẩn gom chữ vào một bộ đệm trước.
| Chế độ đệm | Áp dụng cho | Xả khi nào |
|---|---|---|
| Đệm theo dòng | stdout khi ra terminal | Gặp ký tự xuống dòng, hoặc đầy, hoặc gọi fflush |
| Đệm đầy | stdout khi ra file hoặc pipe | Đầy bộ đệm, thường 4 KB, hoặc gọi fflush |
| Không đệm | stderr | Ngay lập tức |
Thí nghiệm chứng minh
#include <stdio.h>
#include <unistd.h>
int main(void)
{
printf("A không xuống dòng");
fprintf(stderr, "B là lỗi\n");
sleep(2);
printf(" C\n");
return 0;
}Ba cách kiểm soát
fflush(stdout); /* xả ngay một lần */
setvbuf(stdout, NULL, _IONBF, 0); /* tắt đệm hoàn toàn, chậm nhưng an toàn */
setvbuf(stdout, NULL, _IOLBF, 0); /* ép đệm theo dòng kể cả khi ra file */Tự làm thử
- In số 255 ở bốn hệ: mười, tám, mười sáu chữ thường, mười sáu chữ hoa. Có tiền tố đầy đủ.
- Dùng giá trị trả về của
printfđể in một bảng hai cột tự căn, không dùng độ rộng cố định. - Viết thanh tiến trình chạy từ 0 tới 100 phần trăm. Thử bỏ
fflushvà quan sát khác biệt. - Chạy chương trình
dem.ctheo cả ba cách ở trên, ghi lại thứ tự chữ hiện ra và giải thích. - Viết chương trình in ra rồi cố tình gây lỗi truy cập bộ nhớ. So sánh log nhận được khi in bằng
printfvà khi in bằngfprintf(stderr, ...).
Trình chấm điểm tự động sẽ được bổ sung ở giai đoạn sau. Hiện tại bạn tự chạy thử trên máy.
Tóm tắt
- printf trả về số ký tự đã in, hoặc số âm nếu lỗi.
\nlà một ký tự, và\rđưa con trỏ về đầu dòng mà không xuống dòng.- stdout đệm theo dòng khi ra terminal, đệm đầy khi ra file, còn stderr không đệm.
- Thông báo lỗi luôn ghi ra stderr để không bị mất và tách được khỏi dữ liệu.
- Chuỗi không có
\nthì phải gọifflush(stdout)nếu muốn thấy ngay.