زبان برنامه نویسی سی/پیش پردازنده‌های تعریفی و شرطی

ویکی‎کتاب، کتابخانهٔ آزاد

پیش پردازنده‌ها، علاوه بر ماکروهای سراسری ، دارای دستوراتی هستند که به آنها دستورات مستقیم می گوئیم . همان طور که پیشتر نیز بدان اشاره نمودیم ، این دستورات با یک علامت شارپ # آغاز می‌شوند که درست پس از نوشتن علامت شارپ باید کد دستور مستقیم را بنویسیم که مطابق با مطالب پیشین ، دستورات مستقیم ، همواره باید در یک خط جدا نوشته شوند. یعنی هر دستور مستقیم پیش پردازنده در یک خط نوشته می‌گردد

#define

از این دستور برای تعریف یک شناسه ( به عنوان ماکرو ) استفاده می‌شود . نحوه عملکرد این دستور بدین شکل است که پیش پردازنده define# را می‌نویسید ، سپس در مقابل آن با یک فاصله یا فضای سفید دیگر یک نام می‌نویسیم ( که شناسه است ) و دوباره با یک فاصله ( یا فضای سفید دیگر ) مقدار عددی که قرار است جایگزین شناسه شود را می‌نویسیم . دقت کنید که این عمل ، مقدار دهی نیست ، بلکه جایگزینی است . یعنی زمانی که ما می‌نویسیم :

#define PI 3.1415

در این مثال پس از این تعریف هر جای برنامه که از PI استفاده شده باشد کامپایلر ، آن را با عدد 3.1415 جایگزین خواهد نمود به این گونه ماکروها که یک عدد جایگزین شناسه می‌شود ، ماکروی شیئ-گونه گفته می‌شود

مثال بعدی :

#define ADD(x, y) (x+y)

در مثال فوق یک ماکروی تابع-گونه ( همانند یک تابع که هنوز با تابع آشنا نشده‌اید اما تابع یک شناسه و یا داده است که ممکن است یک یا چند شناسه و داده دیگر را عنوان پارامتر بگیرد و روی آنها پردازش انجام دهد و نتیجه را به عنوان خروجی عرضه کند ، به شناسه‌ها یا داده‌هایی که بعداً با فراخوانی تابع به جای پارامترهای آن به تابع نسبت داده می‌شود نیز آرگومان می‌گوئیم ) به نام ADD تعریف گردیده که که دو پارامتر x و y را می‌گیرد و آنها را با هم جمع می‌نماید و نتیجه آن در شناسه ADD ذخیره می‌شود . اگر این تعریف را در برنامه بنویسید ؛ کامپایلر هرجای برنامه پس از این تعریف که ADD را بخواند که در مقابل آن دو عدد به آن ارجاع داده شده باشد ( که ممکن است دو متغیر عددی که داخل آنها عدد ذخیره شده باشد نیز باشند ) آنها را با هم جمع می‌کند و داخل ADD ذخیره می‌کند

باید بدانید که تعریف ماکروها ، ارزیابی می‌شوند و مقداری که قرار است جایگزین شناسه شود ، بعد از ارزیابی تمام پیش‌پردازنده‌ها و به دست آمدن مقدار نهایی ارزیابی شده ، جایگزین شناسه می‌شود . در مورد تابع‌ها نیز اگر تعریف تابع ، وابسته به تعریف تابعی پیش از خود باشد یا وابسته به تعریف یک شناسه شیئ-مانند باشد ؛ ابتدا مقادیر ارزیابی و جایگزین تعریف تابع ما می‌شوند و در آخر ، مقداری که قرار است جایگزین شود به دست آمده و جایگزین شناسه تابع-مانند می‌شود . مثال :

#define HUN 100
#define RAD HUN

در اینجا HUN مخفف Hundred با مقدار 100 تعریف می‌شود و سپس RAD مخفف Radius با HUN تعریف می‌شود که کامپایلر آن را همانند RAD 100 ارزیابی می‌کند و هر جا RAD را ببیند آن را با 100 جایگزین می‌کند

مثال :

#define FOURBYTES 32
#define foo(X) X+FOURBYTES

ماکروی تابع-مانند foo هر مقداری را که بگیرد با 32 جمع می‌کند و نتبجه را در شناسه foo ذخیره می‌کند . شما همچنین می‌توانید داده‌های داخل برنامه خود را به عنوان پارامتر برای ماکروی تابع-مانند تعریف کنید ( یعنی به جای اینکه پارامترهایی که برای ماکروی تابع مانند تعریف می‌کنید از داده‌های داخل برنامه خود استفاده کنید ) . ضمن اینکه در کامپایلر GCC یک قابلیت افزونه وجود دارد که با استفاده از آن شما می‌توانید تعریف ماکروی تابع-مانند را با کاراکتر بک‌اسلش «\» ادامه دهید و کدهای ماکروی تابع مانند را در خط بعدی بنویسید که کامپایلر خط بعدی را در ادامه خط قبلی خواهد دید ( فقط دقت کنید که این کار باعث می‌شود هر دو یا سه یا چند خط با هم یکی شمرده شوند و زمانی که دیباگر به شما اخطار می‌دهد و محل خطا را با شماره خط متن برنامه شما می‌نویسد ، خطوطی که با بک‌اسلش پیوند خورده باشند را یکی حساب می‌کند و متفاوت با شماره خطی است که شما در متن برنامه خود در ویرایشگر متن می‌بینید )

