التعبيرات النمطية (Regular Expressions) في لغة C++
التعبيرات النمطية أو ما يُعرف بـ Regular Expressions هي أداة قوية وأساسية تُستخدم في معالجة النصوص والتحقق من الأنماط داخل البيانات النصية. تُعد التعبيرات النمطية واحدة من أهم الأدوات التي تُستخدم في مجالات متعددة مثل تحليل النصوص، التحقق من صحة المدخلات، البحث والاستبدال، واستخراج المعلومات. في لغة C++، توافرت هذه الميزة ضمن مكتبة القياسية منذ معيار C++11، مما أتاح للمطورين القدرة على استخدام التعبيرات النمطية بطريقة مدمجة وسلسة دون الحاجة إلى مكتبات خارجية.
هذا المقال يُقدم شرحاً مفصلاً وموسعاً عن التعبيرات النمطية في C++، من حيث المفاهيم الأساسية، كيفية استخدامها، المكونات المختلفة لها، وأمثلة تطبيقية، بالإضافة إلى استعراض أداء التعبيرات النمطية في C++ مقارنةً بلغات أخرى.
مفهوم التعبيرات النمطية Regular Expressions
التعبيرات النمطية هي تسلسل من الأحرف يُستخدم لوصف نمط معين داخل النصوص. يمكن التعبير عن نمط معين سواءً كان رقمًا، كلمة، عنوان بريد إلكتروني، أو حتى تعبير معقد يتضمن شروطاً متعددة باستخدام هذه الأنماط.
مثلاً، لو أردنا التحقق من أن نص معين يحتوي على رقم هاتف، فيمكننا تعريف نمط تعبير نمطي يحدد الشكل المتوقع لهذا الرقم، ومن ثم التحقق مما إذا كان النص يُطابق هذا النمط.
التعبيرات النمطية تتألف من أحرف عادية وأحرف خاصة تسمى “الرموز الخاصة” أو “المقيدات” التي تحدد كيفية المطابقة. في C++، تُستخدم التعبيرات النمطية ضمن المكتبة القياسية ، والتي تحتوي على عدة أصناف (Classes) ووظائف تسمح بإنشاء واستخدام هذه الأنماط.
المكتبة القياسية للتعبيرات النمطية في C++
أدخل معيار C++11 مكتبة التي تُقدم الدعم الرسمي للتعبيرات النمطية، وتتضمن مجموعة من الأصناف والوظائف التي تسهل التعامل معها، مثل:
-
std::regex: لتخزين التعبير النمطي. -
std::smatchأوstd::cmatch: لتخزين نتائج المطابقة. -
std::regex_match: لفحص ما إذا كان النص بالكامل يطابق النمط. -
std::regex_search: للبحث عن نمط معين داخل النص. -
std::regex_replace: لاستبدال جزء من النص يتطابق مع النمط.
هذه المكتبة تدعم أنماطاً مختلفة (Syntax Types) منها ECMAScript (النمط الافتراضي)، POSIX، وغيرها.
بناء التعبير النمطي في C++
يمكن تعريف تعبير نمطي باستخدام كائن من نوع std::regex، ويتطلب المُنشئ تمرير سلسلة نصية تمثل النمط.
cpp#include
#include
int main() {
std::regex pattern("[a-zA-Z]+");
std::string text = "HelloWorld";
if (std::regex_match(text, pattern)) {
std::cout << "النص يحتوي فقط على أحرف أبجدية." << std::endl;
} else {
std::cout << "النص يحتوي على أحرف غير أبجدية." << std::endl;
}
return 0;
}
في المثال أعلاه، التعبير النمطي [a-zA-Z]+ يعني “واحد أو أكثر من الأحرف الأبجدية الصغيرة أو الكبيرة”. الدالة regex_match تتحقق مما إذا كان النص كاملاً يطابق هذا النمط.
الرموز الأساسية في التعبيرات النمطية
تعتمد التعبيرات النمطية على رموز خاصة تُحدد القواعد التي تُطابق بها النصوص. أهم هذه الرموز:
| الرمز | المعنى |
|---|---|
. |
أي حرف واحد عدا السطر الجديد (\n) |
^ |
بداية النص |
$ |
نهاية النص |
* |
صفر أو أكثر من العنصر السابق |
+ |
واحد أو أكثر من العنصر السابق |
? |
صفر أو واحد من العنصر السابق |
{n} |
عدد محدد من التكرار |
{n,} |
على الأقل n مرات |
{n,m} |
من n إلى m مرات |
[] |
مجموعة أحرف (مثلاً: [abc]) |
| ` | ` |
() |
تحديد مجموعة فرعية داخل النمط |
\d |
رقم (أي رقم من 0 إلى 9) |
\w |
حرف أبجدي رقمي أو underscore (_) |
\s |
مسافة بيضاء (مسافة، تبويب، سطر جديد) |
\b |
حدود الكلمة |
مقارنة بين regex_match و regex_search
-
regex_match: تتحقق ما إذا كان النص كاملاً يطابق التعبير النمطي. -
regex_search: تتحقق ما إذا كان هناك جزء من النص يطابق التعبير النمطي.
مثال:
cpp#include
#include
int main() {
std::regex pattern("\\d+");
std::string text = "User123Data";
bool matchFull = std::regex_match(text, pattern);
bool searchPart = std::regex_search(text, pattern);
std::cout << "مطابقة كاملة: " << (matchFull ? "نعم" : "لا") << std::endl;
std::cout << "بحث جزئي: " << (searchPart ? "نعم" : "لا") << std::endl;
return 0;
}
النتيجة ستكون أن regex_match ترجع لا لأن النص يحتوي على أحرف غير الأرقام، بينما regex_search ترجع نعم لأن جزءًا من النص يحتوي على أرقام.
استخراج النتائج والمجموعات الفرعية (Capture Groups)
عند وجود أقواس () داخل التعبير النمطي، يمكن استخراج الأجزاء المطابقة لهذه المجموعات بشكل منفصل. تُستخدم لذلك أنواع مثل std::smatch التي تحتوي على المجموعات المطابقة.
مثال:
cpp#include
#include
int main() {
std::string text = "My phone number is 123-456-7890.";
std::regex pattern("(\\d{3})-(\\d{3})-(\\d{4})");
std::smatch matches;
if (std::regex_search(text, matches, pattern)) {
std::cout << "رقم الهاتف كاملاً: " << matches[0] << std::endl;
std::cout << "الجزء الأول: " << matches[1] << std::endl;
std::cout << "الجزء الثاني: " << matches[2] << std::endl;
std::cout << "الجزء الثالث: " << matches[3] << std::endl;
}
return 0;
}
في المثال أعلاه، matches[0] تمثل النص المطابق بالكامل، بينما matches[1] إلى matches[3] تمثل الأجزاء التي حُددت بواسطة الأقواس.
استبدال النصوص باستخدام التعبيرات النمطية
توفر مكتبة دالة std::regex_replace لاستبدال الأجزاء المطابقة داخل النص بنص آخر.
cpp#include
#include
int main() {
std::string text = "My email is [email protected]";
std::regex email_pattern(R"((\w+@\w+\.\w+))");
std::string replaced = std::regex_replace(text, email_pattern, "[محمي]");
std::cout << replaced << std::endl;
return 0;
}
الناتج سيكون:
csharpMy email is [محمي]
هنا تم استبدال البريد الإلكتروني الموجود في النص بـ “[محمي]”.
أنواع الأنماط (Syntax Types) في التعبيرات النمطية
تدعم مكتبة C++ عدة أنواع من قواعد بناء التعبيرات النمطية، منها:
| النوع | الوصف |
|---|---|
ECMAScript |
النمط الافتراضي، شائع الاستخدام |
basic |
نمط POSIX الأساسي |
extended |
نمط POSIX الممتد |
awk |
نمط يشابه نمط أداة AWK |
grep |
نمط يشابه أداة GREP |
egrep |
نمط يشابه أداة EGREP |
يمكن تحديد النوع عند إنشاء كائن التعبير النمطي:
cppstd::regex pattern("a.*b", std::regex_constants::extended);
أداء التعبيرات النمطية في C++
على الرغم من القوة الكبيرة، إلا أن التعبيرات النمطية قد تؤثر على أداء البرنامج عند استخدامها بشكل مكثف أو في أنماط معقدة جداً. ومع ذلك، فإن المكتبة القياسية في C++ تقدم أداءً جيداً مقارنة بمكتبات خارجية أخرى.
ينصح دائماً بتقليل التعقيد في الأنماط واستخدام التعبيرات البسيطة قدر الإمكان لتحسين الأداء، كما يمكن إعادة استخدام كائنات std::regex بدلاً من إنشائها بشكل متكرر داخل الحلقات.
بعض الأمثلة التطبيقية على استخدام التعبيرات النمطية في C++
التحقق من صحة بريد إلكتروني
cpp#include
#include
bool isValidEmail(const std::string& email) {
const std::regex pattern(R"((\w+)(\.|\w)*@(\w+)\.(\w+))");
return std::regex_match(email, pattern);
}
int main() {
std::string email = "[email protected]";
std::cout << (isValidEmail(email) ? "صحيح" : "غير صحيح") << std::endl;
return 0;
}
استخراج الأرقام من نص
cpp#include
#include
int main() {
std::string text = "هناك 15 تفاحة و20 برتقالة.";
std::regex number_pattern("\\d+");
auto numbers_begin = std::sregex_iterator(text.begin(), text.end(), number_pattern);
auto numbers_end = std::sregex_iterator();
for (std::sregex_iterator i = numbers_begin; i != numbers_end; ++i) {
std::smatch match = *i;
std::cout << "رقم: " << match.str() << std::endl;
}
return 0;
}
نصائح عملية عند استخدام التعبيرات النمطية في C++
-
اختبار الأنماط بدقة: قبل تضمين التعبير النمطي في برنامجك، من الأفضل تجربته عبر أدوات أو بيئات اختبار لتجنب الأخطاء المنطقية.
-
تقليل تعقيد التعبيرات: تعبير نمطي معقد قد يكون بطيئاً في التنفيذ، حاول تبسيطه.
-
إعادة استخدام كائنات regex: لإنشاء كائن regex عملية مكلفة مقارنة باستخدامه، لذا يُفضل إعادة استخدام الكائنات.
-
الاهتمام بالأداء: إذا كانت العملية تتم بشكل متكرر في برامج تتطلب أداء عالٍ، يُفضل تقييم استخدام التعبيرات النمطية مقابل حلول أخرى.
خلاصة
التعبيرات النمطية في C++ تُعد أداة فعالة وقوية في معالجة النصوص، من خلال مكتبة قياسية حديثة توفر للمبرمجين واجهة بسيطة وموحدة للعمل مع الأنماط النصية المعقدة. بتعلم بناء الأنماط النمطية وفهم كيفية استخدامها بشكل صحيح، يمكن للمبرمجين تحقيق عمليات بحث، تحقق، واستخراج معلومات متقدمة ضمن النصوص بشكل مباشر وبكفاءة.
تطبيقات التعبيرات النمطية في C++ واسعة ومتنوعة، تشمل التحقق من صحة المدخلات، معالجة ملفات السجلات، التلاعب بالنصوص، وأكثر من ذلك. وباستخدام قواعد بناء النمط المختلفة والوظائف المتنوعة التي توفرها مكتبة ، يمكن توظيف هذه التقنية بمرونة وكفاءة في مختلف المشاريع البرمجية.
المصادر والمراجع:
-
كتاب “The C++ Standard Library” لـ Nicolai M. Josuttis (يتناول مكتبة C++ القياسية بالتفصيل بما في ذلك التعبيرات النمطية).
-
التوثيق الرسمي لمكتبة
في C++ عبر موقع cppreference.com
هذا المقال قدم شرحاً مستفيضاً يغطي كافة الجوانب العملية والنظرية الخاصة بالتعبيرات النمطية في C++ مع أمثلة وأساسيات لا غنى عنها لأي مبرمج يسعى لاستخدام هذه الأداة في مشاريع حقيقية.

