std::string: văn bản tự quản lý chính nó
Ở trang trước bạn đã thấy smart pointer giúp một đối tượng sở hữu tài nguyên và tự dọn dẹp nó. std::string là cùng ý tưởng đó áp dụng cho văn bản: nó sở hữu một bộ đệm ký tự, mở rộng bộ đệm khi bạn nối thêm, và giải phóng nó khi chuỗi ra khỏi phạm vi. Bạn không bao giờ gọi new, không bao giờ đếm byte, và không bao giờ phải lo thiếu ký tự kết thúc null.
Để dùng nó, hãy include <string>. Lớp này nằm trong namespace std.
Toán tử + đó thực sự làm việc: nó cấp phát một bộ đệm mới đủ lớn cho cả hai phần và sao chép chúng vào. Với char* thô, tức một con trỏ thông thường trỏ vào bộ đệm ký tự, bạn sẽ phải dùng strcpy và strcat rồi cầu mong bộ đệm đủ lớn. std::string làm biến mất cả nhóm lỗi đó.
Tạo và nối chuỗi
Bạn có thể nối chuỗi bằng +, nhưng công cụ chủ lực để mở rộng một chuỗi tại chỗ là +=. Nó nối thêm vào bộ đệm hiện có thay vì tạo một chuỗi hoàn toàn mới mỗi lần, điều này quan trọng trong vòng lặp.
Một điều hay gây bất ngờ: + cần ít nhất một toán hạng đã là std::string. Hai chuỗi literal chỉ là const char*, nên "a" + "b" không biên dịch được: không có operator+ cho hai con trỏ thô. Hãy biến một trong hai thành string trước:
string s = "a" + "b"; // error: can't add two const char*
string s = string("a") + "b"; // fine - left side is a std::string
string s = "a"s + "b"; // fine in C++14+, the "s" literal suffix
Lưu ý dạng cuối cùng dùng hậu tố s từ <string> (using namespace std::string_literals;), biến một literal thành std::string trực tiếp.
Truy cập vào bên trong chuỗi
std::string hoạt động như một container chứa char, nên bạn có thể truy cập theo chỉ số, duyệt qua nó và lấy ra từng phần.
substr(pos, len) trả về một chuỗi hoàn toàn mới được sao chép từ chuỗi gốc; nó không sửa chuỗi nguồn. Cẩn thận với giới hạn: word[10] trên một chuỗi 5 ký tự là hành vi không xác định: nó không ném ngoại lệ, chỉ đọc ra dữ liệu rác. Nếu bạn muốn truy cập có kiểm tra và ném std::out_of_range, hãy dùng word.at(10) thay cho word[10].
Một cái bẫy kinh điển khác: .size() trả về kiểu không dấu (size_t). Viết một vòng lặp ngược như for (size_t i = word.size() - 1; i >= 0; --i) sẽ không bao giờ kết thúc, vì i không dấu không thể nhỏ hơn 0: nó quay vòng thành một số rất lớn. Hãy dùng chỉ số có dấu hoặc viết lại điều kiện khi bạn cần duyệt chuỗi theo chiều ngược.
Tìm kiếm và thay thế
find tìm một chuỗi con hoặc ký tự và trả về chỉ số bắt đầu. Khi không tìm thấy, nó trả về hằng số đặc biệt std::string::npos: luôn so sánh với hằng số này thay vì cho rằng find trả về -1.
Để sửa văn bản tại chỗ, replace(pos, len, text) thay một đoạn bằng nội dung mới (có thể khác độ dài), còn insert/erase thêm hoặc xóa từng phần:
Chuỗi và số
Văn bản và số không tự chuyển đổi cho nhau: "42" là ba ký tự, không phải số nguyên 42. Thư viện chuẩn có các hàm chuyển đổi theo cả hai chiều. Dùng stoi, stod và các hàm tương tự để chuyển văn bản thành số, và to_string cho chiều ngược lại.
Có hai cái bẫy ở đây. Thứ nhất, stoi("abc") ném std::invalid_argument, nên hãy bảo vệ đầu vào không đáng tin cậy bằng một khối try/catch. Thứ hai, to_string(3.99) cho ra đầy đủ 3.990000: nếu bạn cần kiểm soát độ chính xác và định dạng, đó là việc của string stream, cũng chính là nơi chúng ta sẽ đến tiếp theo.
try {
int n = std::stoi(userInput);
} catch (const std::invalid_argument&) {
std::cout << "That wasn't a number.\n";
}
Tiếp theo: nhập và xuất
Bạn đã in chuỗi bằng cout suốt từ đầu, nhưng đọc chuỗi từ người dùng lại có những bất ngờ riêng: cin >> name dừng ở dấu cách đầu tiên, nên một dòng đầy đủ như "Ada Lovelace" phải dùng std::getline. Trang tiếp theo nói kỹ về nhập và xuất trong C++: các toán tử stream, đọc cả dòng, và kết hợp >> với getline mà không bị vướng ký tự xuống dòng còn sót lại.
Câu hỏi thường gặp
std::string và char* trong C++ khác nhau thế nào?
std::string là một lớp sở hữu và tự quản lý vùng nhớ của nó, tự mở rộng khi cần và tự dọn dẹp. char* chỉ là một con trỏ thô tới các ký tự kết thúc bằng '\0': bạn phải tự quản lý bộ đệm, độ dài và vòng đời. Hãy ưu tiên std::string cho gần như mọi việc; nó loại bỏ cả một nhóm bug tràn bộ đệm và con trỏ treo.
Làm sao lấy độ dài chuỗi trong C++?
Gọi .size() hoặc tên khác của nó là .length() trên một std::string: name.size() trả về số ký tự dưới dạng size_t. Cả hai trả về cùng một giá trị; size() được dùng phổ biến hơn vì giống với mọi container STL khác.
Làm sao chuyển chuỗi sang số trong C++?
Dùng std::stoi cho int, std::stod cho double, và các hàm tương tự (stol, stof). Ví dụ: int n = std::stoi("42");. Các hàm này ném std::invalid_argument nếu văn bản không phải là số, nên hãy bọc chúng trong try/catch khi đầu vào không đáng tin cậy.