خوش آموز درخت تو گر بار دانش بگیرد، به زیر آوری چرخ نیلوفری را


آموزش زبان ++C : اعلامیه های پیشاپیش (Forward declarations)

آموزش زبان ++C : اعلامیه های پیشاپیش (Forward declarations)
نویسنده : امیر انصاری
به این برنامه نمونۀ به ظاهر بی گناه و معصوم، که در ادامه آمده است، و نام فایل آن add.cpp می باشد، یک نگاهی بیندازید :



#include "iostream"

int main()
{
std::cout << "The sum of 3 and 4 is: " << add(3, 4) << std::endl;
return 0;
}

int add(int x, int y)
{
return x + y;
}

شما انتظار خواهید داشت تا این برنامه نتایج زیر را تولید کند :

The sum of 3 and 4 is: 7

اما واقعیت اینست که این برنامه کامپایل نخواهد شد و خطای زیر را تولید خواهد کرد :

add.cpp(5) : error C3861: 'add': identifier not found

دلیل اینکه این برنامه کامپایل نخواهد شد؛ اینست که کامپایلر فایل ها را به صورت متوالی و ترتیبی می خواند. وقتی کامپایلر در تابع main به فراخوانی تابع add می رسد، هنوز نمی داند که تابع add چه می باشد، چرا که ما هنوز تابع add را معرفی نکرده ایم و در واقع تابع add بعد از تابع main نوشته شده است. همین مساله منجر به ایجاد خطای “identifier not found” (شناسه یافت نشد) می گردد.

برای اینکه بتوانیم این خطا را برطرف کنیم، باید به این واقعیت اشاره کنیم که کامپایلر هنوز نمی داند add چه می باشد. دو روش عمومی برای حل این مشکل وجود دارد.

راه حل اول : تابع add را به قبل از تابع main منتقل کنیم :

#include "iostream"

int add(int x, int y)
{
return x + y;
}

int main()
{
std::cout << "The sum of 3 and 4 is: " << add(3, 4) << std::endl;

std::cin.get();
return 0;
}

به این ترتیب، زمانی که تابع main تابع add را فراخوانی می کند، کامپایلر دیگر می داند که add چیست. از آنجا که این برنامه، یک برنامه کوچک و ساده بود، این جابجایی کار نسبتاً ساده ای بود. با این حال، در یک برنامه بزرگ، پیدا کردن اینکه کدام تابع، توابع دیگری را فراخوانی می کنند و برقرار کردن نظم و ترتیب بین آنها کار بسیار مشکل و خسته کننده ای خواهد بود.

علاوه بر این، این گزینه همیشه امکان پذیر نیست. فرض کنیم برنامه ای نوشته ایم که دارای دو تابع با اسامی A و B می باشد. اگر تابع A تابع B را فراخوانی کند، و همچنین تابع B نیز تابع A را فراخوانی کند، هیچ روشی وجود نخواهد داشت تا این دو تابع را به صورت مرتب شده پشت سر هم بیاوریم. اگر تابع A را در ابتدا بیاورید، کامپایلر شکایت می کند که نمیداند B چیست. و اگر تابع B را در ابتدا بیاورید، کامپایلر این بار شکایت خواهد کرد که نمی داند A چیست.

راه حل دوم : از روش اعلامیه های پیشاپیش (Forward declarations) استفاده کنید.

نمونه اولیه تابع (Function prototypes) و اعلامیه های پیشاپیش توابع (forward declaration)


یک اعلامیه پیشاپیش (forward declaration) به ما این امکان را می دهد تا وجود یک شناسه را به کامپایلر اطلاع بدهیم، قبل از اینکه هنوز آن شناسه را به صورت واقعی معرفی کرده باشیم.

در مورد توابع، اعلامیه پیشاپیش (forward declaration) به ما امکان می دهد تا در مورد وجود یک تابع به کامپایلر اطلاع رسانی کنیم، در حالی که هنوز بدنه تابع را ایجاد نکرده ایم. در این صورت، هنگامی که کامپایلر با فراخوانی یک تابع مواجه می شود، متوجه خواهد شد که مشغول فراخوانی یک تابع هستیم، و بررسی می کند تا مطمئن شود که تابع مربوطه را به درستی فراخوانی کرده ایم، یا خیر، صرف وجود این اعلامیه پیشاپیش (forward declaration) برای بررسی های اولیه کامپایلر کفایت می کند، حتی اگر هنوز نداند که تابع در کجای برنامه معرفی شده باشد.

