العمليات مع السلاسل. وظائف معالجة السلسلة في وظيفة C C التي تعكس السلسلة

04.09.2023

خطوط. إدخال/إخراج السلسلة. تنسيق الإدخال/الإخراج. معالجة السلسلة باستخدام وظائف لغة C القياسية.

1.1. إعلان وتهيئة السلاسل.

السلسلة عبارة عن مصفوفة من الأحرف تنتهي بالحرف الفارغ '\0'. يتم تعريف السلسلة كمصفوفة أحرف عادية، على سبيل المثال،

شار S1؛ // سلسلة طويلة مكونة من تسعة أحرف

شار *s2; // المؤشر إلى السلسلة

الفرق بين المؤشرين s1 و s2 هو أن المؤشر s1 هو ثابت مسمى، والمؤشر s2 هو متغير.

يتم وضع ثوابت السلسلة ضمن علامات اقتباس مزدوجة، على عكس الأحرف التي يتم وضعها ضمن علامات اقتباس مفردة. على سبيل المثال،

"هذه سلسلة."

لا يمكن أن يتجاوز طول ثابت السلسلة 509 حرفًا وفقًا للمعيار. ومع ذلك، تسمح العديد من التطبيقات بأطوال سلسلة أطول.

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

char s1 = "هذه سلسلة.";

يوجد في لغة البرمجة C عدد كبير من الوظائف للعمل مع السلاسل، والتي تم وصف نماذجها الأولية في ملفات الرأس stdlib.h وstring.h. سيتم مناقشة العمل مع هذه الوظائف في الفقرات التالية.

1.2. إدخال/إخراج السلسلة.

لإدخال سلسلة من وحدة التحكم، استخدم الوظيفة

char* get(char *str);

الذي يكتب سلسلة إلى العنوان str ويعيد عنوان السلسلة المدخلة. تتوقف الوظيفة عن الإدخال إذا واجهت حرف "\n" أو EOF (نهاية الملف). انتقل إلى الرمز خط جديدلم يتم نسخها. يتم وضع بايت صفر في نهاية سطر القراءة. في حالة نجاحها، تقوم الدالة بإرجاع مؤشر إلى السطر المقروء، وإذا لم تنجح، فإنها ترجع NULL.

لإخراج سلسلة إلى وحدة التحكم، استخدم الوظيفة القياسية

int يضع (const char *s);

والذي، في حالة نجاحه، يُرجع رقمًا غير سالب، وإذا لم ينجح، يُرجع EOF.

تم وصف النماذج الأولية لوظيفة get وput في ملف الرأس stdio.h.

#يشمل

printf("سلسلة الإدخال:");

1.3. تنسيق الإدخال/الإخراج.

لإدخال البيانات المنسقة من وحدة التحكم، استخدم الوظيفة

int scanf (const char *format, ...);

والتي، في حالة نجاحها، تُرجع عدد وحدات البيانات المقروءة، وإذا لم تنجح، تُرجع EOF. يجب أن تشير معلمة التنسيق إلى السلسلة المراد تنسيقها، والتي تحتوي على مواصفات تنسيق الإدخال. يجب أن يتطابق عدد وأنواع الوسائط التي تتبع سلسلة التنسيق مع عدد وأنواع تنسيقات الإدخال المحددة في سلسلة التنسيق. إذا لم يتم استيفاء هذا الشرط، فإن نتيجة الدالة لا يمكن التنبؤ بها.

تصف المسافة أو الأحرف "\t" أو "\n" في سلسلة التنسيق حرفًا واحدًا أو أكثر فارغًا في دفق الإدخال، والذي يتضمن الأحرف التالية: مسافة، '\t'، '\n'، '\v' ، '\ف'. تتخطى الدالة scanf الأحرف الفارغة في دفق الإدخال.

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

بشكل عام، تبدو مواصفات تنسيق الإدخال كما يلي:

%[*] [العرض] [المعدلات] النوع

يشير الرمز "*" إلى الإغفال عند إدخال حقل محدد بواسطة هذه المواصفات؛

- يحدد "العرض" الحد الأقصى لعدد الأحرف التي يتم إدخالها وفقًا لهذه المواصفات؛

يمكن أن يأخذ النوع القيم التالية:

ج- مصفوفة الحروف،

s - سلسلة من الأحرف، يتم فصل الأسطر بأحرف فارغة،

د - عدد صحيح بعلامة 10 ث / ث،

i - عدد صحيح، يعتمد نظام الأرقام على أول رقمين،

ش - عدد صحيح غير موقع في 10 ثانية / ثانية،

س - عدد صحيح غير موقع في 8 ث / ث،

x، X – عدد صحيح غير موقع عند 16 ثانية/ثانية،

e، E، f، g، G – رقم عائم،

ع - مؤشر إلى مؤشر،

n - مؤشر إلى عدد صحيح،

[…] - مجموعة من الأحرف الممسوحة ضوئيًا، على سبيل المثال، .

وفي الحالة الأخيرة، سيتم إدخال الأحرف المحاطة بين قوسين مربعين فقط من دفق الإدخال. إذا كان الحرف الأول داخل الأقواس المربعة هو '^'، فسيتم إدخال الأحرف غير الموجودة في المصفوفة فقط. يتم تحديد نطاق الأحرف في المصفوفة باستخدام الرمز "-". عند إدخال أحرف، يتم أيضًا إدخال الأحرف الفارغة البادئة والبايت الفارغ الزائد للسلسلة.

يمكن أن تأخذ المعدلات القيم التالية:

ح – عدد صحيح قصير،

l، L – عدد صحيح طويل أو عائم،

وتستخدم فقط للأرقام الصحيحة أو العائمة.

يوضح المثال التالي استخدامات الدالة scanf. لاحظ أن محدد التنسيق، بدءًا من إدخال الرقم العائم، يسبقه مسافة.

#يشمل

printf("أدخل عددًا صحيحًا:");

scanf("%d", &n);

printf("أدخل مزدوجًا:");

scanf("%lf", &d);

printf("إدخال حرف:");

scanf("%c",&c);

printf("أدخل سلسلة:");

scanf("%s", &s);

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

لإخراج البيانات المنسقة إلى وحدة التحكم، استخدم الوظيفة

int printf (const char *format, ...);

والتي، في حالة نجاحها، تُرجع عدد وحدات إخراج البيانات، وإذا لم تنجح، تُرجع EOF. معلمة التنسيق عبارة عن سلسلة تنسيق تحتوي على مواصفات لتنسيقات الإخراج. يجب أن يتطابق عدد وأنواع الوسائط التي تتبع سلسلة التنسيق مع عدد وأنواع مواصفات تنسيق الإخراج المحددة في سلسلة التنسيق. بشكل عام، تبدو مواصفات تنسيق الإخراج كما يلي:

%[الأعلام] [العرض] [.الدقة] [المعدلات] النوع

- "الأعلام" هي رموز مختلفة تحدد تنسيق الإخراج؛

- يحدد "العرض" الحد الأدنى لعدد الأحرف التي يتم إخراجها وفقًا لهذه المواصفات؛

- يحدد ".precision" الحد الأقصى لعدد الأحرف المعروضة؛

- "المعدلات" تحدد نوع الوسائط؛

- يحدد "النوع" نوع الوسيطة.

لإخراج الأعداد الصحيحة الموقعة، يتم استخدام تنسيق الإخراج التالي:

%[-] [+ | المساحة] [العرض] [ل] د

- - محاذاة لليسار، افتراضي - لليمين؛

+ - يتم عرض علامة "+"، لاحظ أنه بالنسبة للأرقام السالبة، يتم عرض علامة "-" دائمًا؛

"المسافة" - يتم عرض مسافة في موضع الحرف؛

د - نوع البيانات int.

لإخراج أعداد صحيحة غير موقعة، استخدم تنسيق الإخراج التالي:

%[-] [#] [العرض] [ل]

# - الأولي 0 هو إخراج الأرقام في 8 ج/ج أو الأولي 0x أو 0X للأرقام في 16 ج/ج،

ل - مُعدِّل نوع البيانات الطويل؛

ش – عدد صحيح في 10ج/ج،

س – عدد صحيح في 8 ج/ج،

س، X – عدد صحيح عند 16 ج/ج.

يتم استخدام تنسيق الإخراج التالي لعرض أرقام الفاصلة العائمة:

%[-] [+ | مساحة] [العرض] [.الدقة]

"الدقة" - تشير إلى عدد الأرقام بعد العلامة العشرية للتنسيقات f وe وE أو عدد الأرقام المهمة للتنسيقات g وG. يتم تقريب الأرقام. الدقة الافتراضية هي ستة أرقام عشرية؛

و - رقم النقطة الثابتة،

e - رقم في الشكل الأسي، يُشار إلى الأس بالحرف "e"،

E - رقم في الشكل الأسي، يُشار إلى الأس بالحرف "E"،

g - أقصر تنسيقات f أو g،

G - أقصر تنسيقات f أو G.

printf("n = %d\n f = %f\n e = %e\n E = %E\n f = %.2f"، -123, 12.34, 12.34, 12.34, 12.34);

// يطبع: n = 123 f = 12.340000 e = 1.234000e+001 E = 1.234000E+001 f = 12.34

1.4. تنسيق السلاسل.

هناك أنواع مختلفة من وظائف scanf وprintf التي تم تصميمها لتنسيق السلاسل وتسمى sscanf وsprintf، على التوالي.

int sscanf (const char *str, const char *format, ...);

يقرأ البيانات من السلسلة المحددة بواسطة str، وفقًا لسلسلة التنسيق المحددة بواسطة التنسيق. في حالة النجاح، يتم إرجاع عدد البيانات المقروءة، وفي حالة عدم النجاح، يتم إرجاع EOF. على سبيل المثال،

#يشمل

char str = "a 10 1.2 String No input";

sscanf(str, "%c %d %lf %s", &c, &n, &d, s);

printf("%c\n", c); // المطبوعات: أ

printf("%d\n", n); // المطبوعات: 10

printf("%f\n", d); // المطبوعات: 1.200000

printf("%s\n", s); // يطبع: سلسلة

int sprintf (char *buffer, const char *format, ...);

يقوم بتنسيق السلسلة وفقًا للتنسيق المحدد بواسطة معلمة التنسيق ويكتب النتيجة الناتجة إلى المخزن المؤقت لصفيف الأحرف. تقوم الدالة بإرجاع عدد الأحرف المكتوبة إلى المخزن المؤقت لصفيف الأحرف، باستثناء البايت الفارغ للإنهاء. على سبيل المثال،

#يشمل

char str = "c = %c, n = %d, d = %f, s = %s";

char s = "هذه سلسلة.";

sprintf(buffer, str, c, n, d, s);

printf("%s\n", buffer); // يطبع: c = c، n = 10، d = 1.200000، s = هذه سلسلة

1.5. تحويل السلاسل إلى بيانات رقمية.

يتم تقديم النماذج الأولية لوظائف تحويل السلاسل إلى بيانات رقمية في ملف الرأس stdlib.h، والذي يجب تضمينه في البرنامج.

لتحويل سلسلة إلى عدد صحيح، استخدم الدالة

int atoi (const char *str);

شار *ستر = "-123";

n = atoi(str); // ن = -123

لتحويل سلسلة إلى عدد صحيح طويل، استخدم الدالة

long int atol (const char *str);

والتي، في حالة نجاحها، تُرجع العدد الصحيح الذي تم تحويل السلسلة إليه، وإذا لم تنجح، تُرجع 0. على سبيل المثال،

شار *ستر = "-123";

ن = أتول(ستر); // ن = -123

لتحويل سلسلة إلى رقم مزدوج، استخدم الدالة

مزدوج atof(const char *str);

والتي، في حالة النجاح، تُرجع رقمًا عائمًا من النوع المزدوج، والذي يتم تحويل السلسلة إليه، وفي حالة الفشل، 0. على سبيل المثال،

شار *ستر = "-123.321";

n = atof(str); // ن = -123.321

تؤدي الوظائف التالية وظائف مشابهة لـ atoi وatol وatof، ولكنها توفر وظائف أكثر تقدمًا.

long int strtol (const char *str, char **endptr, int base);

يحول السلسلة str إلى رقم int طويل، والذي يُرجعه. معلمات هذه الوظيفة لها الأغراض التالية.

إذا كان الأساس هو 0، فإن التحويل يعتمد على أول حرفين من str:

إذا كان الحرف الأول عبارة عن رقم من 1 إلى 9، فمن المفترض أن يتم تمثيل الرقم بـ 10 c/c؛

إذا كان الحرف الأول هو الرقم 0 والحرف الثاني هو رقم من 1 إلى 7، فمن المفترض أن يتم تمثيل الرقم في 8 ج/ج؛

إذا كان الحرف الأول هو 0 والثاني هو "X" أو "x"، فمن المفترض أن يتم تمثيل الرقم في 16 c/c.

إذا كان الأساس رقمًا يتراوح بين 2 و36، فسيتم اعتبار هذه القيمة أساسًا لنظام الأرقام، وأي حرف خارج نظام الأرقام يتوقف عن التحويل. في أنظمة الأرقام ذات الأساس 11 إلى الأساس 36، تُستخدم الرموز "A" إلى "Z" أو "a" إلى "z" لتمثيل الأرقام.

يتم تعيين قيمة الوسيطة endptr بواسطة الدالة strtol. تحتوي هذه القيمة على مؤشر إلى الحرف الذي أوقف تحويل السلسلة النصية. تقوم الدالة strtol بإرجاع الرقم المحول في حالة نجاحه، و0 في حالة نجاحه.

n = strtol ("12a"، &p، 0)؛

printf("n = %ld, %stop = %c, n, *p); // ن = 12، توقف = أ

n = strtol("012b", &p, 0);

printf("n = %ld, %stop = %c, n, *p); // ن = 10، توقف = ب

n = strtol ("0x12z"، &p، 0)؛

printf("n = %ld, %stop = %c, n, *p); // ن = 18، توقف = ض

ن = سترتول ("01117"، &p، 0)؛

printf("n = %ld, %stop = %c, n, *p); // ن = 7، توقف = 7

strtol طويل غير موقع (const char *str, char **endptr, int base);

تعمل بشكل مشابه للدالة strtol، ولكنها تحول التمثيل الرمزي لرقم إلى عدد من النوع غير الموقع طويل int.

strtod مزدوج (const char *str، char **endptr)؛

تحويل التمثيل الرمزي لرقم إلى رقم مزدوج.

تتوقف كافة الوظائف المدرجة في هذه الفقرة عن العمل عندما تواجه الحرف الأول الذي لا يتناسب مع تنسيق الرقم المعني.

بالإضافة إلى ذلك، إذا تجاوزت قيمة حرف الرقم نطاق القيم المقبولة لنوع البيانات المقابل، فإن الوظائف atof وstrtol وstrtoul وstrtod تقوم بتعيين قيمة متغير errno على ERANGE. تم تعريف المتغير errno وثابت ERANGE في ملف رأس math.h. في هذه الحالة، تقوم الدالتان atof وstrtod بإرجاع القيمة HUGE_VAL، وترجع الدالة strtol القيمة LONG_MAX أو LONG_MIN، وترجع الدالة strtoul القيمة ULONG_MAX.

يمكن استخدام الوظائف غير القياسية itoa وltoa وutoa وecvt وfcvt وgcvt لتحويل البيانات الرقمية إلى سلاسل أحرف. ولكن من الأفضل استخدام وظيفة sprintf القياسية لهذه الأغراض.

1.6. الميزات القياسيةللعمل مع السلاسل.

يناقش هذا القسم وظائف العمل مع السلاسل، والتي تم وصف نماذجها الأولية في الملف الرأسي string.h.

1. مقارنة السلسلة.يتم استخدام الدالتين strcmp وstrncmp لمقارنة السلاسل.

int strcmp (const char *str1, const char *str2);

يقارن معجميا السلاسل str1 وstr2 ويعيد -1 أو 0 أو 1 إذا كانت str1 أقل من أو تساوي أو أكبر من str2 على التوالي.

int strncmp (const char *str1, const char *str2, size_t n);

يقارن المعجمي على الأكثر الأحرف n الأولى من السلاسل str1 وstr2. ترجع الدالة -1 أو 0 أو 1 إذا كانت الأحرف n الأولى من str1 أقل من أو تساوي أو أكبر من الأحرف n الأولى من str2 على التوالي.

// مثال لمقارنة السلسلة

#يشمل

#يشمل

شار str1 = "أأ ب"؛

شار str2 = "أأأ";

char str3 = "aa bb cc";

printf("%d\n", strcmp(str1, str3)); // المطبوعات: -1

printf("%d\n", strcmp(str1, str1)); // المطبوعات: -0

printf("%d\n", strcmp(str1, str2)); // المطبوعات: 1

printf("%d\n", strncmp(str1, str3, 5)); // المطبوعات: 0

2. نسخ الخطوط.تُستخدم الدالتان strcpy وstrncpy لنسخ السلاسل.

char *strcpy (char *str1, const char *str2);

نسخ السلسلة str2 إلى السلسلة str1. يتم نسخ السلسلة str2 بأكملها، بما في ذلك البايت الفارغ للإنهاء. تقوم الدالة بإرجاع مؤشر إلى str1. إذا تداخلت الخطوط، فإن النتيجة لا يمكن التنبؤ بها.

char *strncpy (char *str1, const char *str2, size_t n);

نسخ أحرف n من السلسلة str2 إلى السلسلة str1. إذا كان str2 يحتوي على أقل من n من الأحرف، فسيتم نسخ البايت الصفري الأخير عدة مرات حسب الضرورة لتوسيع str2 إلى n من الأحرف. تقوم الدالة بإرجاع مؤشر إلى السلسلة str1.

char str2 = "نسخ السلسلة.";

strcpy(str1, str2);

printf(str1); // يطبع: نسخ السلسلة.

4. ربط الخيوط.تُستخدم الدالتان strcat وstrncat لربط السلاسل في سلسلة واحدة.

char* strcat (char *str1, const char *str2);

يقوم بإلحاق السلسلة str2 بالسلسلة str1، مع مسح البايت الصفري الزائد للسلسلة str1. تقوم الدالة بإرجاع مؤشر إلى السلسلة str1.

char* strncat (char *str1, const char *str2, size_t n);

يقوم بإلحاق عدد n من الأحرف من السلسلة str2 إلى السلسلة str1، مع مسح البايت الصفري الزائد من السلسلة str1. تقوم الدالة بإرجاع مؤشر إلى السلسلة str1. إذا كان طول السلسلة str2 أقل من n، فسيتم إلحاق الأحرف المضمنة في السلسلة str2 فقط. بعد تسلسل السلاسل، تتم دائمًا إضافة بايت فارغ إلى str1. تقوم الدالة بإرجاع مؤشر إلى السلسلة str1.

#يشمل

#يشمل

شار str1 = "سلسلة";

شار str2 = "تسلسل";

char str3 = "نعم لا";

strcat(str1, str2);

printf("%s\n", str1); // يطبع: سلسال السلسلة

strncat(str1, str3, 3);

printf("%s\n", str1); // يطبع: سلسلة السلسلة نعم

5. ابحث عن حرف في سلسلة.للبحث عن حرف في سلسلة، استخدم الوظائف strchr وstrrchr وstrspn وstrcspn وstrpbrk.

char* strchr (const char *str, int c);

يبحث عن التواجد الأول للحرف المحدد بواسطة c في السلسلة النصية. في حالة نجاحها، تقوم الدالة بإرجاع مؤشر إلى الحرف الأول الذي تم العثور عليه، وإذا لم تنجح، فإنها ترجع NULL.

char* strrchr (const char *str, int c);

يبحث عن آخر تواجد للحرف المحدد بواسطة c في السلسلة النصية. في حالة نجاحها، تقوم الدالة بإرجاع مؤشر إلى الحرف الأخير الذي تم العثور عليه، وإذا لم تنجح، فإنها ترجع NULL.

#يشمل

#يشمل

char str = "بحث char";

printf("%s\n", strchr(str, "r")); // يطبع: البحث ص

printf("%s\n", strrchr(str, "r")); // المطبوعات: RCH

size_t strspn (const char *str1, const char *str2);

تقوم بإرجاع فهرس الحرف الأول من str1 غير الموجود في str2.

size_t strcspn (const char *str1, const char *str2);

تقوم بإرجاع فهرس الحرف الأول من str1 الذي يظهر في str2.

شار str = "123 اي بي سي";

printf ("n = %d\n"، strspn (str، "321")؛ // المطبوعات: n = 3

printf ("n = %d\n"، strcspn (str، "cba")؛ // المطبوعات: n = 4

char* strpbrk (const char *str1, const char *str2);

يعثر على الحرف الأول في السلسلة str1 الذي يساوي أحد الأحرف في السلسلة str2. في حالة نجاحها، تقوم الدالة بإرجاع مؤشر إلى هذا الحرف، وإذا لم تنجح، فإنها ترجع NULL.

شار str = "123 اي بي سي";

printf("%s\n", strpbrk(str, "bca")); // المطبوعات: اي بي سي

6. مقارنة السلسلة.يتم استخدام الدالة strstr لمقارنة السلاسل.

char* strstr (const char *str1, const char *str2);

يبحث عن التواجد الأول لـ str2 (بدون البايت الفارغ الزائد) في str1. في حالة نجاحها، تقوم الدالة بإرجاع مؤشر إلى السلسلة الفرعية التي تم العثور عليها، وإذا لم تنجح، فإنها ترجع NULL. إذا كان مؤشر str1 يشير إلى سلسلة ذات طول صفري، فسترجع الدالة مؤشر str1.

شار str = "123 اي بي سي 456؛

printf ("%s\n"، strstr (str، "abc")؛ // طباعة: abc 456

7. تحليل سلسلة إلى الرموز.يتم استخدام الدالة strtok لتحليل سلسلة إلى رموز مميزة.

char* strtok (char *str1, const char *str2);

تقوم بإرجاع مؤشر إلى الرمز المميز (الكلمة) التالي في السلسلة str1، حيث تكون محددات الرمز المميز عبارة عن أحرف من السلسلة str2. إذا لم يكن هناك المزيد من الرموز المميزة، ترجع الدالة NULL. عند الاستدعاء الأول للدالة strtok، يجب أن تشير المعلمة str1 إلى سلسلة تم ترميزها، وفي الاستدعاءات اللاحقة، يجب تعيين هذه المعلمة على NULL. بعد العثور على رمز مميز، تكتب الدالة strtok بايتًا خاليًا بعد هذا الرمز المميز بدلاً من المحدد.

#يشمل

#يشمل

char str = "12 34 ab cd";

ع = strtok(str, " ");

printf("%s\n", p); // يطبع القيم في عمود: 12 34 ab cd

p = strtok(NULL, " ");

8. تحديد طول السلسلة.يتم استخدام الدالة strlen لتحديد طول السلسلة.

size_t strlen (const char *str);

إرجاع طول السلسلة، متجاهلاً البايت الفارغ الأخير. على سبيل المثال،

شار str = "123";

printf("len = %d\n", strlen(str)); // المطبوعات: لين = 3

1.7. وظائف للعمل مع الذاكرة.

يصف ملف الرأس string.h أيضًا وظائف العمل مع كتل الذاكرة، والتي تشبه الوظائف المقابلة للعمل مع السلاسل.

void* memchr (const void *str, int c, size_t n);

يبحث عن التواجد الأول للحرف المحدد بواسطة c في n بايت من السلسلة النصية.

int memcmp (const void *str1, const void *str2, size_t n);

يقارن البايتات n الأولى من السلاسل str1 وstr2.

void* memcpy (const void *str1, const void *str2, size_t n);

نسخ البايتات n الأولى من السلسلة str1 إلى السلسلة str2.

void* memmove (const void *str1, const void *str2, size_t n);

نسخ البايتات n الأولى من str1 إلى str2، مما يضمن معالجة السلاسل المتداخلة بشكل صحيح.

void* memset (const void *str, int c, size_t n);

نسخ الحرف المحدد بواسطة c إلى أول n بايت من str.

إعلان السلاسل

السلسلة في لغة C عبارة عن مصفوفة أحادية البعد من الأحرف، العنصر الأخير منها هو حرف نهاية السطر - صفر (سلسلة منتهية بصفر، أي سلسلة منتهية بـ NULL).

يمكن الإعلان عن متغير من نوع السلسلة في لغة C بثلاث طرق، اثنتان منها تقومان بتهيئة السلسلة في وقت الإعلان.

الطريقة الأولى:

إعلانات صفيف الأحرف (تذكر إضافة مسافة للإنهاء الفارغ):

شار ق؛

الطريقة الثانية:

قم بتعيين قيمة أولية لمتغير السلسلة (في هذه الحالة، يمكن للمترجم حساب طول السلسلة نفسها):

Char s = "مثال لتهيئة السلسلة";

على يمين علامة التعيين يوجد ثابت السلسلة. تتم إضافة الصفر ('\0') تلقائيًا إلى نهاية السطر. يتم وضع ثوابت سلسلة الأحرف في الفصل ذاكرة ثابتة.

الطريقة الثالثة:

إشارة ضمنية إلى أنه يتم استخدام مصفوفة. على يسار علامة التعيين يوجد مؤشر للرمز:

Char *s="خيار التهيئة الثاني";

سيكون المتغير s بمثابة مؤشر إلى هذا الموقع كبش، حيث يوجد ثابت السلسلة. المأزق المحتمل في هذا النوع من التدوين هو أن المؤشر إلى الحرف يُشار إليه غالبًا على أنه سلسلة. الإدخال أدناه هو مجرد مؤشر إلى حرف، حيث لا توجد مساحة متوفرة للسلسلة:

شار * ق؛

إدخال سلسلة من جهاز إدخال قياسي (لوحة المفاتيح)

هناك مجموعة من الوظائف للعمل مع السلاسل. بالنسبة للإدخال من جهاز إدخال قياسي (لوحة المفاتيح)، تُستخدم وظائف المكتبة من وحدة الإدخال/الإخراج القياسية في أغلب الأحيان: scanfو يحصل.

لإدخال سلسلة باستخدام الدالة scanf، يستخدم التنسيق « %s» ولاحظ أنه لا يتم استخدام علامة العنوان قبل معرف السطر « & » ، نظرًا لأن المصفوفة أحادية البعد ممثلة بالفعل بمؤشر إلى بدايتها:

Scanf("%s"، s);

وظيفة يحصل ()يقرأ الأحرف حتى يصل إلى حرف السطر الجديد. تقبل الدالة كافة الأحرف حتى حرف السطر الجديد، ولكنها لا تتضمنه. تتم إضافة صفر إنهاء ('\0') إلى نهاية السطر. وظيفة يحصل ()يضع تسلسل الأحرف المقروءة من لوحة المفاتيح في معلمة سلسلة ويعيد مؤشرًا إلى هذه السلسلة (إذا كانت العملية ناجحة)، أو NULL (في حالة وجود خطأ). في المثال أدناه، إذا تمت العملية بنجاح، فسيتم عرض خطين متطابقين على الشاشة:

#يشمل int main() ( char s; char *p; p=gets(s); printf(" \n تم إدخال السلسلة %s.",s); if (p) printf(" \n تم إدخال السلسلة %s دخلت."، ع)؛ العودة 0؛ )

بشكل عابر، لاحظ أن الدالة get تُستخدم غالبًا لإدخال أي بيانات من لوحة المفاتيح على شكل سلسلة بغرض التحويل الإضافي بواسطة الدالة sscanf إلى التنسيق المطلوبأو للتحليل الأولي للبيانات المدخلة، على سبيل المثال:

#يشمل #يشمل #يشمل int main() ( char s; int x, err; do ( printf(" \n أدخل عددًا صحيحًا -> "); get(s); err=sscanf(s, "%d,&x); if (err) !=1) printf(" \n خطأ في الإدخال. " ) while (err!=1); printf("\n تم إدخال عدد صحيح -> %d"، x )

سلاسل الطباعة إلى الإخراج القياسي (شاشة العرض)

لإخراج السلاسل إلى جهاز الإخراج القياسي (شاشة العرض)، يمكنك استخدام وظيفتين printfو يضع. تم تمرير الدالة printf "%s" كتنسيق. تكمن راحة استخدام هذه الوظيفة في أنه بالإضافة إلى السلسلة، يمكنك عرض بيانات الأنواع الأخرى على الفور. ميزة الميزة يضعهو أنه بعد طباعة السطر، فإنه ينتقل تلقائيا إلى السطر التالي.

وظائف للعمل مع السلاسل

لتحويل السلاسل في لغة C، يتم توفير مكتبة السلسلة. كل وظيفة لها تنسيق التسجيل الخاص بها (النموذج الأولي).

تمت مناقشة الوظائف الأكثر استخدامًا في هذه المقالة. - يقرأ

مثال على البرامج (القائمة) التي تعمل مع السلاسل

في البرنامج، يمكن تعريف السلاسل على النحو التالي:

  • كثوابت السلسلة؛
  • كمصفوفات الأحرف؛
  • عبر مؤشر إلى نوع الحرف؛
  • مثل صفائف من السلاسل.

بالإضافة إلى ذلك، يجب توفير تخصيص الذاكرة لتخزين السلسلة.

يتم التعامل مع أي تسلسل من الأحرف المحاطة بعلامتي اقتباس مزدوجتين "" على أنه ثابت السلسلة.

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

يتم تخصيص خلايا RAM متتالية لتخزين السلسلة. لذا فإن السلسلة عبارة عن مجموعة من الأحرف. يتم تخصيص بايت واحد لتخزين رمز كل حرف في السلسلة.

لوضع بعض أحرف الخدمة في سلسلة ثابتة، يتم استخدام مجموعات الأحرف. لذلك، إذا كنت بحاجة إلى تضمين حرف اقتباس مزدوج في سلسلة، فيجب أن يسبقه حرف شرطة مائلة عكسية: '\'' .

توجد ثوابت السلسلة في الذاكرة الثابتة. عنوان البداية لسلسلة من الأحرف في اقتباسات مزدوجةيتم التعامل معه كعنوان سلسلة. غالبًا ما تُستخدم ثوابت السلسلة لتوفير تفاعل المستخدم في وظائف مثل printf().

عند تحديد مصفوفة الأحرفعليك أن تخبر المترجم بحجم الذاكرة المطلوبة.

شار م؛

يمكن للمترجم أيضًا تحديد حجم مصفوفة الأحرف بشكل مستقل إذا تم تحديد تهيئة المصفوفة عند الإعلان عنها كثابت سلسلة:

شار m2=;
شار m3=( "T"، "i"، "x"، "i"، "e"، "، "d"، "o"، "l"، "i"، "n"، "s"، "، "p "، "o"، "l"، "n"، "y"، "، "s"، "v"، "e"، "zh"، "e"، "y"، "، "m"، "ز"، و"ل"، و"س"، و"y"، و"\0"};

في هذه الحالة، الأسماء m2 وm3 هي مؤشرات إلى العناصر الأولى للمصفوفات:

  • m2 يعادل &m2
  • m2 يعادل "G"
  • m2 يعادل 'o'
  • m3 يعادل &m3
  • m3 يعادل 'x'

عند الإعلان عن مصفوفة أحرف وتهيئتها باستخدام سلسلة ثابتة، يمكنك تحديد حجم المصفوفة بشكل صريح، ولكن الحجم المحدديجب أن يكون المصفوفة أكبر من حجم ثابت سلسلة التهيئة:

شار م2= "تنام قمم الجبال في ظلام الليل.";

لتعيين سلسلة يمكنك استخدامها مؤشر لنوع الحرف.

شار *m4;

في هذه الحالة، عند الإعلان عن مصفوفة، يمكن تعيين عنوان المصفوفة للمتغير m4:

م4 = م3؛
*m4 يعادل m3="T"
*(m4+1) يعادل m3="و"

هنا m3 هو ثابت المؤشر. لا يمكنك تغيير m3، لأن هذا يعني تغيير موقع (عنوان) المصفوفة في الذاكرة، على عكس m4 .

بالنسبة للمؤشر، يمكنك استخدام عملية الزيادة (الانتقال إلى الحرف التالي):

مصفوفات من سلاسل الأحرف

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

شار * الشاعر = ( "مات الشاعر!"، "- عبد الشرف -",
"سقط"، "افتراءت عليه الشائعات..."};

في هذه الحالة، الشاعر عبارة عن مصفوفة تتكون من أربعة مؤشرات لسلاسل الأحرف. كل سلسلة أحرف عبارة عن مصفوفة أحرف، لذا هناك أربعة مؤشرات للمصفوفة. يشير مؤشر الشاعر إلى السطر الأول:
*شاعرمقابل "ف",
*شاعر[ل]مقابل "-" .

يتم تنفيذ التهيئة وفقًا للقواعد المحددة للمصفوفات.
النصوص المقتبسة تعادل تهيئة كل سلسلة في المصفوفة. فاصلة تفصل المجاورة
تسلسلات.
يمكنك أيضًا تعيين حجم سلاسل الأحرف بشكل صريح باستخدام وصف مثل هذا:

شاعر شار؛

الفرق هو أن هذا النموذج يحدد مصفوفة "مستطيلة" تكون فيها جميع الصفوف بنفس الطول.

مجموعة مجانية

وصف

شار * شاعر؛


يحدد مصفوفة حرة، حيث يتم تحديد طول كل سطر بواسطة المؤشر الذي يقوم بتهيئة هذا الخط. المصفوفة الحرة لا تضيع الذاكرة.

عمليات السلسلة

معظم عمليات C التي تتعامل مع السلاسل تعمل باستخدام المؤشرات. لوضع سلسلة من الأحرف في ذاكرة الوصول العشوائي (RAM)، يجب عليك:

  • تخصيص كتلة من ذاكرة الوصول العشوائي (RAM) للمصفوفة؛
  • تهيئة السلسلة.

لتخصيص ذاكرة لتخزين سلسلة، يمكن استخدام وظائف تخصيص الذاكرة الديناميكية. في هذه الحالة، من الضروري مراعاة حجم الخط المطلوب:

شار * الاسم؛
الاسم = (شار *)malloc(10);
scanf("%9s" , name);

يتم استخدام الدالة scanf() لإدخال سلسلة، ولا يمكن أن تتجاوز السلسلة المدخلة 9 أحرف. سيحتوي الحرف الأخير على "\0" .

وظائف إدخال السلسلة

يمكن استخدام الدالة scanf() لإدخال سلسلة. ومع ذلك، تم تصميم scanf() لاسترداد كلمة بدلاً من سلسلة. إذا كنت تستخدم التنسيق "%s" للإدخال، فسيتم إدخال السطر قبل (ولكن ليس بما في ذلك) الحرف الفارغ التالي، والذي يمكن أن يكون مسافة أو علامة تبويب أو سطرًا جديدًا.

لإدخال سلسلة، بما في ذلك المسافات، استخدم الدالة

شار * يحصل على (شار *)؛


أو ما يعادلها

char * get_s(char *);

يتم تمرير مؤشر السلسلة المراد إدخالها كوسيطة للوظيفة. تطلب الوظيفة من المستخدم إدخال سلسلة، والتي تضعها في مصفوفة حتى يضغط المستخدم يدخل.

وظائف إخراج السلسلة

لإخراج السلاسل، يمكنك استخدام الوظيفة التي تمت مناقشتها مسبقًا

printf("%s" , str); // شارع - مؤشر إلى السلسلة

أو بصيغة مختصرة

printf(str);

يمكن أيضًا استخدام الوظيفة لإخراج السلاسل

int يضع (char *s);

الذي يطبع السلسلة وينقل المؤشر إلى سطر جديد (على عكس printf() ). يمكن أيضًا استخدام الدالة puts()‎ لإخراج ثوابت السلسلة المحاطة بعلامات اقتباس.

وظيفة إدخال الأحرف

يمكن استخدام الوظيفة لإدخال الأحرف

شار getchar();


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

وظيفة إخراج الأحرف

يمكن استخدام الوظيفة لإخراج الأحرف

شار بوتشار(شار);


والتي تقوم بإرجاع قيمة الحرف المراد طباعته وطباعة الحرف الذي تم تمريره كوسيطة إلى الشاشة.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26

#يشمل
#يشمل
#يشمل
إنت الرئيسي () (
شار ق، سيم؛
عدد كثافة العمليات، ط؛
نظام("chcp 1251");
نظام("cls");
برينتف ( "أدخل السلسلة:");
get_s(s);
برينتف ( "أدخل الحرف:");
sym = getchar();
العد = 0؛
لـ (i = 0; s[i] != "\0" ; i++)
{
إذا (س[i] == سيم)
العد++;
}
printf("في السطر\n");
يضع (ق)؛ // إخراج سلسلة
printf("الحرف");
بوتشار(سيم); // إخراج الرمز
برينتف ( "يحدث %d مرة"، عدد)؛
getchar(); getchar();
العودة 0؛
}

نتيجة التنفيذ

الوظائف الرئيسية للمكتبة القياسية string.h

يتم عرض الوظائف الرئيسية للمكتبة القياسية string.h في الجدول.

وظيفة وصف

شار * strcat (شار * s1، شار * s2)

يقوم بإلحاق s2 بـ s1، ويعيد s1

شار * strncat (شار * s1، شار * s2، int n)

يُلحق n على الأكثر من الأحرف s2 إلى s1، وينهي السلسلة بـ "\0"، ويعيد s1

شار *strсpy (شار *s1، شار *s2)

نسخ السلسلة s2 إلى السلسلة s1، بما في ذلك "\0"، وإرجاع s1
);
strncpy(m3, m1, 6); // لا يضيف "\0" في نهاية السطر
يضع( "النتيجة strncpy(m3, m1, 6)");
يضع(م3);
strcpy(m3, m1);
يضع( "النتيجة strcpy(m3, m1)");
يضع(م3);
يضع( "نتيجة strcmp(m3, m1) هي");
printf("%d" , strcmp(m3, m1));
strncat(m3, m2, 5);
يضع( "النتيجة strncat(m3, m2, 5)");
يضع(م3);
strcat(m3, m2);
يضع( "النتيجة strcat(m3, m2)");
يضع(م3);
يضع( "عدد الأحرف في السلسلة m1 هو strlen(m1) :");
printf("%d\n" , strlen(m1));
_strnset(m3, "f" , 7);
يضع( "النتيجة strnset(m3, "f" , 7)");
يضع(م3);
_strset(m3, "k" );
يضع( "النتيجة strnset(m3, "k")");
يضع(م3);
getchar();
العودة 0؛
}

نتيجة التنفيذ

هابرا، مرحبا!

منذ وقت ليس ببعيد، حدثت لي حادثة مثيرة للاهتمام إلى حد ما، شارك فيها أحد معلمي إحدى كليات علوم الكمبيوتر.

تطورت المحادثة حول برمجة Linux ببطء إلى حد أن هذا الشخص يجادل بأن تعقيد برمجة الأنظمة مبالغ فيه إلى حد كبير. أن لغة C بسيطة مثل المطابقة، في الواقع، مثل نواة Linux (على حد تعبيره).

كان معي جهاز كمبيوتر محمول يعمل بنظام Linux، والذي يحتوي على مجموعة من الأدوات المساعدة للتطوير في لغة C (gcc، vim، make، valgrind، gdb). لا أتذكر الهدف الذي وضعناه لأنفسنا بعد ذلك، ولكن بعد بضع دقائق كان خصمي أمام هذا الكمبيوتر المحمول، جاهزًا تمامًا لحل المشكلة.

وقد ارتكب حرفيًا في الأسطر الأولى خطأً فادحًا عند تخصيص الذاكرة لـ... سطر.

Char *str = (char *)malloc(sizeof(char) * strlen(buffer));
المخزن المؤقت - متغير مكدس يتم فيه كتابة البيانات من لوحة المفاتيح.

أعتقد أنه سيكون هناك بالتأكيد أشخاص سيتساءلون: "كيف يمكن أن يكون هناك أي خطأ في هذا؟"
صدقوني، يمكن.

وماذا بالضبط - اقرأ على القطة.

القليل من النظرية - نوع من LikBez.

إذا كنت تعرف، انتقل إلى العنوان التالي.

السلسلة في لغة C عبارة عن مصفوفة من الأحرف، والتي يجب أن تنتهي دائمًا بـ "\0" - حرف نهاية السطر. يتم الإعلان عن السلاسل الموجودة على المكدس (الثابت) على النحو التالي:

شار str[n] = ( 0 );
n هو حجم مصفوفة الأحرف، وهو نفس طول السلسلة.

المهمة (0) - "تصفير" السلسلة (اختياري، يمكنك الإعلان عنها بدونها). والنتيجة هي نفس تشغيل الوظائف memset(str, 0, sizeof(str)) وbzero(str, sizeof(str)). يتم استخدامه لمنع ترك البيانات المهملة في متغيرات غير مهيأة.

يمكنك أيضًا تهيئة سلسلة على المكدس على الفور:

Char buf = "نص المخزن المؤقت الافتراضي\n";
بالإضافة إلى ذلك، يمكن الإعلان عن سلسلة كمؤشر ويمكن تخصيص ذاكرة لها على الكومة:

Char *str = malloc(size);
الحجم - عدد البايتات التي نخصصها للسلسلة. تسمى هذه السلاسل ديناميكية (نظرًا لأن الحجم المطلوب يتم حسابه ديناميكيًا + يمكن زيادة حجم الذاكرة المخصصة في أي وقت باستخدام وظيفة realloc()).

في حالة متغير المكدس، استخدمت الرمز n لتحديد حجم المصفوفة؛ وفي حالة متغير الكومة، استخدمت حجم الرمز. وهذا يعكس تمامًا الجوهر الحقيقي للفرق بين الإعلان الموجود على المكدس والإعلان مع تخصيص الذاكرة على الكومة، لأن n يُستخدم عادةً عند الحديث عن عدد العناصر. والحجم قصة مختلفة تماما..

سوف يساعدنا فالجريند

وقد ذكرت ذلك أيضًا في مقالتي السابقة. Valgrind (، اثنان - طريقة صغيرة) - جدًا برنامج مفيد، مما يساعد المبرمج على تعقب تسرب الذاكرة وأخطاء السياق - بالضبط الأشياء التي تظهر غالبًا عند العمل مع السلاسل.

دعونا نلقي نظرة على قائمة قصيرة تنفذ شيئًا مشابهًا للبرنامج الذي ذكرته ونقوم بتشغيله من خلال valgrind:

#يشمل #يشمل #يشمل #define HELLO_STRING "Hello, Habr!\n" void main() ( char *str = malloc(sizeof(char) * strlen(HELLO_STRING)); strcpy(str, HELLO_STRING); printf("->\t%s" , شارع);
وفي الواقع نتيجة البرنامج:

$ gcc main.c $ ./a.out -> مرحبًا حبر!
لا شيء غير عادي حتى الآن. الآن لنقم بتشغيل هذا البرنامج باستخدام valgrind!

$ valgrind --tool=memcheck ./a.out ==3892== Memcheck، كاشف أخطاء الذاكرة ==3892== حقوق الطبع والنشر (C) 2002-2015، وGNU GPL"d، بقلم جوليان سيوارد وآخرون. == 3892== إعادة تشغيل Valgrind-3.12.0 وLibVEX باستخدام -h للحصول على معلومات حقوق الطبع والنشر ==3892== الأمر: ./a.out ==3892== ==3892== كتابة غير صالحة بالحجم 2 ==3892= = عند 0x4005B4: main (في /home/indever/prg/C/public/a.out) ==3892== العنوان 0x520004c هو 12 بايت داخل كتلة بحجم 13 تخصيصًا d ==3892== عند 0x4C2DB9D: malloc (vg_replace_malloc.c:299) ==3892== بواسطة 0x400597: رئيسي (في /home/indever/prg/C/public/a.out) ==3892== ==3892== قراءة غير صالحة للحجم 1 == 3892== عند 0x4C30BC4: strlen (vg_replace_strmem.c:454) == 3892== بواسطة 0x4E89AD0: vfprintf (في /usr/lib64/libc-2.24.so) == 3892== بواسطة 0x4E90718: printf (في /usr/ lib64/libc-2.24.so) ==3892== بواسطة 0x4005CF: main (في /home/indever/prg/C/public/a.out) ==3892== العنوان 0x520004d هو 0 بايت بعد كتلة بحجم 13 alloc"d ==3892== عند 0x4C2DB9D: malloc (vg_replace_malloc.c:299) ==3892== بواسطة 0x400597: main (في /home/indever/prg/C/public/a.out) ==3892== -> أهلا حبر! ==3892== ==3892== ملخص الكومة: ==3892== قيد الاستخدام عند الخروج: 0 بايت في 0 كتل ==3892== إجمالي استخدام الكومة: تخصيصان، 2 مجانيان، 1037 بايت مخصصة ==3892= ===3892== تم تحرير كافة كتل الكومة - لا توجد تسريبات محتملة ==3892== بالنسبة لعدد الأخطاء المكتشفة والممنوعة، أعد التشغيل باستخدام: -v ==3892== ملخص الخطأ: 3 أخطاء من سياقين (مقموع: 0 من 0)
==3892== تم تحرير كافة كتل الكومة - لا يوجد أي تسرب محتمل- لا توجد تسريبات، وهذا خبر جيد. لكن الأمر يستحق أن تغمض عينيك قليلاً (على الرغم من أنني أريد أن أشير إلى أن هذا مجرد ملخص، والمعلومات الرئيسية في مكان مختلف قليلاً):

==3892== ملخص الخطأ: 3 أخطاء من سياقين (ممنوع: 0 من 0)
3 أخطاء. في سياقين. في مثل هذا البرنامج البسيط. كيف!؟

نعم، بسيط جدا. "الشيء المضحك" برمته هو أن الدالة strlen لا تأخذ في الاعتبار حرف نهاية السطر - "\0". حتى لو قمت بتحديده بشكل صريح في السطر الوارد (#define HELLO_STRING “Hello, Habr!\n\0”)، فسيتم تجاهله.

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

في الواقع، الإصدار الصحيح من البرنامج سيبدو كما يلي:

#يشمل #يشمل #يشمل #define HELLO_STRING "Hello, Habr!\n" void main() ( char *str = malloc(sizeof(char) * (strlen(HELLO_STRING) + 1)); strcpy(str, HELLO_STRING); printf("->\ t%s"، str); free(str); )
لنقم بتشغيله من خلال valgrind:

$ valgrind --tool=memcheck ./a.out -> مرحبًا حبر! ==3435== ==3435== ملخص الكومة: ==3435== قيد الاستخدام عند الخروج: 0 بايت في 0 كتل ==3435== إجمالي استخدام الكومة: تخصيصان، 2 مجانيان، 1038 بايت مخصصة ==3435= ===3435== تم تحرير كافة كتل الكومة - لا توجد تسريبات محتملة ==3435== بالنسبة لعدد الأخطاء المكتشفة والممنوعة، أعد التشغيل باستخدام: -v ==3435== ملخص الخطأ: 0 أخطاء من 0 سياقات (مقموعة: 0 من 0)
عظيم. لا توجد أخطاء، +1 بايت من الذاكرة المخصصة ساعد في حل المشكلة.

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

ومع ذلك، كما تعلمون، (strlen(str) + 1) هو هذا الحل. نحن نواجه مشكلتين:

  1. ماذا لو كنا بحاجة إلى تخصيص ذاكرة لسلسلة تم إنشاؤها باستخدام، على سبيل المثال، s(n)printf(..)؟ نحن لا نؤيد الحجج.
  2. مظهر. يبدو سطر الإعلان المتغير فظيعًا. يتمكن بعض اللاعبين أيضًا من إرفاق (char *) بـ malloc، كما لو أنهم يكتبون تحت الإيجابيات. في البرنامج الذي تحتاج فيه إلى معالجة السلاسل بشكل منتظم، فمن المنطقي العثور على حل أكثر أناقة.
دعونا نتوصل إلى حل يرضينا ويرضي فالجريند.

سنبرينتف ()

int snprintf(char *str, size_t size, const char *format, ...);- دالة - امتداد لـ sprintf، الذي يقوم بتنسيق سلسلة وكتابتها على المؤشر الذي تم تمريره كوسيطة أولى. وهو يختلف عن sprintf() في أن str لن يكتب بايتًا أكبر من الحجم المحدد.

تحتوي الوظيفة على ميزة واحدة مثيرة للاهتمام - على أي حال، تقوم بإرجاع حجم السلسلة التي تم إنشاؤها (دون مراعاة حرف نهاية السطر). إذا كانت السلسلة فارغة، فسيتم إرجاع 0.

إحدى المشكلات التي وصفتها باستخدام strlen تتعلق بوظائف sprintf() وsnprintf(). لنفترض أننا بحاجة إلى كتابة شيء ما في السلسلة النصية. يحتوي السطر الأخير على قيم المتغيرات الأخرى. يجب أن يكون دخولنا شيئًا مثل هذا:

Char * str = /* تخصيص الذاكرة هنا */; sprintf(str, "Hello, %s\n", "Habr!");
السؤال الذي يطرح نفسه: كيفية تحديد مقدار الذاكرة التي يجب تخصيصها لسلسلة السلسلة؟

Char * str = malloc(sizeof(char) * (strlen(str, "Hello, %s\n", "Habr!") + 1));

#يشمل - لن ينجح. يبدو النموذج الأولي لوظيفة strlen() كما يلي:
لا يعني const char *s أن السلسلة التي تم تمريرها إلى s يمكن أن تكون سلسلة ذات تنسيق متغير.

الخاصية المفيدة للدالة snprintf()، والتي ذكرتها أعلاه، ستساعدنا هنا. دعونا نلقي نظرة على الكود الخاص بالبرنامج التالي:

#يشمل #يشمل #يشمل void main() ( /* بما أن snprintf() لا يأخذ في الاعتبار حرف نهاية السطر، فإننا نضيف حجمه إلى النتيجة */ size_t need_mem = snprintf(NULL, 0, "Hello, %s!\n", "Habr") + sizeof("\0"); char *str = malloc(needed_mem); ;
قم بتشغيل البرنامج في valgrind:

$ valgrind --tool=memcheck ./a.out -> مرحبًا حبر! ==4132== ==4132== ملخص الكومة: ==4132== قيد الاستخدام عند الخروج: 0 بايت في 0 كتل ==4132== إجمالي استخدام الكومة: تخصيصان، 2 مجانيان، 1041 بايت مخصصة ==4132= ===4132== تم تحرير كافة كتل الكومة - لا توجد تسريبات محتملة ==4132== بالنسبة لعدد الأخطاء المكتشفة والممنوعة، أعد التشغيل باستخدام: -v ==4132== ملخص الخطأ: 0 أخطاء من 0 سياقات (ممنوع: 0 من 0) $
عظيم. لدينا دعم الحجة. نظرًا لأننا نمرر القيمة null كوسيطة ثانية للدالة snprintf()، فإن الكتابة إلى مؤشر فارغ لن تتسبب أبدًا في حدوث خطأ Seagfault. ومع ذلك، على الرغم من ذلك، ستظل الدالة تُرجع الحجم المطلوب للسلسلة.

ولكن من ناحية أخرى، كان علينا تقديم متغير إضافي، وهو التصميم

Size_t need_mem = snprintf(NULL, 0, "Hello, %s!\n", "Habr") + sizeof("\0");
يبدو أسوأ مما كان عليه في حالة strlen ().

بشكل عام، يمكن إزالة + sizeof("\0") إذا قمت بتحديد "\0" بشكل صريح في نهاية سطر التنسيق (size_t need_mem = snprintf(NULL, 0, "Hello, %s!\n \0 "، "Habr")؛)، ولكن هذا ليس ممكنًا دائمًا بأي حال من الأحوال (اعتمادًا على آلية معالجة السلسلة، يمكننا تخصيص بايت إضافي).

نحن بحاجة إلى القيام بشيء ما. فكرت قليلاً وقررت أن هذا هو الوقت المناسب لمناشدة حكمة القدماء. دعونا نصف دالة ماكرو ستستدعي snprintf() بمؤشر فارغ كالوسيطة الأولى، وفارغ كالوسيطة الثانية. ودعنا لا ننسى نهاية السطر!

#define strsize(args...) snprintf(NULL, 0, args) + sizeof("\0")
نعم، قد يكون ذلك جديدًا بالنسبة للبعض، لكن وحدات ماكرو لغة C تدعم عددًا متغيرًا من الوسائط، وتخبر علامة الحذف المعالج المسبق أن وسيطة دالة الماكرو المحددة (في حالتنا، الوسائط) تتوافق مع عدة وسائط حقيقية.

دعونا نتحقق من الحل الذي توصلنا إليه عمليًا:

#يشمل #يشمل #يشمل #define strsize(args...) snprintf(NULL, 0, args) + sizeof("\0") void main() ( char *str = malloc(strsize("Hello, %s\n", "Habr! ")); sprintf(str, "Hello, %s\n", "Habr!"); printf("->\t%s", str); free(str); )
لنبدأ مع فالجروند:

$ valgrind --tool=memcheck ./a.out -> مرحبًا حبر! ==6432== ==6432== ملخص الكومة: ==6432== قيد الاستخدام عند الخروج: 0 بايت في 0 كتل ==6432== إجمالي استخدام الكومة: تخصيصان، 2 مجانيان، 1041 بايت مخصصة ==6432= ===6432== تم تحرير كافة كتل الكومة - لا توجد تسريبات محتملة ==6432== بالنسبة لعدد الأخطاء المكتشفة والممنوعة، أعد التشغيل باستخدام: -v ==6432== ملخص الخطأ: 0 أخطاء من 0 سياقات (مقموعة: 0 من 0)
نعم، لا توجد أخطاء. كل شيء صحيح. وفالجريند سعيد، ويمكن للمبرمج أن ينام أخيرًا.

ولكن في النهاية، سأقول شيئًا آخر. في حال أردنا تخصيص ذاكرة لأي سلسلة (حتى مع الوسائط) فهي موجودة بالفعل حل جاهز للعمل بشكل كامل.

نحن نتحدث عن وظيفة asprintf:

#define _GNU_SOURCE /* راجع feature_test_macros(7) */ #include int asprintf(char **strp, const char *fmt, ...);
يأخذ مؤشرًا إلى سلسلة (**strp) كوسيطة أولى ويخصص الذاكرة للمؤشر الذي تم إلغاء الإشارة إليه.

برنامجنا المكتوب باستخدام asprintf() سيبدو كما يلي:

#يشمل #يشمل #يشمل الفراغ الرئيسي () ( char *str; asprintf(&str, "Hello, %s!\n", "Habr"); printf("->\t%s", str); free(str); )
وفي الواقع، في فالجريند:

$ valgrind --tool=memcheck ./a.out -> مرحبًا حبر! ==6674== ==6674== ملخص الكومة: ==6674== قيد الاستخدام عند الخروج: 0 بايت في 0 كتل ==6674== إجمالي استخدام الكومة: 3 عمليات تخصيص، 3 عمليات مجانية، 1,138 بايت مخصصة ==6674= ===6674== تم تحرير كافة كتل الكومة - لا توجد تسريبات محتملة ==6674== بالنسبة لعدد الأخطاء المكتشفة والممنوعة، أعد التشغيل باستخدام: -v ==6674== ملخص الخطأ: 0 أخطاء من 0 سياقات (مقموعة: 0 من 0)
كل شيء على ما يرام، ولكن، كما ترون، تم تخصيص المزيد من الذاكرة، وهناك الآن ثلاثة مخصصات، وليس اثنين على الأنظمة المدمجة الضعيفة، واستخدام هذه الوظيفة غير مرغوب فيه.
بالإضافة إلى ذلك، إذا كتبنا man asprintf في وحدة التحكم، فسنرى:

مطابقة لهذه الوظائف هي امتدادات GNU، وليست في C أو POSIX. وهي متوفرة أيضًا ضمن *BSD. يقوم تطبيق FreeBSD بتعيين strp إلى NULL عند حدوث خطأ.

وهذا يوضح أن هذه الوظيفة متاحة فقط في مصادر جنو.

خاتمة

في الختام، أريد أن أقول إن العمل مع السلاسل في C هو موضوع معقد للغاية يحتوي على عدد من الفروق الدقيقة. على سبيل المثال، لكتابة رمز "آمن" عند تخصيص الذاكرة ديناميكيًا، يوصى باستخدام وظيفة calloc() بدلاً من malloc() - calloc يملأ الذاكرة المخصصة بالأصفار. أو، بعد تخصيص الذاكرة، استخدم الدالة memset(). وإلا، فإن البيانات المهملة التي كانت موجودة في البداية في منطقة الذاكرة المخصصة قد تسبب مشاكل أثناء تصحيح الأخطاء، وأحيانًا عند العمل مع السلسلة.

أكثر من نصف مبرمجي لغة C الذين أعرفهم (معظمهم مبتدئون) الذين قاموا بحل مشكلة تخصيص الذاكرة للسلاسل بناءً على طلبي، فعلوا ذلك بطريقة أدت في النهاية إلى أخطاء في السياق. في حالة واحدة - حتى تسرب الذاكرة (حسنًا، نسي الشخص القيام بذلك مجانًا(str)، وهذا لا يحدث أبدًا لأي شخص). في واقع الأمر، دفعني هذا إلى إنشاء هذا الإبداع الذي قرأتموه للتو.

آمل أن تكون هذه المقالة مفيدة لشخص ما. لماذا أقوم بكل هذه الضجة - لا توجد لغة بسيطة. في كل مكان لديه الدقيقة الخاصة به. وكلما زادت دقة اللغة التي تعرفها، كان كودك أفضل.

أعتقد أنه بعد قراءة هذه المقالة، سيصبح الكود الخاص بك أفضل قليلاً :)
بالتوفيق يا حبر

العلامات: خطوط ج. مصفوفة شار.

السلاسل في C. مقدمة.

هذه مقالة تمهيدية عن سلاسل C. أكثر وصف تفصيليوستكون هناك أمثلة عندما نتعلم العمل بالذاكرة والمؤشرات. في جهاز الكمبيوتر، يتم تخزين كافة القيم كأرقام. والخطوط أيضًا لا يوجد بها رموز أو حروف. المصطلح عبارة عن مجموعة من الأرقام. يتوافق كل رقم مع حرف معين، وهو مأخوذ من جدول التشفير. عند عرضه على الشاشة، يتم عرض الرمز بطريقة معينة.
تُستخدم المصفوفات من نوع char لتخزين السلاسل. أكرر مرة أخرى - نوع char رقمي، ويخزن بايت واحد من البيانات. ولكن وفقا لجدول الترميز، يرتبط كل من هذه الأرقام بحرف ما. وفي الاتجاه المعاكس - يتم تحديد كل رمز من خلاله رقم سريفي جدول الترميز.
على سبيل المثال

#يشمل #يشمل الفراغ الرئيسي () (char c = "A"؛ int i = 65؛ printf ("display as char %c\n"، c)؛ printf("display as int %d\n"، c); printf(" العرض كـ char %c\n"، i); printf("العرض كـ char %d\n"، i); getch(); )

لقد أنشأنا متغيرين، أحدهما من النوع شار، آخر كثافة العمليات. يحتوي الحرف "A" على قيمة عددية تبلغ 65. وهو حرف وليس سلسلة، ولذلك فهو محاط بعلامات اقتباس مفردة. يمكننا طباعته كرسالة

Printf("العرض كـ char %c\n"، c);

وبعد ذلك سيتم الإخراج
أ
إذا قمت بإخراجه كرقم، فسيكون كذلك
65
يمكنك أن تفعل الشيء نفسه مع الرقم 65، الذي تم تخزينه في متغير مثل كثافة العمليات.
الأحرف الخاصة لها أيضًا أرقامها الخاصة

#يشمل #يشمل الفراغ الرئيسي() ( printf("%c", "\a"); printf("%d", "\a"); printf("%c", 7); getch(); )

هنا سيكون "الإخراج" أولاً زمارةثم قيمتها العددية ثم الإشارة الصوتية مرة أخرى.
السلسلة في C هي مصفوفة من النوع شار، العنصر الأخير الذي يخزن الحرف الطرفي "\0". القيمة الرقمية لهذا الحرف هي 0، لذا يمكننا القول أن المصفوفة تنتهي بالصفر.
على سبيل المثال

#يشمل #يشمل باطلة رئيسية () (شار كلمة؛ كلمة = "A"؛ كلمة = "B"؛ كلمة = "C"؛ كلمة = "\0"؛ // كلمة = 0؛ ما يعادل printf("%s"، كلمة) ;

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

كلمة = "\0"؛

ثم سيتم إخراج سلسلة من الأحرف ذات الطول التعسفي حتى تتم مواجهة البايت الأول المملوء بالأصفار.

#يشمل #يشمل باطلة الرئيسية () (شار كلمة = "ABC"؛ نص شار = ("H"، "E"، "L"، "L"، "O")؛ printf("%s\n"، كلمة)؛ printf ("%s"، نص)؛

في في هذه الحالةكل شيء صحيح. تنتهي السلسلة "ABC" بالصفر، ونقوم بتهيئة مصفوفة الكلمات بها. تتم تهيئة السلسلة النصية حرفًا بحرف، ويتم ملء جميع الأحرف المتبقية، كما يلي من الفصل الخاص بالمصفوفات، بالأصفار.

خطوط القراءة

من أجل طلب سلسلة من المستخدم، تحتاج إلى إنشاء مخزن مؤقت. يجب تحديد حجم المخزن المؤقت مسبقًا حتى تتناسب الكلمة المدخلة معه. عند قراءة السطور، هناك خطر من قيام المستخدم بإدخال بيانات أكثر مما يسمح به المخزن المؤقت. ستتم قراءة هذه البيانات ووضعها في الذاكرة، وستحل محل قيم الآخرين. بهذه الطريقة، يمكنك تنفيذ هجوم عن طريق كتابة البايتات اللازمة، والتي، على سبيل المثال، تستحق الانتقال إلى قسم التعليمات البرمجية مع البرمجيات الخبيثة، أو تسجيل البيانات.

#يشمل #يشمل الفراغ الرئيسي () (مخزن مؤقت char؛ scanf("%19s"، مخزن مؤقت)؛ printf("%s"، مخزن مؤقت)؛ getch(); )

في هذه الحالة، يقتصر عدد الأحرف المدخلة على 19 حرفًا، ويكون حجم المخزن المؤقت أكبر بمقدار 1، نظرًا لأنه من الضروري تخزين الحرف الطرفي. دعونا نكتب برنامج بسيط، الذي يطالب المستخدم بسلسلة ويعيد طولها.

#يشمل #يشمل void main() ( char buffer; unsigned len = 0; scanf("%127s", buffer); while (buffer != "\0") ( len++; ) printf("length(%s) == %d" ، المخزن المؤقت، لين)؛

بما أن القيمة الرقمية للحرف "\0" هي صفر، فيمكننا الكتابة

بينما (المخزن المؤقت ! = 0) ( len++; )

أو حتى أقصر

بينما (المخزن المؤقت) ( len++; )

والآن لنكتب برنامجًا يطلب من المستخدم كلمتين ويقارن بينهما

#يشمل #يشمل /* ستكون نتيجة المقارنة الرقم 0 إذا كانت الكلمات تساوي 1 إذا كانت الكلمة الأولى أكبر من الثانية بالترتيب المعجمي -1 إذا كانت الكلمة الثانية أكبر */ void main() ( char firstWord; / / الكلمة الأولى char SecondWord; // الكلمة الثانية غير موقعة i; // العداد int cmpResult = 0; // نتيجة المقارنة scanf("%127s", firstWord);< 128; i++) { if (firstWord[i] >SecondWord[i]) ( // أكثر حتى لو كانت الكلمة الثانية قد انتهت بالفعل، لأن // إذن تنتهي بصفر cmpResult = 1;break; ) else if (firstWord[i]< secondWord[i]) { cmpResult = -1; break; } } printf("%d", cmpResult); getch(); }

وبما أن كل حرف له قيمة عددية، فيمكن مقارنتها مع بعضها البعض كأرقام. بالإضافة إلى ذلك، عادة (ولكن ليس دائمًا!) يتم ترتيب الحروف في جداول الترميز أبجديًا. لذلك، سيتم أيضًا الفرز حسب القيمة الرقمية حسب الترتيب الأبجدي.