6- كامپايل جداگانۀ توابع

 توابع کتابخانۀ C++ استاندارد به همين شکل پياده‌سازي شده‌اند و هنگامي که يکي از آن توابع را در برنامه‌هايتان به کار مي‌بريد بايد با دستور راهنماي پيش‌پردازنده، فايل آن توابع را به برنامه‌تان ضميمه کنيد.

اين کار چند مزيت دارد:

1- اولين مزيت «مخفي‌سازي اطلاعات» است.

2-مزيت ديگر اين است که توابع مورد نياز را مي‌توان قبل از اين که برنامۀ اصلي نوشته شود، جداگانه آزمايش نمود.

3-سومين مزيت اين است که در هر زماني به راحتي مي‌توان تعريف توابع را عوض کرد بدون اين که لازم باشد برنامۀ اصلي تغيير يابد.

4-چهارمين مزيت هم اين است که مي‌توانيد يک بار يک تابع را کامپايل و ذخيره کنيد و از آن پس در برنامه‌هاي مختلفي از همان تابع استفاده ببريد.

تابع max() را به خاطر بياوريد. براي اين که اين تابع را در فايل جداگانه‌اي قرار دهيم، تعريف آن را در فايلي به نام max.cpp ذخيره مي‌کنيم. فايل max.cpp شامل کد زير است:

max.cpp

 

int max(int x, int y)

{ if (x < y) return y;

 else return x;

}

حال كافي است عبارت:#include را به اول برنامه اصلي وقبل ازmain()  اضافه كنيم:

#include

int main()

{ // tests the max() function:

 int m, n;

 do

 { cin >> m >> n;

 cout << "\tmax(" << m << "," << n << ") = "

 << max(m,n) << endl;

 }

 while (m != 0);}

 

نحوۀ کامپايل کردن فايل‌ها و الصاق آن‌ها به يکديگر به نوع سيستم عامل و نوع کامپايلر بستگي دارد. در سيستم عامل ويندوز معمولا توابع را در فايل‌هايي از نوع DLL کامپايل و ذخيره مي‌کنند و سپس اين فايل را در برنامۀ اصلي احضار مي‌نمايند. فايل‌هاي DLL را به دو طريق ايستا و پويا مي‌توان مورد استفاده قرار داد. براي آشنايي بيشتر با فايل‌هاي DLL به مرجع ويندوز و کامپايلرهاي C++ مراجعه کنيد.

 

6- متغيرهاي محلي، توابع محلي

متغير محلي، متغيري است که در داخل يک بلوک اعلان گردد. اين گونه متغيرها فقط در داخل همان بلوکي که اعلان مي‌شوند قابل دستيابي هستند.

چون بدنۀ تابع، خودش يک بلوک است پس متغيرهاي اعلان شده در يک تابع متغيرهاي محلي براي آن تابع هستند.

اين متغيرها فقط تا وقتي که تابع در حال کار است وجود دارند.

 پارامترهاي تابع نيز متغيرهاي محلي محسوب مي‌شوند.

 

* مثال 7-5 تابع فاكتوريل

اعداد فاكتوريل را در مثال 8-5 ديديم. فاكتوريل عدد صحيح n برابر است با:

n! = n(n-1)(n-2)..(3)(2)(1)

تابع زير، فاکتوريل عدد n را محاسبه مي‌کند‌:

long fact(int n)

{ //returns n! = n*(n-1)*(n-2)*...*(2)*(1)

 if (n < 0) return 0;

 int f = 1;

 while (n > 1)

 f *= n--;

 return f;

}

اين تابع دو متغير محلي دارد: n و f پارامتر n يک متغير محلي است زيرا در فهرست پارامترهاي تابع اعلان شده و متغير f نيز محلي است زيرا درون بدنۀ تابع اعلان شده است.

 

همان گونه که متغيرها مي‌توانند محلي باشند، توابع نيز مي‌توانند محلي باشند.

 تابع محلي 