برای نوشتن یک اعلامیه پیشاپیش (forward declaration)، ما از یک نوع بیانیه اعلامیه (declaration statement) که نمونه اولیه تابع (function prototype) نامیده می شود، استفاده خواهیم کرد. نمونه اولیه تابع (function prototype) شامل نوع داده بازگشتی تابع، نام تابع و پارامترهای تابع می باشد، اما نیازی به بدنه تابع (قسمتی که بین آکولادهای باز و بسته قرار دارد) نخواهد داشت. از آنجایی که نمونه اولیه تابع (function prototype) یک بیانیه می باشد، در انتهای آن باید علامت سمی کالن (;) قرار داده شود.

در اینجا نمونه اولیه تابع (function prototype)، مربوط به تابع add را می بینید :

int add(int x, int y); // function prototype includes return type, name, parameters, and semicolon.  No function body!

در ادامه مثال اول درس را که کامپایل نمی شد با استفاده از نمونه اولیه تابع (function prototype) به عنوان یک اعلامیه پیشاپیش (forward declaration) اصلاح کرده ایم :

#include "iostream"

int add(int x, int y); // forward declaration of add() (using a function prototype)

int main()
{
std::cout << "The sum of 3 and 4 is: " << add(3, 4) << std::endl; // this works because we forward declared add() above
return 0;
}

int add(int x, int y) // even though the body of add() isn't defined until here
{
return x + y;
}

حالا، وقتی که کامپایلر در تابع main به فراخوانی add می رسد، دقیقاً می داند که add چیست، و به درستی می داند که add یک تابع است که نوع خروجی آن integer می باشد و دو پارامتر ورودی از نوع integer نیز دریافت می کند، بنابراین کامپایلر به مشکل بر نخواهد خورد و کارش را به درستی انجام خواهد داد.

شایان ذکر است، که در نمونه اولیه تابع (function prototype) الزامی به مشخص کردن نام پارامترها ندارید، و صرف اینکه نوع پارامترها معرفی شوند، کفایت خواهد کرد. در کد بالا شما می توانید نمونه اولیه تابع (function prototype) خود را به شکل زیر نیز بنویسید :

int add(int, int);

با این حال، ما ترجیح می دهیم تا نام پارامترها را نیز درنمونه اولیه تابع (function prototype) بیاوریم، زیرا با یک نگاه به نمونه اولیه تابع، می توانیم درکی از تابع مربوطه پیدا کنیم و نیاز نخواهیم داشت تا برای فهمیدن کلیت آن به بدنه اصلی تابع مراجعه نماییم.

نکته : شما می توانید به سادگی با کپی کردن معرفی تابع، نمونه اولیه آن را بسازید. فقط فراموش نکنید که سمی کالن را در انتهای نمونه اولیه قرار بدهید.

فراموش کردن بدنه تابع


یک سوال که بسیاری از برنامه نویسان جدید می پرسند، اینست که : اگر ما یک تابع را به صورت اعلامیه پیشاپیش (forward declaration) معرفی کنیم اما بدنه آن را فراموش کنیم تا ایجاد کنیم، چه اتفاقی خواهد افتاد؟

پاسخ اینست که : بستگی دارد. اگر اعلامیه پیشاپیش (forward declaration) ایجاد شده باشد، اما آن تابع در طول برنامه، هرگز فراخوانی نشده باشد، برنامه به درستی کامپایل شده و همینطور اجرا می شود. با این حال، اگر اعلامیه پیشاپیش (forward declaration) ایجاد شده باشد، اما بدنه ایجاد نشده باشد و در عین حال آن تابع نیز دارای فراخوانی باشد، برنامه به درستی کامپایل خواهد شد، اما لینکر (linker) خطا خواهد داد که نمیتواند تابع مربوطه را پیدا کند.

برنامه زیر را در نظر بگیرید :

#include "iostream"

int add(int x, int y); // forward declaration of add() using function prototype

int main()
{
std::cout << "The sum of 3 and 4 is: " << add(3, 4) << std::endl;

std::cin.get();
return 0;
}

در این برنامه، ما تابع add را به صورت اعلامیه پیشاپیش (forward declaration) معرفی کرده ایم، همچنین تابع add را در برنامه فراخوانی کرده ایم، اما در هیچ کجای برنامه بدنه این تابع را معرفی نکرده ایم. وقتی سعی کنیم تا این برنامه را کامپایل و اجرا کنیم، با خطای زیر مواجه خواهیم شد.

Error	1	error LNK2001: unresolved external symbol "int __cdecl add(int,int)" (?add@@YAHHH@Z)
Error 2 error LNK1120: 1 unresolved externals

