std::string: نص يدير نفسه بنفسه
في الصفحة السابقة رأيت كيف تتيح المؤشرات الذكية لكائن أن يمتلك موردًا ويُنظّفه تلقائيًا. وstd::string هو الفكرة نفسها مطبَّقة على النص: فهو يمتلك مخزنًا مؤقتًا من المحارف، ويوسّعه عند الإضافة، ويحرّره عندما تخرج السلسلة من النطاق. لن تستدعي new أبدًا، ولن تعدّ البايتات، ولن تقلق بشأن محرف إنهاء صفري مفقود.
لاستخدامه، ضمِّن <string>. يقع الصنف في فضاء الأسماء std.
يقوم المعامل + بعمل حقيقي - فهو يحجز مخزنًا مؤقتًا جديدًا كبيرًا بما يكفي للجزأين معًا وينسخهما داخله. أما مع char* خام فستلجأ إلى strcpy وstrcat وتأمل أن يكون مخزنك المؤقت كبيرًا بما يكفي. وstd::string يجعل هذه الفئة من الأخطاء بأكملها تختفي.
بناء السلاسل ودمجها
يمكنك الدمج بـ +، لكن أداة العمل الأساسية لتنمية سلسلة في مكانها هي +=. فهي تُلحق بالمخزن المؤقت القائم بدلًا من إنتاج سلسلة جديدة تمامًا في كل مرة، وهذا مهم داخل الحلقات.
مفاجأة شائعة: يتطلب + أن يكون أحد المعاملين على الأقل من نوع std::string بالفعل. فالحرفيّتان النصيّتان مجرد const char*، لذا فإن "a" + "b" لا يُترجَم - إذ لا يوجد operator+ لمؤشرين خامين. اجعل أحدهما سلسلة أولًا:
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
لاحظ أن الصيغة الأخيرة تستخدم اللاحقة s من <string> (using namespace std::string_literals;)، التي تحوّل الحرفيّة مباشرةً إلى std::string.
الوصول إلى داخل السلسلة
يتصرّف std::string مثل حاوية من char، فيمكنك فهرسته والمرور عليه وطلب أجزاء منه.
تُعيد substr(pos, len) سلسلة جديدة تمامًا منسوخة من الأصل؛ وهي لا تعدّل المصدر. انتبه للحدود: فاستخدام word[10] على سلسلة من 5 محارف هو سلوك غير معرَّف - لن يُطلق استثناءً، بل سيقرأ بيانات عشوائية. وإذا أردت وصولًا مُتحقَّقًا منه يُطلق std::out_of_range، فاستخدم word.at(10) بدلًا من word[10].
مزلق كلاسيكي آخر: تُعيد .size() نوعًا غير مُشار (size_t). وكتابة حلقة عكسية مثل for (size_t i = word.size() - 1; i >= 0; --i) لا تنتهي أبدًا، لأن i غير المُشار لا يمكن أن ينزل دون الصفر - بل يلتف ليصبح رقمًا ضخمًا. استخدم فهرسًا مُشارًا أو أعد بناء الشرط عندما تحتاج إلى المرور على السلسلة بالعكس.
البحث والاستبدال
تحدّد find سلسلة فرعية أو محرفًا وتُعيد فهرس البداية. وعندما يكون الهدف غير موجود، تُعيد الثابت الخاص std::string::npos - قارن دائمًا به بدلًا من افتراض أن find تُعيد -1.
لتغيير النص في مكانه، تستبدل replace(pos, len, text) مقطعًا بمحتوى جديد (قد يكون بطول مختلف)، بينما تضيف insert/erase أجزاءً أو تزيلها:
السلاسل والأرقام
النص والأرقام لا يتحوّلان من تلقاء نفسيهما - فـ "42" هي ثلاثة محارف، وليست العدد الصحيح 42. تمنحك المكتبة القياسية دوال تحويل في كلا الاتجاهين. استخدم stoi وstod وأخواتها لتحليل النص إلى أرقام، وto_string للاتجاه المعاكس.
هناك مزلقان هنا. الأول، أن stoi("abc") تُطلق std::invalid_argument، لذا احمِ الدخل غير الموثوق بـ try/catch. والثاني، أن to_string(3.99) تعطي القيمة الكاملة 3.990000 - فإن احتجت إلى التحكم في الدقة والتنسيق، فتلك مهمة تيارات السلاسل (string streams)، وهي بالضبط وجهتنا التالية.
try {
int n = std::stoi(userInput);
} catch (const std::invalid_argument&) {
std::cout << "That wasn't a number.\n";
}
التالي: الإدخال والإخراج
ظللت تطبع السلاسل بـ cout طوال الوقت، لكن قراءتها من المستخدم لها مفاجآتها الخاصة - فـ cin >> name يتوقف عند أول مسافة، لذا فإن سطرًا كاملًا مثل "Ada Lovelace" يحتاج إلى std::getline بدلًا منه. تتناول الصفحة التالية الإدخال والإخراج في C++ كما ينبغي: معاملات التيار، وقراءة الأسطر كاملةً، ومزج >> مع getline دون التعثّر بأسطر جديدة متبقّية.
الأسئلة الشائعة
ما الفرق بين std::string و char* في C++؟
std::string صنف يمتلك ذاكرته الخاصة ويديرها، وينمو عند الحاجة، ويُنظّف نفسه تلقائيًا. أما char* فهو مجرد مؤشر خام إلى المحارف مع '\0' للإنهاء - وأنت من يدير المخزن المؤقت والطول ومدة الحياة بنفسك. فضّل std::string في كل شيء تقريبًا؛ فهو يزيل فئات كاملة من أخطاء تجاوز سعة المخزن المؤقت والمؤشرات المعلّقة.
كيف تحصل على طول السلسلة النصية في C++؟
استدعِ .size() أو اسمها البديل .length() على std::string: تُعيد name.size() عدد المحارف بنوع size_t. كلتاهما تُعيد القيمة نفسها؛ وsize() هو الأسلوب الأكثر شيوعًا لأنه يتوافق مع كل حاويات STL الأخرى.
كيف تحوّل سلسلة نصية إلى رقم في C++؟
استخدم std::stoi للنوع int، وstd::stod للنوع double، وما يماثلها (stol، stof). مثال: int n = std::stoi("42");. هذه الدوال تطلق std::invalid_argument إذا لم يكن النص رقمًا، لذا غلّفها بـ try/catch عندما يكون الدخل غير موثوق.