Menu

Nhập xuất trong C++: cin, cout, getline và stream

Cách nhập xuất qua console hoạt động trong C++: in ra bằng cout, đọc bằng cin, bug kinh điển về ký tự xuống dòng khi dùng getline sau cin, và cách khôi phục khi nhập liệu thất bại.

Trang này có trình soạn thảo chạy trực tiếp. Sửa, chạy và xem đầu ra ngay lập tức.

C++ giao tiếp với console như thế nào

C++ nhập xuất qua console bằng các stream trong header <iostream>. Bạn ghi ra ngoài bằng cout (character output) và đọc từ người dùng bằng cin (character input). Chúng dùng hai toán tử mà bạn đã gặp dưới dạng phép dịch bit, ở đây được dùng vào việc khác:

  • << là toán tử chèn (insertion): nó đưa dữ liệu vào cout.
  • >> là toán tử trích xuất (extraction): nó lấy dữ liệu ra khỏi cin và đưa vào một biến.

Một mẹo nhớ chiều: mũi tên chỉ theo hướng dữ liệu chảy. Giờ bạn đã biết cách lưu văn bản trong chuỗi, hãy cùng đưa văn bản đó vào và ra khỏi chương trình.

In ra bằng cout

cout gửi mọi thứ bạn chèn vào tới đầu ra chuẩn. Bạn có thể nối nhiều << trong một câu lệnh, thoải mái trộn văn bản, số và biến.

Mỗi << nối tiếp vào cùng một dòng cho đến khi bạn chèn ký tự xuống dòng. Có hai cách để làm vậy: '\n' chèn một ký tự xuống dòng, còn endl chèn ký tự xuống dòng và flush bộ đệm ra màn hình. Việc flush tốn chi phí thật, nên trong một vòng lặp in hàng nghìn dòng, hãy ưu tiên '\n': stream tự flush khi cần (và luôn flush khi chương trình kết thúc).

// Fine for a one-off message:
cout << "Done" << endl;

// In a hot loop, prefer this - no forced flush each iteration:
for (int i = 0; i < 1000000; ++i)
    cout << i << '\n';

Đọc bằng cin và >>

cin >> variable đọc một token được phân tách bởi khoảng trắng và chuyển nó sang kiểu của biến. Nó bỏ qua mọi dấu cách hay ký tự xuống dòng ở đầu, rồi dừng ở khoảng trắng tiếp theo.

Vì >> dừng ở khoảng trắng, nó rất hợp để đọc từng con số và từng từ, nhưng vô dụng với cả một câu: cin >> word với đầu vào hello world chỉ đọc được hello và để world lại trong bộ đệm cho lần đọc sau.

Đọc cả dòng bằng getline

Để lấy trọn một dòng, kể cả dấu cách, hãy dùng getline(cin, line), hàm này đọc mọi thứ cho đến phím Enter vào một std::string.

Đây là công cụ phù hợp bất cứ khi nào đầu vào có thể chứa dấu cách: họ tên, địa chỉ, câu văn. Chỉ có một cái bẫy chờ bạn ngay khi bạn dùng lẫn getline với >>.

Trang về getline trình bày riêng hàm này: ký tự phân tách tùy chọn, đọc đến hết đầu vào, và đọc tệp từng dòng với std::ifstream.

Bẫy ký tự xuống dòng khi dùng cin với getline

Đây là bug nhập xuất phổ biến nhất trong C++. Khi bạn viết cin >> n, phép trích xuất đọc con số nhưng để lại ký tự xuống dòng (phím Enter bạn vừa nhấn) trong bộ đệm đầu vào. Lệnh getline tiếp theo thấy ngay ký tự xuống dòng còn sót đó, coi như dòng đã kết thúc, và trả cho bạn một chuỗi rỗng mà không hề dừng lại chờ nhập.

int age;
string city;