این خطایی که مشاهده می کنید مربوط به لینکر (linker) میباشد و برنامه بدون اشکال کامپایل شده است. در واقع این برنامه با وجودی که درست کامپایل می شود در زمان لینک شدن با شکست مواجه می شود، چرا که بدنه تابع add هرگز تعریف نشده است و لینکر (linker) آن را نخواهد یافت.

سایر انواع اعلامیه های پیشاپیش (Forward declarations)


اعلامیه های پیشاپیش (Forward declarations) اغلب با توابع استفاده می شوند. با این حال می توانیم در مورد سایر انواع شناسه هادر ++C نیز از اعلامیه های پیشاپیش (Forward declarations) استفاده کنیم، مانند متغیرها و انواع معرفی شده توسط کاربر (user-defined types). در مورد سایر انواع شناسه ها (identifiers)، نحوه نگارش اعلامیه پیشاپیش (forward declaration) متفاوت می باشد.

در مورد استفاده از اعلامیه های پیشاپیش (Forward declarations) در مورد سایر انواع شناسه ها (identifiers) در درس های آینده، صحبت خواهیم کرد.

اعلامیه ها (Declarations) در مقایسه با تعاریف (definitions)


در زبان ++C، شما اغلب کلمات اعلامیه (declaration) و تعریف (definition) را زیاد خواهید شنید. اما منظور از آنها چیست؟ شما در حال حاضر چارچوب فکری لازم برای درک بین این دو را دارید.

یک تعریف (definition) در واقع پیاده سازی یا نمونه گیری از یک شناسه می باشد و منجر می شود تا حافظه ای به آن شناسه تخصیص داده شود. در ادامه چند مثال از تعاریف (definitions) آورده ایم :

int add(int x, int y) // defines function add()
{
return x + y;
}

int x; // instantiates (causes memory to be allocated for) an integer variable named x

شما برای هر شناسه (identifier) فقط می توانید یک تعریف (definition) داشته باشید. یک تعریف (definition) برای راضی نگهداشتن برنامه لینکر (linker) ایجاد می شود.

یک اعلامیه (declaration) یک بیانیه است که در مورد یک شناسه (identifier) اطلاع رسانی می کند. در ادامه چند مثال از اعلامیه ها (declarations) آمده است :

int add(int x, int y); // declares a function named "add" that takes two int parameters and returns an int.  No body!
int x; // declares an integer variable named x

یک اعلامیه (declaration) تمام چیزی است که کامپایلر را راضی نگه خواهد داشت. برای همین است که صرف آوردن اعلامیه های پیشاپیش (Forward declarations) منجر می شود تا کامپایلر خوشحال باشد. با این حال، اگر فراموش کنید که برای این اعلامیه ها، تعاریف (definitions) مربوطه را ایجاد کنید، برنامه لینکر (linker) از شما شاکی خواهد شد.

اگر دقت کرده باشید int x هم در اعلامیه ها (declarations) و هم در تعاریف (definitions) وجود دارد. دلیل این مساله اینست که در زبان ++C همه تعاریف (definitions) در واقع خودشان اعلامیه (declaration) نیز هستند. از آنجا که int x یک تعریف (definition) می باشد، قطعا یک اعلامیه (declaration) نیز هست.

با این حال، زیر مجموعه کوچکی از اعلامیه ها (declarations) وجود دارند که خودشان تعریف (definition) نیستند، مانند نمونه اولیه تابع (function prototype). به آنها اعلامیه های خالص (pure declarations) گفته می شود. نوع دیگری از اعلامیه های خالص (pure declarations) شامل اعلامیه پیشاپیش (forward declaration) متغیرها، کلاس ها، و انواع داده، می باشد. در این مورد در درس های آینده بیشتر صحبت خواهیم کرد و نیازی نیست تا نگران چیزی شوید. شما می توانید برای هر شناسه (identifier) به هر تعداد که نیاز باشد اعلامیه خالص (pure declaration) داشته باشید، البته فراموش نکنید که فقط یک اعلامیه کفایت می کند و بیشتر از آن زائد می باشد.


آموزش قبلی : آموزش زبان ++C : فاصله ها و قالب بندی ساده متن

آموزش بعدی : آموزش زبان ++C : برنامه هایی با چندین فایل



نمایش دیدگاه ها (0 دیدگاه)

دیدگاه خود را ثبت کنید:

انتخاب تصویر ویرایش حذف
توجه! حداکثر حجم مجاز برای تصویر 500 کیلوبایت می باشد.