C: دوال السلاسل النصية
دوال السلاسل كمفتاح ربط ومفك براغي في صندوق الأدوات — كل أداة تحل مشكلة محددة: نسخ، وصل، مقارنة، بحث. أتقنها، وتعامل مع النص بثقة.
1. نظرة عامة على string.h
<string.h> هو ملف الرأس الأساسي لعمليات السلاسل في مكتبة C القياسية، يوفر دوال للنسخ والوصل والمقارنة والبحث. يجب تضمينه قبل الاستخدام:
#include <string.h>
2. strcpy و strncpy
(1) strcpy
تنسخ strcpy السلسلة المصدر إلى مصفوفة الوجهة، بما في ذلك \0:
char dest[20];
strcpy(dest, "Hello World");
printf("%s\n", dest);
النموذج الأولي: char *strcpy(char *dest, const char *src);
strcpy من حجم مخزن الوجهة. إذا كانت السلسلة المصدر أكبر من مصفوفة الوجهة، تكتب خارج الحدود. تأكد من أن مصفوفة الوجهة كبيرة بما يكفي قبل استخدامها.
(2) strncpy
تحدّ strncpy من أقصى عدد من البايتات للنسخ، مما يجعلها أكثر أمانًا:
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 السلسلة المصدر بنهاية السلسلة الوجهة:
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 تلقائيًا:
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 سلسلتين معجميًا، محرفًا بمحرف:
int result = strcmp("abc", "abd");
معاني القيمة المرجعة:
< 0: s1 أصغر من s2= 0: s1 يساوي s2> 0: s1 أكبر من s2
if (strcmp(s1, s2) == 0) {
printf("Equal\n");
}
strcmp لمقارنة السلاسل، ولا تستخدم == أبدًا. == يقارن عناوين المؤشرات، وليس محتويات السلاسل.
(2) strncmp
تقارن strncmp أول n محرف فقط:
if (strncmp(str, "GET ", 4) == 0) {
printf("GET request\n");
}
مفيدة لمقارنة البادئات أو تحديد نطاق المقارنة.
▶ مثال
ترتيب مصفوفة سلاسل بالترتيب المعجمي:
#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;
}
alice
bob
charlie
david
eve
5. strlen
تُرجع strlen الطول الفعّال للسلسلة (بدون \0):
char s[] = "Hello";
printf("%zu\n", strlen(s));
printf("%zu\n", sizeof(s));
المخرجات: 5 و6. strlen تعدّ حتى \0; sizeof تتضمن \0.
تجتاز strlen السلسلة للعثور على \0، بتعقيد زمني O(n). إذا استخدمت طول نفس السلسلة عدة مرات، خزّن النتيجة:
size_t len = strlen(s);
for (size_t i = 0; i < len; i++) {
}
بدلًا من استدعاء strlen في شرط الحلقة في كل تكرار.
6. strchr و strrchr
تبحث strchr عن أول ظهور لمحرف في سلسلة:
const char *p = strchr("Hello World", 'o');
if (p != NULL) {
printf("Found: %s\n", p);
}
المخرجات: "o World". تُرجع مؤشرًا إلى ذلك المحرف، أو NULL إذا لم يُوجد.
تبحث strrchr عن آخر ظهور لمحرف:
const char *p = strrchr("Hello World", 'o');
if (p != NULL) {
printf("Last occurrence: %s\n", p);
}
المخرجات: "orld".
7. strstr
تبحث strstr عن أول ظهور لسلسلة فرعية في سلسلة:
const char *p = strstr("Hello World", "World");
if (p != NULL) {
printf("Substring at: %s\n", p);
}
المخرجات: "World". تُرجع NULL إذا لم تُوجد.
إيجاد جميع حالات الظهور:
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 مخصصة
size_t my_strlen(const char *s) {
size_t len = 0;
while (s[len] != '\0') {
len++;
}
return len;
}
(2) strcpy مخصصة
char *my_strcpy(char *dest, const char *src) {
char *d = dest;
while ((*d++ = *src++) != '\0') {
}
return dest;
}
هذا الشكل المدمج: *d++ = *src++ ينسخ المحرف أولًا، ثم يقدّم كلا المؤشرين، حتى يُنسخ \0.
(3) strcmp مخصصة
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 مخصصة واختبار:
#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;
}
Hello, World!
أولًا أوجد \0 في نهاية dest، ثم انسخ محتويات src بدءًا من ذلك الموضع.
❓ أسئلة شائعة
\0 تلقائيًا؟strncpy في الأصل لملء مخازن مؤقتة ذات طول ثابت (مثل حقول أسماء الملفات): تَحشو بـ \0 عندما تكون أقصر من n، لكن لا تُضيف \0 عند بلوغ n بايت. عند استخدامها كبديل آمن لـ strcpy، يجب إضافة \0 يدويًا.strcmp مهمة؟==0 و <0 و >0.strcat غير فعّالة لوصلات متعددة — ما البديل؟strcat تمسح من بداية dest حتى النهاية قبل الإلحاق، مما يجعل الكفاءة الإجمالية O(n^2) للوصلات المتعددة. يمكنك تتبع موضع النهاية الحالي يدويًا والإلحاق مباشرة من هناك.📖 ملخص
strcpy/strncpyتنسخ السلاسل؛ strncpy أكثر أمانًا لكن تتطلب إنهاءً صفريًا يدويًاstrcat/strncatتصل السلاسل؛ strncat تُضيف\0تلقائيًاstrcmp/strncmpتقارن السلاسل؛ لا تستخدم==أبدًا لمقارنة محتويات السلاسلstrchr/strrchrتبحث عن محارف؛strstrتبحث عن سلاسل فرعية- التطبيقات المخصصة تساعد على فهم المبادئ الأساسية؛ استخدم المكتبة القياسية في المشاريع الفعلية
📝 تمارين
- اكتب دالة
void str_tolower(char *s)تحوّل جميع الحروف الكبيرة في سلسلة إلى صغيرة. - اكتب دالة
int str_count_char(const char *s, char ch)تعدّ عدد مرات ظهور المحرفchفي السلسلةs. - بدون استخدام أي دوال من string.h، اكتب
my_strstrلتنفيذ بحث السلاسل الفرعية.