تا اینجا دیدیم که متغیرهایی مثل line و longest در تابع main فقط داخل همون تابع قابلاستفاده هستن.
یعنی این متغیرها محلی (local) هستن — چون داخل بدنهی main تعریف شدن و هیچ تابع دیگهای نمیتونه مستقیماً بهشون دسترسی داشته باشه.
همین موضوع برای متغیرهای داخل بقیهی تابعها هم صدق میکنه؛ مثلاً متغیر i در تابع getline هیچ ربطی به i در تابع copy نداره.
هر متغیر محلی فقط وقتی تابع اجرا میشه «بهوجود میاد» و وقتی تابع تموم میشه «از بین میره».
به همین دلیل بهشون میگیم متغیرهای خودکار (automatic variables) — یعنی متغیرهایی که خودشون در هنگام اجرای تابع ساخته و بعد حذف میشن.
(در فصل ۴ به نوع خاصی از متغیرها به نام static هم میپردازیم که برعکس، مقدار خودشون رو بین چند بار اجرای تابع حفظ میکنن.)
رفتار متغیرهای خودکار
چون متغیرهای خودکار با هر بار فراخوانی تابع ساخته و حذف میشن، مقدارشون بین دفعات مختلف حفظ نمیمونه.
یعنی اگر داخل تابع مقداردهی نشن، شامل دادههای تصادفی (garbage value) خواهند بود.
پس همیشه باید قبل از استفاده، مقداردهی اولیه بشن.
تعریف متغیرهای خارجی (External Variables)
حالا فرض کن بخوایم دادهای رو بین چند تابع مختلف به اشتراک بذاریم.
بهجای اینکه اون داده رو هر بار بهصورت آرگومان بین تابعها رد و بدل کنیم، میتونیم از متغیر خارجی استفاده کنیم — یعنی متغیری که خارج از همهی تابعها تعریف شده باشه.
در این حالت، تمام تابعها میتونن با نام اون متغیر بهش دسترسی پیدا کنن.
(مشابه چیزی که در زبانهای Fortran و Pascal با متغیرهای عمومی یا “Common” انجام میشد.)
چون متغیرهای خارجی در طول اجرای برنامه «همیشه وجود دارن» و با پایان تابع از بین نمیرن، مقدارشون هم بعد از برگشت از تابع حفظ میشه.
بنابراین میتونیم ازشون برای ارتباط بین تابعها استفاده کنیم.
تعریف و اعلان (Definition vs Declaration)
یه متغیر خارجی باید دقیقاً یکبار تعریف بشه (معمولاً در بالای فایل سورس، خارج از تابعها).
این کار باعث میشه حافظه برای اون متغیر رزرو بشه.
سپس هر تابعی که میخواد از اون متغیر استفاده کنه، باید اون رو اعلان (declare) کنه تا نوعش شناخته بشه.
اعلان معمولاً با کلیدواژهی extern انجام میشه.
بازنویسی برنامهی طولانیترین خط با متغیرهای خارجی
بیایید برنامهی «پیدا کردن طولانیترین خط» رو بازنویسی کنیم تا بهجای ارسال آرگومانها، از متغیرهای خارجی استفاده کنه.
#include <stdio.h>
#define MAXLINE 1000 /* حداکثر طول مجاز خط ورودی */
int max; /* بیشترین طولی که تا حالا دیده شده */
char line[MAXLINE]; /* خط فعلی ورودی */
char longest[MAXLINE]; /* طولانیترین خط ذخیره شده */
int getline(void);
void copy(void);
/* چاپ طولانیترین خط ورودی - نسخهای با متغیرهای خارجی */
main()
{
int len;
extern int max;
extern char longest[];
max = 0;
while ((len = getline()) > 0)
if (len > max) {
max = len;
copy();
}
if (max > 0)
printf("%s", longest);
return 0;
}
تابع getline
/* getline: نسخهی مخصوص با استفاده از متغیر خارجی */
int getline(void)
{
int c, i;
extern char line[];
for (i = 0; i < MAXLINE - 1 && (c=getchar()) != EOF && c != '\n'; ++i)
line[i] = c;
if (c == '\n') {
line[i] = c;
++i;
}
line[i] = '\0';
return i;
}
تابع copy
/* copy: نسخهی مخصوص با استفاده از متغیر خارجی */
void copy(void)
{
int i;
extern char line[], longest[];
i = 0;
while ((longest[i] = line[i]) != '\0')
++i;
}
تحلیل برنامه
در ابتدای برنامه، متغیرهای max, line و longest بهصورت خارجی (global) تعریف شدن.
بهخاطر همین، تابعهای main, getline و copy همگی میتونن از اونها استفاده کنن.
اما برای اینکه هر تابع بدونه نوع اون متغیرها چیه، باید درونشون اعلان (extern) انجام بشه.
در واقع، از نظر نحوی تعریف متغیر خارجی شبیه تعریف محلیه، فقط بیرون از تابع نوشته میشه.
نکته دربارهی extern
در بعضی شرایط میشه از extern صرفنظر کرد.
اگر متغیر خارجی قبل از استفادهی تابع در فایل تعریف شده باشه، کامپایلر خودش اون رو میشناسه.
در نتیجه اعلان extern درون تابعها الزامی نیست.
به همین دلیل معمولاً همهی متغیرهای خارجی در ابتدای فایل نوشته میشن و بقیهی تابعها دیگه نیازی به extern ندارن.
برنامههای چندفایلی و فایلهای هدر (Header Files)
وقتی برنامهای از چند فایل سورس تشکیل شده باشه، ممکنه متغیری در file1.c تعریف شده باشه ولی در file2.c یا file3.c استفاده بشه.
در این حالت باید در اون فایلها اعلان extern انجام بشه تا کامپایلر بدونه متغیر مربوطه وجود داره.
برای راحتی کار، معمولاً اعلان همهی متغیرها و توابع در یک فایل جدا بهنام header (با پسوند .h) نوشته میشه و در ابتدای هر فایل سورس با دستور #include اضافه میشه.
مثلاً توابع کتابخونهای استاندارد در فایلهایی مثل <stdio.h> تعریف شدن.
نکته دربارهی void در اعلان تابعها
در نسخهی جدید زبان C، اگه بخوایم بگیم تابعی هیچ آرگومانی نمیگیره، باید از void استفاده کنیم.
در نسخههای قدیمی C، نوشتن int getline() بدون پارامتر یعنی «اعلان قدیمی» و بررسی آرگومانها غیرفعال میشد.
بنابراین بهتره همیشه برای لیست خالی از void استفاده کنیم تا کد استاندارد باقی بمونه.
تفاوت Definition و Declaration
در زبان C، باید بین این دو واژه تفاوت قائل شد:
-
Definition (تعریف): جایی که حافظهی متغیر واقعاً ایجاد میشه.
-
Declaration (اعلان): جایی که فقط نوع و نام متغیر معرفی میشه، بدون تخصیص حافظه.
هشدار: زیادهروی در استفاده از متغیرهای خارجی!
ممکنه وسوسه بشیم که برای راحتی کار، همهچیز رو extern کنیم تا بین تابعها مشترک باشن.
اما این کار معمولاً خطرناک و اشتباهه
چرا؟
چون باعث میشه ارتباط دادهها بین قسمتهای مختلف برنامه نامشخص بشه.
توابع ممکنه ناخواسته مقادیر رو تغییر بدن، و در نتیجه اشکالیابی (debugging) سختتر میشه.
بهتره فقط وقتی واقعاً نیاز داریم از متغیر خارجی استفاده کنیم.
در غیر این صورت، رد کردن دادهها از طریق آرگومانهای تابع، روشی امنتر و شفافتره.
جمعبندی
در این بخش یاد گرفتیم:
-
متغیرهای محلی فقط در زمان اجرای تابع وجود دارن.
-
متغیرهای خارجی در تمام طول اجرای برنامه فعال میمونن.
-
از extern برای معرفی متغیرهای خارجی به تابعها استفاده میکنیم.
-
بهتره از متغیرهای خارجی فقط در مواقع ضروری استفاده کنیم تا کد تمیز و قابلدرک باقی بمونه.
-
تفاوت مهمی بین تعریف و اعلان وجود داره.
-
و در نهایت، یاد گرفتیم چطور میشه برنامهی طولانیترین خط رو با استفاده از متغیرهای خارجی بازنویسی کرد.
تمرینها
تمرین ۱–۲۰:
برنامهای بنویس که کاراکتر tab (\t) را با تعداد مناسب فاصله (space) جایگزین کند تا به نزدیکترین محل توقف تب برسد.
(به این برنامه detab میگوییم.)
آیا مقدار فاصلهها باید ثابت باشد یا قابلتغییر؟
تمرین ۱–۲۱:
برنامهای بنویس که برعکس مورد قبل، رشتهای از فاصلهها را با حداقل تعداد تب و فاصله جایگزین کند (entab).
در صورت برابر بودن، تب را ترجیح بده یا فاصله را؟
تمرین ۱–۲۲:
برنامهای بنویس که خطوط خیلی بلند را به چند خط کوتاهتر تقسیم کند، درست قبل از ستون nام.
اگر هیچ فاصله یا تب قبل از آن نباشد، باید رفتار منطقی داشته باشد.
تمرین ۱–۲۳:
برنامهای بنویس که تمام توضیحات (comments) را از یک برنامهی C حذف کند، بدون اینکه رشتههای داخل کوتیشن را خراب کند.
تمرین ۱–۲۴:
برنامهای بنویس که کد C را از نظر خطاهای سینتکسی ساده (مثل پرانتز یا آکولاد باز و بسته نشده) بررسی کند.
توجه کن که باید رشتهها، کوتیشنها و escape sequenceها را درست در نظر بگیری.
آفرین
با یادگیری این بخش، حالا تقریباً بخش اصلی و «هستهی کلاسیک زبان C» رو پشت سر گذاشتی.
با همین ابزارها، میتونی برنامههای نسبتاً بزرگ و واقعی بنویسی.
در فصلهای بعدی با ویژگیهای پیشرفتهتر و سازمانیافتهتر زبان C آشنا میشیم.