| مقالات الشبكة العربية لمطوري الألعاب... |
و مجموع هذه القيم 128. انتظر قليلاً، أنا لم أقل أن مجموع محارف (characters)
الـ ASCII (في نظام ANSI/ISO 8859-1) يساوي
128. أجل حيث كان هناك إصدارين من ASCII : احتوى الأول على 128 محرف.
و قد عمل بشكل جيد في أمريكا حيث لا لزوم للأحرف الصوتية في الإنكليزية
(مثلاً كل من resume و résumé متعادلتان)
و حيث لم يكن يوجد الرمز £ (British Pound)
، ولكنه كان يفتعل الكثير من المشكلات في جنوبي و شمالي
الولايات المتحدة حيث للأحرف الصوتية و رموزها أهمية كبيرة، و لو أنك نظرت لأبعد
من ذلك ستجد لغات لا تعتمد أبداً على الألفبائية اللاتينية أمثال العربية و
الكورية و اليابانية...
الإصدار الثاني من ASCII كان 256 حرف و يسمى هذا الإصدار ASCII (نظام ANSI/ISO 8859-1).
و يعتبر إمتداداً للإصدار السابق حيث أضيف
إلى 128 حرف السابقة 128 أخرى عبارة عن مجموعة من الرموز
(مثل: رموز العملات) و الأحرف الصوتية. و الآن لا يزال جدول ASCII
المظلوم (الذي يعبر عن كل محرف منه بـ 1 بايت فقط) غير
كفؤ لكي يحتوي على جميع لغات العالم (بعضها يزيد عدد أحرفه عن 21،000 حرف!).
لحلِّ المشكلة السابقة علينا إيجاد جدول أوسع (أعرض) حتى يستوعب جميع حروف لغات العالم، وذلك بأن نستخدم قيمة بعرض 2 بايت للأحرف التي تحتاج ذلك و 1 بايت للأحرف السابقة و هكذا سنصبح قادرين على كتابة أحرف العالم الكثيرة مع الحفاظ على المساحة في الذاكرة (و التي كانت مهمة آنذاك). و قد اعتمدت Microsoft هذا النظام في إصدار نظم خاصة بالبلاد التي تحتاج إلى أحرف خاصة مثل: اليابان و الصين. ولكن ألا تلاحظ معي أن مشكلة سوف تظهر ألا و هي كيف سيعرف هذا المعالج أن هذا الحرف معرف بـ 1 بايت و ذاك بـ 2 بايت. ثم إذا كنت أستخدم مؤشراً لحرف من نوع char و يحتاج لـ 2 بايت ما هو البايت الذي يليه (طبعاً الحل في مثل هذه الحالات أن أجعل المؤشر يشير إلى موقع بداية مصفوفة ثم أتابع مع العنصر الذي يليه والذي يشكل البايت رقم 2) .
المشكلة الأساسية في الحل السابق هو الفوضى العارمة التي سيحييها DBCS في
معالجة ما إذا كان هناك بايت سابق و أين موقعه. و هنا يأتي الساحر Unicode بعد
تطور المعاجات و الذواكر و يبتدع حلاً جديداً و هو جعل جميع الحروف (بما فيها
ASCII و DBCS) ذات عرض 2 بايت!
ترا..ترا..ترا.. (هذا
هو الحل العظيم الذي أمضينا صفحة كاملة و نحن نحاول أن نقدمه). و هذا سيجعل
بمقدورنا التعبير عن 65،536 محرف، أي بأضعاف المحارف التي نحتاجها، فيدعم بذلك كل
اللغات الموجودة في العالم. إن ما يميز الـ Unicode عن الـ DBCS
هو وحدانية المعالجة حيث أن جميع الحرف متساوية في الحجم، فكلها بعرض 2 بايت.
من المعروف أنه لتعريف متغير (variable) يحمل حرفاً من جدول ASCII نستخدم المعرّف char و بالتالي لتعريف مؤشر له نستخدم char*. و الآن ماذا نستخدم لتعريف حرف من جدول Unicode ؟ نستخدم wchar_t و الذي يعرف في الملف WCHAR.H على النحو:
typedef unsigned short wchar_t;
إن المتغير من نوع short هو 16-bit (في نظام Win32) و هو المطلوب.
لذا لتعريف متغير يحمل حرفاً من جدول Unicode نستخدم ما يلي:
wchar_t c = 'a';
wchar_t c[] = L"Unicode";
لاحظ استخدامي للحرف L من (Literal) قبل إشارة التنصيص في المثال الثاني و التي تخبر أن كل محرف من مصفوفة المحارف التالية هو بعرض 2 بايت. و هناك طريقة ثانية لأداء نفس المهمة و هي باستخدام التعبير TEXT و المعرف على النحو :
#define TEXT(x) L##x
و يمكنك إستخدامه على النحو:
wchar_t c[] = TEXT("Unicode");
و الآن، وبعد أن عرفت كيف تعرف متغيراً من نوع wchar_t و تهيّؤه. يجب أن تعرف كيف تتعامل مع البيانات في هذا المتغير. فإذا أردت نسخه، معرفة عدد أحرفه أو مقارنته مع آخر فإنك تستخدم إجراءً ما في كل مرّة. لا شك أنك عملت مع المتغيرات من نوع char و طبقت عليها العمليات السابقة. ولكن هل علمت بوجود إجراءات خاصة بمتغيرات wchar_t. فمثلاً لو أردنا معرفة عدد محارف جملة (String) ما من نوع char فإننا نجد ذلك عن طريق استخدام الإجراء strlen(char*) على النحو التالي:
char c[] = "Hello";
printf ("The length of the variable (c) is %d", strlen(c));
و طبعاً ستكون النتيجة:
The length of the variable (c) is 5
و الآن إذا أردنا تطبيق هذا الإجراء على متغير من نوع wchar_t على النحو :
wchar_t w[] = TEXT("Hello");
printf ("The length of the variable (w) is %d", strlen((char*)w)); // Typecast to char*
ماذا ستكون النتيجة ؟ هل تستطيع أن تحزر ؟ خطأ! ليس 5 إنما على النحو :
The length of the variable (w) is 1
ولكن لماذا ؟ ما الذي حدث ؟ هذا بسيط. إذا كان عندنا متغير من النوع wchar_t ذو القيمة 'H أو (0x0048) فإنه يمثل بالرقم 48 ثم 00، فبالمثل يؤخذ ترحيبنا على الشكل:
0x0048 0x0065 0x006C 0x006C 0x006F
أو :
48 00 65 00 6C 00 6C 00 6F 00
و الآن راجع آلية عمل الإجراء strlen. حلل ثم أعطني الإجابة . . . . . هل سأنتظر طويلاً؟ حسناً، نعلم أن التابع strlen يعدّ كل حرف يمر عليه في مصفوفة الحروف حتى يصل إلى قيمة النهاية 0 (Terminator) فينهي العد و يقدم النتيجة. و الآن ماذا فعل إجراؤنا العزيز؟ عدّ أول حرف و هو الـ H ثم انتقل إلى الثاني فوجده 0 فأنهى العد و أعطانا النتيجة النهائية 1. و الآن ماذا؟ هل علينا إعادة صياغة كل إجراء حتى يعمل بشكل صحيح مع wchar_t ؟ ليس تماماً، لأن C/C++ فعلت ذلك عنك و كل ما عليك هو معرفة هذا الإجراء و استخدامه بالشكل الصحيح. فمثلاً حتى تحصل على طول متغير من نوع wchar_t فما عليك إلا استخدام الإجراء wcslen(wchar_t *) على النحو :
wchar_t w = TEXT("Hello")
printf ("The length of the variable (w) is %d", wcslen(w));
الآن أنت محق لأن النتيجة هنا ستكون فعلاً:
The length of the variable (w) is 5
و بإمكانك استكشاف كل الإجراءات التي يمكن أن تحتاجها في العمل مع wchar_t في الملف WCHAR.H كما ستجد في هذا الملف الإجراءات التقليدية التي تستخدمها للتعامل مع المتغيرات من نوع char.
و الآن هل يجب علي أن أكتب نسختين من برنامجي؟ واحدة بدون Unicode لتتوافق مع أنظمة التشغيل التي لا تدعمه و أخرى باستخدامه لكي أستطيع أن أجعل برنامجي متوافقاً مع عدة لغات تحت نظام تشغيل يدعم الـ Unicode ؟ بالطبع لا، فـ Microsoft فكرت بهذا الموضوع و وضعت الحل في الملف TCHAR.H فجعلت كل الإصدارات من الإجراءات و معرفات المتغيرات موحدة. كيف؟ بالإستناد إلى تعريفك (#define) لعبارة UNICODE أو عدمها في أول سطر من برنامجك، فإن المترجم ينتقي الإجراء المناسب. فمثلاً الإجراء يُـعرف _tcslen على أنه wcslen إذا كانت العبارة UNICODE معرفة في برنامجك، و على أنه strlen إذا لم تكن، وذلك بالشكل:
#ifdef UNICODE
#define _tcslen wcslen
#else
#define _tcslen strlen
#endif
و هلمّ جرّاً. حتى أن Micorsoft عرفت النوع TCHAR على أنه char أو wchar_t بالإستناد إلى نفس القاعدة.
قد تنظر إلى هذا الموضوع باستهزاء و عدم مبالاة حالياً. ولكن صدقني سيكون هذا الموضوع أول ما تحتاج إذا أردت أن تبرمج برنامجاً يخرج خارج نطاق -ربما- الوطن العربي. و تبقى أشياء كثيرة معلّقة قد لا أعلمها أو أُغفلت عن ذكرها و إذا أردت أن تتوسع بالموضوع فعليك بالمراجع التي سأذكرها في نهاية الصفحة.
أما الآن فأتمنى أن تكون قد استفدت من هذا المقال، و أتمنى أن تتاح لي الفرصة لكتابة مقالات أخرى.
| المصطلح | معناه |
|---|---|
| Character |
محرف. قيمة عددية تمثل حرفاً من الأحرف الهجائية أو رقماً أو رمزاً خاصاً أو أي شيء آخر مما يستطيع المستخدم إدخاله عبر لوحة المفاتيح!
إضافة إلى ذلك، قد يعبر المحرف عن أوامر تحكم غير مطبوعة (لا تظهر على الشاشة)، مثل المحرف
NUL و (Backspace) BS و هكذا. يعتمد نظام التشغيل على جدول ربط (Mapping Table) يربط القيم العددية للمحرف بمكافئاتها الرسومية، مثلاُ المحرف رقم 65 يرسم على الشكل A. |
| String | مصفوفة حروف (جملة). و لمزيد من الدقة : مصفوفة محارف. إن الـ string هو أي سلسلة من المحارف المتعاقبة. مثلاً: “Hello” هي string كما أن “Hello, World!” أيضاً string. |
| ASCII | إختصار للجملة: The American Standard Code for Information Interchange الشفرة الأمريكية الموحدة لتبادل المعلومات، و الصادرة عن هيئة ANSI. و هي عبارة عن جدول فيه مجموعة من الأرقام كل رقم يقابل محرفاً (حرفاً هجائياً، رمزاً، رقماً، أو زر تحكم) . و هي القيم التي يتعامل معها المعالج بدلاً من التعامل مع الرموز نفسها. |
| Unicode | مثل ASCII، هو جدول يربط أغلب المحارف الموجودة في لغات العالم بقيم عددية من 2 بايت (16 بت)، مما يعطيه إمكانية التعبير عن 65,563 محرفاً. يدعم في جميع أنظمة التشغيل المعتمدة تقنية NT، مثل WinXP، Win2000، WinNT (بكافة إصداراته). |
| DBCS | اختصار للجملة Double-Byte Character Set
و هو (مثل Unicode)، جدول محارف عالمي. اعتمد فيه على 2 بايت لبعض المحارف و بايت واحد فقط لبعضها الآخر (مما سبب بعض المشاكل) و السبب في هذا أن المساحة على زمن DBCS كانت مهمة جداً. |
| ANSI |
الأحرف الأولى من (American National Standards Institute)، المؤسسة الأمريكية للقياسيات الوطنية.
منظمة من مجموعات عمل صناعية غير مأجورة. تشكلت عام 1918م من أجل تطوير المواصفات القياسية لعمليات التجارة، التعامل و الاتصالات. ANSI هي الممثل الأمريكي للمنظمة العالمية للمواصفات القياسية (ISO)، و قد طورت قواعد استخدام لغات البرمجة، متضمنةً C/C++، FORTRAN و COBOL. |