Trong câu trả lời này, tôi sẽ giả định rằng bạn đang đọc và giải thích các dòng văn bản . Có lẽ bạn đang nhắc người dùng, người đang gõ một cái gì đó và nhấn RETURN. Hoặc có lẽ bạn đang đọc các dòng văn bản có cấu trúc từ một tệp dữ liệu nào đó.
Vì bạn đang đọc các dòng văn bản, nên tổ chức mã của bạn xung quanh chức năng thư viện là đọc, một dòng văn bản. Hàm Standard là fgets()
, mặc dù có các hàm khác (bao gồm getline
). Và sau đó bước tiếp theo là diễn giải dòng văn bản đó bằng cách nào đó.
Đây là công thức cơ bản để gọi fgets
để đọc một dòng văn bản:
char line[512];
printf("type something:\n");
fgets(line, 512, stdin);
printf("you typed: %s", line);
Điều này chỉ đơn giản là đọc trong một dòng văn bản và in lại. Như đã viết, nó có một vài hạn chế, chúng ta sẽ nhận được trong một phút. Nó cũng có một tính năng rất tuyệt vời: số 512 mà chúng tôi đã chuyển qua làm đối số thứ hai fgets
là kích thước của mảng
line
mà chúng tôi yêu cầu fgets
đọc vào. Thực tế này - rằng chúng ta có thể cho biết fgets
nó được phép đọc bao nhiêu - có nghĩa là chúng ta có thể chắc chắn rằng fgets
sẽ không tràn mảng bằng cách đọc quá nhiều vào nó.
Vì vậy, bây giờ chúng ta biết cách đọc một dòng văn bản, nhưng nếu chúng ta thực sự muốn đọc một số nguyên, hoặc một số dấu phẩy động, hoặc một ký tự hoặc một từ đơn thì sao? (Tức là, những gì nếu
scanf
cuộc gọi chúng tôi đang cố gắng để cải thiện trên đã sử dụng một specifier định dạng như %d
, %f
, %c
, hay %s
?)
Thật dễ dàng để diễn giải lại một dòng văn bản - một chuỗi - như bất kỳ thứ gì trong số này. Để chuyển đổi một chuỗi thành một số nguyên, cách đơn giản nhất (mặc dù không hoàn hảo) là thực hiện nó là gọi atoi()
. Để chuyển đổi thành một số dấu phẩy động, có atof()
. (Và cũng có những cách tốt hơn, như chúng ta sẽ thấy trong một phút.) Đây là một ví dụ rất đơn giản:
printf("type an integer:\n");
fgets(line, 512, stdin);
int i = atoi(line);
printf("type a floating-point number:\n");
fgets(line, 512, stdin);
float f = atof(line);
printf("you typed %d and %f\n", i, f);
Nếu bạn muốn người dùng nhập một ký tự (có thể y
hoặc
n
là phản hồi có / không), bạn có thể chỉ cần lấy ký tự đầu tiên của dòng, như thế này:
printf("type a character:\n");
fgets(line, 512, stdin);
char c = line[0];
printf("you typed %c\n", c);
(Tất nhiên, điều này bỏ qua khả năng người dùng đã gõ phản hồi đa ký tự; nó lặng lẽ bỏ qua bất kỳ ký tự phụ nào được nhập.)
Cuối cùng, nếu bạn muốn người dùng gõ một chuỗi chắc chắn không chứa khoảng trắng, nếu bạn muốn xử lý dòng đầu vào
hello world!
vì chuỗi được "hello"
theo sau bởi một thứ khác (đó là những gì scanf
định dạng %s
sẽ làm), trong trường hợp đó, tôi đã bị xơ một chút, rốt cuộc, không dễ để diễn giải lại dòng theo cách đó, vì vậy, câu trả lời cho điều đó một phần của câu hỏi sẽ phải chờ một chút.
Nhưng trước tiên tôi muốn quay lại ba điều tôi đã bỏ qua.
(1) Chúng tôi đã gọi
fgets(line, 512, stdin);
để đọc vào mảng line
và trong đó 512 là kích thước của mảng line
để fgets
biết không tràn vào nó. Nhưng để chắc chắn rằng 512 là số phù hợp (đặc biệt, để kiểm tra xem có thể ai đó đã điều chỉnh chương trình để thay đổi kích thước không), bạn phải đọc lại bất cứ nơi nào line
được khai báo. Điều đó gây phiền toái, vì vậy có hai cách tốt hơn để giữ kích thước đồng bộ. Bạn có thể, (a) sử dụng bộ tiền xử lý để đặt tên cho kích thước:
#define MAXLINE 512
char line[MAXLINE];
fgets(line, MAXLINE, stdin);
Hoặc, (b) sử dụng sizeof
toán tử C :
fgets(line, sizeof(line), stdin);
(2) Vấn đề thứ hai là chúng tôi chưa kiểm tra lỗi. Khi bạn đọc đầu vào, bạn phải luôn kiểm tra khả năng xảy ra lỗi. Nếu vì bất kỳ lý do gì fgets
không thể đọc dòng văn bản bạn yêu cầu, nó chỉ ra điều này bằng cách trả về một con trỏ null. Vì vậy, chúng ta nên làm những việc như
printf("type something:\n");
if(fgets(line, 512, stdin) == NULL) {
printf("Well, never mind, then.\n");
exit(1);
}
Cuối cùng, có một vấn đề là để đọc một dòng văn bản,
fgets
đọc các ký tự và điền chúng vào mảng của bạn cho đến khi nó tìm thấy \n
ký tự kết thúc dòng đó và nó cũng điền \n
ký tự vào mảng của bạn . Bạn có thể thấy điều này nếu bạn sửa đổi ví dụ trước của chúng tôi một chút:
printf("you typed: \"%s\"\n", line);
Nếu tôi chạy cái này và gõ "Steve" khi nó nhắc tôi, nó sẽ in ra
you typed: "Steve
"
Điều đó "
trên dòng thứ hai là bởi vì chuỗi nó đọc và in ra thực sự là "Steve\n"
.
Đôi khi, dòng mới bổ sung đó không thành vấn đề (như khi chúng tôi gọi
atoi
hoặc atof
, vì cả hai đều bỏ qua bất kỳ đầu vào không phải là số nào sau số), nhưng đôi khi nó rất quan trọng. Vì vậy, thường chúng tôi sẽ muốn loại bỏ dòng mới đó. Có một số cách để làm điều đó, mà tôi sẽ nhận được trong một phút. (Tôi biết tôi đã nói điều đó rất nhiều. Nhưng tôi sẽ quay lại với tất cả những điều đó, tôi hứa.)
Tại thời điểm này, bạn có thể suy nghĩ: "Tôi nghĩ bạn nói scanf
là không tốt, và cách nào khác này sẽ tốt hơn rất nhiều Nhưng. fgets
Đang bắt đầu trông giống như một phiền toái gọi. scanf
Là dễ dàng như vậy tôi không thể tiếp tục sử dụng nó!? "
Chắc chắn, bạn có thể tiếp tục sử dụng scanf
, nếu bạn muốn. (Và đối với
những điều thực sự đơn giản, theo một cách nào đó thì đơn giản hơn.) Nhưng, xin vui lòng, đừng khóc với tôi khi nó làm bạn thất bại do một trong 17 quirks và foibles của nó, hoặc đi vào một vòng lặp vô hạn vì đầu vào của bạn không mong đợi, hoặc khi bạn không thể tìm ra cách sử dụng nó để làm điều gì đó phức tạp hơn. Và hãy xem fgets
những phiền toái thực tế:
Bạn luôn phải xác định kích thước mảng. Chà, tất nhiên, điều đó không gây phiền toái gì cả - đó là một tính năng, bởi vì tràn bộ đệm là một điều thực sự tồi tệ.
Bạn phải kiểm tra giá trị trả lại. Trên thực tế, đó là một rửa, bởi vì để sử dụngscanf
chính xác, bạn cũng phải kiểm tra giá trị trả lại của nó.
Bạn phải lột bỏ \n
lưng. Đây là, tôi thừa nhận, một phiền toái thực sự. Tôi ước có một chức năng Tiêu chuẩn mà tôi có thể chỉ cho bạn rằng không có vấn đề nhỏ này. (Xin vui lòng không ai đưa lên gets
.) Nhưng so vớiscanf's
17 phiền toái khác nhau, tôi sẽ nhận điều này phiền toái fgets
bất cứ ngày nào.
Rồi sao để bạn tước dòng mới đó? Ba cách:
(a) Cách rõ ràng:
char *p = strchr(line, '\n');
if(p != NULL) *p = '\0';
(b) Cách khéo léo & nhỏ gọn:
strtok(line, "\n");
Thật không may, cái này không phải lúc nào cũng hoạt động.
(c) Một cách nhỏ gọn và tối nghĩa khác:
line[strcspn(line, "\n")] = '\0';
Và bây giờ đã hết cách, chúng ta có thể quay lại với một thứ khác mà tôi đã bỏ qua: sự không hoàn hảo của atoi()
và atof()
. Vấn đề với họ là họ không cung cấp cho bạn bất kỳ dấu hiệu thành công hay thất bại nào: họ lặng lẽ bỏ qua đầu vào không có chữ số và họ lặng lẽ trả về 0 nếu không có đầu vào số nào cả. Các lựa chọn thay thế ưa thích - cũng có một số lợi thế nhất định - là strtol
và strtod
.
strtol
cũng cho phép bạn sử dụng một cơ sở khác ngoài 10, nghĩa là bạn có thể nhận được hiệu ứng của (trong số những thứ khác) %o
hoặc %x
vớiscanf
. Nhưng chỉ ra cách sử dụng các chức năng này một cách chính xác là một câu chuyện, và sẽ quá mất tập trung từ những gì đã biến thành một câu chuyện khá phân mảnh, vì vậy tôi sẽ không nói gì thêm về chúng bây giờ.
Phần còn lại của câu chuyện chính liên quan đến đầu vào mà bạn có thể đang cố phân tích nó phức tạp hơn chỉ là một số hoặc ký tự. Điều gì sẽ xảy ra nếu bạn muốn đọc một dòng chứa hai số hoặc nhiều từ được phân tách bằng khoảng trắng hoặc dấu chấm câu cụ thể? Đó là nơi mọi thứ trở nên thú vị và nơi mọi thứ có thể trở nên phức tạp nếu bạn đang cố gắng thực hiện mọi thứ bằng cách sử dụng scanf
và bây giờ có nhiều tùy chọn hơn khi bạn đọc sạch một dòng văn bản fgets
, mặc dù toàn bộ câu chuyện về tất cả các tùy chọn đó có lẽ có thể lấp đầy một cuốn sách, vì vậy chúng ta sẽ chỉ có thể làm trầy xước bề mặt ở đây.
Kỹ thuật yêu thích của tôi là chia dòng thành các "từ" được phân tách bằng khoảng trắng, sau đó thực hiện thêm một số từ với mỗi "từ". Một chức năng tiêu chuẩn chính để thực hiện điều này là
strtok
(cũng có vấn đề của nó và cũng đánh giá một cuộc thảo luận hoàn toàn riêng biệt). Sở thích riêng của tôi là một chức năng chuyên dụng để xây dựng một loạt các con trỏ cho mỗi "từ" tách rời, một chức năng tôi mô tả trong
các ghi chú khóa học này . Ở bất cứ giá nào, một khi bạn đã có "từ", bạn có thể xử lý thêm từng từ, có lẽ với cùng atoi
/ atof
/ strtol
/ strtod
chức năng mà chúng tôi đã xem xét.
Nghịch lý thay, mặc dù chúng ta đã dành một lượng thời gian và nỗ lực khá lớn ở đây để tìm ra cách di chuyển khỏi scanf
, một cách tốt khác để đối phó với dòng văn bản chúng ta vừa đọc
fgets
là chuyển nó đến sscanf
. Theo cách này, bạn kết thúc với hầu hết các lợi thế của scanf
, nhưng không có hầu hết các nhược điểm.
Nếu cú pháp đầu vào của bạn đặc biệt phức tạp, có thể phù hợp để sử dụng thư viện "regrec" để phân tích cú pháp.
Cuối cùng, bạn có thể sử dụng bất cứ giải pháp phân tích cú pháp ad hoc nào phù hợp với bạn. Bạn có thể di chuyển qua dòng một ký tự tại một thời điểm bằng một
char *
con trỏ kiểm tra các ký tự mà bạn mong đợi. Hoặc bạn có thể tìm kiếm các ký tự cụ thể sử dụng chức năng thích strchr
hoặc strrchr
hoặc strspn
hoặc strcspn
hoặc strpbrk
. Hoặc bạn có thể phân tích / chuyển đổi và bỏ qua các nhóm ký tự chữ số bằng cách sử dụng strtol
hoặc các
strtod
hàm mà chúng ta đã bỏ qua trước đó.
Rõ ràng có nhiều điều có thể nói, nhưng hy vọng phần giới thiệu này sẽ giúp bạn bắt đầu.
(r = sscanf("1 2 junk", "%d%d", &x, &y)) != 2
không phát hiện xấu như văn bản không phải là số.