تخطَّ إلى المحتوى

النصوص في C++‎: string وأهم دوالها

الدرس 8 من 11 · C++

في درس المتغيرات تعرّفت على string سريعاً: نوع جاهز يريحك من مصفوفات الحروف المتعبة في C. لكن string يخفي عشرات الدوال المفيدة. في هذا الدرس ستتعلم كيف تقيس النص وتقطعه وتبحث فيه وتعدّله، وكيف تحوّله إلى أرقام والعكس.

كل أمثلة الدرس تحتاج هذين السطرين في أول الملف:

#include <iostream>
#include <string>

النص سلسلة حروف، ولكل حرف رقم يبدأ من صفر، مثل المصفوفات تماماً:

string word = "Hello";
cout << word.size() << endl;
cout << word[0] << endl;
cout << word.front() << " " << word.back() << endl;
cout << boolalpha << word.empty() << endl;
for (char c : word) {
cout << c << "-";
}
cout << endl;
الناتج
5
H
H o
false
H-e-l-l-o-
  • size(): عدد الحروف، ولها اسم ثانٍ مطابق هو length().
  • word[0]: الحرف الأول، ونوعه char.
  • front() و back(): أول حرف وآخر حرف.
  • empty(): هل النص فارغ؟ و boolalpha تجعل cout تطبع true و false بدل 1 و 0.

تخيّل النص مثل عقد من الخرز: كل خرزة حرف، ولكل خرزة مكان مرقّم في الخيط.

بعكس Java و C#‎، النص في C++‎ قابل للتعديل في مكانه:

string name = "sara";
name[0] = 'S';
name += " Ali";
name.push_back('!');
cout << name << endl;
name.pop_back();
name.insert(0, "Dr. ");
cout << name << endl;
name.erase(0, 4);
cout << name << endl;
الناتج
Sara Ali!
Dr. Sara Ali
Sara Ali
  • +=: تضيف نصاً في النهاية، و push_back تضيف حرفاً واحداً.
  • insert(موضع, نص): تُدخل نصاً في موضع معيّن.
  • erase(موضع, عدد): تحذف عدداً من الحروف ابتداءً من موضع.
string email = "omar@example.com";
size_t at = email.find('@');
cout << at << endl;
cout << email.substr(0, at) << endl;
cout << email.substr(at + 1) << endl;
if (email.find('#') == string::npos) {
cout << "لا توجد علامة #" << endl;
}
cout << boolalpha << email.starts_with("omar") << endl;
cout << email.ends_with(".org") << endl;
الناتج
4
omar
example.com
لا توجد علامة #
true
false
  • find: تُرجع موضع أول ظهور. وإذا لم تجد شيئاً تُرجع القيمة الخاصة string::npos.
  • substr(بداية, طول): الرقم الثاني طول وليس موضع نهاية. وإذا حذفته أخذت حتى آخر النص.
  • starts_with و ends_with: متاحتان منذ C++20.

replace تستبدل جزءاً محدداً بالموضع والطول. ومع find تستبدل كلمة بأخرى:

string text = "I like Java";
size_t pos = text.find("Java");
if (pos != string::npos) {
text.replace(pos, 4, "C++");
}
cout << text << endl;
الناتج
I like C++

العلامة == تقارن محتوى النصين مباشرة، والعلامتان < و > تقارنان أبجدياً:

string a = "apple";
string b = "banana";
cout << boolalpha;
cout << (a == "apple") << endl;
cout << (a == b) << endl;
cout << (a < b) << endl;
cout << (string("Apple") == a) << endl;
الناتج
true
false
true
false

المقارنة حساسة لحالة الحروف: Apple لا تساوي apple. لتجاهل الحالة حوّل النصين إلى حروف صغيرة أولاً، كما سترى بعد قليل.

لا توجد دالة جاهزة تحوّل النص كله، لكن الحلقة تؤدي المهمة مع toupper و tolower من المكتبة <cctype>:

