Bài 29.5: Bài tập thực hành Stringstream trong C++

Bài 29.5: Bài tập thực hành Stringstream trong C++
Chào mừng quay trở lại với series học lập trình C++! Chúng ta đã cùng nhau đi qua rất nhiều khái niệm cơ bản và nâng cao. Đến với bài hôm nay, chúng ta sẽ khám phá một công cụ cực kỳ hữu ích khi làm việc với chuỗi và dữ liệu trong C++: Stringstream!
Bạn đã quen với việc nhập dữ liệu từ bàn phím bằng cin
và xuất dữ liệu ra màn hình bằng cout
. Nhưng điều gì sẽ xảy ra nếu bạn muốn "đọc" dữ liệu từ một chuỗi có sẵn thay vì từ bàn phím? Hoặc bạn muốn "ghi" dữ liệu (như số, biến) vào một chuỗi thay vì in ra màn hình? Đây chính là lúc stringstream
tỏa sáng rực rỡ!
Stringstream là gì và tại sao nó lại "ngon"?
Stringstream là một lớp được cung cấp trong thư viện chuẩn C++ thông qua header <sstream>
. Về cơ bản, nó cho phép bạn coi một đối tượng string
như một luồng (stream). Điều này có nghĩa là bạn có thể sử dụng các toán tử >>
(extraction) để trích xuất dữ liệu từ chuỗi, và toán tử <<
(insertion) để chèn dữ liệu vào chuỗi, giống hệt cách bạn làm với cin
và cout
.
Lợi ích to lớn của stringstream
là khả năng kết hợp sự linh hoạt của luồng I/O với khả năng lưu trữ và thao tác của string
. Nó giải quyết hiệu quả hai vấn đề phổ biến:
- Parsing (Phân tích/Trích xuất): Lấy các phần dữ liệu có cấu trúc (như số, từ, v.v.) ra khỏi một chuỗi. Ví dụ: trích xuất tuổi và tên từ chuỗi "John 30".
- Formatting (Định dạng/Ghép nối): Kết hợp nhiều loại dữ liệu khác nhau (số nguyên, số thực, chuỗi) thành một chuỗi duy nhất theo định dạng mong muốn. Ví dụ: tạo chuỗi "Sản phẩm A - Giá: 150.00 USD" từ các biến riêng lẻ.
Thư viện <sstream>
cung cấp ba lớp chính:
istringstream
: Cho phép đọc/trích xuất dữ liệu từ chuỗi (Input String Stream).ostringstream
: Cho phép ghi/chèn dữ liệu vào chuỗi (Output String Stream).stringstream
: Kết hợp cả hai chức năng đọc và ghi (Bidirectional String Stream).
Trong thực tế, stringstream
là lớp được sử dụng phổ biến nhất vì sự tiện lợi của nó cho cả hai mục đích.
Thực hành: Bắt tay vào code với Stringstream!
Để sử dụng stringstream
, bạn chỉ cần include header <sstream>
.
#include <iostream>
#include <sstream> // Thêm thư viện này
#include <string>
// Có thể cần thêm các thư viện khác tùy vào dữ liệu bạn xử lý, ví dụ:
// #include <vector>
// #include <iomanip>
Ví dụ 1: Parsing cơ bản - Trích xuất số và từ từ chuỗi
Giả sử bạn có một chuỗi chứa cả số và chữ, và bạn muốn tách chúng ra. stringstream
làm điều này rất dễ dàng.
#include <iostream>
#include <sstream>
#include <string>
int main() {
string du_lieu_text = "So luong: 100, Mat hang: Ao Thun";
stringstream ss(du_lieu_text); // Khởi tạo stringstream với chuỗi cần xử lý
string phan_dau;
int so_luong;
string phan_giua;
string mat_hang;
// Sử dụng toán tử >> để trích xuất dữ liệu
// >> sẽ dừng lại khi gặp khoảng trắng hoặc dấu phân cách mặc định
ss >> phan_dau >> so_luong >> phan_giua >> mat_hang;
cout << "Phan dau: " << phan_dau << endl; // Output: Phan dau: So
cout << "So luong: " << so_luong << endl; // Output: So luong: 100
cout << "Phan giua: " << phan_giua << endl; // Output: Phan giua: Mat
cout << "Mat hang: " << mat_hang << endl; // Output: Mat hang: Ao
return 0;
}
Giải thích:
Chúng ta tạo một stringstream
tên là ss
và khởi tạo nó với du_lieu_text
. Sau đó, chúng ta sử dụng toán tử >>
để đọc dữ liệu từ ss
vào các biến phan_dau
, so_luong
, phan_giua
, mat_hang
. Giống như cin
, stringstream
sẽ tự động bỏ qua khoảng trắng và chuyển đổi kiểu dữ liệu khi cần thiết (ví dụ: từ chuỗi "100" sang số nguyên 100
).
Tuy nhiên, bạn thấy ở ví dụ trên, việc trích xuất dựa vào khoảng trắng có thể không đúng ý nếu dữ liệu có cấu trúc phức tạp hơn (ví dụ: "So luong:" bị tách thành "So").
Ví dụ 2: Parsing với Delimiter - Sử dụng getline
Khi dữ liệu được phân cách bởi một ký tự cụ thể (như dấu phẩy trong CSV - Comma Separated Values), chúng ta thường kết hợp stringstream
với hàm getline
.
#include <iostream>
#include <sstream>
#include <string>
#include <vector> // Để lưu các phần dữ liệu
int main() {
string du_lieu_csv = "Tran Van A,25,Ha Noi,Ky Su";
stringstream ss(du_lieu_csv); // Khởi tạo với chuỗi CSV
string phan_du_lieu;
vector<string> cac_thong_tin;
// Sử dụng getline để đọc từng phần, phân cách bởi dấu ','
// getline(luong, bien_lưu, ky_tu_phan_cach)
while (getline(ss, phan_du_lieu, ',')) {
cac_thong_tin.push_back(phan_du_lieu);
}
// Xuất các thông tin đã trích xuất
if (cac_thong_tin.size() >= 4) {
cout << "Ho Ten: " << cac_thong_tin[0] << endl; // Output: Ho Ten: Tran Van A
cout << "Tuoi: " << cac_thong_tin[1] << endl; // Output: Tuoi: 25
cout << "Dia Chi: " << cac_thong_tin[2] << endl;// Output: Dia Chi: Ha Noi
cout << "Nghe Nghiep: " << cac_thong_tin[3] << endl; // Output: Nghe Nghiep: Ky Su
} else {
cout << "Chuoi CSV khong dung dinh dang." << endl;
}
return 0;
}
Giải thích:
Ở đây, chúng ta dùng getline(ss, phan_du_lieu, ',')
. Hàm này sẽ đọc từ luồng ss
cho đến khi gặp ký tự ,
và lưu phần đọc được vào biến phan_du_lieu
. Vòng lặp while
sẽ tiếp tục cho đến khi getline
không đọc được gì nữa (đến cuối chuỗi). Mỗi phần dữ liệu (tên, tuổi, địa chỉ, nghề nghiệp) được đẩy vào một vector
.
Ví dụ 3: Formatting cơ bản - Ghép nối dữ liệu thành chuỗi
Bây giờ, hãy xem cách sử dụng stringstream
để tạo ra một chuỗi từ các biến có kiểu dữ liệu khác nhau.
#include <iostream>
#include <sstream>
#include <string>
int main() {
string ten_san_pham = "Laptop XYZ";
int so_luong_ton = 50;
double gia_tien = 1250.75;
stringstream ss; // Khởi tạo stringstream rỗng
// Sử dụng toán tử << để chèn dữ liệu vào stringstream
ss << "Thong tin san pham: "
<< ten_san_pham
<< " - So luong: "
<< so_luong_ton
<< " - Gia: "
<< gia_tien
<< " USD.";
// Lấy chuỗi kết quả từ stringstream
string chuoi_thong_tin = ss.str();
cout << chuoi_thong_tin << endl; // Output: Thong tin san pham: Laptop XYZ - So luong: 50 - Gia: 1250.75 USD.
return 0;
}
Giải thích:
Chúng ta tạo một stringstream
rỗng. Sau đó, dùng toán tử <<
để "in" các biến và chuỗi cố định vào ss
. stringstream
tự động chuyển đổi các kiểu dữ liệu (int, double) thành dạng chuỗi và nối chúng lại. Cuối cùng, hàm ss.str()
trả về toàn bộ nội dung của stringstream
dưới dạng một string
.
Ví dụ 4: Formatting nâng cao với Manipulators
Giống như khi sử dụng cout
, bạn có thể sử dụng các manipulator (bộ điều khiển định dạng) từ thư viện <iomanip>
với stringstream
để kiểm soát cách dữ liệu được chuyển đổi thành chuỗi.
#include <iostream>
#include <sstream>
#include <string>
#include <iomanip> // Cần thư viện này cho các manipulator
int main() {
double diem_trung_binh = 8.56789;
int ma_san_pham_hex = 255; // Tương đương FF trong hex
stringstream ss;
// Định dạng số thực với số chữ số thập phân cố định
ss << "Diem trung binh (2 chu so thap phan): "
<< fixed << setprecision(2) // Sử dụng manipulator
<< diem_trung_binh << endl; // Output: Diem trung binh (2 chu so thap phan): 8.57
// Định dạng số nguyên sang hệ thập lục phân (hexadecimal)
ss << "Ma san pham (Hex): "
<< hex << uppercase // Sử dụng manipulator
<< ma_san_pham_hex << endl; // Output: Ma san pham (Hex): FF
// Lấy chuỗi kết quả
string chuoi_dinh_dang = ss.str();
cout << chuoi_dinh_dang;
/* Output:
Diem trung binh (2 chu so thap phan): 8.57
Ma san pham (Hex): FF
*/
return 0;
}
Giải thích:
Chúng ta include <iomanip>
để sử dụng fixed
và setprecision(2)
(để định dạng số thực với 2 chữ số sau dấu thập phân), cùng với hex
và uppercase
(để hiển thị số nguyên ở dạng hệ 16 và chữ hoa). Các manipulator này hoạt động với stringstream
theo cách tương tự như với cout
, cho phép bạn kiểm soát chi tiết hơn quá trình chuyển đổi dữ liệu sang chuỗi.
Ví dụ 5: Xóa và Tái sử dụng Stringstream
Nếu bạn cần sử dụng lại cùng một đối tượng stringstream
cho một thao tác khác, bạn cần "dọn dẹp" nó. Có hai việc cần làm: xóa nội dung buffer và xóa các cờ trạng thái (ví dụ: cờ EOF - End Of File).
#include <iostream>
#include <sstream>
#include <string>
int main() {
stringstream ss;
// Lần dùng 1: Ghi dữ liệu vào
ss << "Hello " << "World!";
cout << "Noi dung lan 1: " << ss.str() << endl; // Output: Noi dung lan 1: Hello World!
// Chuẩn bị tái sử dụng: Xóa buffer và cờ trạng thái
ss.str(""); // Gán chuỗi rỗng để xóa nội dung
ss.clear(); // Xóa các cờ trạng thái (như EOF, failbit)
// Lần dùng 2: Ghi dữ liệu khác vào
ss << "So luong: " << 99;
cout << "Noi dung lan 2: " << ss.str() << endl; // Output: Noi dung lan 2: So luong: 99
// Chuẩn bị tái sử dụng lần nữa: Xóa buffer và cờ trạng thái
ss.str("123 456"); // Gán chuỗi mới để đọc
ss.clear(); // Xóa cờ trạng thái (rất quan trọng khi đọc lại sau khi đã đọc hết)
// Lần dùng 3: Đọc dữ liệu từ chuỗi mới
int x, y;
ss >> x >> y;
cout << "Doc du lieu lan 3: x = " << x << ", y = " << y << endl; // Output: Doc du lieu lan 3: x = 123, y = 456
return 0;
}
Giải thích:
ss.str("")
sẽ thiết lập nội dung bên trong stringstream
thành một chuỗi rỗng, hiệu quả là xóa buffer. Tuy nhiên, nếu trước đó bạn đã đọc hết dữ liệu từ stringstream
, cờ trạng thái eofbit
(end-of-file bit) sẽ được bật lên, khiến các thao tác đọc hoặc ghi tiếp theo bị lỗi. ss.clear()
sẽ xóa tất cả các cờ trạng thái (bao gồm eofbit
, failbit
, badbit
), đưa stringstream
về trạng thái tốt (goodbit
) và sẵn sàng cho các thao tác mới. Khi bạn muốn đọc từ một chuỗi mới, bạn dùng ss.str("chuoi moi")
để gán chuỗi đó vào buffer, và vẫn cần ss.clear()
để đảm bảo trạng thái luồng là sạch.
Một vài lưu ý nhỏ khi dùng Stringstream
- Kiểm tra lỗi: Khi thực hiện các thao tác đọc (
>>
) từstringstream
, hãy luôn kiểm tra xem việc đọc có thành công không. Bạn có thể làm điều này giống như vớicin
, ví dụ:if (ss >> bien) { ... }
hoặc kiểm tra trạng thái của luồng sau khi đọc:if (!ss.fail()) { ... }
. - Hiệu quả: Đối với việc nối chuỗi đơn giản, việc sử dụng toán tử
+
hoặc phương thứcappend()
củastring
có thể nhanh hơn và đơn giản hơn.stringstream
thực sự phát huy thế mạnh khi bạn cần định dạng phức tạp, chuyển đổi kiểu dữ liệu giữa số/kiểu khác và chuỗi, hoặc phân tích chuỗi dựa trên cấu trúc phức tạp hơn là chỉ nối đơn thuần. - Chọn đúng lớp: Mặc dù
stringstream
dùng được cho cả hai mục đích, đôi khi sử dụngistringstream
hoặcostringstream
có thể rõ ràng hơn về ý định của bạn (chỉ đọc hoặc chỉ ghi).
Bài tập ví dụ: C++ Bài 18.B2: Kiểm tra nhu cầu đăng ký
Kiểm tra nhu cầu đăng ký
FullHouse Dev cần tổ chức một buổi giảng dạy trực tuyến và cần thiết lập một cuộc họp kéo dài chính xác X phút.
Nền tảng họp trực tuyến hỗ trợ cuộc họp tối đa 30 phút mà không cần đăng ký, và cuộc họp không giới hạn thời gian nếu có đăng ký.
Hãy xác định xem FullHouse Dev có cần đăng ký hay không để thiết lập cuộc họp.
INPUT FORMAT
- Dòng đầu tiên chứa số nguyên T — số lượng bộ test.
- Mỗi bộ test chứa một số nguyên X — thời lượng của buổi giảng dạy.
OUTPUT FORMAT
- Với mỗi bộ test, in ra một dòng duy nhất, YES nếu FullHouse Dev cần đăng ký, ngược lại in NO.
CONSTRAINTS
- 1 ≤ T ≤ 100
- 1 ≤ X ≤ 100
Ví dụ
Input
4
50
3
30
80
Output
YES
NO
NO
YES
Giải thích:
- Test 1: Không có đăng ký, nền tảng chỉ cho phép thời lượng 30 phút. Vì FullHouse Dev cần tổ chức buổi giảng dạy 50 phút, nên cần phải đăng ký.
- Test 2: Không cần đăng ký vì buổi giảng chỉ kéo dài 3 phút.
- Test 3: Không cần đăng ký vì buổi giảng kéo dài đúng 30 phút.
- Test 4: Cần đăng ký vì buổi giảng kéo dài 80 phút, vượt quá giới hạn 30 phút của phiên bản không đăng ký. Chào bạn, đây là hướng dẫn để giải bài toán này bằng C++ theo yêu cầu:
Đọc số lượng bộ test: Đầu tiên, bạn cần đọc số nguyên
T
từ đầu vào. Đây là số lần bạn sẽ phải xử lý logic kiểm tra.Vòng lặp xử lý test case: Sử dụng một vòng lặp (ví dụ:
for
hoặcwhile
) để lặp lạiT
lần. Mỗi lần lặp tương ứng với một bộ test.Trong mỗi lần lặp:
- Đọc thời lượng cuộc họp: Đọc số nguyên
X
(thời lượng buổi giảng dạy) cho bộ test hiện tại. - Kiểm tra điều kiện: Nền tảng cho phép cuộc họp tối đa 30 phút mà không cần đăng ký. Điều này có nghĩa là nếu thời lượng
X
lớn hơn 30 phút, bạn sẽ cần đăng ký. Ngược lại (nếuX
nhỏ hơn hoặc bằng 30 phút), bạn không cần đăng ký. - In kết quả:
- Nếu điều kiện cần đăng ký đúng (nghĩa là
X
lớn hơn 30), in ra "YES". - Ngược lại, in ra "NO".
- Nếu điều kiện cần đăng ký đúng (nghĩa là
- Đọc thời lượng cuộc họp: Đọc số nguyên
Xuống dòng: Sau khi in "YES" hoặc "NO" cho mỗi bộ test, hãy đảm bảo in ký tự xuống dòng (
\n
hoặc sử dụngendl
) để kết quả của các bộ test không bị dính vào nhau.
Gợi ý kỹ thuật C++:
- Sử dụng thư viện
iostream
cho việc nhập (cin
) và xuất (cout
). - Sử dụng câu lệnh điều kiện
if/else
để kiểm traX > 30
. - Vòng lặp
while (T--)
là một cách ngắn gọn để lặp lạiT
lần sau khi đã đọc giá trịT
.
Comments