كل دالة في <string.h> حلقة صغيرة على مصفوفة char تتوقّف عند المنهي الصفري. وبمجرّد وضوح ذلك، تكفّ المكتبة عن أن تبدو قائمة أسماء مبهمة وتبدأ في أن تبدو حفنة الحلقات التي كنت ستكتبها بنفسك. تغطّي هذه الصفحة ما يحمل كل العمل الحقيقي تقريبًا - وقواعد تحديد الأحجام التي تمنعها من الكتابة بعد نهاية مخازنك.
وكل ما هنا يحتاج تضمينًا واحدًا:
#include <string.h>
strlen: كم محرفًا
تعدّ strlen البايتات حتى المنهي دون تضمينه. وتُرجِع size_t، فاطبعها بـ %zu.
strlen تساوي 6؛ وsizeof تساوي 32. الأولى هي النص، والثانية هي التخزين - انظر السلاسل النصية لتعرف لماذا يهمّ هذا التمييز إلى هذا الحد.
وتمشي strlen على السلسلة كاملة في كل استدعاء، فلا تضعها في شرط حلقة على السلسلة الثابتة نفسها:
/* re-counts the whole string on every iteration */
for (size_t i = 0; i < strlen(s); i++) { ... }
/* count once */
size_t n = strlen(s);
for (size_t i = 0; i < n; i++) { ... }
strcpy وstrncpy: النسخ
تنسخ strcpy(dst, src) المحارف زائد المنهي. وليست لديها فكرة عن حجم dst، فعلى المستدعي ضمان أن تحمل dst على الأقل strlen(src) + 1 بايتًا.
وتأخذ strncpy عددًا أقصى، لكن لها فخًّا شهيرًا: فحين يكون طول المصدر n بايتًا على الأقل تنسخ n محرفًا بالضبط وبلا منهٍ. أنهِ السلسلة دائمًا بنفسك:
نمط sizeof dst - 1 مع المنهي الصريح هو ما ينبغي حفظه. (كما تحشو strncpy المصادر القصيرة بأصفار حتى n بايتًا، وهذا عمل مهدور للمخازن الكبيرة - فقد صُمّمت لسجلّات ثابتة العرض لا للأمان.)
strcat وstrncat: الدمج
تُلحِق strcat(dst, src) القيمة src بنهاية ما في dst سلفًا. ويجب أن تحمل الوجهة السلسلتين زائد منهٍ واحد، وأن تحتوي سلفًا على سلسلة صالحة - فالإلحاق بذاكرة غير مهيّأة سلوك غير معرَّف.
لاحظ char path[64] = "/home/ada"; بدل char path[64]; - فالمهيّئ هو ما يجعلها سلسلة صالحة لأول strcat.
وتُلحِق strncat(dst, src, n) على الأكثر n محرفًا وتضيف منهيًا دائمًا، فـ n هي المساحة المتبقّية لا حجم المخزن كاملًا:
strncat(dst, src, sizeof dst - strlen(dst) - 1);
وتعيد استدعاءات strcat المتكرّرة مسحَ الوجهة لإيجاد نهايتها في كل مرة. وبناء سلسلة طويلة في حلقة بتلك الطريقة تربيعي؛ ولأي شيء ذي حجم، تتبّع موضع الكتابة بنفسك أو استخدم snprintf، المغطّاة في تحويل السلاسل النصية.
strcmp: المقارنة
تقارن == العناوين، فهي خاطئة لمصفوفتين متمايزتين تحملان النص نفسه. أما strcmp فتقارن المحارف وتُرجِع إشارة الفرق.
ثلاث قواعد:
- افحص
strcmp(a, b) == 0للمساواة. فكتابةif (strcmp(a, b))تعني "إن كانتا مختلفتين"، وهو ما يُقرأ معكوسًا عند الجميع تقريبًا. - الإشارة وحدها معرَّفة. لا تقارن النتيجة بـ
1أو-1. - والترتيب بحسب قيمة البايت، فـ
"Zebra"تُرتَّب قبل"apple"في ASCII. وللمقارنة غير الحسّاسة لحالة الأحرف، حوّل نسختين إلى الحروف الصغيرة أولًا - فـstrcasecmpالشائعة امتداد من POSIX لا C معيارية.
وstrncmp(a, b, n) تقارن أول n محرفًا فقط، وهي الطريقة النظيفة لفحص بادئة:
strchr وstrstr: البحث
تجد strchr(s, ch) أول ظهور لمحرف؛ وتجد strrchr آخره. وتجد strstr(haystack, needle) سلسلة فرعية. وتُرجِع الثلاث مؤشّرًا داخل السلسلة الأصلية، أو NULL حين لا تطابق.
طرح المؤشّر المُرجَع من البداية يعطي الفهرس؛ ونوعه ptrdiff_t، يُطبَع بـ %td. ولأن النتيجة تشير إلى داخل المصفوفة الأصلية، فإن at + 1 هي بقية السلسلة بلا أي نسخ إطلاقًا - وهو أسلوب C اصطلاحي شائع جدًا.
وافحص NULL دائمًا قبل المراجعة. فـ strchr(email, '@') + 1 على سلسلة بلا @ تحسب عنوانًا من NULL ولم يعد البرنامج معرَّفًا.
تقسيم سلسلة نصية
لا توجد دالة تقسيم، لكن strchr مع منهٍ تؤدّي المهمّة في المكان. يقطع هذا المثال بريدًا إلكترونيًا إلى مستخدم ونطاق:
تنسخ memcpy عددًا دقيقًا من البايتات دون اكتراث بالمنهيات، وهو ما تريده حين تعرف الطول سلفًا. ثم يُكتب المنهي يدويًا.
مثال معالَج: توحيد صيغة اسم
جمعًا للجولة - اقتطاع ومقارنة ونسخ ودمج:
كل كتابة محدودة بـ sizeof الوجهة نفسها، وكل strncpy متبوعة بمنهٍ صريح. وذلك الانضباط، مطبَّقًا باتّساق، هو ما يجعل شيفرة السلاسل النصية في C آمنة.
قواعد أحجام تستحق الحفظ
- تحتاج
strcpyإلىstrlen(src) + 1بايتًا حرًّا في الوجهة. - وتحتاج
strcatإلىstrlen(dst) + strlen(src) + 1. - وبعد
strncpy، اكتبdst[n - 1] = '\0'بنفسك. - ولأجل
strncat، يكون العدد هو المساحة المتبقّية:sizeof dst - strlen(dst) - 1. - افحص
strchrوstrstrبحثًا عنNULLقبل استخدام النتيجة. - استخدم
sizeof dstفقط حين تكونdstمصفوفة حقيقية. فداخل دالة تأخذchar *dst، تعطيsizeofحجم المؤشّر - مرّر طول المخزن كوسيط منفصل.
الأسئلة الشائعة
ماذا تُرجِع strcmp في لغة C؟
صفرًا حين تتطابق السلسلتان، وقيمة سالبة حين تُرتَّب الأولى قبل الثانية، وقيمة موجبة حين تُرتَّب بعدها. والإشارة وحدها ذات معنى - فلا تفترض أبدًا أنها تُرجِع -1 أو 1. والفحص الذي تريده هو if (strcmp(a, b) == 0) في كل الأحوال تقريبًا.
لماذا لا أستطيع مقارنة السلاسل النصية بـ == في لغة C؟
a == b تقارن العنوانين لا المحارف. فمصفوفتان منفصلتان تحملان "cat" تعيشان في عنوانين مختلفين، فتكون المقارنة خاطئة رغم تطابق النص. استخدم strcmp(a, b) == 0 من <string.h>.
ما الفرق بين strcpy وstrncpy؟
تنسخ strcpy حتى '\0' في المصدر دون أي فكرة عن حجم الوجهة. أما strncpy فتتوقّف بعد n بايتات على الأكثر - لكن إن كان المصدر بذلك الطول فإنها لا تنسخ منهيًا، فعليك كتابة dst[n - 1] = '\0'; بنفسك. ولا واحدة منهما آمنة تلقائيًا؛ فانضباط الأحجام مسؤوليتك.
كيف أجد سلسلة فرعية في لغة C؟
strstr(haystack, needle) تُرجِع مؤشّرًا إلى أول ظهور، أو NULL إن لم يوجد. ولأنها تُرجِع مؤشّرًا داخل السلسلة الأصلية، فإن found - haystack يعطيك الفهرس. واستخدم strchr حين تبحث عن محرف واحد.