مثال :


#include<string.h>
#define endof( string ) \
(string + strlen( string ))

در مثال بالا ماکروی تابع-مانند endof را با پارامتر string تعریف کردیم تا هرجا رشته‌ای را گرفت ، رشته را با تعداد کاراکترهای رشته جمع کند و آن را عرضه کند و این قطعه کد را در دو خط نوشتیم که به کمک بک‌اسلش \ یک خط حساب می‌شوند ( تعداد کاراکترهای رشته را با کمک تابع کتابخانه‌ای strlen که در فایل سرآیند string.h تعریف شده است به دست می‌آوریم )

قابلیت‌های define که در ذیل آمده همگی افزونه‌های GCC هستند

رشته کردن :

اگر در تعریف یک ماکروی تابع-مانند ، پارامتری را تعریف کنید و به عنوان پردازش روی پارامتر ، یک شارپ را بنویسید و سپس نام خود پارامتر را بنویسید ، تعریف شما بدین گونه خواهد بود که تابع هر متنی را بگیرد آن را به عنوان یک رشته به عنوان خروجی عرضه خواهد کرد . مثال :

#define string( parm ) # parm

string( abc )

در مثال بالا تابع string یک پارامتر با نام parm دارد که به صورت parm# بر روی آن پرازدش صورت می‌گیرد ؛ یعنی هر متنی که به string داده شود آن را به صورت رشته عرضه خواهد کرد که به این نوع تعریف رشته کردن می‌گوئیم . در مثال بالا تابع string را احضار نمودیم و abc را به آن نسبت دادیم که به صورت "abc" بازگردانده خواهد شد . دقت کنید که در عمل رشته کردن کاراکتر دابل کوت «"» در صورت وجود در متن نسبت داده شده ، به صورت "\ بازگردانده می‌شود و \ به صورت \\ بازگردانده خواهد شد

الحاق کردن :

اگر در تعریف ماکروی تابع-مانند ، در پردازشی که بر روی پارامترها یا با استفاده از آنها انجام می‌دهیم ، نام دو شناسه یا داده را بنویسیم که بین آنها دو علامت شارپ ( پشت سر هم ) قرار داشته باشد ، هر دوی آنچه که پیش و بعد از دو علامت شارپ # قرار دارند با هم یکی می‌شوند . مثال :

#define glue( x, y ) x ## y
glue( 12, 34 )

در مثال بالا x و y پشت سر هم قرار خواهند گرفت و با هم یکی خواهند شد ؛ بنابراین 12 و 34 با هم یکپارچه می‌شوند و 1234 به صورت خروجی برای glue که احضار شده در خواهد آمد

لیست متغیر پارامترها :

گاهی نمی‌دانید که چند آرگومان به تابع شما ارجاع داده خواهد شد ؛ بنابراین نمی‌دانید که باید چند پارامتر تعریف کنید . البته در ماکروهای تابع-مانند این مسئله فقط شامل کاراکترهای ارجاع داده شده به ماکروی تابع-مانند شما می‌شود ( برخلاف داخل متن برنامه که می‌توانید تابع‌هایی تعریف کنید که پارامترهایی دارند که تعدادشان معین نیست و می‌توانند تعداد نامعلومی آرگومان دریافت کنند و بر روی آنها پردازش انجام دهند که در مورد ماکروهای تابع-مانند نمی‌توانید پردازشی بر روی کاراکترهای ارجاع شده انجام دهید یا اگر عدد باشند به صورت کاراکتری با آنها رفتار خواهد شد ) ماکروی __VA_ARGS__ فقط می‌تواند در دستور مستقیم define و برای ماکروهای تابع مانند به کار رود . اگر یک ماکروی تابع-مانند تعریف کنید که در لیست پارامترهای آن ، در انتهای لیست یک ... , قرار داشته باشد ؛ هر آنچه که به ماکروی تابع-مانند ما در هنگام احضار نسبت داده شود را می‌توانید با __VA_ARGS__ بازگردانید ( دقت کنید فقط در انتهای لیست مجاز به استفاده از آن هستید و در جای دیگر مجاز نیستید و کامپایلر از شما خطا خواهد گرفت ) . دقت کنید که تعداد آرگومان‌هایی که قرار است به ماکروی تابع-مانند شما نسبت داده شوند ، معین نیست . بنابراین با نوشتن __VA_ARGS__ در تعریف ماکروی تابع-مانند هر آنچه که به ماکروی تابع-مانند نسبت داده شود ، آن گونه که تعریف کرده باشید بازگردانده خواهند شد ( خود آرگومان‌ها ، دقیقاً همان گونه که نسبت داده شده‌اند ) . مثال :

