C: السلاسل النصية — الأساسيات
سلسلة C كعقد تنتهي بحبة شفافة — كل حبة محرف، والأخيرة هي المنهي غير المرئي \0 الذي يُشير "العقد تنتهي هنا".
1. مصفوفات المحارف والسلاسل النصية
لا يوجد في C نوع سلاسل مخصص. السلسلة هي في الأساس مصفوفة محارف منتهية بـ \0 (المحرف الصفري). قيمة \0 في ASCII هي 0، ويُشير إلى نهاية السلسلة.
char str[6] = {'H', 'e', 'l', 'l', 'o', '\0'};
هذه المصفوفة بها 6 عناصر: 5 محارف مرئية + 1 \0. بدون \0، هي مجرد مصفوفة محارف عادية ولا يمكن استخدامها كسلسلة.
القيم الحرفية للسلاسل (علامات اقتباس مزدوجة) تُلحق \0 تلقائيًا في النهاية:
char str[] = "Hello";
طول المصفوفة 6 (5 محارف + 1 \0)؛ يحسبه المترجم تلقائيًا. يمكنك أيضًا تحديد طول أكبر؛ تُملأ المواضع الزائدة بـ \0:
char str[10] = "Hello";
str[5] حتى str[9] جميعها \0.
'H' تُمثّل محرفًا (1 بايت)؛ علامات الاقتباس المزدوجة "Hello" تُمثّل سلسلة (مع \0 ضمني). ليسا نفس الشيء.
2. إعلان السلاسل وتهيئتها
(1) أسلوب مصفوفة المحارف
char s1[] = "abc";
char s2[10] = "abc";
char s3[] = {'a', 'b', 'c', '\0'};
الثلاثة متكافئة مع السلسلة "abc". لاحظ أن s3 تحتاج إضافة \0 يدويًا.
(2) أسلوب مؤشر المحارف
const char *s4 = "abc";
s4 يشير إلى أول محرف في قيمة حرفية للسلسلة. تُخزَّن القيم الحرفية للسلاسل في منطقة القراءة فقط؛ محاولة تعديلها سلوك غير معرّف:
s4[0] = 'A';
هذا خطأ. مصفوفات المحارف، من ناحية أخرى، قابلة للتعديل:
s1[0] = 'A';
قانوني — محتويات s1 تصبح "Abc".
const عند توجيه مؤشر إلى قيمة حرفية للسلسلة؛ سيحذرك المترجم إذا حاولت تعديلها.
(3) المؤشرات مقابل المصفوفات
char arr[] = "hello";
const char *ptr = "hello";
arrمصفوفة: تُخصَّص ذاكرتها على المكدس، يمكن تعديل محتوياتها،sizeof(arr)يساوي 6ptrمؤشر: يشير إلى قيمة حرفية للسلسلة في ذاكرة القراءة فقط،sizeof(ptr)يساوي حجم المؤشر (4 أو 8 بايت)
3. إدخال وإخراج السلاسل
(1) printf و scanf
استخدم محدد التنسيق %s لإدخال وإخراج السلاسل:
char name[20];
scanf("%s", name);
printf("Hello, %s!\n", name);
تتوقف scanf عن القراءة عند المسافة البيضاء (مسافة أو سطر جديد أو جدولة)، لذا لا يمكنها قراءة سطر كامل يحتوي مسافات.
لا تتحقق scanf من حجم المخزن المؤقت؛ المدخلات التي تتجاوز طول المصفوفة تسبب فيضانًا. يمكنك تحديد طول القراءة:
scanf("%19s", name);
هذا يقرأ 19 محرفًا على الأكثر، تاركًا 1 موضع لـ \0.
(2) puts و gets
تُخرج puts سلسلة وتُلحق سطرًا جديدًا تلقائيًا:
char msg[] = "Hello";
puts(msg);
هذا يكافئ printf("%s\n", msg);.
تقرأ gets سطرًا كاملًا (بما في ذلك المسافات) حتى سطر جديد:
char line[100];
gets(line);
(3) fgets
fgets هي البديل الآمن لـ gets:
char line[100];
fgets(line, sizeof(line), stdin);
المعاملات هي: مخزن التخزين المؤقت، وعدد البايتات الأقصى للقراءة (بما في ذلك \0)، وتيار الإدخال. تحتفظ fgets بالسطر الجديد في نهاية السلسلة (إذا قُرئ سطر جديد وكان هناك مساحة).
نمط شائع لإزالة السطر الجديد الزائد:
#include <stdio.h>
#include <string.h>
int main(void) {
char line[100];
fgets(line, sizeof(line), stdin);
int len = strlen(line);
if (len > 0 && line[len - 1] == '\n') {
line[len - 1] = '\0';
}
printf("[%s]\n", line);
return 0;
}
▶ مثال
قراءة اسم المستخدم وسطر تعريفي:
#include <stdio.h>
#include <string.h>
void trim_newline(char *s) {
int len = strlen(s);
if (len > 0 && s[len - 1] == '\n') {
s[len - 1] = '\0';
}
}
int main(void) {
char name[30];
char intro[200];
printf("Enter your name: ");
fgets(name, sizeof(name), stdin);
trim_newline(name);
printf("Introduce yourself: ");
fgets(intro, sizeof(intro), stdin);
trim_newline(intro);
printf("\n--- Personal Info ---\n");
printf("Name: %s\n", name);
printf("Intro: %s\n", intro);
return 0;
}
Enter your name: Alice
Introduce yourself: I am a C beginner
--- Personal Info ---
Name: Alice
Intro: I am a C beginner
4. دالة strlen
مُعرَّفة في <string.h> وتُرجع طول السلسلة (بدون \0):
#include <string.h>
char s[] = "Hello";
printf("%zu\n", strlen(s));
المخرجات: 5. لاحظ أن sizeof(s) يساوي 6 (بما في ذلك \0)، بينما strlen(s) يساوي 5 (بدون \0).
كيف تعمل strlen: تعدّ المحارف من البداية حتى تصادف \0:
size_t my_strlen(const char *s) {
size_t len = 0;
while (s[len] != '\0') {
len++;
}
return len;
}
إذا لم تنتهِ مصفوفة المحارف بـ \0، ستستمر strlen في القراءة حتى تصادف بايت صفري بالصدفة، وهذا خطير جدًا.
5. مقدمة في مؤشرات المحارف
يمكن لمؤشر المحارف أن يشير إلى مصفوفة محارف أو قيمة حرفية للسلسلة:
char arr[] = "world";
const char *ptr = "world";
ptr = arr;
printf("%s\n", ptr);
يمكن إعادة إسناد المؤشر للإشارة إلى سلسلة مختلفة، لكن اسم المصفوفة ثابت ولا يمكن تعديله:
ptr = "another string";
قانوني. لكن:
arr = "another string";
غير قانوني — لا يمكن إسناد اسم مصفوفة.
اجتياز سلسلة بمؤشر:
const char *p = "Hello";
while (*p != '\0') {
printf("%c", *p);
p++;
}
printf("\n");
*p يلغي الإشارة للحصول على المحرف الحالي؛ p++ ينتقل إلى موضع المحرف التالي. خطوة المؤشر يحددها النوع المُشار إليه؛ char * يتحرك بايت واحد في كل خطوة.
▶ مثال
طباعة سلسلة بترتيب عكسي باستخدام مؤشر محارف:
#include <stdio.h>
#include <string.h>
void print_reverse(const char *s) {
const char *p = s + strlen(s) - 1;
while (p >= s) {
putchar(*p);
p--;
}
putchar('\n');
}
int main(void) {
print_reverse("Hello");
print_reverse("ABCDEF");
return 0;
}
olleH
FEDCBA
أولًا، انقل المؤشر p إلى آخر محرف في السلسلة (موضع واحد قبل \0)، ثم أخرج المحارف من الخلف إلى الأمام.
❓ أسئلة شائعة
'a' و "a"؟'a' ثابت محرفي يشغل 1 بايت؛ "a" ثابت سلسلة يشغل 2 بايت ('a' زائد '\0')، ونوعه char[].sizeof و strlen نتائج مختلفة للسلاسل؟sizeof تُرجع الذاكرة الكلية التي تشغلها المصفوفة (بما في ذلك \0)؛ strlen تُرجع عدد المحارف قبل \0.fgets سطرًا جديدًا؟fgets السطر الجديد في المخزن المؤقت. عادةً تحتاج للتحقق منه يدويًا واستبداله بـ \0 لإزالته.📖 ملخص
- سلاسل C هي مصفوفات محارف منتهية بـ
\0؛\0هو ما تستخدمه دوال السلاسل لاكتشاف النهاية - مصفوفات المحارف تسمح بتعديل المحتوى؛ المؤشرات التي تشير إلى قيم حرفية لا تسمح بذلك
- استخدم
fgetsبدلًا منgetsلقراءة السلاسل؛ حدد حجم المخزن المؤقت دائمًا strlenتُرجع عدد المحارف الفعّالة؛sizeofتُرجع الحجم الكلي للمصفوفة- مؤشرات المحارف يمكنها اجتياز سلسلة، متحركةً موضع محرف واحد في كل خطوة
📝 تمارين
- اكتب برنامجًا يستخدم
fgetsلقراءة سطر نصي ويعدّ عدد الحروف والأرقام والمحارف الأخرى. - اكتب دالة
void str_replace_char(char *s, char old, char new_ch)تستبدل جميع حالات ظهورoldفي السلسلةsبـnew_ch. - اكتب دالة تحسب طول سلسلة باستخدام مؤشرات محارف، بدون استخدام
strlen.