Trong bất cứ một ngôn ngữ lập trình nào, việc tương tác trực tiếp với máy tính là rất quan trọng và cần thiết. Một chương trình được viết ra cần có khả năng nhận dữ liệu từ người dùng và hiển thị những kết quả thu được sau tính toán. Do đó việc thành thạo trong việc nhập/xuất dữ liệu là điều không thể thiếu đối với mỗi lập trình viên. Bài viết này sẽ giúp hiểu rõ hơn về thao tác nhập/xuất dữ liệu thông qua phương tiện nhập xuất chuẩn.
Tổng quan
Trong C hay bất cứ ngôn ngữ lập trình nào khác, việc nhập, xử lý và xuất dữ liệu có thể được thực hiện theo hai cách:
- Thông qua phương tiện nhập/xuất chuẩn (Standard Input/Output).
- Thông qua tập tin (File)
Bài viết này sẽ chỉ đề cập đến Standard Input/Output.
Thư viện nhập xuất chuẩn trong C là <stdio.h>
, do đó các lập trình viên cần khai báo thư viện này trước khi thực hiện việc nhập xuất dữ liệu bằng chỉ thị #include.
<stdio.h> cung cấp cho hai hàm nhập và xuất dữ liệu có định dạng:
printf()
: Hàm xuất có định dạng.scanf()
: Hàm nhập có định dạng.
Output với printf
Hàm printf trong C có nguyên mẫu của hàm (prototype) như sau:
int printf(“<chuỗi định dạng>”, <danh sách tham số>);
Trong đó, int là kiểu trả về của hàm, là giá trị đại diện cho hàm sau khi hết phạm vi của hàm. Chuỗi định dạng (Format string) có nhiệm vụ định dạng dữ liệu xuất ra màn hình. Danh sách tham số có thể bao gồm biến, hằng số, biểu thức và hàm (function) và được phân cách bằng dấu “,
”.
Chuỗi định dạng
Chuỗi định dạng phải tương ứng với danh sách tham số về số lượng, kiểu dữ liệu và thứ tự. Chuỗi định dạng luôn được đặt bên trong cặp dấu “” và bao gồm một hoặc nhiều thành phần sau:
- Ký tự văn bản (Text characters): là những ký tự in được, bao gồm các chữ cái, chữ số và các ký tự đặc biệt trong bảng mã ASCII.
- Các ký tự không in được: bao gồm một số ký tự điều khiển như tab (
\t
), xuống dòng (\n
), khoảng trắng, … Khoảng trắng thường được sử dụng để phân cách các trường (field) dữ liệu được xuất ra. - Lệnh định dạng: là các lệnh quy định cách mà dữ liệu được xuất ra màn hình.
Lệnh định dạng có cấu trúc như sau:
%[flags][width][.precision][length]specifier
Trong đó, flags
, width
, .precision
, length
là một số bổ từ tùy chọn để thay đổi đặc tả gốc cho phù hợp với mong muốn của lập trình viên.
Specifier là đặc tả cho kiểu dữ liệu. Một số đặc tả thường dùng:
d , i |
Số nguyên có dấu |
u |
Số nguyên không dấu |
f |
Số thực |
c |
Ký tự |
s |
Chuỗi ký tự |
Ngoài ra còn một số bổ từ và lệnh định dạng khác đề cập đến.
#include <stdio.h> int main () { printf ("String only\n"); //'\n' isn't shown printf ("%s\n", "Also string"); printf ("int, float, char: %d, %f, %c\n", 7411, 3.14, 'R'); printf ("Preceding with blanks: d\n", 7411); printf ("float rounding: %.2f\n", 3.1415929); return 0; }
Input với scanf
Prototype của hàm scanf
trong C như sau:
int scanf(“<chuỗi định dạng>”, <danh sách tham số>);
Tương tự như hàm printf
, danh sách tham số của scanf
cũng được phân cách bằng dấu “,
”. Tuy nhiên, tham số phải được truyền vào dưới dạng tham chiếu, tức truyền vào địa chỉ của biến. Tham chiếu của các kiểu dữ liệu cơ bản (primitive data type) như int
, float
, char
, … là &
(address-of operator) cùng với tên biến. Đối với các kiểu dữ liệu dẫn xuất (ví dụ như chuỗi ký tự), tham chiếu đơn giản là tên biến.
Chuỗi định dạng
Chuỗi định dạng của hàm scanf
có thể bao gồm những thành phần sau:
- Whitespace character: bao gồm khoảng trắng, ký tự xuống dòng và tab. Hàm
scanf
sẽ tự động bỏ qua bất kỳ whitespace character nào có mặt trong chuỗi định dạng. - Lệnh định dạng: có chức năng định dạng dữ liệu nhập vào từ bàn phím. Nó sẽ quyết định nhận lượng dữ liệu bao nhiêu cho biến tương ứng.
- Các ký tự khác: dùng để phân cách các lệnh định dạng. Các ký tự này dùng để nhập vào dữ liệu một cách có định dạng (ví dụ như ngày tháng năm). Khi nhập bắt buộc phải nhập đúng theo định dạng đó. Do đó chỉ nên sử dụng trong một số trường hợp đơn giản, quen thuộc và phải có chú thích để người sử dụng không bị nhầm lẫn.
Tương tự như hàm printf
, chuỗi định dạng của scanf
có cấu trúc như sau:
%[*][width][length]specifier
Hiện thực ví dụ sau để nắm được cách sử dụng hàm scanf
cũng như các lệnh định dạng thông dụng:
#include <stdio.h> int main () { printf ("Welcome to Stdio!\n"); int a; float b; printf ("Enter an integer and a float: "); scanf ("%d %f", &a, &b); printf ("The integer you've entered is %d\nThe float you've entered is %f\n", a, b); char *s = new char[100]; printf ("Enter a string: "); scanf ("%s", s); printf ("The string you've entered is %s\n", s); delete[] s; int dd, mm, yyyy; printf ("Enter a date: "); //Format: dd/mm/yyyy scanf ("%d/%d/%d", &dd, &mm, &yyyy); printf ("The date you've entered is %2d/%2d/%4d\n", dd, mm, yyyy); return 0; }
Một số vấn đề thường gặp
Không truyền tham chiếu vào hàm scanf
Đây là lỗi thường gặp nhất. Với những project nhỏ thì lỗi này có thể phát hiện được dễ dàng. Tuy nhiên, khi làm việc với những dự án lớn, việc phát hiện và khắc phục lỗi này là cực kỳ khó khăn, vì đây không phải là lỗi trong quá trình Build nên không xác định được vị trí dòng code bị lỗi. Do đó lỗi này là đặc biệt nghiêm trọng và cần được khắc phục trong quá trình học tập.
Cách khắc phục: thêm toán tử &
vào trước tên biến (primitive data type).
Undeclared variables/Uninitialize variables
Sử dụng biến mà không khai báo hoặc quên khởi tạo giá trị ban đầu cho biến. Đây không phải là lỗi nghiêm trọng và rất dễ khắc phục, tuy nhiên nó cho thấy sự thiếu cẩn thận trong công việc, hãy cố gắng khắc phục sớm.
Trôi dòng lệnh khi xử lý chuỗi
Trong C, dòng (stream) vào tiêu chuẩn là stdin
. Các hàm như scanf
, gets
, getchar
, … đều nhận dữ liệu từ stdin
. Khi trên stdin
không còn dữ liệu, các hàm nhập dữ liệu sẽ yêu cầu người dùng nhập vào từ bàn phím. Các hàm trên chỉ nhận đủ dữ liệu mà chúng yêu cầu (trong trường hợp này, scanf
chỉ nhận chuỗi không có khoảng trắng), do đó một phần dữ liệu còn sót lại trên stdin
, có thể là ký tự \n
hoặc phần dữ liệu sau khoảng trắng. Điều này ảnh hưởng đến các hàm nhập dữ liệu phía sau.
Có thể khắc phục bằng cách làm rỗng bộ đệm stdin
trước mỗi hàm nhập để đảm bảo độ chính xác của chương trình. Câu lệnh như sau:
fflush(stdin);
Hàm fflush()
nằm trong thư viện <stdio.h>
nên có thể dễ dàng sử dụng mà không phải include thêm thư viện khác.
Error C4996 trong Visual Studio 2012 về sau
Từ Visual Studio 2012 về sau, một số hàm đã được Microsoft loại bỏ hoặc thay thế bởi những hàm tương tự. Lý do là những hàm này không còn an toàn nữa.
Có khá nhiều cách khắc phục lỗi này, xin hướng dẫn một vài cách như sau:
- Sử dụng các hàm được khuyến nghị bởi Microsoft. Các hàm này được hiển thị trong thông báo lỗi (
scanf_s
thay choscanf
,printf_s
thay choprintf
, …) - Khai báo thêm một cờ (flag) nhằm bỏ qua các cảnh báo này ở đầu chương trình. Khai báo như sau:
#define _CRT_SECURE_NO_WARNINGS
- Một cách khác nữa là tắt cảnh báo trực tiếp trong project. Vào Project Properties → Configuration Properties → C/C++ > Preprocessor. Tại dòng Preprocessor Definitions, chọn Edit và thêm dòng
_CRT_SECURE_NO_WARNINGS
. Nhấn OK để thay đổi có hiệu lực.
Đọc thêm Compiler Warning C4996 và C Run-time Library hay _CRT_SECURE_NO_WARNINGS (CRT).