النوع std::string
والذي يدعى السلسلة النصية في العربية هو كائن يمثّل سلاسل من المحارف، ويوفر صنف string
القياسي خيارًا بسيطًا وآمنًا ومتعدّد الاستخدامات لتخزين سلاسل المحارف ومعالجتها، وذلك موازنة بمصفوفات المحارف، وstring
هو جزء من فضاء الاسم std
، وقد صار معياريًا في عام 1998.
تقطيع السلاسل النصية
يمكن تقطيع سلسلة نصيَّة إلى سلاسل نصيَّة أصغر تدعى الوحدات أو القطع (tokens)، تُسمّى هذه العمليَّة بالترميز المُقطَّع للسلاسل النصيَّة أو تقطيع السلاسل النصية (String Tokenization).
ولدينا عدّة طرق لتقطيع سلسلة نصّية، وسنُدرجها من الأقل إلى الأكثر تكلفة في وقت التشغيل:
-
الطريقة الأولى:
std::strtok
هي أقل طرق تقطيع السلاسل النصّية تكلفة، كما تسمح بتعديل الفاصل (delimiter) بين القطع (tokens)، غير أن هذه الطريقة تعتريها ثلاثة إشكاليات في C++:
-
لا يمكن استخدام
std::strtok
على عدّة سلاسل نصّية في نفس الوقت (باستثناء بعض التطبيقات implementations التي تمكّن من ذلك، مثل:strtok_s
) -
لا يمكن استخدام
std::strtok
على عدّة خيوط أو عمليات (threads) في وقت واحد (لكن قد يتغيّر ذلك بحسب التطبيق كما هو الحال في Visual Studio) -
استدعاء
std::strtok
يُعدّل السلسلة النصية التي يعمل عليها، لذلك لا يمكن استخدامه على السلاسل النصّية من النوعconst strings
أوconst char*
أو السلاسل النصية المجردة، ويجب نسخ السلاسل النصّية المُدخلة لتقطيع أيٍّ من هذه السلاسل باستخدامstd::strtok
أو للعمل على السلاسل النصّية التي ينبغي حفظ محتوياتها، ثم يمكن العمل على النسخة بعد ذلك .
ستكون تكلفة هذه الخيارات جزءًا من تكلفة حجز القطع، لكن إذا كنت تريد استخدام خوارزميات سريعة ولم تتمكن من تجاوز إشكاليات std::strtok
، فربما عليك النظر في خيار hand-spun solution.
// السلسلة النصية المراد تقطيعها std::string str { "The quick brown fox" }; // المتجه الذي سنخزن فيه القطع. vector < std::string > tokens; for (auto i = strtok( & str[0], " "); i != NULL; i = strtok(NULL, " ")) tokens.push_back(i);
-
الطريقة الثانية:
std::istream_iterator
يستخدم std::istream_iterator
هنا عامل الاستخراج من المجرى بشكل تكراري، وإذا كانت السلسلة النصّية المعطاة مفصولة بمسافات فارغة فسنستطيع التوسعة على المعامِل std::strtok
بالتخلص من صعوباته، مما يسمح بالترميز المقطَّع المُضمَّن (inline)، ومن ثم دعم إنشاء متجهات السلاسل النصّية الثابتة (const vector<string>
) ودعم محرف مسافات فاصلة متعددة، انظر:
// السلسلة النصية المراد تقطيعها const std::string str("The quick \tbrown \nfox"); std::istringstream is(str); // المتجه الذي سنخزن فيه القطع. const std::vector<std::string> tokens = std::vector<std::string>( std::istream_iterator<std::string>(is), std::istream_iterator<std::string>());
-
الطريقة الثالثة:
std::regex_token_iterator
تَستخدم std::regex_token_iterator
تعبيرًا نمطيًّا أو std::regex
للقيام بعملية التقطيع بشكل تكراري، وهذا يسمح بتعريف الفاصل بشكل أكثر مرونة، مثل الفاصلات ,
والمسافات الفارغة، انظر:
الإصدار ≥ C++ 11
// السلسلة النصية المُراد تقطيعها const std::string str { "The ,qu\\,ick ,\tbrown, fox" }; const std::regex re { "\\s*((?:[^\\\\,]|\\\\.)*?)\\s*(?:,|$)" }; // المتجه الذي سنخزن فيه القطع. const std::vector < std::string > tokens { std::sregex_token_iterator(str.begin(), str.end(), re, 1), std::sregex_token_iterator() };
انظر هذا المثال الحي، وهذا مثال آخر في درس التعابير النمطية للمزيد من التفاصيل.
التحويل إلى مؤشر (const) char*
استخدم الدالة التابعة c_str()
لتمكين مؤشّر const char*
من الوصول إلى بيانات سلسلة نصّية، واعلم أنّ المؤشّر يبقى صالحًا ما دامت السلسلة النصّية ضمن النطاق (scope) ولم يمسّها تغيير، هذا يعني أنه لا يمكن استدعاء توابع غير ثابتة على الكائن.
الإصدار ≥ C++ 17
استخدم الدالة التابعة data()
للحصول على مؤشّر قابل للتغيير char*
، والذي يمكن استخدامه لمعالجة السلسلة النصّية.
الإصدار ≥ C++ 11
ويمكن الحصول على مؤشّر char*
قابل للتعديل عن طريق أخذ عنوان الحرف الأول: &s[0]
، سيُعيد ذلك في C++ 11 سلسلةً نصّية مُنسّقة جيدًا ومنتهية بالمحرف الفارغ (null-terminated). لاحظ أنّ &s[0]
ستكون مُنسّقة حتى لو كانت s
فارغة، بينما تكون &s.front()
غير مُحدّدة إن كانت s
فارغة.
الإصدار ≥ C++ 17 في المثال التالي، تشير كل من cstr
و data
إلى "This is a string.\0"
:
std::string str("This is a string."); const char* cstr = str.c_str(); const char* data = str.data(); std::string str("This is a string.");
انسخ محتويات str
لفك lifetime
من كائن std::string
:
std::unique_ptr<char []> cstr = std::make_unique<char[]>(str.size() + 1);
// حل بديل للسطر أعلاه، غير محصن من الاعتراضات. // char* cstr_unsafe = new char[str.size() + 1]; std::copy(str.data(), str.data() + str.size(), cstr); cstr[str.size()] = '\0'; // ينبغي إضافة سلسلة نصّية منتهية بحرف فارغ // delete[] cstr_unsafe; std::cout << cstr.get();
استخدام الصنف std::string_view
الإصدار ≥ C++ 17
قدّمت C++ 17 الصنف std::string_view
، وهو ببساطة مدىً غير مالك (non-owning range) من المحارف الثابتة (const char
)، والقابلة للتنفيذ إمّا على هيئة زوج من المؤشّرات، أو مؤشّر وطول (length)، وهو نوع معاملات أفضل للدوالّ التي تتطلب سلاسل نصّية غير قابلة للتعديل.
وكان قبل الإصدار C++ 17 ثلاثة خيارات لفعل هذا: الأول: وسيط واحد، قد يقوم بالتخصيص إن لم تكن بيانات المستدعي في سلسلة نصية مثل سلسلة نصية مجردة أو <vector<char
:
void foo(std::string const& s);
الثاني: وسيطان، يجب أن يمررهما في كل مكان.
void foo(const char* s, size_t len);
والثالث: وسيط واحد، لكن يجب أن يستدعي ()strlen
.
void foo(const char* s);
يستطيع المستدعي تمرير مزود بيانات محرفية لكن سيكون على ()foo
أن يتواجد في ترويسة.
template < class StringT > void foo(StringT const& s);
يمكن استبدال كلّ ما في المثال السابق بالشيفرة التالية، ومزاياها أنها بوسيط واحد، مع ربط أقوى، وبدون نُسخ بغض النظر عن كيفية تخزين المستدعي للبيانات.
void foo(std::string_view s);
لاحظ أنّ std::string_view
لا يمكنها تعديل البيانات الأساسية (underlying data) الخاصّة بها.
string_view
مفيدة في حال أردت تجنّب عمليات النسخ غير الضرورية، كما تقدم مجموعة من وظائف السلاسل النصّية، رغم أنّ سلوك بعض الدوالّ قد يختلف، انظر المثال التالي لسلسلة نصية طويلة بدون داعي:
std::string str = "lllloooonnnngggg sssstttrrriiinnnggg"; // سلسلة نصّية طويلة
استخدام string::subsr
سيعيد سلسلة نصية جديدة، وهذا مكلف إن كانت السلسلة طويلة، انظر:
std::cout << str.substr(15, 10) << '\n';
وعليه فلا نحبذ هذا الأسلوب، ومن ناحية أخرى فلن تنشئ الطريقة الصحيحة هنا أي نسخ إضافية، انظر:
std::string_view view = str;
وستعيد string_view::substr
سلسلة string_view
جديدة:
std::cout << view.substr(15, 10) << '\n';
التحويل إلى std::wstring
تُمثَّل تسلسلات المحارف في C++ عبر تخصيص الصنف std::basic_string
بنوع محرفي أصلي (native)، والمجموعتان الرئيسيتان المُعرّفتان في المكتبة القياسية هما std::string
و std::wstring
:
-
std::string
هي سلسلة مبنية بعناصر من نوعchar
-
std::wstring
مبنية بعناصر من نوعwchar_t
استخدم لأجل التحويل بين النوعين، wstring_convert
:
#include <string> #include <codecvt> #include <locale> std::string input_str = "this is a -string-, which is a sequence based on the -char- type."; std::wstring input_wstr = L"this is a -wide- string, which is based on the -wchar_t- type."; // التحويل std::wstring str_turned_to_wstr = std::wstring_convert<std::codecvt_utf8<wchar_t>>().from_bytes(input_str); std::string wstr_turned_to_str = std::wstring_convert<std::codecvt_utf8<wchar_t>>().to_bytes(input_wstr);
لتحسين قابلية الاستخدام وسهولة القراءة، عرِّف دوالًا لتنفيذ عملية التحويل:
#include <string> #include <codecvt> #include <locale> using convert_t = std::codecvt_utf8 < wchar_t > ; std::wstring_convert < convert_t, wchar_t > strconverter; std::string to_string(std::wstring wstr) { return strconverter.to_bytes(wstr); } std::wstring to_wstring(std::string str) { return strconverter.from_bytes(str); }
مثال تطبيقي:
std::wstring a_wide_string = to_wstring("Hello World!");
هذا أفضل وأوضح بكثير من السطر التالي:
td::wstring_convert<std::codecvt_utf8<wchar_t>>().from_bytes("HelloWorld!")
لاحظ أنّ char
و wchar_t
لا تقتضيان الترميز (encoding) ولا تُعطيان أيّ إشارة عن الحجم بالبايتات، على سبيل المثال، تُستخدم wchar_t
عادة كنوع بيانات ثنائي البايت، وعادة ما تحتوي على بيانات مُرمّزة بترميز UTF-16 في ويندوز (أو UCS-2 في الإصدارات السابقة لنظام ويندوز 2000) أو كنوع بيانات رباعي البايت مُرمّز باستخدام الترميز UTF-32 في لينكس.
هذا على النقيض من الأنواع الحديثة char16_t
و char32_t
، والتي جاءت في الإصدار C++ 11، إذ هي كبيرة بما يكفي لاحتواء أيّ محرف من UTF16 أو UTF32 على الترتيب.
الموازنة المعجمية
يمكن موازنة سلسلتين نصّيتين معجميًا باستخدام المعاملات ==
و !=
و <
و <=
و >
و >=
:
std::string str1 = "Foo"; std::string str2 = "Bar"; assert(!(str1 < str2)); assert(str > str2); assert(!(str1 <= str2)); assert(str1 >= str2); assert(!(str1 == str2)); assert(str1 != str2);
تستخدِم كل هذه الدوالّ التابع std::string::compare()
لإجراء المقارنات وإعادة القيمة البوليانية المناسبة.
وفيما يلي شرح عامّ لمعَاملات الموازنة:
-
المُعامل
==
إذا كانت str1.length() == str2.length()
، وتطابقت أزواج المحارف (character pairs)، فسيعيد المُعاملُ القيمةَ true
، وإلا فسَيعيد false
.
-
المُعامل
!=
إذا كانت str1.length() != str2.length()
أو لم تتطابق أزواج المحارف، فسَيعيد المُعامل القيمة true
، وإلا فسيعيد false
.
-
المُعامل
<
أو المُعامل>
يبحثان عن أول زوج غير متطابق من المحارف، ويقارن بينهما ثم يعيد النتيجة البوليانية تبعًا لنتيجَة الموازنة.
-
المُعامل
<=
أو المُعامل>=
يبحث عن أول زوج غير متطابق من المحارف، ويقارن بينهما ثم يعيد النتيجة البوليانية.
ملاحظة: يشير مصطلح زوج المحارف (character pair) إلى المحارف المتقابلة في كلا السلسلتين، أي الحرفين الذين يوجَدين في نفس الموضع من السلسلتين النّصّيتين. مثلًا، لنفترض أنّ لدينا سلسلتين نصّيتين str1
و str2
، وطولاهما n
و m
على التوالي، في هذه الحالة ستكون أزواج المحارف في كلتَي السلسلتين النصّيتين هي الأزواج str1
و str2
حيث i = 0, 1, 2, …, max(n,m). في حال عدم وجود حرف يقابل الفهرس i، أي عندما يكون i أكبر من أو يساوي n
أو m
، فسيتم اعتبارُه القيمةَ الأصغر.
فيما يلي مثال على استخدام <
:
std::string str1 = "Barr"; std::string str2 = "Bar"; assert(str2 < str1);
خطوات الموازنة هي كالتالي:
-
موازنة الحرفين الأوّلين،
'B' == 'B'
- ثم متابعة. -
موازنة بين الحرفين الثانيين،
'a' == 'a'
- ثم متابعة. -
موازنة بين الحرفين الثّالثين،
'r' == 'r'
- ثم متابعة. -
استُنفِذ نطاق
str2
الآن، في حين أنّ نطاقstr1
ما يزال فيه محارف زائدة. وبالتالي يكون لدينا:str2 < str1
.
تقليم المحارف في بداية أو نهاية السلسلة النصية
يتطلب هذا المثال الترويسات
الإصدار ≥ C++ 11
تقليم (trim) تسلسل أو سلسلة نصّية يعني إزالة جميع العناصر الأولى أو الأخيرة التي تحقق شرطًا معينًا، ونقلّم أولًا العناصر الأخيرة لأنها لا تتطلب تحريك أيّ عنصر، ثم نقلّم العناصر الأولى.
لاحظ أنّ التعميمات أدناه تعمل مع جميع أنواع السلاسل النصّية الأساسية std::basic_string
، مثل std::string
و std::wstring
، وأيضًا على الحاويات مثل std::vector
و std::list
.
template < typename Sequence, // list أو vector أي سلسلة نصّية أساسية، مثل typename Pred > // شرط Sequence& trim(Sequence& seq, Pred pred) { return trim_start(trim_end(seq, pred), pred); }
يعتمد تقليم العناصر الأخيرة على إيجاد العنصر الأخير غير المطابق للشّرط، وبدء عملية المحو من هناك:
template < typename Sequence, typename Pred > Sequence & trim_end(Sequence & seq, Pred pred) { auto last = std::find_if_not(seq.rbegin(), seq.rend(), pred); seq.erase(last.base(), seq.end()); return seq; }
أمّا تقليم العناصر الأولية، فيعتمد على تحديد العنصر الأوّل الذي لا يحقّق الشرط وبدء عملية المحو من بداية السلسلة وحتى هناك:
template < typename Sequence, typename Pred > Sequence & trim_start(Sequence & seq, Pred pred) { auto first = std::find_if_not(seq.begin(), seq.end(), pred); seq.erase(seq.begin(), first); return seq; }
استخدم الدالّة std::isspace()
كشرط إذا أردت تقليم المسافات الفارغة في سلسلة نصّية :
std::string& trim(std::string& str, const std::locale& loc = std::locale()) { return trim(str, [&loc](const char c){ return std::isspace(c, loc); }); } std::string& trim_start(std::string& str, const std::locale& loc = std::locale()) { return trim_start(str, [&loc](const char c){ return std::isspace(c, loc); }); } std::string& trim_end(std::string& str, const std::locale& loc = std::locale()) { return trim_end(str, [&loc](const char c){ return std::isspace(c, loc); }); }
وبالمثل، يمكننا استخدام الدالّة std::iswspace()
مع السلاسل النصّية std::wstring
.
إذا كنت ترغب في إنشاء تسلسل جديد عبر إنشاء نسخة مُقلَّمة، فيمكنك استخدام دالّة منفصلة على النحو التالي:
template < typename Sequence, typename Pred > Sequence trim_copy(Sequence seq, Pred pred) { // بالقيمة seq تمرير trim(seq, pred); return seq; }
استبدال السلاسل الصّية
الاستبدال بالموضع
استخدم التابع replace
الخاص بالصنف std::string
لاستبدال جزء من سلسلة نصّية. أيضًا، التابع replace
له الكثير من التحميلات الزائدة (overloads) المفيدة:
// عرِّف سلسلة نصّية std::string str = "Hello foo, bar and world!"; std::string alternate = "Hello foobar"; //1) str.replace(6, 3, "bar"); //"Hello bar, bar and world!" //2) str.replace(str.begin() + 6, str.end(), "nobody!"); //"Hello nobody!" //3) str.replace(19, 5, alternate, 6, 6); //"Hello foo, bar and foobar!"
الإصدار ≥ C++ 14
//4) str.replace(19, 5, alternate, 6); //"Hello foo, bar and foobar!" //5) str.replace(str.begin(), str.begin() + 5, str.begin() + 6, str.begin() + 9); //"foo foo, bar and world!" //6) str.replace(0, 5, 3, 'z'); //"zzz foo, bar and world!" //7) str.replace(str.begin() + 6, str.begin() + 9, 3, 'x'); //"Hello xxx, bar and world!"
الإصدار ≥ C++ 11
//8) str.replace(str.begin(), str.begin() + 5, { 'x', 'y', 'z' }); //"xyz foo, bar and world!"
استبدال سلسلة نصّية بأخرى
استبدل بالظهور الأول فقط للسلسلة النصّية with
السلسلة replace
في str
:
std::string replaceString(std::string str, const std::string& replace, const std::string& with) { std::size_t pos = str.find(replace); if (pos != std::string::npos) str.replace(pos, replace.length(), with); return str; }
استبدال السّلسلة النصّية with
بالسلسلة replace
أينما ظهرت في str
:
std::string replaceStringAll(std::string str, const std::string& replace, const std::string& with) { if (!replace.empty()) { std::size_t pos = 0; while ((pos = str.find(replace, pos)) != std::string::npos) { str.replace(pos, replace.length(), with); pos += with.length(); } } return str; }
التحويل إلى سلسلة نصية
استخدم std::ostringstream
لتحويل أيّ نوع قابل للإجراء (streamable type) إلى تمثيله النصي -أي إلى سلسلة نصّية-، عن طريق إدراج الكائن المُراد تحويله في كائن std::ostringstream
(عبر معامل إدراج المجرى <<
)، ثم تحويل std::ostringstream
بأكملها إلى سلسلة نصّية.
مثلًا، بالنسبة للأعداد الصحيحة int
:
#include <sstream> int main() { int val = 4; std::ostringstream str; str << val; std::string converted = str.str(); return 0; }
اكتب دالّة التحويل الخاصة بك:
template < class T > std::string toString(const T & x) { std::ostringstream ss; ss << x; return ss.str(); }
هذه الدالّة ستعمل بنجاح، لكنّ أداءها أقلّ كفاءة. يمكن أن تنفذ الأصناف المُعرَّفة من قبل المستخدم عاملَ إدراج المجرى، انظر المثال التالي حيث نكتب تمثيلًا نصيًا ل a
في out
:
std::ostream operator << (std::ostream & out, const A & a) { return out; }
الإصدار ≥ C++ 11
بصرف النظر عن مجاري التدفق، أصبح ممكنًا منذ الإصدار C++ 11 أن نستخدم الدالّة std::to_string
(و std::to_wstring
) والتي حُمِّلت تحميلًا زائدًا في جميع الأنواع الأساسية، وصارت تعيد تمثيلًا نصيًّا للمعامل المُمرّر إليها.
std::string s = to_string(0x12f3); // "4851" القيمة s بعد هذا ستحتوي
التقسيم (Splitting)
استخدم std::string::substr
لتقسِيم السلاسل النصّية. هناك نوعان من هذا التابع، يأخذ الأول موضع البداية، والذي ستبدأ منه السلسلة النصّية الفرعية (substring) المُعادة. يجب أن يكون موضع البداية جزءًا من النطاق (0, str.length()]
:
std::string str = "Hello foo, bar and world!"; std::string newstr = str.substr(11); // "bar and world!"
فيما يأخذ التابع الثاني موضع البداية والطول الإجمالي للسّلسلة النصّية الفرعية الجديدة. ولن تتجاوز السلسلة النصّية الفرعية نهاية السلسلة النصّية المصدرية مهما كان الطول المُمرَّر:
std::string str = "Hello foo, bar and world!"; std::string newstr = str.substr(15, 3); // "and"
لاحظ أنّك تستطيع استدعاء substr
بدون أي وسائط، وفي هذه الحالة ستُعاد نسخة مضبوطة من السلسلة النصية:
std::string str = "Hello foo, bar and world!"; std::string newstr = str.substr(); // "Hello foo, bar and world!"
الوصول إلى محرف من السلسلة النصية
هناك عدة طرق لاستِخراج الحروف من السلاسل النصّية، ولكلّ منها خصوصياتها.
std::string str("Hello world!");
operator
يعيد مرجعًا إلى الحرف الموجود عند الفهرس n. لا يتحقّق المُعامل td::string::operator[]
من حدود السلسلة النصّية، ولا يرفع اعتراضًا (exception) في حال تجاوزها، لذلك فالمُستدعي هو المسؤول عن التحقق من أنّ الفهرس يقع في حدود السلسلة النصّية:
char c = str[6]; // 'w'
at(n)
يعيد مرجعًا إلى الحرف الموجود عند الفهرس n. يتحقق المُعامل std::string::at
من الحدود، ويرفع اعتراض std::out_of_range
إذا لم يكن الفهرس ضمن حدود السلسلة النصّية:
char c = str.at(7); // 'o'
الإصدار ≥ C++ 11
ملاحظة: في كلا المثالين أعلاه، سيكون السّلوك غير محدد في حال كانت السلسلة النصية فارغة.
front()
يعيد مرجعًا إلى الحرف الأول:
char c = str.front(); // 'H'
back()
يعيد مرجعًا إلى الحرف الأخير:
char c = str.back(); // '!'
التحقق من كون سلسلة نصية سابقة (prefix) لسلسلة أخرى
الإصدار ≥ C++ 14
في الإصدار C++ 14، يمكن إنجاز ذلك بسهولة عبر std::mismatch، والذي يُعيد الزوج الأوّل غير المتطابق من النطاقين:
std::string prefix = "foo"; std::string string = "foobar"; bool isPrefix = std::mismatch(prefix.begin(), prefix.end(), string.begin(), string.end()).first == prefix.end();
قبل الإصدار C++ 14، كان هناك ما يُسمّى إصدار النطاق ونصف (range-and-a-half version) من التابع mismatch()
، ولكنه لم يكن آمنًا في حال كانت السلسلة النصّية الثانية أقصر من الأولى.
الإصدار < C++ 14
لا زال بإمكاننا استخدام إصدار النطاق ونصف من التابع std::mismatch()
، لكن سيكون عليك التحقّق أولًا من أنّ السلسلة النصّية الأولى أقصر من الثانية أو تعادلها:
bool isPrefix = prefix.size() <= string.size() && std::mismatch(prefix.begin(), prefix.end(), string.begin(), string.end()).first == prefix.end();
الإصدار ≥ C++ 17
يمكننا كتابة الموازنة التي نريدها مباشرة مع std::string_view
، دون الحاجة إلى القلق بشأن الحِمل الزائد في الذاكرة (allocation overhead) أو إنشاء النُّسخ:
bool isPrefix(std::string_view prefix, std::string_view full) { return prefix == full.substr(0, prefix.size()); }
التكرار على المحارف
الإصدار ≥ C++ 17
تدعم السلاسل النصّية المُكرّرات، وعليه تستطيع استخدام حلقة نطاقية (ranged based loop) للتكرار على المحارف:
std::string str = "Hello World!"; for (auto c: str) std::cout << c;
يمكنك أيضًا استخدام حلقة for
"تقليدية" للتكرار على المحارف:
std::string str = "Hello World!"; for (std::size_t i = 0; i < str.length(); ++i) std::cout << str[i];
التحويل إلى الأعداد الصحيحة أو العَشرية
يمكن تحويل سلسلة نصّية تحتوي على عدد إلى نوع عددي صحيح أو عشري باستخدام دوالّ التحويل.
ملاحظة: تتوقف جميع هذه الدوالّ عن تحليل السلسلة النصّية المُدخلة بمجرد أن تصادف محرفًا غير رقمي. مثلاً، ستُحوّل السلسلة النصّية "123abc"
إلى 123.
تحوّل مجموعة الدوالّ std::ato*
السلاسل النصّية من نمط لغة C (مصفوفات المحارف) إلى أنواع عددية صحيحة أو عشرية:
std::string ten = "10"; double num1 = std::atof(ten.c_str()); int num2 = std::atoi(ten.c_str()); long num3 = std::atol(ten.c_str());
الإصدار ≥ C++ 11
long long num4 = std::atoll(ten.c_str());
إلا أنّه يفضَّل تجنّب استخدام هذه الدوالّ لأنها تُعيد 0
في حال فشلت في تحليل السلسلة النصّية. وهذا سيء لأنّ القيمة 0
قد تُفسَّر على أنّها نتيجة عددية صالحة. مثلًا، إن كانت السلسلة النصية المُدخلة هي "0"، فسيكون من المستحيل تحديد ما إذا كان التحويل قد فشل أم لا بغضّ النظر عن النتيجة.
وتحوّل الدوالّ الحديثة std::sto*
السلاسل النصّية إلى أعداد صحيحة أو عشرية، وتطلق استثناءً في حال فشلت في تحليل السلسلة النصية. استخدم هذه الدّوال كلما أمكن:
الإصدار ≥ C++ 11
std::string ten = "10"; int num1 = std::stoi(ten); long num2 = std::stol(ten); long long num3 = std::stoll(ten); float num4 = std::stof(ten); double num5 = std::stod(ten); long double num6 = std::stold(ten);
كذلك، فإنّ هذه الدوالّ يمكنها أن تتعامل أيضًا مع السلاسل النصّية الثُمانية (octal) والست عشرية (hex)، وذلك على عكس دوالّ std::ato*
. والمعامل الثاني هو مؤشّر يشير إلى أوّل حرف غير مُحوَّل في السلسلة النصية المُدخَلَة، أمّا المعامل الثالث فيمثّل الأساس الرقمي (base) الذي يجب استخدامه. يُستخدَم الحرف 0
للرصد التلقائي للأعداد الثُمانية (تبدأ بـ 0
) والست عشرية (تبدأ بـ 0x
أو 0X
)، أما القيم الأخرى فتمثّل الأساس الذي يجب استخدامه.
std::string ten = "10"; std::string ten_octal = "12"; std::string ten_hex = "0xA"; int num1 = std::stoi(ten, 0, 2); // 2 int num2 = std::stoi(ten_octal, 0, 8); // 10 long num3 = std::stol(ten_hex, 0, 16); // 10 long num4 = std::stol(ten_hex); // 0 long num5 = std::stol(ten_hex, 0, 0); // 0x تعيد 10 لأنها رصدت
ضم السلاسل النصّية
يمكنك ضمّ (concatenate) السلاسل النصّية باستخدام المُعاملين المُحمَّلين تحميلًا زائدًا +
و +=
.
استخدام المُعامل +
:
std::string hello = "Hello"; std::string world = "world"; std::string helloworld = hello + world; // "Helloworld"
استخدام المُعامل +=
:
std::string hello = "Hello"; std::string world = "world"; hello += world; // "Helloworld"
يمكنك أيضًا ضمّ السلاسل النصّية من نمط لغة C، بما في ذلك السلاسل النصّية المجردة:
std::string hello = "Hello"; std::string world = "world"; const char *comma = ", "; std::string newhelloworld = hello + comma + world + "!"; // "Hello, world!"
يمكنك أيضًا استخدام push_back()
لإضافة حرف واحد إلى السلسلة النصية:
std::string s = "a, b, "; s.push_back('c'); // "a, b, c"
لدينا يوجد أيضًا تابع append()
، والذي يشبه إلى حد كبير +=
:
std::string app = "test and "; app.append("test"); // "test and test"
التحويل بين ترميزات المحارف
التحويل بين الترميزات (encodings) أمر سهل في C++ 11، ويمكن لمعظم المصرّفات إنجازه بطريقة مستقلّة عن المنصة (cross-platform) عبر الترويستين
#include <iostream> #include <codecvt> #include <locale> #include <string> using namespace std; int main() { // wstring و utf8 يحول بين سلاسل wstring_convert < codecvt_utf8_utf16 < wchar_t >> wchar_to_utf8; // utf16 وسلاسل utf8 يحول بين سلاسل wstring_convert < codecvt_utf8_utf16 < char16_t > , char16_t > utf16_to_utf8; wstring wstr = L"foobar"; string utf8str = wchar_to_utf8.to_bytes(wstr); wstring wstr2 = wchar_to_utf8.from_bytes(utf8str); wcout << wstr << endl; cout << utf8str << endl; wcout << wstr2 << endl; u16string u16str = u"foobar"; string utf8str2 = utf16_to_utf8.to_bytes(u16str); u16string u16str2 = utf16_to_utf8.from_bytes(utf8str2); return 0; }
لاحظ أنّ Visual Studio 2015 يدعم مثل هذه التحويلات، ولكن بسبب خلل موجود في مكتبتها، فينبغي استخدام قالب مختلف لـ wstring_convert
عند التعامل مع الترميز char16_t
:
using utf16_char = unsigned short; wstring_convert<codecvt_utf8_utf16<utf16_char>, utf16_char> conv_utf8_utf16; void strings::utf16_to_utf8(const std::u16string& utf16, std::string& utf8) { std::basic_string<utf16_char> tmp; tmp.resize(utf16.length()); std::copy(utf16.begin(), utf16.end(), tmp.begin()); utf8 = conv_utf8_utf16.to_bytes(tmp); }
البحث عن محرف أو أكثر في السلسلة النصية
استخدام التابع std::string::find
لإيجاد حرف أو سلسلة نصّية أخرى، والذي يعيد موضع الحرف الأوّل من التطابق الأوّل. أما إن لم يُعثر على أيّ تطابق، فستُعاد القيمة std::string::npos
std::string str = "Curiosity killed the cat"; auto it = str.find("cat"); if (it != std::string::npos) std::cout << "Found at position: " << it << '\n'; else std::cout << "Not found!\n";
ستكون النتيجة 21.
تُوسَّع فرص البحث بالدوال التالية:
find_first_of // إيجاد أول ظهور للمحارف find_first_not_of // إيجاد أول غياب للمحارف find_last_of // إيجارد آخر ظهور للمحارف find_last_not_of // إيجاد آخر غياب للمحارف
تتيح لك لك هذه الدوالّ أن تبحث عن المحارف من نهاية السلسلة النصّية، وكذلك البحث عن الحالات السلبية (أي المحارف غير الموجودة في السلسلة). انظر المثال التالي:
std::string str = "dog dog cat cat"; std::cout << "Found at position: " << str.find_last_of("gzx") << '\n';
ستكون النتيجة 6.
ملاحظة: لا تبحث الدوالّ المذكورة أعلاه عن السلاسل النصّية الفرعية (substrings)، بل عن المحارف الموجودة في سلسلة البحث، ففي المثال أعلاه عُثِر على الظهور الأخير لـ 'g'
في الموضع 6
بينما لم يُعثر على المحارف الأخرى.
هذا الدرس جزء من سلسلة مقالات عن C++.
ترجمة -بتصرّف- للفصل Chapter 47: std::string من كتاب C++ Notes for Professionals
أفضل التعليقات
لا توجد أية تعليقات بعد
انضم إلى النقاش
يمكنك أن تنشر الآن وتسجل لاحقًا. إذا كان لديك حساب، فسجل الدخول الآن لتنشر باسم حسابك.