#define shuffle(a, b, ...) b,##__VA_ARGS__##,a
shuffle(x, y, z, t, u)

در مثال بالا یک ماکروی تابع-مانند به نام shuffle تعریف نمودیم که دو پارامتر با نام‌ها a و b برای آن تعریف شده و یک سه نقطه ( ellipsis ) در انتهای لیست پارامترها قرار گرفته است که پردازش برای تابع بدین شکل است که ابتدا آرگومان دومی را که دریافت کند باز می‌گرداند و سپس هر آنچه که بعد از آرگومان دوم دریافت کرده باشد را بازگردانده و در نهایت آرگومان اول را باز می‌گرداند . پس در هنگام احضار shuffle که به صورت shuffle(a,b,c,d,e) تعریف شده است و فرستادن آرگومان‌های x, y, z, t, u خروجی ما بدین شکل خواهد بود :

x, y, z, t, u

#undef

این دستور پیش پردازنده برای ملغا کردن تعریف یک یا چند ماکرو که توسط دستور پیش پردازنده define# ، پیش از خود تعریف شده‌اند به کار می‌رود . یعنی هر آنچه را که توسط define# تعریف نموده باشیم ، پس از تعریف می توانیم به وسیله دستور undef# آن را لغو کرده و از کار بیاندازیم . گاهی لازم است تا در قطعه‌ای از کد خود شناسه‌ای را جایگزین کنیم که در ادامه برنامه چنین نیازی را نداریم . در این مواقع ، می‌تواینم از این دستور مستقیم استفاده نمائیم

مثال :

#define Byte 8
CODE 1 Byte
CODE 2
#undef Byte
CODE 3 Byte

در مثال فوق ، یک شناسه با نام Byte یا همان بایت که احتمالاً با آن آشنایی دارید که همان واحد سنجش حجم اطلاعات در دنیای دیجیتال بوده و به مقدار 8 بیت می باشد را تعریف نموده و سپس در کد اول استفاده نموده‌ایم . اما از آنجایی که پس از کد دوم با دستور مستقیم undef# آن را ملغا کرده‌ایم ، در کد سوم Byte با 8 جایگزین نخواهد گردید چرا که Byte توسط undef ملغا شده است و انگار که تعریف نشده باشد و در نتیجه استفاده از آن مجاز نخواهد بود از این روی کامپایلر خطا می گیرد و اگر قصد استفاده از آن را دارید باید Byte را ( دوباره ) تعریف کنید ( یا به شکل یک ماکرو یا به عنوان نام متغیرها و داده‌ها ؛ تا مجاز به استفاده از آن باشید )

#ifdef

