يفعل typedef شيئًا صغيرًا واحدًا: يمنح نوعًا موجودًا سلفًا اسمًا آخر. فلا يُنشأ نوع جديد، ولا تُجنَّب ذاكرة، ولا يتغيّر شيء في سلوك القيمة. إنه أداة تسمية - وفي C، حيث تطول أسماء الأنواع المدمجة (unsigned long long وstruct Employee وvoid (*)(int))، يساوي الاسم الجيد الكثير.
الصيغة الأساسية
تُقرأ الصياغة كتعريف متغيّر مثبّت عليه typedef من الأمام. وحيث يقع اسم المتغيّر، تضع اسم النوع الجديد:
unsigned long count; // declares a variable named count
typedef unsigned long ulong; // declares a TYPE named ulong
تلك هي القاعدة كلها، وهي تفسّر كل typedef ستقرؤه يومًا، بما في ذلك الغريبة المظهر لاحقًا في هذه الصفحة.
لاحظ ما لا يشتريه لك الزوج Celsius/Fahrenheit: أمان الأنواع. فكلاهما مجرّد double، فتمرير قيمة فهرنهايت حيث تُتوقَّع مئوية يُترجَم بسرور. فالأسماء توثّق القصد للبشر؛ ولا تُنشئ فحصًا.
اصطلاح typedef struct
هذا هو سبب احتواء معظم برامج C على typedef أصلًا. ففي C يتضمّن اسم نوع البنية الكلمةَ المفتاحية:
struct Point { int x; int y; };
struct Point p; /* the word "struct" every single time */
ويطوي typedef ذلك:
اقرأها بالقاعدة السابقة: انزع typedef فيبقى لديك struct Point {...} Point;، وهو تعريف متغيّر اسمه Point. أعِد typedef فتصبح Point اسم نوع بدلًا من ذلك.
بوسم أو بدونه
يمكنك ترك الوسم كليًا، فتحصل على بنية مجهولة الاسم لا تحمل إلا اسم typedef:
typedef struct {
int x;
int y;
} Point; /* no "struct Point" exists - only "Point" */
وذلك أنظف، ولسجلّ بيانات عادي هو الأسلوب الشائع. لكنه يفشل لحظة احتياج البنية إلى ذكر نفسها، لأن اسم typedef لا يصلح للاستخدام حتى ينتهي تعريفه هو:
/* Does NOT compile: "Node" is not a type yet inside its own braces. */
typedef struct {
int value;
Node *next;
} Node;
أبقِ الوسم فتعمل الإشارة الذاتية، لأن struct Node صالحة للاستخدام بمجرّد رؤية الوسم:
والقاعدة العملية: أبقِ الوسم. فهو يكلّف كلمة واحدة، ويطابق اسم typedef فلا يلتبس شيء، ويترك الباب مفتوحًا للإشارة الذاتية وللتصريحات المسبقة في الترويسات. انظر البنى والمؤشّرات لما تنمو إليه تلك العقدة المترابطة.
التصريحات المسبقة والأنواع المعتمة
لأن للمؤشّر إلى بنية حجمًا معروفًا حتى حين تكون محتويات البنية مجهولة، تستطيع ترويسة تسليم نوع دون كشف أعضائه:
/* stack.h */
typedef struct Stack Stack; /* declared, not defined */
Stack *stack_create(void);
void stack_push(Stack *s, int value);
int stack_pop(Stack *s);
void stack_destroy(Stack *s);
ويعيش struct Stack { ... }; كاملًا في stack.c ولا يستطيع أحد من الخارج مسّ أعضائه. ونمط النوع المعتم هذا هو كيف تفرض مكتبات C التغليف، وهو بالضبط ما يكون عليه FILE في المكتبة القياسية - إذ تحصل على FILE * من fopen ولا يُقال لك شيء عمّا بداخله. انظر التعامل مع الملفات.
typedef للتعدادات والاتّحادات
ينطبق الاصطلاح نفسه على النوعين المركّبين الآخرين:
وبدون typedef ستكونان enum Color c; وunion Number n;. انظر التعدادات والاتّحادات لما تفعلانه فعليًا.
typedef لمؤشّر دالة
هنا يكفّ typedef عن كونه رفاهية ويقترب من الضرورة. فصياغة مؤشّر الدالة الخام صعبة القراءة على نحو شهير:
void (*handler)(int, const char *); /* a variable */
void register_cb(void (*cb)(int, const char *)); /* a parameter */
سمِّ النوع مرة فتصير الاثنتان مقروءتين:
طبّق قاعدة نزع typedef مجددًا: فـ int (*IntBinaryOp)(int, int); وحدها ستعرّف متغيّرًا اسمه IntBinaryOp يشير إلى دالة تأخذ عددين صحيحين وتُرجِع عددًا صحيحًا. ومع typedef، يصبح ذلك اسم النوع. المزيد في مؤشّرات الدوال.
typedef مقابل #define
يبدوان قابلين للتبادل للأسماء البديلة البسيطة وليسا كذلك:
#define INT_PTR int *
typedef int *int_ptr;
INT_PTR a, b; /* expands to: int *a, b; -- b is a plain int! */
int_ptr c, d; /* both c and d are int * */
#define استبدال نصّي أعمى يُؤدّى قبل أن يرى المترجم الشيفرة؛ وtypedef تصريح حقيقي يفهمه المترجم، وينطبق على كل اسم في القائمة. استخدم typedef للأنواع واحفظ #define للثوابت والماكرو.
متى يضرّ typedef
يستطيع typedef أيضًا إخفاء معلومات يحتاجها القارئ. وأحدّ مثال هو إخفاء مؤشّر:
typedef struct Node *NodeRef; /* the star disappears */
NodeRef n = get_node();
بالنظر إلى NodeRef n، لا يستطيع القارئ معرفة ما إذا كانت n يمكن أن تكون NULL، أو يجب تحريرها، أو ما إذا كانت n ونسخة منها تتشاركان الكائن نفسه، أو ما إذا كان الوصول إلى الأعضاء بـ . أم ->. والأسئلة الأربعة كلها يجيب عنها struct Node *n فورًا.
والمكتبة القياسية توافق: فهي تعرّف typedef للبنية (FILE) وتترك النجمة عند موضع الاستخدام (FILE *fp). ويتبع ذلك إرشادان معقولان:
- افعل typedef للبنى والاتّحادات والتعدادات ومؤشّرات الدوال - فالضجيج المزال لا يحمل معلومة.
- لا تفعل typedef لمؤشّر لمجرّد تقصيره، ولا typedef لنوع أساسي لتمويهه (فـ
typedef int Boolean;تدعو أحدهم إلى تخزين7فيه - استخدمboolمن<stdbool.h>بدلًا منه، وهو مغطّى في القيم المنطقية).
وأسماء الأنواع ثابتة العرض في المكتبة القياسية داخل <stdint.h> (uint32_t وint64_t وsize_t) كلها typedef، وهي أفضل حجة لهذه الخاصية: فهي تقول ما هي بالضبط، وقابلة للنقل، ولا تخفي شيئًا.
الأسئلة الشائعة
ماذا يفعل typedef في لغة C؟
يمنح نوعًا موجودًا اسمًا ثانيًا. فـ typedef unsigned long ulong; تعني أن ulong وunsigned long النوع نفسه من ذلك الحين. ولا يُنشئ نوعًا جديدًا ولا تخزينًا جديدًا - بل طريقة أقصر أو أوضح لكتابة نوع تملكه سلفًا.
ما اصطلاح typedef struct في لغة C؟
typedef struct Point { int x; int y; } Point; تعرّف البنية وتسمّي النوع Point في جملة واحدة، فتستطيع كتابة Point p; بدل struct Point p;. والاحتفاظ بالوسم (struct Point) يهمّ حين تحتاج البنية إلى الإشارة إلى نفسها، كما تفعل عقدة القائمة المترابطة.
ما الفرق بين typedef و#define؟
يتولّى المترجم typedef وينشئ اسمًا بديلًا حقيقيًا للنوع؛ أما #define فاستبدال نصّي يؤدّيه المعالج المسبق قبل الترجمة. ويعضّ ذلك الفرق مع المؤشّرات: فـ #define PTR int* تجعل PTR a, b; تتمدّد إلى int* a, b; - فـ a وحدها مؤشّر. أما typedef int *PTR; فتجعل كليهما مؤشّرين.
هل أعرّف typedef لنوع مؤشّر في لغة C؟
عادةً لا. فـ typedef struct Node *NodeRef; تخفي أن النوع مؤشّر، فلا يستطيع القرّاء معرفة ما إذا كان المتغيّر يمكن أن يكون NULL، أو يجب تحريره، أو يحتاج ->. ويُبقي FILE * في المكتبة القياسية النجمة مرئية لهذا السبب بالضبط. عرّف typedef للبنية لا للمؤشّر إليها.