يک تابع محلي تابعي است که درون يک تابع ديگر به کار رود. با استفاده از چند تابع ساده و ترکيب آن‌ها مي‌توان توابع پيچيده‌تري ساخت. به مثال زير نگاه کنيد.  

در رياضيات، تابع جايگشت را با p(n,k) نشان مي‌دهند. اين تابع بيان مي‌کند که به چند طريق مي‌توان k عنصر دلخواه از يک مجموعۀ n عنصري را کنار يکديگر قرار داد. براي اين محاسبه از رابطۀ زير استفاده مي‌شود:

 

Image and video hosting by TinyPic

 

اين تابع، خود از تابع ديگري که همان تابع فاکتوريل است استفاده کرده است.

 شرط به کار رفته در دستور if براي محدود کردن حالت‌هاي غير ممکن استفاده شده است. در اين حالت‌ها، تابع مقدار 0 را برمي‌گرداند تا نشان دهد که يک ورودي اشتباه وجود داشته است.

پس به 12 طريق مي‌توانيم دو عنصر دلخواه از يک مجموعۀ چهار عنصري را کنار هم بچينيم. براي دو عنصر از مجموعۀ {1, 2, 3, 4} حالت‌هاي ممکن عبارت است از:

12, 13, 14, 21, 23, 24, 31, 32, 34, 41, 42, 43

كد زير تابع جايگشت را پياده‌سازي‌ مي‌كند:

 

long perm(int n, int k)

{// returns P(n,k), the number of the permutations of k from n:

 if (n < 0) || k < 0 || k > n) return 0;

 return fact(n)/fact(n-k);

}

برنامۀ آزمون براي تابع perm() در ادامه آمده است:

long perm(int,int);

// returns P(n,k), the number of permutations of k from n:

int main()

{ // tests the perm() function:

 for (int i = -1; i < 8; i++)

 { for (int j= -1; j <= i+1; j++)

 cout << " " << perm(i,j);

 cout << endl; }

}

خروجی :

0 0

0 1 0

0 1 1 0

0 1 2 2 0

0 1 3 6 6 0

0 1 4 12 24 24 0

0 1 5 20 60 120 120 0

0 1 6 30 120 360 720 720 0

0 1 7 42 210 840 2520 5040 5040 0

7- تابع void

لازم نيست يك‌ تابع‌ حتما مقداري را برگرداند. در C++ براي مشخص کردن چنين توابعي از کلمۀ کليدي void به عنوان نوع بازگشتي تابع استفاده مي‌کنند

يک تابع void تابعي است که هيچ مقدار بازگشتي ندارد.

از آن‌جا كه يك تابع void مقداري را برنمي‌گرداند، نيازي به دستور return نيست ولي اگر قرار باشد اين دستور را در تابع void قرار دهيم، بايد آن را به شکل تنها استفاده کنيم بدون اين که بعد از کلمۀ return هيچ چيز ديگري بيايد:

return;

در اين حالت دستور return فقط تابع را خاتمه مي‌دهد.

 

 8- توابع بولي

در بسياري از اوقات لازم است در برنامه، شرطي بررسي شود.

اگر بررسي اين شرط به دستورات زيادي نياز داشته باشد، بهتر است که يک تابع اين بررسي را انجام دهد. اين کار مخصوصا هنگامي که از حلقه‌ها استفاده مي‌شود بسيار مفيد است.

توابع بولي فقط دو مقدار را برمي‌گردانند: true يا false . 

 

اسم توابع بولي را معمولا به شکل سوالي انتخاب مي‌کنند زيرا توابع بولي هميشه به يک سوال مفروض پاسخ بلي يا خير مي‌دهند.

مثال 10-5 تابعي‌ كه‌ اول بودن اعداد را بررسي مي‌كند

کد زير يك تابع بولي است كه تشخيص مي‌دهد آيا عدد صحيح ارسال شده به آن، اول است يا خير:

