
Này các lập trình viên GenZ tương lai, ngồi xuống đây, anh Creyt có món quà tinh thần muốn tặng các em. Hôm nay, chúng ta sẽ cùng khám phá một công cụ mà anh hay gọi là 'Siêu năng lực của Thám tử Dữ liệu' – đó chính là Regex hay còn gọi là Biểu thức chính quy.
Regex Là Gì? Để Làm Gì?
Hãy tưởng tượng thế này: bạn đang lướt TikTok, và bạn muốn tìm tất cả các video có hashtag #CodingLife nhưng chỉ những video mà số lượt tim (like) là một con số có 5 chữ số trở lên. Hoặc bạn đang làm một trang đăng ký và muốn chắc chắn rằng email người dùng nhập vào là đúng định dạng tên@domain.com, chứ không phải tên@domain hay tên.com.
Trong thế giới lập trình, dữ liệu là một biển lớn. Việc tìm kiếm, lọc, hoặc kiểm tra các 'mẫu' (patterns) trong biển dữ liệu đó bằng cách thủ công thì chẳng khác nào dùng kính lúp đi tìm hạt cát. Regex chính là hệ thống sonar cao cấp, là kính lúp vạn năng, là bộ công cụ phân tích DNA của bạn trong thế giới chuỗi (strings).
Nói một cách hàn lâm hơn (kiểu Harvard một chút cho nó ngầu), Regex là một ngôn ngữ đặc tả mẫu (pattern description language). Nó cho phép chúng ta định nghĩa một chuỗi các ký tự đặc biệt và thông thường để tạo thành một 'mẫu'. Khi bạn đưa mẫu này vào một chuỗi lớn hơn, Regex sẽ giúp bạn:
- Tìm kiếm: Phát hiện xem chuỗi có chứa mẫu đó không, hoặc tìm tất cả các vị trí mà mẫu xuất hiện.
- Xác thực (Validation): Kiểm tra xem một chuỗi có hoàn toàn khớp với mẫu định trước không (ví dụ: định dạng email, số điện thoại).
- Thay thế (Replacement): Tìm kiếm và thay thế các phần của chuỗi khớp với mẫu bằng một chuỗi khác.
- Trích xuất (Extraction): Lấy ra các phần cụ thể của chuỗi khớp với các nhóm trong mẫu.
Nó không phải là một ngôn ngữ lập trình theo kiểu C++ hay Python, mà là một 'ngôn ngữ' nhỏ được nhúng vào hầu hết các ngôn ngữ lập trình để xử lý chuỗi.
Code Ví Dụ Minh Hoạ (C++)
Trong C++, chúng ta có thư viện <regex> được giới thiệu từ C++11 để làm việc với biểu thức chính quy. Thư viện này cung cấp các lớp và hàm mạnh mẽ để thực hiện các thao tác trên.
1. Xác thực Email cơ bản (std::regex_match)
Giả sử bạn muốn kiểm tra xem một chuỗi có phải là định dạng email hợp lệ không. Regex cho email có thể khá phức tạp, nhưng ta sẽ bắt đầu với một cái cơ bản:
^: Bắt đầu chuỗi.[a-zA-Z0-9._%+-]+: Một hoặc nhiều ký tự chữ cái (hoa/thường), số, hoặc.,_,%,+,-.@: Ký tự@bắt buộc.[a-zA-Z0-9.-]+: Một hoặc nhiều ký tự chữ cái, số, hoặc.,-.\.: Dấu chấm.(cần escape vì.là ký tự đặc biệt trong regex).[a-zA-Z]{2,}: Hai hoặc nhiều ký tự chữ cái (cho phần đuôi tên miền như.com,.vn).$: Kết thúc chuỗi.
#include <iostream>
#include <string>
#include <regex> // Thư viện regex
int main() {
std::string email1 = "creyt.dev@example.com";
std::string email2 = "invalid-email";
std::string email3 = "creyt@sub.domain.co.uk";
// Regex cho định dạng email cơ bản
std::regex email_pattern("^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.([a-zA-Z]{2,}|[a-zA-Z]{2,}\\.([a-zA-Z]{2,}))$");
std::cout << "Kiem tra email: " << email1 << std::endl;
if (std::regex_match(email1, email_pattern)) {
std::cout << "-> HOP LE!" << std::endl;
} else {
std::cout << "-> KHONG HOP LE!" << std::endl;
}
std::cout << "\nKiem tra email: " << email2 << std::endl;
if (std::regex_match(email2, email_pattern)) {
std::cout << "-> HOP LE!" << std::endl;
} else {
std::cout << "-> KHONG HOP LE!" << std::endl;
}
std::cout << "\nKiem tra email: " << email3 << std::endl;
if (std::regex_match(email3, email_pattern)) {
std::cout << "-> HOP LE!" << std::endl;
} else {
std::cout << "-> KHONG HOP LE!" << std::endl;
}
return 0;
}
Giải thích: std::regex_match sẽ kiểm tra xem TOÀN BỘ chuỗi đầu vào có khớp hoàn toàn với mẫu regex hay không. Nếu chỉ một phần khớp, nó sẽ trả về false.
2. Tìm kiếm và Trích xuất các số trong chuỗi (std::regex_search, std::smatch)
Giả sử bạn có một đoạn văn bản và muốn tìm tất cả các con số trong đó.
\d+: Một hoặc nhiều chữ số (tương đương[0-9]+).
#include <iostream>
#include <string>
#include <regex>
int main() {
std::string text = "Trong nam 2023, chung ta co 12 thang va 365 ngay. Nhiet do trung binh la 25 do C.";
// Regex de tim cac chu so (\d) mot hoac nhieu lan (+)
std::regex number_pattern("\\d+");
std::smatch match; // Doi tuong luu ket qua tim thay
std::cout << "Cac so tim thay trong chuoi:\n";
// Lap qua chuoi de tim tat ca cac match
// std::sregex_iterator la mot iterator giup duyet qua cac ket qua match
for (std::sregex_iterator it(text.begin(), text.end(), number_pattern), end_it;
it != end_it; ++it) {
match = *it;
std::cout << "- " << match.str() << std::endl; // .str() lay chuoi match duoc
}
return 0;
}
Giải thích: std::regex_search tìm kiếm một mẫu trong chuỗi và trả về true nếu tìm thấy, false nếu không. std::smatch là một đối tượng chứa thông tin về kết quả khớp, bao gồm chuỗi con được tìm thấy và vị trí của nó. Vòng lặp với std::sregex_iterator giúp chúng ta tìm kiếm và trích xuất tất cả các lần xuất hiện của mẫu.
3. Thay thế chuỗi (std::regex_replace)
Bạn muốn thay thế tất cả các từ 'C++' thành 'Cộng Cộng' trong một văn bản.
\bC\+\+:\blà word boundary (biên giới từ), đảm bảo khớp cả từ 'C++' chứ không phải 'MyC++Project'.\+cần escape.
#include <iostream>
#include <string>
#include <regex>
int main() {
std::string article = "C++ la mot ngon ngu manh me. Toi thich lap trinh C++.";
// Regex de tim tu "C++" (can escape dau +)
// \b dam bao chi match khi "C++" la mot tu rieng biet
std::regex cpp_pattern("\\bC\\+\\+\\b");
std::string replaced_article = std::regex_replace(article, cpp_pattern, "Cộng Cộng");
std::cout << "Original: " << article << std::endl;
std::cout << "Replaced: " << replaced_article << std::endl;
return 0;
}
Giải thích: std::regex_replace nhận vào chuỗi gốc, mẫu regex, và chuỗi thay thế. Nó sẽ tìm tất cả các vị trí khớp với mẫu và thay thế chúng.