cin >> age;            // you type 30 and press Enter; '\n' stays in the buffer
getline(cin, city);    // reads the leftover '\n' -> city is "" (empty!)

Cách sửa là bỏ ký tự xuống dòng còn sót đó bằng cin.ignore sau >> và trước getline:

cin.ignore(numeric_limits<streamsize>::max(), '\n') bỏ qua các ký tự cho đến khi đã nuốt một ký tự xuống dòng (hoặc chạm cuối đầu vào). Đây là phiên bản chắc chắn: dạng ngắn cin.ignore() chỉ bỏ một ký tự duy nhất, và sẽ hỏng nếu người dùng gõ thêm dấu cách sau con số. Hãy tập thói quen dùng dạng đầy đủ.

Khi nhập liệu thất bại

Nếu người dùng gõ chữ ở chỗ bạn chờ một con số, phép trích xuất sẽ thất bại: cin rơi vào trạng thái lỗi và biến đích không nhận giá trị nhập (từ C++11 nó được đặt thành 0). Tệ hơn, một khi cin đã ở trạng thái lỗi, mọi lần đọc sau đó cũng bị bỏ qua trong im lặng, nên bạn có thể rơi vào vòng lặp vô hạn.

Việc khôi phục luôn gồm hai bước: cin.clear() đặt lại các cờ lỗi để stream dùng được trở lại, và cin.ignore(...) vứt bỏ những ký tự gây lỗi vẫn còn kẹt trong bộ đệm. Bỏ qua ignore thì đầu vào sai vẫn nằm đó, và lệnh >> tiếp theo lại thất bại: vòng lặp vô hạn kinh điển. Kiểm tra cin >> n trực tiếp trong điều kiện hoạt động được vì stream chuyển thành false khi ở trạng thái lỗi.

Những lỗi cần tránh

  • Dùng cin >> s để đọc một câu. Nó dừng ở dấu cách đầu tiên. Hãy dùng getline cho mọi thứ có dấu cách.
  • Quên cin.ignore giữa >> và getline. Ký tự xuống dòng còn sót cho bạn một dòng rỗng. Hãy xóa bộ đệm trước.
  • Dùng endl ở khắp nơi. Mỗi lần dùng là một lần buộc flush. Mặc định dùng '\n' và chỉ dùng endl khi đầu ra thật sự phải hiện ra ngay.
  • Bỏ mặc một cin bị lỗi. Gõ chữ khi đọc số làm cin hỏng; luôn clear() rồi ignore() trước khi đọc lại.

Câu hỏi thường gặp

Vì sao getline bị bỏ qua ngay sau khi dùng cin trong C++?

cin >> x đọc con số nhưng để lại ký tự xuống dòng do bạn nhấn Enter trong bộ đệm. Lệnh getline tiếp theo đọc đến ký tự xuống dòng còn sót đó và trả về chuỗi rỗng ngay lập tức. Hãy xóa nó trước bằng cin.ignore(numeric_limits<streamsize>::max(), '\n'); sau >> và trước getline.

endl và \n trong C++ khác nhau thế nào?

Cả hai đều kết thúc dòng, nhưng endl còn flush bộ đệm đầu ra ra màn hình, còn '\n' chỉ chèn một ký tự xuống dòng. Việc flush có cái giá của nó: trong một vòng lặp chạy dày đặc, hãy ưu tiên '\n' và để stream tự flush. Chỉ dùng endl khi bạn thực sự cần đầu ra hiện ra ngay.

Làm sao đọc cả một dòng văn bản có dấu cách trong C++?

Dùng getline(cin, line), không phải cin >> line. Toán tử >> dừng ở khoảng trắng đầu tiên, nên chỉ lấy được một từ. getline đọc mọi thứ cho đến phím Enter vào một std::string, kể cả dấu cách.

Hình minh họa các ngôn ngữ lập trình của Coddy

Học lập trình cùng Coddy

Bắt đầu