bool isPrime(int n)

{ // returns true if n is prime, false otherwise:

 float sqrtn = sqrt(n);

 if (n < 2) return false; // 0 and 1 are not primes

 if (n < 4) return true; // 2 and 3 are the first primes

 if (n%2 == 0) return false; // 2 is the only even prime

 for (int d=3; d <= sqrtn; d += 2)

 if (n%d == 0) return false; // n has a nontrivial divisor

 return true; // n has no nontrivial divisors

}

9- توابع ورودي/خروجي (I/O)

بخش‌هايي از برنامه که به جزييات دست و پا گير مي‌پردازد و خيلي به هدف اصلي برنامه مربوط نيست را مي‌توان به توابع سپرد. در چنين شرايطي سودمندي توابع محسوس‌تر مي‌شود.

فرض کنيد نرم‌افزاري براي سيستم آموزشي دانشگاه طراحي کرده‌ايد که سوابق تحصيلي دانشجويان را نگه مي‌دارد. در اين نرم‌افزار لازم است که سن دانشجو به عنوان يکي از اطلاعات پروندۀ دانشجو وارد شود. اگر وظيفۀ دريافت سن را به عهدۀ يک تابع بگذاريد، مي‌توانيد جزيياتي از قبيل کنترل ورودي معتبر، يافتن سن از روي تاريخ تولد و ... را در اين تابع پياده‌سازي کنيد بدون اين که از مسير برنامۀ اصلي منحرف شويد.

 

قبلا نمونه‌اي از توابع خروجي را ديديم. تابع PrintDate() در مثال 9-5 هيچ چيزي به برنامۀ اصلي برنمي‌گرداند و فقط براي چاپ نتايج به کار مي‌رود.

 اين تابع نمونه‌اي از توابع خروجي است؛ يعني توابعي که فقط براي چاپ نتايج به کار مي‌روند و هيچ مقدار بازگشتي ندارند.

 توابع ورودي نيز به همين روش کار مي‌کنند اما در جهت معکوس. يعني توابع ورودي فقط براي دريافت ورودي و ارسال آن به برنامۀ اصلي به کار مي‌روند و هيچ پارامتري ندارند.

 

مثال بعد يک تابع ورودي را نشان مي‌دهد.

مثال 11-5 تابعي براي دريافت سن كاربر

تابع سادۀ زير، سن کاربر را درخواست مي‌کند و مقدار دريافت شده را به برنامۀ اصلي مي‌فرستد. اين تابع تقريبا هوشمند است و هر عدد صحيح ورودي غير منطقي را رد مي‌کند و به طور مکرر درخواست ورودي معتبر مي‌کند تا اين که يک عدد صحيح در محدودۀ 7 تا 120 دريافت دارد:

int age()

{ // prompts the user to input his/her age and returns that value:

 int n;

 while (true)

 { cout << "How old are you: ";

 cin >> n;

 if (n < 0) cout << "\a\tYour age could not

 be negative.";

 else if (n > 120) cout << "\a\tYou could not

 be over 120.";

 else return n;

 cout << "\n\tTry again.\n";

 }

}

يك برنامۀ آزمون و خروجي حاصل از آن در ادامه آمده است:

int age()

int main()

{ // tests the age() function:

 int a = age();

 cout << "\nYou are " << a << " years old.\n";

}

 

خروجی:

How old are you? 125

 You could not be over 120

 Try again.

How old are you? -3

 Your age could not be negative

 Try again.

How old are you? 99

You are 99 years old.

 

تا اين‌ لحظه‌ تمام‌ پارامترهايي كه‌ در توابع‌ ديديم‌ به‌ طريق‌ مقدار ارسال‌ شده‌اند. يعني‌ ابتدا مقدار متغيري که در فراخواني تابع ذکر شده برآورد مي‌شود و سپس اين مقدار به پارامترهاي محلي تابع فرستاده مي‌شود.

