C: دوال السلاسل النصية

دوال السلاسل كمفتاح ربط ومفك براغي في صندوق الأدوات — كل أداة تحل مشكلة محددة: نسخ، وصل، مقارنة، بحث. أتقنها، وتعامل مع النص بثقة.

1. نظرة عامة على string.h

<string.h> هو ملف الرأس الأساسي لعمليات السلاسل في مكتبة C القياسية، يوفر دوال للنسخ والوصل والمقارنة والبحث. يجب تضمينه قبل الاستخدام:

C
#include <string.h>

2. strcpy و strncpy

(1) strcpy

تنسخ strcpy السلسلة المصدر إلى مصفوفة الوجهة، بما في ذلك \0

C
char dest[20];
strcpy(dest, "Hello World");
printf("%s\n", dest);

النموذج الأولي: char *strcpy(char *dest, const char *src);

⚠️ ملاحظة: لا تتحقق strcpy من حجم مخزن الوجهة. إذا كانت السلسلة المصدر أكبر من مصفوفة الوجهة، تكتب خارج الحدود. تأكد من أن مصفوفة الوجهة كبيرة بما يكفي قبل استخدامها.

(2) strncpy

تحدّ strncpy من أقصى عدد من البايتات للنسخ، مما يجعلها أكثر أمانًا:

C
char dest[6];
strncpy(dest, "Hello World", sizeof(dest) - 1);
dest[sizeof(dest) - 1] = '\0';
printf("%s\n", dest);

النموذج الأولي: char *strncpy(char *dest, const char *src, size_t n);

تنسخ strncpy n بايت على الأكثر. إذا كانت السلسلة المصدر أقصر من n، تُملأ المواضع المتبقية بـ \0; وإذا كانت السلسلة المصدر n بايت أو أكثر، لا تُلحق \0 تلقائيًا. لذا، يجب إضافته يدويًا.

💡 نصيحة: احتفظ دائمًا بمساحة لـ \0 عند استخدام strncpy، وألحق \0 يدويًا بعد النسخ.


3. strcat و strncat

(1) strcat

تُلحق strcat السلسلة المصدر بنهاية السلسلة الوجهة:

C
char buf[50] = "Hello";
strcat(buf, " World");
printf("%s\n", buf);

النموذج الأولي: char *strcat(char *dest, const char *src);

يُستبدل \0 الموجود في نهاية السلسلة الوجهة، ويُوضع \0 جديد بعد نتيجة الوصل.

⚠️ ملاحظة: يجب أن يكون في مصفوفة الوجهة مساحة كافية لنتيجة الوصل. لا تتحقق strcat من حجم المخزن المؤقت.

(2) strncat

تحدّ strncat من أقصى عدد من المحارف للإلحاق وتُضيف \0 تلقائيًا

TEXT 📖 للعرض فقط
char buf[10] = "Hi";
strncat(buf, " World!", sizeof(buf) - strlen(buf) - 1);
printf("%s\n", buf);

النموذج الأولي: char *strncat(char *dest, const char *src, size_t n);

تُلحق n محرفًا على الأكثر، ثم تُضيف \0 تلقائيًا. هي أكثر أمانًا من strncpy لأنها تضمن الإنهاء الصفري.


4. strcmp و strncmp

(1) strcmp

تقارن strcmp سلسلتين معجميًا، محرفًا بمحرف:

C
int result = strcmp("abc", "abd");

معاني القيمة المرجعة:

TEXT 📖 للعرض فقط
if (strcmp(s1, s2) == 0) {
    printf("Equal\n");
}
⚠️ ملاحظة: استخدم strcmp لمقارنة السلاسل، ولا تستخدم == أبدًا. == يقارن عناوين المؤشرات، وليس محتويات السلاسل.

(2) strncmp

تقارن strncmp أول n محرف فقط:

C
if (strncmp(str, "GET ", 4) == 0) {
    printf("GET request\n");
}

مفيدة لمقارنة البادئات أو تحديد نطاق المقارنة.

▶ مثال

ترتيب مصفوفة سلاسل بالترتيب المعجمي:

C
#include <stdio.h>
#include <string.h>

void sort_strings(char arr[][32], int n) {
    int i, j;
    char temp[32];

    for (i = 0; i < n - 1; i++) {
        for (j = 0; j < n - 1 - i; j++) {
            if (strcmp(arr[j], arr[j + 1]) > 0) {
                strcpy(temp, arr[j]);
                strcpy(arr[j], arr[j + 1]);
                strcpy(arr[j + 1], temp);
            }
        }
    }
}

int main(void) {
    char names[5][32] = {
        "david",
        "alice",
        "charlie",
        "bob",
        "eve"
    };
    int i;

    sort_strings(names, 5);

    for (i = 0; i < 5; i++) {
        printf("%s\n", names[i]);
    }
    return 0;
}
▶ جرّب الكود
TEXT 📖 للعرض فقط
alice
bob
charlie
david
eve

5. strlen

تُرجع strlen الطول الفعّال للسلسلة (بدون \0):

C
char s[] = "Hello";
printf("%zu\n", strlen(s));
printf("%zu\n", sizeof(s));

المخرجات: 5 و6. strlen تعدّ حتى \0sizeof تتضمن \0.

تجتاز strlen السلسلة للعثور على \0، بتعقيد زمني O(n). إذا استخدمت طول نفس السلسلة عدة مرات، خزّن النتيجة:

TEXT 📖 للعرض فقط
size_t len = strlen(s);
for (size_t i = 0; i < len; i++) {
}

بدلًا من استدعاء strlen في شرط الحلقة في كل تكرار.


