Menu

stringstream trong C++: phân tích và ghép chuỗi

Cách dùng std::stringstream, istringstream và ostringstream để phân tích văn bản, tách chuỗi theo khoảng trắng, chuyển đổi giữa chuỗi và số, và tạo chuỗi có định dạng ngay trong bộ nhớ.

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.

Một stream nằm trong một chuỗi

Bạn đã biết cin và cout: các stream nối với bàn phím và màn hình. String stream cũng là ý tưởng đó, chỉ khác là dữ liệu nằm trong một std::string trong bộ nhớ. Bạn ghi vào nó bằng << và lấy giá trị ra bằng >>, y như các stream của console, nhưng không có gì chạm tới terminal.

Chỉ một ý tưởng đó giải quyết hai vấn đề thường ngày: phân tích văn bản thành các giá trị có kiểu, và ghép một chuỗi có định dạng từ nhiều mảnh khác nhau. Mọi thứ nằm trong header <sstream> và có ba loại:

  • istringstream: chỉ đọc, để phân tích đầu vào.
  • ostringstream: chỉ ghi, để tạo đầu ra.
  • stringstream: cả hai chiều.

ss.str() trả về mọi thứ đã ghi từ trước đến giờ dưới dạng một string thông thường. Các toán tử << đã định dạng int thành văn bản y như cout làm, chỉ khác là bạn giữ lại kết quả thay vì in ra.

Phân tích: lấy ra các giá trị có kiểu

Sức mạnh thật sự thể hiện ở >>. Đưa vào stream một đoạn văn bản, mỗi lần trích sẽ chuyển một token thành kiểu bạn yêu cầu, bỏ qua khoảng trắng ở giữa. Đây là cách gọn gàng để tách một dòng thành nhiều trường có kiểu:

Mỗi >> đọc đến khoảng trắng tiếp theo rồi chuyển đổi: "Ada" thành string, "36" thành int, "5.5" thành double. Để ý ở đây dùng istringstream: kiểu chỉ đọc này cho thấy rõ bạn chỉ đang phân tích.

Chuyển chuỗi sang số (an toàn)

Một việc thường gặp là biến một chuỗi như "42" thành int. stringstream làm được việc đó, và còn cho bạn biết văn bản có thật sự là một số hợp lệ hay không, điều mà atoi không bao giờ làm:

Ở đây ss >> value đọc 123, dừng ở chữ a và thành công, nên hãy luôn kiểm tra toàn bộ chuỗi nếu điều đó quan trọng. Một cách kiểm tra chắc chắn là đọc số rồi xác nhận không còn gì đáng kể sót lại. Với các chuyển đổi một giá trị đơn giản, std::stoi, std::stod và các hàm tương tự ngắn gọn hơn; hãy dùng stringstream khi một chuỗi chứa nhiều giá trị trộn lẫn.

Tách chuỗi theo ký tự phân cách

Tách văn bản là việc được tìm kiếm nhiều nhất với string stream. Kết hợp một stream với std::getline, hàm có thể đọc đến bất kỳ ký tự phân cách nào, không chỉ ký tự xuống dòng:

Mẫu while (getline(...)) lặp cho đến khi stream hết dữ liệu, vì getline trả về chính stream và stream được đánh giá là false khi không còn gì. Mỗi token được thêm vào một vector, nơi tự nhiên để gom một số lượng mảnh tách ra không cố định. Bỏ đối số thứ ba đi thì bạn sẽ tách theo khoảng trắng bằng >>; hãy chọn ký tự phân cách mà dữ liệu của bạn dùng.

Tạo chuỗi trong bộ nhớ

Theo chiều ngược lại, ostringstream ghép một chuỗi có định dạng mà không có nguy cơ tràn bộ đệm như sprintf hay việc nối chuỗi thủ công dễ vỡ. Nó rất hợp cho dòng log, tên tệp hay thông báo ghép từ số và chữ:

Mọi định dạng <iomanip> bạn dùng với cout như setw, setfill, setprecision, hex đều hoạt động y hệt trên string stream, nên bạn có đầu ra đệm số 0, độ rộng cố định hay hệ thập lục phân được ghi thẳng vào một string.

Cái bẫy: đặt lại trước khi dùng lại

Cái bẫy ai cũng dính là dùng lại một stream mà không đặt lại nó. Khi stream đã đọc hết dữ liệu, các cờ eof và fail của nó bật lên và giữ nguyên, và mọi lệnh >> sau đó đều âm thầm không làm gì: không báo lỗi, chỉ có giá trị cũ:

stringstream ss("10");
int a, b;
ss >> a;            // a = 10, stream now at end -> eof flag set
ss.str("20");       // load new text...
ss >> b;            // FAILS SILENTLY - error flags are still set, b is unchanged

Bạn phải xóa cả trạng thái lỗi lẫn nội dung. Thứ tự đúng là clear() trước, rồi đến str():

Hai lỗi liên quan: ss.clear() đặt lại các cờ nhưng không làm rỗng bộ đệm (dùng ss.str("") cho việc đó), và một stringstream mà bạn cứ nối thêm bằng << sẽ lớn mãi. Hãy tạo một stream mới bên trong vòng lặp thay vì dùng lại một stream đã bẩn ở mỗi lần lặp.

Tiếp theo: mảng

String stream cho bạn một cách gọn gàng để biến văn bản thành giá trị có kiểu và ngược lại, thường là cả một danh sách giá trị, như vector bạn đã điền khi tách CSV. Để lưu và truy cập theo chỉ số các tập giá trị cố định, bạn cần container cơ bản nhất của ngôn ngữ. Tiếp theo, chúng ta sẽ tìm hiểu mảng: cách khai báo, truy cập theo chỉ số, duyệt qua, và tránh việc truy cập ngoài phạm vi, nguyên nhân của rất nhiều hành vi không xác định trong C++.

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

stringstream trong C++ là gì?

std::stringstream là một stream dựa trên một string thay vì bàn phím hay tệp. Bạn ghi vào nó bằng << và đọc ra bằng >>, giống hệt cout và cin, nên nó là công cụ chuẩn để phân tích văn bản và tạo chuỗi trong bộ nhớ. Nó nằm trong header <sstream>.

Làm sao chuyển chuỗi sang int trong C++ bằng stringstream?

Đưa văn bản vào một stream rồi trích ra một số: stringstream ss("42"); int n; ss >> n;. Kiểm tra if (ss) (hoặc if (ss >> n)) để chắc chắn việc chuyển đổi thật sự thành công. Với trường hợp đơn giản, std::stoi ngắn gọn hơn, nhưng stringstream phát huy tác dụng khi một chuỗi chứa nhiều giá trị trộn lẫn.

Tại sao phải gọi clear() trên stringstream?

Khi một stream đã đọc hết dữ liệu, các bit eof/fail của nó bị giữ nguyên và mọi lệnh >> sau đó đều âm thầm không làm gì. Nếu bạn dùng lại cùng một stringstream với nội dung mới, hãy gọi ss.clear() để xóa các cờ lỗi đó, rồi ss.str(newText) để nạp dữ liệu mới; nếu không, các lần đọc sẽ thất bại mà không báo gì.

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