مثلا در فراخواني cube(x) ابتدا مقدار x برآورد شده و سپس اين مقدار به متغير محلي n در تابع فرستاده مي‌شود و پس از آن تابع کار خويش را آغاز مي‌کند. در طي اجراي تابع ممکن است مقدار n تغيير کند اما چون n محلي است هيچ تغييري روي مقدار x نمي‌گذارد.

 

پس خود x به تابع نمي‌رود بلکه مقدار آن درون تابع کپي مي‌شود.

 تغيير دادن اين مقدار کپي شده درون تابع هيچ تاثيري بر x اصلي ندارد. به اين ترتيب تابع مي‌تواند مقدار x را بخواند اما نمي‌تواند مقدار x را تغيير دهد.

به همين دليل به x يک پارامتر «فقط خواندني» مي‌گويند.

 وقتي ارسال به وسيلۀ مقدار باشد، هنگام فراخواني تابع مي‌توان از عبارات استفاده کرد.

مثلا تابع cube() را مي‌توان به صورتcube(2*x-3)  فراخواني کرد يا به شکل cube(2*sqrt(x)-cube(3)) فراخواني نمود. در هر يک از اين حالات، عبارت درون پرانتز به شکل يک مقدار تکي برآورد شده و حاصل آن مقدار به تابع فرستاده مي‌شود.

 

10- ارسال به طريق ارجاع (آدرس)

ارسال به طريق مقدار باعث مي‌شود که متغيرهاي برنامۀ اصلي از تغييرات ناخواسته در توابع مصون بمانند.

 اما گاهي اوقات عمدا مي‌خواهيم اين اتفاق رخ دهد. يعني مي‌خواهيم که تابع بتواند محتويات متغير فرستاده شده به آن را دست‌کاري کند. در اين حالت از ارسال به طريق ارجاع ‌استفاده مي‌کنيم.

براي اين که مشخص کنيم يک پارامتر به طريق ارجاع ارسال مي‌شود، علامت   را به نوع پارامتر در فهرست پارامترهاي تابع اضافه مي‌کنيم. اين باعث مي‌شود که تابع به جاي اين که يک کپي محلي از آن آرگومان ايجاد کند، خود آرگومان محلي را به کار بگيرد.

 به اين ترتيب تابع هم مي‌تواند مقدار آرگومان فرستاده شده را بخواند و هم مي‌تواند مقدار آن را تغيير دهد. در اين حالت آن پارامتر يک پارامتر «خواندني-نوشتني» خواهد بود.

 

هر تغييري که روي پارامتر خواندني-نوشتني در تابع صورت گيرد به طور مستقيم روي متغير برنامۀ اصلي اعمال مي‌شود. به مثال زير نگاه کنيد.

* مثال 12-5 تابع‌ swap()

تابع‌ كوچك‌ زير در مرتب کردن داده‌ها کاربرد فراوان دارد:

void swap(float& x, float& y)

{ // exchanges the values of x and y:

 float temp = x;

 x = y;

 y = temp;

}

هدف‌ اين تابع جابجا کردن دو عنصري است که به آن فرستاده مي‌شوند. براي اين منظور پارامترهاي x و y به صورت پارامترهاي ارجاع تعريف شده‌اند:

float& x, float& y

 

عملگر ارجاع‌ & موجب‌ مي‌شود كه‌ به جاي x و y آرگومان‌هاي ارسالي قرار بگيرند. برنامۀ آزمون و اجراي آزمايشي آن در زير آمده است:

void swap(float&, float&)

// exchanges the values of x and y:

int main()

{ // tests the swap() function:

 float a = 55.5, b = 88.8;

 cout << "a = " << a << ", b = " << b << endl;

 swap(a,b);

 cout << "a = " << a << ", b = " << b << endl;

}

خروجی:

a = 55.5, b = 88.8

a = 88.8, b = 55.5

 