6. strchr و strrchr

تبحث strchr عن أول ظهور لمحرف في سلسلة:

C
const char *p = strchr("Hello World", 'o');
if (p != NULL) {
    printf("Found: %s\n", p);
}

المخرجات: "o World". تُرجع مؤشرًا إلى ذلك المحرف، أو NULL إذا لم يُوجد.

تبحث strrchr عن آخر ظهور لمحرف:

TEXT 📖 للعرض فقط
const char *p = strrchr("Hello World", 'o');
if (p != NULL) {
    printf("Last occurrence: %s\n", p);
}

المخرجات: "orld".


7. strstr

تبحث strstr عن أول ظهور لسلسلة فرعية في سلسلة:

C
const char *p = strstr("Hello World", "World");
if (p != NULL) {
    printf("Substring at: %s\n", p);
}

المخرجات: "World". تُرجع NULL إذا لم تُوجد.

إيجاد جميع حالات الظهور:

TEXT 📖 للعرض فقط
const char *text = "abababab";
const char *p = text;
while ((p = strstr(p, "ab")) != NULL) {
    printf("Position %ld\n", (long)(p - text));
    p++;
}

بعد كل إيجاد، قدّم المؤشر بمقدار واحد واستمر في البحث عن حالات الظهور اللاحقة.


8. تطبيقات مخصصة لدوال السلاسل

فهم كيف تعمل الدوال القياسية داخليًا يساعدك على إدراك جوهر عمليات السلاسل.

(1) strlen مخصصة

C
size_t my_strlen(const char *s) {
    size_t len = 0;
    while (s[len] != '\0') {
        len++;
    }
    return len;
}

(2) strcpy مخصصة

C
char *my_strcpy(char *dest, const char *src) {
    char *d = dest;
    while ((*d++ = *src++) != '\0') {
    }
    return dest;
}

هذا الشكل المدمج: *d++ = *src++ ينسخ المحرف أولًا، ثم يقدّم كلا المؤشرين، حتى يُنسخ \0.

(3) strcmp مخصصة

C
int my_strcmp(const char *s1, const char *s2) {
    while (*s1 == *s2) {
        if (*s1 == '\0') return 0;
        s1++;
        s2++;
    }
    return (unsigned char)*s1 - (unsigned char)*s2;
}

مقارنة محرف بمحرف: استمر أثناء التساوي، توقف عند الاختلاف أو \0. أرجع الفرق للدلالة على الترتيب. التحويل إلى unsigned char يضمن أن قيم المحارف السالبة لا تؤثر على نتيجة المقارنة.

▶ مثال

strcat مخصصة واختبار:

C
#include <stdio.h>

char *my_strcat(char *dest, const char *src) {
    char *d = dest;
    while (*d != '\0') {
        d++;
    }
    while ((*d++ = *src++) != '\0') {
    }
    return dest;
}

int main(void) {
    char buf[50] = "Hello";
    my_strcat(buf, ", ");
    my_strcat(buf, "World!");
    printf("%s\n", buf);
    return 0;
}
▶ جرّب الكود
TEXT 📖 للعرض فقط
Hello, World!

أولًا أوجد \0 في نهاية dest، ثم انسخ محتويات src بدءًا من ذلك الموضع.


❓ أسئلة شائعة

س لماذا لا تُلحق strncpy بـ \0 تلقائيًا؟
ج صُمِّمت strncpy في الأصل لملء مخازن مؤقتة ذات طول ثابت (مثل حقول أسماء الملفات): تَحشو بـ \0 عندما تكون أقصر من n، لكن لا تُضيف \0 عند بلوغ n بايت. عند استخدامها كبديل آمن لـ strcpy، يجب إضافة \0 يدويًا.
س هل القيمة الرقمية المحددة التي تُرجعها strcmp مهمة؟
ج يجب أن تتحقق فقط مما إذا كانت موجبة أو سالبة أو صفر — لا تعتمد على القيمة الدقيقة. تطبيقات مختلفة تُرجع فروقًا مختلفة؛ الكود القابل للنقل يستخدم فقط ==0 و <0 و >0.
س strcat غير فعّالة لوصلات متعددة — ما البديل؟
ج كل strcat تمسح من بداية dest حتى النهاية قبل الإلحاق، مما يجعل الكفاءة الإجمالية O(n^2) للوصلات المتعددة. يمكنك تتبع موضع النهاية الحالي يدويًا والإلحاق مباشرة من هناك.
س ما فائدة تنفيذ دوال السلاسل بنفسك؟
ج فهم المبادئ الأساسية، وأسئلة المقابلات الشائعة، والتطوير المدمج حيث قد لا تتوفر المكتبة القياسية. لكن في المشاريع الفعلية، فضّل دوال المكتبة القياسية — فهي مختبرة ومُحسَّنة بدقة.

📖 ملخص

📝 تمارين

  1. اكتب دالة void str_tolower(char *s) تحوّل جميع الحروف الكبيرة في سلسلة إلى صغيرة.
  2. اكتب دالة int str_count_char(const char *s, char ch) تعدّ عدد مرات ظهور المحرف ch في السلسلة s.
  3. بدون استخدام أي دوال من string.h، اكتب my_strstr لتنفيذ بحث السلاسل الفرعية.
Web-Tutorial.com

فريق Web-Tutorial التقني

منصة دروس برمجية يديرها عدة مطورين. كل درس يتم كتابته ومراجعته بواسطة مطورين متخصصين في المجال. نعمل على ضمان دقة وموثوقية المحتوى — إذا لاحظت أي مشكلة، فيرجى إخبارنا.

100%