Mẹo Vặt (Best Practices) Từ Giảng Viên Creyt
- Đừng cố gắng viết một regex 'thần thánh' ngay lập tức: Bắt đầu từ những mẫu nhỏ, đơn giản, sau đó ghép nối chúng lại. Giống như xây nhà, phải xây từng viên gạch chứ không thể đổ nguyên cái nhà một lúc.
- Dùng công cụ online để test: Các trang như
regex101.comhayregexr.comlà bạn thân của lập trình viên khi làm việc với regex. Chúng giúp bạn viết, kiểm tra và giải thích regex của mình một cách trực quan. - Comment Regex của bạn: Khi regex trở nên dài và phức tạp, hãy thêm chú thích. Nó giúp bạn và đồng đội hiểu được 'ý đồ' của mẫu đó sau này. C++ không có cách comment trực tiếp trong regex string, nhưng bạn có thể thêm comment trong code giải thích regex đó làm gì.
- Hiểu về Greedy vs. Non-Greedy: Mặc định, các lượng từ như
*,+là 'greedy' – chúng sẽ khớp với chuỗi dài nhất có thể. Thêm?sau lượng từ (*?,+?,??) sẽ biến chúng thành 'non-greedy' – khớp với chuỗi ngắn nhất có thể. Đây là một điểm cực kỳ quan trọng, có thể khiến kết quả của bạn đi chệch hướng nếu không hiểu rõ. - Performance: Regex có thể là một con quái vật ngốn tài nguyên nếu không được viết cẩn thận, đặc biệt với các chuỗi rất dài hoặc các mẫu lồng ghép phức tạp (backtracking). Luôn cân nhắc hiệu suất khi dùng regex trong các ứng dụng cần tốc độ cao.
- Escape ký tự đặc biệt: Nếu bạn muốn khớp với một ký tự có ý nghĩa đặc biệt trong regex (như
.,*,+,?,(,),[,],{,},^,$,|,\), bạn phải 'escape' nó bằng dấu gạch chéo ngược\(trong C++ string literal thì là\\).
Học Thuật Sâu (Kiểu Harvard nhưng Dễ Hiểu)
Regex không phải là phép thuật, nó dựa trên lý thuyết ngôn ngữ hình thức và các mô hình tính toán như Tự động hữu hạn (Finite Automata). Cụ thể hơn, hầu hết các công cụ regex hiện đại được triển khai dựa trên NFA (Nondeterministic Finite Automaton). Khi bạn đưa một mẫu regex và một chuỗi đầu vào, về cơ bản, công cụ regex sẽ xây dựng một cỗ máy trạng thái (state machine) từ mẫu đó và 'chạy' chuỗi đầu vào qua cỗ máy đó để xem nó có 'được chấp nhận' hay không.
Điều quan trọng cần nhớ là Regex chỉ có thể xử lý các ngôn ngữ chính quy (regular languages). Điều này có nghĩa là có những cấu trúc ngôn ngữ mà regex không thể xử lý được một cách hiệu quả hoặc không thể xử lý được (ví dụ: khớp các cặp dấu ngoặc lồng nhau vô hạn lần). Đối với những trường hợp phức tạp hơn, bạn cần đến các công cụ phân tích cú pháp (parsers) mạnh mẽ hơn.
Ví Dụ Thực Tế Các Ứng Dụng/Website Đã Ứng Dụng Regex
Regex không phải là lý thuyết suông, nó hiện diện khắp nơi:
- Form Validation trên website: Hầu hết các form đăng ký, đăng nhập đều dùng regex để kiểm tra định dạng email, số điện thoại, mật khẩu, mã zip/post code.
- Tìm kiếm và thay thế trong IDE/Text Editor: Các trình soạn thảo code như VS Code, Sublime Text, IntelliJ IDEA đều có tính năng tìm kiếm/thay thế bằng regex cực kỳ mạnh mẽ.
- Log Analysis: Khi cần phân tích hàng gigabyte log file để tìm các lỗi cụ thể, địa chỉ IP, thời gian xảy ra sự kiện, regex là vô đối.
- URL Routing trong Web Frameworks: Các framework như Express.js (Node.js), Django (Python), Ruby on Rails đều dùng regex để khớp các URL với các hàm xử lý tương ứng.
- Data Scraping/Parsing: Trích xuất thông tin cụ thể từ các trang web hoặc tài liệu không có cấu trúc.
- Code Linters/Formatters: Kiểm tra và định dạng code theo các quy tắc nhất định.
Thử Nghiệm Đã Từng và Hướng Dẫn Nên Dùng Cho Case Nào
Anh Creyt đã từng dùng regex để:
- Tự động sửa lỗi chính tả: Viết một script dùng regex để tìm và thay thế các lỗi chính tả phổ biến trong một tài liệu lớn.
- Trích xuất thông tin sản phẩm từ mô tả: Có một danh sách mô tả sản phẩm hỗn độn, cần lấy ra giá, mã sản phẩm, màu sắc theo các mẫu khác nhau.
- Phân tích nhật ký máy chủ (Server Logs): Tìm kiếm các lỗi 500, các truy cập từ địa chỉ IP đáng ngờ, hoặc các request đến các endpoint cụ thể.
Khi nào nên dùng Regex?
- Khi bạn cần xác thực định dạng của một chuỗi đầu vào (email, số điện thoại, mật khẩu, ngày tháng).
- Khi bạn cần tìm kiếm hoặc trích xuất các phần của chuỗi dựa trên một mẫu phức tạp (ví dụ: tất cả các hashtag, tất cả các URL trong một văn bản).
- Khi bạn cần thay thế nhiều lần các chuỗi con khớp với một mẫu.
- Khi bạn cần phân tích dữ liệu văn bản không có cấu trúc rõ ràng.
Khi nào nên CẨN THẬN khi dùng Regex (hoặc không nên dùng)?
- Khi chỉ cần tìm một chuỗi con cố định: Nếu bạn chỉ muốn tìm "hello" trong "hello world", dùng
string::findsẽ nhanh và đơn giản hơn rất nhiều. Regex là overkill. - Khi mẫu quá phức tạp và khó đọc: Nếu regex của bạn dài hàng chục ký tự và bạn không thể hiểu nó làm gì sau 5 phút, hãy xem xét chia nhỏ vấn đề hoặc dùng một parser chuyên dụng hơn.
- Khi xử lý cấu trúc lồng ghép phức tạp: Ví dụ, kiểm tra xem tất cả các dấu ngoặc
()trong một biểu thức có được đóng đúng cách không. Regex cơ bản không thể đếm số lượng lồng ghép, bạn cần một stack hoặc parser thực sự.
Thử nghiệm với regex là cách tốt nhất để học. Bắt đầu với các mẫu đơn giản như \d (một chữ số), [a-z] (một chữ cái thường), sau đó kết hợp chúng lại với các lượng từ như +, *, ? và các nhóm (). Dần dần, bạn sẽ thấy mình có thể 'đọc' và 'viết' regex như một ngôn ngữ thứ hai vậy. Good luck, các thám tử dữ liệu!
Thuộc Series: C++
Bài giảng này được tự động xuất bản ngẫu nhiên từ thư viện kiến thức. Đừng quên đón xem các Từ khoá Hướng Dẫn tiếp theo nhé!