يصل النص من المستخدمين والملفات وسطر الأوامر على هيئة محارف. فـ "42" ثلاثة بايتات - '4' و'2' و'\0' - ولا شيء فيها هو العدد 42 حتى تحوّله. وتقدّم C جيلين من دوال التحويل: القصيرة القديمة التي لا تستطيع الإبلاغ عن الأخطاء، والأطول التي تستطيع. تعرض هذه الصفحة كليهما وتشرح لماذا تستخدم البرامج الحقيقية المجموعة الثانية.
العائلة القديمة: atoi وatol وatof
تُقرأ كأسمائها: من ASCII إلى int، وإلى long، وإلى double. وسيط واحد ونتيجة واحدة.
تُتخطّى المسافات البيضاء الأولى، وتُقرأ إشارة اختيارية، وتُستهلَك الأرقام، ويُتجاهَل كل ما بعدها. وهذا مريح حتى يكون الإدخال خاطئًا:
تطبع الثلاثة كلها 0. فلا رمز إرجاع ولا راية ولا وسيلة للسؤال "هل نجح ذلك؟" - وقيمة تفيض عن int سلوك غير معرَّف لا خطأ مُبلَّغ عنه. ولثابت نصّي كتبته بنفسك في برنامج تجريبي هذا محتمل. أما لأي شيء يستطيع شخص أو ملف توفيره، فهو مولّد أخطاء صامتة.
الأداة الصحيحة: strtol
strtol (من سلسلة إلى long) هي التحويل نفسه مع قناتين للإبلاغ عمّا حدث.
long strtol(const char *text, char **endptr, int base);
تُضبَط endptr على أول محرف لم تستهلكه الدالة. وbase هو أساس العدد - استخدم 10، أو 0 لتدع بادئة الثابت تقرّر (0x للست عشري، وصفر بادئ للثماني).
وقراءة endptr تجيب عن الأسئلة التي لا تستطيع atoi الإجابة عنها:
end == inputتعني أنه لم يُحوَّل شيء - فالنص لم يبدأ بعدد.*end != '\0'تعني وجود محتوى لاحق. وكون ذلك خطأً قرارك: فهو مقبول عند تحليل"42 apples"، ومشكلة حين كان ينبغي أن يكون السطر كله عددًا.
ويُبلَّغ عن الفيضان منفصلًا عبر errno. والاصطلاح الكامل الصحيح يستحق أن يُكتب مرة ويُعاد استخدامه:
ثلاثة تفاصيل تجعل هذا صحيحًا. errno = 0 قبل الاستدعاء تهمّ لأن المكتبة لا تضبط errno إلا ضبطًا؛ ولا تمسحها أبدًا، فقيمة قديمة من استدعاء سابق ستُقرأ على أنها فشل هذا الاستدعاء. وتُرجِع strtol نوع long، الذي قد يكون أوسع من int، ففحص المدى مقابل INT_MIN/INT_MAX منفصل عن ERANGE. وإرجاع النجاح كحالة مع تسليم القيمة عبر مؤشّر هو كيف تبلّغ دوال C عن "إما قيمة وإما فشل" - فالقيمة المُرجَعة لا تستطيع أداء المهمّتين.
وstrtoul هي النظير غير الموقّع، وstrtoll/strtoull تتوليان long long.
أسس أخرى
يقرأ الأساس 16 ست عشريًا؛ ويفحص الأساس 0 البادئةَ ويقرّر بنفسه.
والأساس 0 فخّ في حالة واحدة بعينها: فعدد عشري محشوّ بصفر مثل "012" يُقرأ ثمانيًا ويخرج 10. فإن كان إدخالك عشريًا، فاكتب 10 صراحةً.
الفاصلة العائمة: strtod
strtod لها الشكل نفسه لأجل double، ولـ atof العيب نفسه الذي لـ atoi.
وتقبل strtod أيضًا الترميز العلمي ("1.5e3")، والأعداد العشرية الست عشرية، والكلمتين "inf" و"nan". وتؤثّر الإعدادات المحلّية في فاصل الكسر العشري، وهو ما يفاجئ من يحلّلون ملفات كُتبت في مكان آخر - وفي الإعداد المحلّي الافتراضي "C" يكون الفاصل نقطة دائمًا.
من الأعداد إلى النص: snprintf
في الاتجاه الآخر، snprintf هي الإجابة المعيارية. تكتب على الأكثر بالحجم الذي تعطيه إياها، وتنهي السلسلة دائمًا، وتُرجِع الطول الذي كانت ستحتاجه النتيجة الكاملة.
والقيمة المُرجَعة هي فحص الاقتطاع. فإن كانت أكبر من حجم المخزن أو مساوية له، فالنص لم يتّسع:
وشيئان لا تلجأ إليهما: itoa ليست جزءًا من C المعيارية، فالشيفرة التي تستخدمها لن تُترجَم في كل مكان؛ وsprintf هي snprintf بلا حدّ حجم، ما يعني أنها ستكتب بعد نهاية مخزنك دون اعتراض.
المحارف المفردة و'0'
محارف الأرقام من '0' إلى '9' مضمون أنها متتالية، فالطرح البسيط يحوّل بين محرف رقم وقيمته.
والأرقام وحدها تتصرّف هكذا - فليس مطلوبًا أن تكون الحروف متتالية، فـ c - 'a' غير قابلة للنقل لحسابات الأبجدية. واحرس الطرح بـ isdigit قبل الوثوق به:
والتحويل إلى unsigned char مطلوب للسبب نفسه المذكور في السلاسل النصية: فدوال <ctype.h> غير معرَّفة للوسائط السالبة، وchar العادي قد يكون موقّعًا.
أي دالة تستخدم
- نصّ من مستخدم أو ملف أو
argv←strtol/strtodمع الفحص الكامل. - ثابت نصّي كتبته بنفسك وتتحكّم فيه ←
atoiمقبولة، وإن لم تكن لافتة. - عدد إلى مخزن ←
snprintf، وافحص القيمة المُرجَعة. - محرف رقم واحد ← اطرح
'0'بعدisdigit. - أبدًا ←
getsوsprintfوitoa.
الأسئلة الشائعة
كيف تحوّل سلسلة نصية إلى عدد صحيح في لغة C؟
strtol(text, &end, 10) هي الإجابة الصحيحة: فهي تُرجِع القيمة، وتضبط end على أول محرف لم تستهلكه، وتبلّغ عن الفيضان عبر errno. أما atoi(text) فأقصر لكنها تُرجِع 0 لكلٍّ من "0" و"hello" دون وسيلة للتمييز بينهما.
ما العيب في atoi في لغة C؟
لا تستطيع الإبلاغ عن الفشل. فـ atoi("abc") تُرجِع 0، وهو لا يتميّز عن صفر حقيقي، والفيضان سلوك غير معرَّف لا خطأ مُبلَّغ عنه. استخدم strtol كلما جاء النص من مستخدم أو ملف أو وسيط سطر أوامر.
كيف أحوّل عددًا صحيحًا إلى سلسلة نصية في لغة C؟
snprintf(buf, sizeof buf, "%d", n). وهي معيارية ومحدودة بحجم المخزن وتُرجِع عدد المحارف التي احتاجتها النتيجة الكاملة - فقيمة مُرجَعة تساوي sizeof buf أو تزيد عليها تخبرك بأن النص اقتُطع. أما itoa فليست من C المعيارية.
كيف أحوّل المحرف '7' إلى العدد 7 في لغة C؟
اطرح '0': int digit = c - '0';. فمحارف الأرقام متتالية في كل مجموعة محارف في C، فيعطي الطرح قيمًا من 0 إلى 9. واحرسه بـ isdigit((unsigned char)c) أولًا، إذ لا معنى للعملية الحسابية لأي شيء آخر.