وقتي‌ فراخواني swap(a,b) اجرا مي‌شود، x به a اشاره مي‌کند و y به b. سپس متغير محلي temp اعلان مي‌شود و مقدار x (که همان a است) درون آن قرار مي‌گيرد. پس از آن مقدار y (که همان b است) درون x (يعني a) قرار مي‌گيرد و آنگاه مقدار temp درون y (يعني b) قرار داده مي‌شود. نتيجۀ نهايي اين است که مقادير a و b با يکديگر جابجا مي شوند. شکل مقابل نشان مي‌دهد که چطور اين جابجايي رخ مي‌دهد:

 

Image and video hosting by TinyPic

 

به‌ اعلان‌ تابع‌ swap() دقت کنيد:

void swap(float&, float&)

اين اعلان شامل عملگر ارجاع‌ & براي‌ هر پارامتر است‌.

 برنامه‌نويسان c عادت دارند که عملگر ارجاع & را به عنوان پيشوند نام متغير استفاده کنند (مثلfloat &x) در C++ فرض مي‌کنيم عملگر ارجاع & پسوند نوع است (مثل float& x)

 به هر حال کامپايلر هيچ فرقي بين اين دو اعلان نمي‌گذارد و شکل نوشتن عملگر ارجاع کاملا اختياري و سليقه‌اي است.

 

تابع f() دو پارامتر دارد که اولي به طريق مقدار و دومي به طريق ارجاع ارسال مي‌شود. فراخواني f(a,b) باعث مي‌شود که a از طريق‌ مقدار به‌ x ارسال شود و b از طريق‌ ارجاع‌ به‌ y فرستاده شود.

 

مثال‌ 13-5 ارسال‌ به‌ طريق‌ مقدار و ارسال‌ به‌ طريق‌ ارجاع‌

اين‌ برنامه، تفاوت‌ بين‌ ارسال‌ به طريق‌ مقدار و ارسال‌ به طريق‌ ارجاع‌ را نشان‌ مي‌دهد:

void f(int,int&);

int main()

{ int a = 22, b = 44;

 cout << "a = " << a << ", b = " << b << endl;

 f(a,b);

 cout << "a = " << a << ", b = " << b << endl;

 f(2*a-3,b);

 cout << "a = " << a << ", b = " << b << endl;}

void f(int x , int& y)

{ x = 88;

 y = 99;}

شکل زير نحوۀ کار تابع f() را نشان مي‌دهد.

Image and video hosting by TinyPic

 

در جدول‌ زير خلاصۀ تفاوت‌هاي بين ارسال از طريق مقدار و ارسال از طريق ارجاع آمده است.

 

ارسال از طريق ارجاع

ارسال از طريق مقدار

int& x;

int x;

پارامتر x يک ارجاع است

پارامتر x يک متغير محلي است

x مترادف با آرگومان است

x يک کپي از آرگومان است

مي‌تواند محتويات آرگومان را تغيير دهد

تغيير محتويات آرگومان ممکن نيست

آرگومان ارسال شده از طريق ارجاع فقط بايد يک متغير باشد

آرگومان ارسال شده از طريق مقدار مي‌تواند يک ثابت، يک متغير يا يک عبارت باشد

آرگومان خواندني-نوشتني است

آرگومان فقط خواندني است

 

 

يكي‌ از مواقعي‌ كه‌ پارامترهاي‌ ارجاع‌ مورد نياز هستند جايي‌ است‌ كه‌ تابع‌ بايد بيش از يك‌ مقدار را بازگرداند.

 دستور return فقط مي‌تواند يك‌ مقدار را برگرداند.

 بنابراين‌ اگر بايد بيش از يك‌ مقدار برگشت داده‌ شود، اين‌ كار را پارامترهاي‌ ارجاع‌ انجام‌ مي‌دهند.

 

* مثال‌ 14-5 بازگشت‌ بيشتر از يك‌ مقدار

تابع‌ زير از طريق دو پارامتر ارجاع، دو مقدار را بازمي‌گرداند: area و circumference (محيط و مساحت‌) براي دايره‌اي که شعاع آن عدد مفروض r است:

