تعرف على البطلين: std::string والصديق القديم char*
\n\nتخيل الموقف: أنت تكتب كودًا بلغة C++، وفجأة تحتاج إلى تمرير نص إلى مكتبة قديمة بلغة C تتطلب const char*. أو تريد استخدام دالة تعدل النص "في مكانه" عبر char*. بينما لديك النوع الحديث والمريح std::string. يطرح سؤال مشروع: "كيفية تحويل std::string إلى const char* أو char*؟".
هذا السؤال من أكثر الأسئلة شيوعًا على Stack Overflow بين مطوري C++ المبتدئين. وليس بدون سبب: عدم فهم كيفية عمل النصوص والمؤشرات قد يؤدي إلى تسرب الذاكرة، وتعطل البرنامج، وأخطاء غريبة. لكن لا تقلق! سنقوم الآن بشرح كل شيء بالتفصيل.
\n\nفي هذه المقالة ستتعلم:
\n- \n
- كيفية الحصول بأمان على
const char*منstd::string. \n - كيفية الحصول على
char*قابل للتعديل ومتى تحتاج ذلك. \n - ما هي المخاطر التي تنتظر المبتدئ في هذا الطريق. \n
- كيفية تجنب الأخطاء النموذجية عند التعامل مع تحويل النصوص في C++. \n
دالة c_str(): مساعدك الأول
\n\nأبسط وأكثر طريقة آمنة للحصول على const char* هي استدعاء الدالة c_str() من كائن std::string. لماذا سميت بهذا الاسم؟ لأنها تُرجع نصًا على نمط C (نص منتهي بقيمة صفرية).
#include <iostream>\n#include <string>\n\nint main() {\n std::string message = "مرحبًا، عالم C++!";\n const char* c_message = message.c_str();\n \n std::cout << c_message << std::endl;\n return 0;\n}\n\nتضمن الدالة c_str() أن المصفوفة النصية المُعادة تنتهي بالحرف الصفري ('\\0'). هذا أمر بالغ الأهمية لجميع دوال C التي تتعامل مع النصوص (مثل strlen، fopen، printf).
تحذير مهم: عمر النص
\n\nالمؤشر الذي تم الحصول عليه عبر c_str() يشير إلى المخزن الداخلي لـ std::string. بمجرد تعديل النص الأصلي أو تدميره، يصبح هذا المؤشر غير صالح. لا تحتفظ به أبدًا "لوقت لاحق"!
std::string* get_string() {\n return new std::string("بيانات مؤقتة");\n}\n\nint main() {\n const char* bad_ptr = get_string()->c_str();\n delete get_string(); // bad_ptr أصبح معلقًا الآن!\n // لا يمكن استخدام bad_ptr بعد الآن — هذا سلوك غير محدد\n}\n\nدالة data(): نفس الشيء تقريبًا، لكن مع فارق
\n\nقبل معيار C++11، كانت الدالة data() تُرجع const char* ولكن بدون ضمان إنهاء النص بالصفر. بدءًا من معيار C++11، تعمل data() و c_str() بشكل متطابق — كلاهما يُرجع نصًا منتهيًا بالصفر. ومع ذلك، للتوافق مع الإصدارات السابقة ووضوح الكود، يُوصى باستخدام c_str() عندما تحتاج إلى نص C.
std::string text = "C++11 والإصدارات الأحدث";\nconst char* ptr1 = text.c_str(); // واضح: نحتاج نص C\nconst char* ptr2 = text.data(); // يعمل أيضًا، لكنه أقل وضوحًا\n\nإذا كنت تكتب كودًا يجب أن يعمل على المعايير القديمة (قبل C++11)، استخدم فقط c_str() لتحويل النص بشكل آمن.
كيف تحصل على char* عادي (غير ثابت)؟
\n\nأحيانًا تحتاج إلى مؤشر قابل للتعديل char*. على سبيل المثال، عند استدعاء دالة C تقوم بتعديل النص: strtok()، sprintf() أو مكتبة قديمة للتعامل مع الملفات. لا توجد طريقة مباشرة "لاستخراج" char* من std::string — وهذا متعمد لحمايتك من الأخطاء.
لكن هناك حل بديل: نسخ محتوى النص إلى مصفوفة جديدة.
\n\n#include <iostream>\n#include <string>\n#include <cstring>\n\nint main() {\n std::string source = "نص قابل للتعديل";\n char* buffer = new char[source.size() + 1];\n std::strcpy(buffer, source.c_str());\n \n // الآن يمكنك تعديل buffer بحرية\n buffer[0] = 'ت';\n \n std::cout << buffer << std::endl;\n \n delete[] buffer; // لا تنس تحرير الذاكرة!\n return 0;\n}\n\nتذكر دائمًا: عندما تخصص ذاكرة بـ new[]، يجب تحريرها بـ delete[]. وإلا فأنت تسبب تسربًا في الذاكرة.