این دستور به معنی « اگر تعریف شده است » if defined می‌باشد ، شما دستور را با یک شناسه می‌نویسید ، اگر شناسه تعریف شده باشد ( توسط دستور define ) آنگاه خطوط پس از دستور ( ifdef ) به اجرا در خواهند آمد و اگر تعریف نشده باشد ، کامپایلر کد های شما را تا جایی که شرط را بشکنید ، نادیده می گیرد ( شکستن آن با دستور endif# امکان پذیر است )

#ifndef

این دستور نیز همانند دستور پیشین شرطی می‌باشد که به وسیله آن کامپایلر بررسی می نماید تا مطمئن شود که شناسه پس از این دستور تعریف نشده باشد . معنی آن « اگر تعریف نشده است » ( if not defined ) می‌باشد و در صورت عدم تعریف شناسه نسبت داده شده به آن کدهای بعد از خود را به اجرا می گذارد و در صورتی که تعریف شده باشد ، کدهای پس از آن نادیده گرفته می‌شوند . این شرط نیز به وسیله دستور endif به پایان می رسد

#if

این دستور به معنی « اگر » می باشد و در صورتی که شرط تعیین شده صحیح باشد ( که مقداری مغایر با 0 خواهد بود ، مثلاً 1 یا 5 ) کدهای پس از خود را به اجرا در می‌آورد و در صورت ناصحیح و خطا بودن شرط تعیین شده ( که مقداری معادل با 0 خواهد بود ) کدهای پس از آن نادیده گرفته می شود . شرط if را نیز می توان با دستور endif ، شکست . عملگر defined که افزونه کامپایلر GCC می‌باشد می‌تواند با دستور متسقیم if ترکیب شود تا تعیین کند که آیا ماکرویی تعریف شده است یا خیر که به صورت if defined# نوشته می‌شود و در مقابل defined شناسه ماکرو را می‌نویسیم تا اگر تعریف شده است مقدار 1 را بازگرداند و کدهای پس از خود را اجرا کند و در غیر این صورت مقدار 0 را باز خواهد گرداند و کدهای داخل if را نادیده می‌گیرد ( شما می‌توانید از عملگرهای دیگر نیز در دستور مستقیم if استفاده کنید )

#elif

این دستور معنی « در غیر این صورت اگر ... » را می دهد . بدین ترتیب این دستور مکمل دستور if خواهد بود و شروط شما را می تواند به صورت تو در تو پیچیده‌تر کند . مثلاً شما میخواهید به کامپایلر برنامه‌ای تفهیم کنید که اگر مقدار مورد نظر شما در شناسه ای وجود دارد ، کدهایی اجرا شوند و اگر مقدار دیگری موجود بود کدهای دیگری را اجرا کند یا اگر باز هم مقدارهای دیگری که مد نظر شما هستند در آن موجود باشند آنگاه کد های دیگری را اجرا نماید . بدین ترتیب اگر یک بار از دستور if در حوزه به خصوصی استفاده نمودیم و همچنان خواستار بررسی شرط های دیگر در همان حوزه باشیم ( مثلاً مقدار موجود در یک شناسه که 16 است را مد نظر قرار دهیم و برای محک زدن آن از شروط if و elif استفاده نمائیم ) این دستور به کار ما خواهد آمد . نحوه عملکرد آن نیز همچون if بر اساس صحیح بودن ( مقدار غیر صفر ) و یا خطا بودن ( مقدار 0 ) می‌باشد . شما می‌توانید از عملگر defined که افزونه GCC می‌باشد برای elif نیز استفاده کنید و دقت کنید که هر if و elif های مربوط به آن را ( به همراه else که در پائین نوشته‌ایم ) به کمک یک endif باید بشکنید

#else

این دستور نیز شرطی می‌باشد ولی به جای آنکه محدوده خاصی را شامل شود ، « هر چیز به غیر از این ( یا این‌ها ) » معنی میدهد . مثلاً شما شروطی را نوشته و میخواهید شرط کنید که هر چیزی به غیر از شروط شما بود ، کد یا کدهای خاصی اجرا شوند . در این صورت از دستور else به صورت تک خطی و تک دستوری استفاده می نمائید . این کد بر خلاف کدهای شرطی پیشین ، نیازی به نوشتن خود شرط ندارد ( تنها دستور else را وارد متن منبع می‌نمائید ) و در صورت کاربرد دستور if و یا if به همراه یک یا چند elif به کار می‌رود و تنها مجاز به استفاده از یک else در ازای هر if خواهید بود ( که می تواند شامل elif های مرتبط به if ما نیز بشود )

#endif

این دستور برای پایان دادن به هر شرط می باشد ، یعنی هر شرط را که بخواهید بشکنید باید یک بار از این دستور استفاده نمائید . پس در صورت عدم وجود شرط ، استفاده از این دستور بی معنیست و همچنین برای شکستن هر کدام از شروط شما به یک endif نیاز می‌باشد و پر واضح است که در صورت وجود شروط زیادتر که بخواهیم شروط را با دستوراتشان پایان دهیم به تعداد شروط باید از endif استفاده کنیم . مثلاً اگر 5 شرط مجزا داریم باید 5 بار و در انتهای هر شرط یک endif را وارد نمائیم . دقت کنید که برای استفاده از endif هر بار شرطی را که نوشتید ، در انتهای آن باید ، شرط را بشکنید و مجاز نیستید تا چند شرط را بنویسید و در انتها با چند دستور endif بخواهید به آنها خاتمه دهید . در واقع هر endif پایان دهنده آخرین شرط باقی مانده می‌باشد . به عبارت دیگر هر endif برای پایان دادن یک if و elseif یا elseif هایش می‌باشد

مثال :

#define a 10
#if a == 10
CODE 1
#elif a == 15
CODE 2
#else
CODE 3
#endif

#define TRUE 1
#ifdef TRUE
CODE 4
#endif
#ifndef TRUE
CODE 5
#endif

در مثال بالا چون a برابر با 10 می باشد ، پس CODE 1 به اجرا در می آید و از این روی کد های CODE 2 و CODE 3 نادیده گرفته می شوند . از طرفی TRUE تعریف شده است ، پس فقط CODE 4 اجرا خواهد گردید و CODE 5 توسط کامپایلر نادیده گرفته می شود چرا که شرط آن صحیح نیست و TRUE تعریف شده است و در پایان با endif شروط را پایان داده‌ایم