Bài 28.5: Bài tập thực hành chuỗi ký tự cơ bản trong C++

Bài 28.5: Bài tập thực hành chuỗi ký tự cơ bản trong C++
Chào mừng bạn đến với Bài 28.5 trong chuỗi hành trình khám phá C++ của chúng ta! Sau khi đã làm quen với khái niệm và cấu trúc của chuỗi ký tự string
, giờ là lúc chúng ta xắn tay áo lên và thực hành để nắm vững các thao tác cơ bản nhất.
Chuỗi ký tự là một kiểu dữ liệu cực kỳ quan trọng và được sử dụng rộng rãi trong hầu hết mọi chương trình C++. Từ việc hiển thị thông báo cho người dùng, đọc dữ liệu từ file, xử lý văn bản, đến giao tiếp qua mạng... đâu đâu cũng có bóng dáng của chuỗi. Việc thành thạo các thao tác với chuỗi sẽ giúp bạn giải quyết được rất nhiều vấn đề trong lập trình.
Trong bài viết này, chúng ta sẽ đi qua các ví dụ minh họa cho các thao tác phổ biến nhất với string
. Hãy cùng bắt đầu nào!
Để sử dụng được string
, bạn cần nhớ thêm thư viện <string>
vào đầu file source code của mình, và thường sẽ cần thêm cả <iostream>
để nhập xuất nữa nhé.
#include <iostream> // Để sử dụng cin, cout
#include <string> // Để sử dụng string
int main() {
// Code của bạn sẽ ở đây
return 0;
}
1. Khai báo và Khởi tạo chuỗi
Bạn có thể khai báo một chuỗi rỗng hoặc khởi tạo nó ngay lập tức với một giá trị ban đầu.
#include <iostream>
#include <string>
int main() {
// Khai báo một chuỗi rỗng
string empty_string;
cout << "Chuỗi rỗng ban đầu: '" << empty_string << "'\n"; // Output: ''
// Khai báo và khởi tạo với một giá trị cụ thể
string greeting = "Xin chào C++!";
cout << "Chuỗi được khởi tạo: '" << greeting << "'\n"; // Output: 'Xin chào C++!'
// Khởi tạo từ một chuỗi khác (sao chép)
string another_string = greeting;
cout << "Chuỗi được sao chép: '" << another_string << "'\n"; // Output: 'Xin chào C++!'
// Khởi tạo chuỗi lặp lại một ký tự
string stars(10, '*'); // Chuỗi gồm 10 ký tự '*'
cout << "Chuỗi ký tự lặp lại: '" << stars << "'\n"; // Output: '**********'
return 0;
}
- Giải thích:
string empty_string;
tạo ra một đối tượng chuỗi rỗng.string greeting = "Xin chào C++!";
sử dụng một string literal (chuỗi nằm trong dấu ngoặc kép) để khởi tạo chuỗi.string another_string = greeting;
sao chép nội dung từ chuỗigreeting
sang chuỗianother_string
.string stars(10, '*');
sử dụng constructor để tạo chuỗi gồm 10 ký tự*
.
2. Lấy độ dài chuỗi (length()
hoặc size()
)
Rất thường xuyên bạn sẽ cần biết một chuỗi dài bao nhiêu ký tự. string
cung cấp hai phương thức cho việc này: .length()
và .size()
. Cả hai thường trả về kết quả như nhau.
#include <iostream>
#include <string>
int main() {
string text = "Lap trinh C++ rat thu vi!";
// Sử dụng length()
int len1 = text.length();
cout << "Độ dài (length()): " << len1 << " ký tự.\n"; // Output: 25
// Sử dụng size()
int len2 = text.size();
cout << "Độ dài (size()): " << len2 << " ký tự.\n"; // Output: 25
return 0;
}
- Giải thích:
- Phương thức
.length()
và.size()
trả về số lượng ký tự trong chuỗi dưới dạng kiểusize_t
(một kiểu số nguyên không âm, thường làunsigned long
). Chúng ta có thể gán nó choint
nếu biết chắc độ dài sẽ không quá lớn, nhưng dùngsize_t
là an toàn nhất.
- Phương thức
3. Truy cập ký tự trong chuỗi ([]
hoặc .at()
)
Bạn có thể truy cập từng ký tự riêng lẻ trong chuỗi dựa vào vị trí (chỉ mục - index) của nó. Chỉ mục bắt đầu từ 0 cho ký tự đầu tiên.
Có hai cách chính: sử dụng toán tử []
hoặc phương thức .at()
.
#include <iostream>
#include <string>
#include <stdexcept> // Cần cho try-catch khi dùng .at()
int main() {
string word = "Hello";
// Truy cập bằng toán tử [] (không kiểm tra giới hạn)
char first_char = word[0]; // Ký tự ở vị trí 0 ('H')
char third_char = word[2]; // Ký tự ở vị trí 2 ('l')
// char out_of_bounds_char = word[10]; // Nguy hiểm! Có thể gây lỗi runtime
cout << "Ký tự đầu tiên (index 0): " << first_char << "\n"; // Output: H
cout << "Ký tự thứ 3 (index 2): " << third_char << "\n"; // Output: l
// Truy cập bằng phương thức .at() (có kiểm tra giới hạn)
try {
char last_char = word.at(word.length() - 1); // Ký tự cuối cùng ('o')
cout << "Ký tự cuối cùng (.at()): " << last_char << "\n"; // Output: o
// Thử truy cập index vượt quá giới hạn - sẽ ném ra exception
// char invalid_char = word.at(10); // Dòng này sẽ gây lỗi và nhảy vào catch block
// cout << "Ký tự không hợp lệ: " << invalid_char << "\n";
} catch (const out_of_range& e) {
// Bắt exception nếu index nằm ngoài phạm vi
cerr << "Lỗi truy cập: " << e.what() << "\n";
}
return 0;
}
- Giải thích:
- Toán tử
[]
cho phép truy cập nhanh, nhưng nó không kiểm tra xem chỉ mục bạn cung cấp có hợp lệ hay không. Nếu bạn dùng chỉ mục ngoài phạm vi, chương trình có thể gặp lỗi không mong muốn (undefined behavior). - Phương thức
.at()
cũng truy cập ký tự tại vị trí đó, nhưng nó kiểm tra giới hạn. Nếu chỉ mục không hợp lệ, nó sẽ ném ra một exception kiểuout_of_range
. Dùng.at()
an toàn hơn khi bạn không chắc chắn về chỉ mục.
- Toán tử
4. Nối chuỗi (Concatenation)
Bạn có thể kết hợp hai hoặc nhiều chuỗi lại với nhau bằng toán tử +
hoặc +=
.
#include <iostream>
#include <string>
int main() {
string part1 = "Hoc C++";
string part2 = " that de dang!";
// Nối chuỗi sử dụng toán tử +
string full_sentence = part1 + part2;
cout << "Nối bằng +: " << full_sentence << "\n"; // Output: Hoc C++ that de dang!
// Nối chuỗi sử dụng toán tử += (nối và gán ngược lại vào chuỗi ban đầu)
string message = "Xin chao";
message += " moi nguoi!";
cout << "Nối bằng +=: " << message << "\n"; // Output: Xin chao moi nguoi!
// Bạn cũng có thể nối chuỗi với string literals hoặc ký tự
string greet = "Hello";
greet += " World"; // Nối với string literal
greet += '!'; // Nối với ký tự đơn
cout << "Nối hỗn hợp: " << greet << "\n"; // Output: Hello World!
return 0;
}
- Giải thích:
- Toán tử
+
tạo ra một chuỗi mới là kết quả của phép nối. Chuỗi ban đầu không thay đổi. - Toán tử
+=
nối chuỗi hoặc ký tự vào cuối chuỗi hiện tại và cập nhật chuỗi đó.
- Toán tử
5. Nhập chuỗi từ bàn phím (cin >>
và getline
)
Đây là một điểm quan trọng cần phân biệt khi làm việc với chuỗi và nhập liệu.
cin >> string_variable;
: Đọc dữ liệu cho đến khi gặp khoảng trắng (dấu cách, tab, xuống dòng) và lưu vào biến chuỗi. Nó chỉ lấy "từ" đầu tiên hoặc một đoạn không chứa khoảng trắng.getline(cin, string_variable);
: Đọc dữ liệu cho đến khi gặp ký tự xuống dòng (\n
) và lưu toàn bộ dòng đó vào biến chuỗi. Đây là cách bạn đọc được cả một câu hoặc một đoạn văn bản có chứa dấu cách.
#include <iostream>
#include <string>
#include <limits> // Cần cho numeric_limits
int main() {
string single_word;
cout << "Nhap mot tu (su dung cin >>): ";
cin >> single_word;
cout << "Ban da nhap: '" << single_word << "'\n";
// *** LUU Y QUAN TRONG SAU KHI DUNG cin >> ***
// cin >> chi doc den khoang trang va de lai ky tu xuong dong (\n) trong buffer.
// Neu sau do ban dung getline, getline se doc ngay ky tu \n do va tuong la ban
// nhap mot dong rong.
// Chung ta can "xoa" ky tu \n do khoi buffer nhap.
// Cách 1: Dùng ignore() - Xóa bỏ tối đa số ký tự lớn nhất cho đến khi gặp ký tự xuống dòng.
cin.ignore(numeric_limits<streamsize>::max(), '\n');
// Cách 2 (ngắn gọn hơn): Dùng ws manipulator - Bỏ qua tất cả các khoảng trắng đầu dòng
// cin >> ws; // Thay thế cho dòng ignore() trên
string full_line;
cout << "Nhap mot dong van ban (su dung getline): ";
getline(cin, full_line);
cout << "Ban da nhap: '" << full_line << "'\n";
return 0;
}
- Giải thích:
- Ví dụ với
cin >>
: Nếu bạn nhập "Hello World",single_word
sẽ chỉ nhận "Hello". Ký tự xuống dòng (khi bạn nhấn Enter) và phần " World" vẫn còn trong bộ nhớ đệm (buffer) củacin
. - Ví dụ với
getline
: Nếu bạn nhập "Lap trinh C++",full_line
sẽ nhận toàn bộ "Lap trinh C++". - Điểm mấu chốt: Khi dùng
cin >>
để đọc một giá trị (có thể là số, hoặc từ), nó sẽ để lại ký tự xuống dòng trong buffer. Nếu lệnh tiếp theo làgetline
,getline
sẽ đọc ngay ký tự xuống dòng còn sót lại đó và kết thúc việc đọc mà không chờ bạn nhập gì thêm. Để khắc phục, bạn cần "làm sạch" buffer trước khi gọigetline
bằng cách sử dụngcin.ignore()
hoặcws
.
- Ví dụ với
6. Tìm kiếm chuỗi con (find()
)
Bạn có thể tìm kiếm sự xuất hiện của một chuỗi con (substring) bên trong một chuỗi khác bằng phương thức .find()
.
#include <iostream>
#include <string>
int main() {
string main_string = "Lap trinh C++ rat tuyet voi, C++ la so 1!";
string search_term1 = "C++";
string search_term2 = "Java";
// Tìm lần xuất hiện đầu tiên của "C++"
size_t found_pos1 = main_string.find(search_term1);
if (found_pos1 != string::npos) {
// string::npos là một giá trị đặc biệt biểu thị "không tìm thấy"
cout << "Tìm thấy '" << search_term1 << "' tại vị trí: " << found_pos1 << "\n"; // Output: 10
} else {
cout << "Không tìm thấy '" << search_term1 << "' trong chuỗi.\n";
}
// Tìm lần xuất hiện đầu tiên của "Java"
size_t found_pos2 = main_string.find(search_term2);
if (found_pos2 != string::npos) {
cout << "Tìm thấy '" << search_term2 << "' tại vị trí: " << found_pos2 << "\n";
} else {
cout << "Không tìm thấy '" << search_term2 << "' trong chuỗi.\n"; // Output: Không tìm thấy 'Java'...
}
// Bạn cũng có thể chỉ định vị trí bắt đầu tìm kiếm
size_t found_pos3 = main_string.find(search_term1, found_pos1 + 1); // Tìm "C++" bắt đầu từ vị trí sau lần tìm thấy đầu tiên (index 10 + 1)
if (found_pos3 != string::npos) {
cout << "Tìm thấy '" << search_term1 << "' lần thứ 2 tại vị trí: " << found_pos3 << "\n"; // Output: 30
} else {
cout << "Không tìm thấy '" << search_term1 << "' lần thứ 2.\n";
}
return 0;
}
- Giải thích:
- Phương thức
.find()
trả về chỉ mục (vị trí) của ký tự đầu tiên của chuỗi con được tìm thấy. - Nếu chuỗi con không được tìm thấy,
.find()
trả về một giá trị đặc biệt làstring::npos
. Đây là một hằng số, thường là giá trị lớn nhất có thể củasize_t
. Bạn luôn phải so sánh kết quả vớistring::npos
để biết liệu có tìm thấy hay không. - Bạn có thể truyền thêm tham số thứ hai vào
.find()
để chỉ định vị trí bắt đầu tìm kiếm trong chuỗi gốc.
- Phương thức
7. Trích xuất chuỗi con (substr()
)
Phương thức .substr()
cho phép bạn tạo một chuỗi mới từ một phần của chuỗi hiện tại.
#include <iostream>
#include <string>
int main() {
string sentence = "Chuoi ky tu rat hay trong C++.";
// Trích xuất từ vị trí 0, lấy 5 ký tự
string sub1 = sentence.substr(0, 5); // "Chuoi"
cout << "substr(0, 5): '" << sub1 << "'\n";
// Trích xuất từ vị trí 19, lấy 3 ký tự
string sub2 = sentence.substr(19, 3); // "C++"
cout << "substr(19, 3): '" << sub2 << "'\n";
// Trích xuất từ vị trí 24 đến hết chuỗi
string sub3 = sentence.substr(24); // "."; nếu chuỗi dài hơn, sẽ lấy đến hết
cout << "substr(24): '" << sub3 << "'\n";
// Cẩn thận khi chỉ số vượt quá giới hạn
try {
string sub4 = sentence.substr(40); // Vị trí 40 không tồn tại
cout << "substr(40): '" << sub4 << "'\n";
} catch (const out_of_range& e) {
cerr << "Lỗi khi trích xuất: " << e.what() << "\n"; // Output: Lỗi khi trích xuất: basic_string::substr: __pos (40) > this->size() (28)
}
return 0;
}
- Giải thích:
.substr(pos, count)
: Trả về một chuỗi mới bắt đầu từ vị trípos
trong chuỗi gốc và có độ dàicount
ký tự..substr(pos)
: Trả về một chuỗi mới bắt đầu từ vị trípos
trong chuỗi gốc và kéo dài đến hết chuỗi.- Nếu
pos
hoặc (pos + count
) vượt quá độ dài chuỗi gốc, phương thức sẽ ném ra một exceptionout_of_range
.
8. So sánh chuỗi
Bạn có thể so sánh hai chuỗi bằng các toán tử so sánh thông thường như ==
, !=
, <
, >
, <=
, >=
. Phép so sánh này là so sánh từ điển (lexicographical), giống như cách các từ được sắp xếp trong từ điển.
#include <iostream>
#include <string>
int main() {
string str1 = "apple";
string str2 = "banana";
string str3 = "apple";
string str4 = "Apple"; // Chú ý: chữ hoa khác chữ thường
// So sánh bằng ==
if (str1 == str3) {
cout << "'" << str1 << "' bằng '" << str3 << "'\n"; // Output: 'apple' bằng 'apple'
}
// So sánh khác !=
if (str1 != str2) {
cout << "'" << str1 << "' khác '" << str2 << "'\n"; // Output: 'apple' khác 'banana'
}
// So sánh theo thứ tự từ điển (<, >, <=, >=)
if (str1 < str2) {
cout << "'" << str1 << "' đứng trước '" << str2 << "' trong từ điển.\n"; // Output: 'apple' đứng trước 'banana'...
}
if (str4 < str1) {
cout << "'" << str4 << "' đứng trước '" << str1 << "' trong từ điển (do 'A' < 'a').\n"; // Output: 'Apple' đứng trước 'apple'...
}
return 0;
}
- Giải thích:
- Các toán tử so sánh làm việc tương tự như khi so sánh số, nhưng áp dụng cho thứ tự ký tự trong bảng mã ASCII (hoặc hệ thống ký tự đang dùng). So sánh diễn ra ký tự một từ trái sang phải.
- Chú ý rằng chữ hoa và chữ thường có giá trị khác nhau trong bảng mã ASCII ('A' khác 'a').
Hãy tự mình thực hành!
Những ví dụ trên chỉ là khởi đầu. string
trong C++ còn có rất nhiều phương thức hữu ích khác như insert()
, erase()
, replace()
, clear()
, empty()
, v.v.
Hãy thử thách bản thân:
- Viết chương trình nhập một câu từ người dùng và đếm xem có bao nhiêu nguyên âm (a, e, i, o, u) trong câu đó.
- Viết chương trình nhập hai chuỗi và kiểm tra xem chuỗi thứ nhất có phải là tiền tố (prefix) của chuỗi thứ hai không.
- Nhập một chuỗi và đảo ngược thứ tự các ký tự của nó.
Việc tự tay gõ lại, chỉnh sửa và thử nghiệm các ví dụ này là cách tốt nhất để bạn ghi nhớ và hiểu sâu sắc. Đừng ngại tìm hiểu thêm các hàm khác của string
trên các trang tài liệu chính thức như cppreference.com nhé!
Bài tập ví dụ: C++ Bài 16.B2: Ngày định mệnh của FullHouse Dev
Ngày định mệnh của FullHouse Dev
FullHouse Dev được một nhà tiên tri cảnh báo phải cẩn thận vào ngày 15 tháng 3 - ngày định mệnh.
Hôm nay là ngày N của tháng 3. Nhiệm vụ của bạn là thông báo cho FullHouse Dev liệu đây có phải là ngày định mệnh hay không, để anh ấy có thể thực hiện các biện pháp phòng ngừa nếu cần thiết.
Định dạng đầu vào
Dòng duy nhất của đầu vào sẽ chứa một số nguyên N, là ngày hiện tại trong tháng 3.
Định dạng đầu ra
In ra "Yes" nếu hôm nay là ngày định mệnh, và "No" nếu không phải (không có dấu ngoặc kép).
Ràng buộc
1 ≤ N ≤ 15
Ví dụ
Ví dụ 1:
Đầu vào:
14
Đầu ra:
No
Giải thích: Ngày 14 tháng 3 không phải là ngày định mệnh, vì vậy FullHouse Dev không cần phải thực hiện biện pháp phòng ngừa đặc biệt nào.
Ví dụ 2:
Đầu vào:
15
Đầu ra:
Yes
Giải thích:
Ngày 15 là ngày định mệnh, vì vậy FullHouse Dev nên thực hiện các biện pháp phòng ngừa đặc biệt.
Tuyệt vời! Đây là hướng dẫn giải bài "Ngày định mệnh của FullHouse Dev" bằng C++ theo yêu cầu của bạn, chỉ tập trung vào hướng đi và sử dụng std
:
Đọc dữ liệu đầu vào: Bạn cần đọc một số nguyên duy nhất từ đầu vào.
- Sử dụng thư viện
iostream
(cầninclude <iostream>
). - Khai báo một biến kiểu
int
để lưu giá trị ngàyN
. - Dùng
cin >> ten_bien_N;
để đọc giá trị vào biến đó.
- Sử dụng thư viện
Kiểm tra điều kiện: Bài toán yêu cầu kiểm tra xem ngày
N
có phải là ngày 15 hay không.- Sử dụng cấu trúc điều kiện
if-else
. - Điều kiện cần kiểm tra là:
ten_bien_N == 15
. Toán tử==
được dùng để so sánh bằng.
- Sử dụng cấu trúc điều kiện
Xuất kết quả:
- Nếu điều kiện ở bước 2 là đúng (N bằng 15), bạn cần in ra chuỗi "Yes".
- Nếu điều kiện là sai (N không bằng 15), bạn cần in ra chuỗi "No".
- Sử dụng
cout << "ChuoiCanIn";
để in ra. - Nhớ thêm
endl;
hoặc'\n';
sau chuỗi in ra để xuống dòng, đảm bảo đúng định dạng đầu ra.
Tóm lại các bước chính:
- Include
<iostream>
. - Trong hàm
main
:- Khai báo biến
int
cho ngày. - Đọc giá trị vào biến đó bằng
cin
. - Sử dụng
if (bien_ngay == 15)
:- Bên trong
if
, dùngcout << "Yes" << endl;
.
- Bên trong
- Sử dụng
else
:- Bên trong
else
, dùngcout << "No" << endl;
.
- Bên trong
- Khai báo biến
Cách tiếp cận này rất ngắn gọn và trực tiếp giải quyết yêu cầu của bài toán bằng các công cụ cơ bản của C++ và thư viện chuẩn (std
).
Comments