void ComputeCircle(double& area, double& circumference, double r)

{ // returns the area and circumference of a circle with radius r:

const double PI = 3.141592653589793;

area = PI*r*r;

circumference = 2*PI*r;

}

برنامۀ آزمون تابع فوق و يک اجراي آزمايشي آن در شکل زير نشان داده شده است:

void ComputerCircle(double&, double&, double);

// returns the area and circumference of a circle with radius r;

int main()

{ // tests the ComputeCircle() function:

 double r, a, c;

 cout << "Enter radius: ";

 cin >> r;

 ComputeCircle(a, c, r);

 cout << "area = " << a << ", circumference = "

 << c << endl;}

 

12- ارسال‌ از طريق‌ ارجاع‌ ثابت‌

ارسال پارامترها به طريق ارجاع دو خاصيت مهم دارد:

 اول اين که تابع مي‌تواند روي آرگومان واقعي تغييراتي بدهد

 دوم اين که از اشغال بي‌مورد حافظه جلوگيري مي‌شود.

روش ديگري نيز براي ارسال آرگومان وجود دارد:

 ارسال از طريق ارجاع ثابت. اين روش مانند ارسال از طريق ارجاع است با اين فرق که تابع نمي‌تواند محتويات پارامتر ارجاع را دست‌کاري نمايد و فقط اجازۀ خواندن آن را دارد.

 براي اين که پارامتري را از نوع ارجاع ثابت اعلان کنيم بايد عبارت const را به ابتداي اعلان آن اضافه نماييم.

مثال‌ 15-5 ارسال‌ از طريق‌ ارجاع‌ ثابت‌

سه طريقه ارسال پارامتر در تابع زير به کار رفته است:

void f(int x, int& y, const int& z)

{ x += z;

 y += z;

 cout << "x = " << x << ", y = " << y << ", z = "

 << z << endl;

}

در تابع فوق اولين‌ پارامتر يعني x از طريق مقدار ارسال مي‌شود، دومين پارامتر يعني y از طريق ارجاع و سومين پارامتر نيز از طريق ارجاع ثابت.

 

برنامۀ آزمون و يک اجراي آزمايشي از مثال قبل:

void f(int, int&, const int&);

int main()

{ // tests the f() function:

 int a = 22, b = 33, c = 44;

 cout << "a = " << a << ", b = " << b << ", c = "

 << c << endl;

 f(a,b,c);

 cout << "a = " << a << ", b = " << b << ", c = "

 << c << endl;

 f(2*a-3,b,c);

 cout << "a = " << a << ", b = " << b << ", c = "

 << c << endl;

}

خروجی:

a = 22, b = 33, c = 44

x = 66, y = 77, z = 44

a = 22, b = 77, c = 44

x = 85, y = 121, z = 44

a = 22, b = 121, c = 44

تابع‌ فوق پارامترهاي‌ x و y را مي‌تواند تغيير دهد ولي‌ قادر نيست پارامتر z را تغيير دهد. تغييراتي که روي x صورت مي‌گيرد اثري روي آرگومان a نخواهد داشت زيرا a از طريق مقدار به تابع ارسال شده. تغييراتي که روي y صورت مي‌گيرد روي آرگومان b هم تاثير مي‌گذارد زيرا b از طريق ارجاع به تابع فرستاده شده.

 

ارسال به طريق ارجاع ثابت بيشتر براي توابعي استفاده مي‌شود که عناصر بزرگ را ويرايش مي‌کنند مثل آرايه‌ها يا نمونۀ کلاس‌ها که در جلسه‌‌هاي بعدي توضيح آن‌ها آمده است. عناصري که از انواع اصلي هستند (مثل int يا float) به طريق مقدار ارسال مي‌شوند به شرطي که قرار نباشد تابع محتويات آن‌ها را دست‌کاري کند.