النصوص في C: دوال string.h بالتفصيل
في درس المصفوفات عرفت أن النص في C ليس نوعاً خاصاً، بل مصفوفة حروف تنتهي بحرف النهاية '\0'. وبعد أن فهمت المؤشرات، حان وقت التعامل مع النصوص باحتراف: نسخها ومقارنتها والبحث فيها وقراءتها من المستخدم بأمان. كل الدوال هنا من المكتبة string.h ما لم نذكر غير ذلك.
مصفوفة أم مؤشر؟
Section titled “مصفوفة أم مؤشر؟”هناك طريقتان لتعريف نص، وبينهما فرق مهم:
char name[] = "Omar";const char *title = "Student";
name[0] = 'A';printf("%s\n", name);printf("%s\n", title);printf("%zu %zu\n", sizeof(name), strlen(name));AmarStudent5 4char name[] = "Omar";: مصفوفة من 5 حروف (أربعة مع حرف النهاية) تملكها أنت، وتستطيع تعديل حروفها.const char *title = "Student";: مؤشر إلى نص ثابت مخزن في منطقة للقراءة فقط. محاولة تعديله تنهي البرنامج، ولهذا نكتبconst.sizeofتعطي حجم المصفوفة كله، وstrlenتعدّ الحروف حتى حرف النهاية.
تخيّل النص في C مثل قطار: العربات حروف، وآخر عربة علامة حمراء '\0' تقول «انتهى القطار». كل دوال النصوص تمشي عربة عربة حتى تصل إلى العلامة.
النسخ: strcpy
Section titled “النسخ: strcpy”لا يمكن نسخ نص بعلامة = بعد تعريفه، فاسم المصفوفة ليس متغيراً عادياً. نستخدم strcpy:
char source[] = "Hello";char copy[20];
strcpy(copy, source);copy[0] = 'J';printf("%s %s\n", source, copy);Hello Jellostrcpy(إلى, من): الوجهة أولاً ثم المصدر، وتنسخ الحروف كلها مع حرف النهاية.
المقارنة: strcmp
Section titled “المقارنة: strcmp”العلامة == بين نصين تقارن العنوانين لا المحتوى. المقارنة الصحيحة بـ strcmp:
printf("%d\n", strcmp("apple", "apple"));printf("%d\n", strcmp("apple", "banana") < 0);printf("%d\n", strcmp("cat", "car") > 0);
char answer[] = "yes";if (strcmp(answer, "yes") == 0) { printf("موافق\n");}011موافقstrcmp تُرجع:
- صفراً إذا تطابق النصان.
- عدداً سالباً إذا كان الأول يأتي قبل الثاني أبجدياً.
- عدداً موجباً إذا كان يأتي بعده.
انتبه: الصفر هنا يعني «متساويان»، مع أن الصفر في الشروط يعني «خطأ». لذلك اكتب المقارنة كاملة == 0 ولا تختصرها.
البحث: strchr و strstr
Section titled “البحث: strchr و strstr”char email[] = "omar@example.com";
char *at = strchr(email, '@');if (at != NULL) { printf("%s\n", at + 1); printf("%d\n", (int)(at - email));}
if (strstr(email, "example") != NULL) { printf("يحتوي على example\n");}if (strstr(email, "gmail") == NULL) { printf("لا يحتوي على gmail\n");}example.com4يحتوي على exampleلا يحتوي على gmailstrchr(نص, حرف): تُرجع مؤشراً إلى أول ظهور للحرف، أوNULLإذا لم يوجد.at + 1: مؤشر إلى ما بعد العلامة، وطباعته بـ%sتعطي بقية النص.at - email: طرح مؤشرين يعطي المسافة بينهما، أي موضع الحرف.strstr(نص, جزء): مثلها لكن تبحث عن نص كامل.
المرور حرفاً حرفاً
Section titled “المرور حرفاً حرفاً”لأن النص ينتهي بـ '\0'، تستطيع المرور عليه دون معرفة طوله. والمكتبة ctype.h فيها دوال لفحص الحروف وتحويلها:
#include <ctype.h>
char text[] = "Room 204b";int digits = 0;
for (int i = 0; text[i] != '\0'; i++) { if (isdigit(text[i])) { digits++; } text[i] = toupper(text[i]);}printf("%s\n", text);printf("%d\n", digits);ROOM 204B3isdigit تفحص هل الحرف رقم، و isalpha هل هو حرف أبجدي، و isspace هل هو مسافة. و toupper و tolower تحوّلان حالة الحرف.
بناء نص بأمان: snprintf
Section titled “بناء نص بأمان: snprintf”snprintf تعمل مثل printf تماماً، لكنها تكتب في مصفوفة بدل الشاشة، ولا تتجاوز الحجم الذي تحدده أبداً:
char line[50];int age = 15;
snprintf(line, sizeof(line), "%s is %d years old", "Omar", age);printf("%s\n", line);printf("%zu\n", strlen(line));
char tiny[6];snprintf(tiny, sizeof(tiny), "%s", "Programming");printf("%s\n", tiny);Omar is 15 years old20Progrفي المثال الثاني النص أطول من المصفوفة، فقصّته snprintf عند خمسة حروف وتركت مكاناً لحرف النهاية، بدل الكتابة خارج الحدود. وقد ينبهك مترجم حديث مثل GCC بتحذير -Wformat-truncation إلى أن النص سيُقص. اجعلها خيارك الأول للنسخ والدمج والتحويل من أرقام إلى نص.
قراءة سطر من المستخدم: fgets
Section titled “قراءة سطر من المستخدم: fgets”scanf("%s", name) تتوقف عند أول مسافة، ولا تعرف حجم مصفوفتك. الأفضل fgets التي تقرأ سطراً كاملاً ولا تتجاوز الحجم:
#include <stdio.h>#include <string.h>
int main(void) { char name[40];
printf("اكتب اسمك الكامل: "); fgets(name, sizeof(name), stdin); name[strcspn(name, "\n")] = '\0';
printf("مرحباً %s\n", name); printf("عدد الحروف: %zu\n", strlen(name)); return 0;}إذا كتب المستخدم Omar Ali سيظهر:
اكتب اسمك الكامل: Omar Aliمرحباً Omar Aliعدد الحروف: 8fgets تحتفظ برمز السطر الجديد \n الناتج عن ضغط Enter في آخر النص. السطر name[strcspn(name, "\n")] = '\0'; يحذفه: strcspn تُرجع موضع أول \n، فنضع مكانه حرف النهاية.
من نص إلى رقم
Section titled “من نص إلى رقم”الدالتان atoi و atof من stdlib.h تحوّلان النص إلى عدد:
#include <stdlib.h>
int total = atoi("250") + 50;printf("%d\n", total);printf("%.1f\n", atof("3.5") * 2);printf("%d\n", atoi("12abc"));printf("%d\n", atoi("abc"));3007.0120atoi تقرأ الأرقام من أول النص وتتوقف عند أول حرف غريب. وإذا لم تجد رقماً أصلاً تُرجع صفراً، فلا تستطيع التمييز بين النص "0" والنص "abc". للتحقق الدقيق من إدخال المستخدم توجد strtol، وهي تخبرك أين توقفت القراءة.
جرّب بنفسك
Section titled “جرّب بنفسك”- اقرأ جملة من المستخدم بـ
fgets، واطبع عدد حروفها وعدد كلماتها (عدّ المسافات وأضف واحداً). - اكتب دالة
void reverse(char text[])تعكس النص في مكانه بتبديل الحروف من الطرفين. - اكتب دالة
int countChar(const char *text, char c)تعدّ مرات ظهور حرف في نص. - تحدٍّ: اكتب دالة
int isPalindrome(const char *word)تُرجع 1 إذا كانت الكلمة تُقرأ من الجهتين بنفس الشكل مثلlevel.
أخطاء شائعة
Section titled “أخطاء شائعة”- المقارنة بـ ==:
if (answer == "yes")تقارن العناوين وتفشل دائماً تقريباً. استخدمstrcmp(...) == 0. - وجهة أصغر من المصدر:
char small[4]; strcpy(small, "Hello");تجاوز للمخزن. احسب الحجم مع حرف النهاية، أو استخدمsnprintf. - نسيان حرف النهاية: إذا ملأت مصفوفة حروف يدوياً ولم تضع
'\0'في آخرها، ستكملprintfالقراءة في ذاكرة عشوائية. - تعديل نص ثابت:
char *s = "Hi"; s[0] = 'h';ينهي البرنامج. عرّفه مصفوفةchar s[] = "Hi";إذا أردت تعديله.
اختبر نفسك
Section titled “اختبر نفسك”سؤال
ماذا تُرجع strcmp("cat", "cat")؟
strcmp تُرجع صفراً عند التطابق، ولذلك نكتب الشرط strcmp(a, b) == 0.
سؤال
لماذا نفضّل fgets على scanf مع %s لقراءة النصوص؟
fgets تستقبل حجم المصفوفة وتلتزم به، وتقرأ المسافات، بعكس scanf التي تتوقف عند أول مسافة.
الخلاصة
Section titled “الخلاصة”char s[]مصفوفة قابلة للتعديل، وconst char *مؤشر إلى نص ثابت.strcpyللنسخ وstrcmpللمقارنة (الصفر يعني التطابق).strchrوstrstrتبحثان وتُرجعان مؤشراً أوNULL.snprintfتبني النصوص دون تجاوز الحجم، وfgetsتقرأ سطراً كاملاً بأمان.atoiوatofتحوّلان النص إلى رقم، وتأكد دائماً أن الوجهة تتسع لما تكتبه.