#include <cctype>
string shout = "hello world";
for (char& c : shout) {
c = toupper(c);
}
cout << shout << endl;
int digits = 0;
for (char c : string("Room 204B")) {
if (isdigit(c)) digits++;
}
cout << digits << endl;
الناتج
HELLO WORLD
3

لاحظ char& في الحلقة الأولى: المرجع يجعلنا نعدّل الحرف الأصلي لا نسخة منه. والمكتبة نفسها فيها isdigit و isalpha و isspace لفحص نوع الحرف.

cin >> name تتوقف عند أول مسافة، فلو كتب المستخدم «Omar Ali» ستقرأ «Omar» فقط. لقراءة السطر كله استخدم getline:

#include <iostream>
#include <string>
using namespace std;
int main() {
string fullName;
cout << "اكتب اسمك الكامل: ";
getline(cin, fullName);
cout << "مرحباً " << fullName << endl;
cout << "عدد الحروف: " << fullName.size() << endl;
return 0;
}

إذا كتب المستخدم Omar Ali سيظهر:

اكتب اسمك الكامل: Omar Ali
مرحباً Omar Ali
عدد الحروف: 8
string price = "250";
int total = stoi(price) + 50;
cout << total << endl;
double pi = stod("3.14");
cout << pi * 2 << endl;
string label = "العدد: " + to_string(total);
cout << label << endl;
الناتج
300
6.28
العدد: 300
  • stoi: من نص إلى int. و stod: من نص إلى double.
  • to_string: من رقم إلى نص. ضرورية لأن "نص" + 5 لا تعمل كما تتوقع في C++‎.

لا توجد دالة split جاهزة، لكن stringstream تعامل النص كأنه cin، فتسحب منه الكلمات واحدة واحدة:

#include <sstream>
stringstream words("red green blue");
string word;
int count = 0;
while (words >> word) {
count++;
cout << count << ": " << word << endl;
}
stringstream csv("10,20,30");
string part;
int sum = 0;
while (getline(csv, part, ',')) {
sum += stoi(part);
}
cout << sum << endl;
الناتج
1: red
2: green
3: blue
60

المعامل الثالث في getline هو الحرف الفاصل، وبه تقسّم أي نص مفصول بفواصل.

  1. اقرأ جملة كاملة من المستخدم بـ getline، واطبع عدد حروفها وعدد كلماتها باستخدام stringstream.
  2. خزّن اسم ملف مثل photo.png، واستخرج الامتداد باستخدام rfind('.') و substr.
  3. اكتب دالة string toLower(string s) تُرجع النص بحروف صغيرة.
  4. تحدٍّ: اكتب دالة bool isPalindrome(const string& s) تُرجع true إذا كانت الكلمة تُقرأ من الجهتين بنفس الشكل مثل level.
  • تجاهل npos: استخدام ناتج find مباشرة في substr دون فحص string::npos يرمي استثناء out_of_range عندما لا يوجد النص.
  • دمج نصين حرفيين: "Hello " + "World" خطأ ترجمة، لأن النصوص الحرفية ليست string. اجعل أحدهما string("Hello ").
  • موضع خارج الحدود: word[10] في نص قصير سلوك غير معرَّف ولا يعطي رسالة خطأ. استخدم word.at(10) التي ترمي استثناءً واضحاً.
  • cin ثم getline: بعد cin >> x يبقى رمز السطر الجديد في الانتظار، فتقرأ getline سطراً فارغاً. أضف cin.ignore(); بينهما.

سؤال

ما ناتج string("Hello").substr(1, 3)؟

سؤال

ماذا تُرجع find إذا لم تجد النص المطلوب؟

  • size() لعدد الحروف، و [i] و front() و back() للوصول إليها.
  • النص في C++‎ قابل للتعديل: += و insert و erase و replace.
  • find تُرجع الموضع أو string::npos، و substr(بداية, طول) تقطع.
  • == و < تقارنان المحتوى، و getline تقرأ سطراً كاملاً بمسافاته.
  • stoi و stod و to_string للتحويل، و stringstream لتقسيم النص.