تابع در ++C
برنامهها واقعی و تجاری بسیار بزرگتر از برنامههایی هستند که تاکنون بررسی کردیم. برای این که برنامههای بزرگ قابل مدیریت باشند، برنامهنویسان این برنامهها را به زیربرنامههایی بخشبندی میکنند. این زیربرنامهها تابع یا Function نامیده میشوند. توابع را میتوان به طور جداگانه کامپایل و آزمایش نمود و در برنامههای مختلف دوباره از آنها استفاده کرد.
توابع كتابخانهای ++C استاندارد
كتابخانۀ ++C استاندارد مجموعهای است که شامل توابع از پیش تعریف شده و سایر عناصر برنامه است. این توابع و عناصر از طریق «سرفایلها» یا هدرها قابل دستیابیاند. قبلا برخی از آنها را استفاده كردهایم: ثابت INT_MAX که در <climits> تعریف شده ، تابع ()sqrt که در <cmath> تعریف شده است و… .
تابع جذر ()sqrt
ریشۀ دوم یك عدد مثبت، جذر آن عدد است. تابع مانند یک برنامۀ کامل، دارای روند ورودی – پردازش – خروجی است هرچند که پردازش، مرحلهای پنهان است. یعنی نمیدانیم که تابع روی عدد 2 چه اعمالی انجام میدهد که 41421/1 حاصل میشود.
مثال: برنامۀ سادۀ زیر، تابع از پیش تعریف شدۀ جذر را به کار میگیرد:
#include <cmath> // defines the sqrt() function #include <iostream> using namespace std; int main() { //tests the sqrt() function: for (int x=0; x < 6; x++) cout << "\t" << x << "\t" << sqrt(x) << endl; }
برای اجرای یك تابع مانند تابع ()sqrt کافی است نام آن تابع به صورت یک متغیر در دستورالعمل مورد نظر استفاده شود، مانند بالا. این کار فراخوانی تابع یا احضار تابع گفته میشود. بنابراین وقتی كد (sqrt(x اجرا شود، تابع ()sqrt فراخوانی میگردد. عبارت x درون پرانتز آرگومان یا پارامتر واقعی فراخوانی نامیده میشود. در چنین حالتی میگوییم كه x توسط «مقدار» به تابع فرستاده میشود. لذا وقتی x=3 است، با اجرای کد (sqrt(x تابع ()sqrt فراخوانی شده و مقدار 3 به آن فرستاده میشود. تابع مذکور نیز حاصل 1.73205 را به عنوان پاسخ برمیگرداند. این فرایند در نمودار زیر نشان داده شده است.
متغیرهای x و y در تابع ()main تعریف شدهاند. مقدار x که برابر با 3 است به تابع ()sqrt فرستاده میشود و این تابع مقدار 1.73205 را به تابع ()main برمیگرداند. جعبهای كه تابع ()sqrt را نشان میدهد به رنگ تیره است، به این معنا كه فرایند داخلی و نحوۀ کار آن قابل رویت نیست.
توابع مثلثاتی در ++C
برنامه زیر از سرفایل <cmath> استفاده میكند. هدف این است که صحت رابطۀ Sin2x=2SinxCosx به شکل تجربی بررسی شود.
#include<iostream> #include <cmath> using namespace std; int main() { for (float x=0; x < 2; x += 0.2) cout << x << "\t\t" << sin(2*x) <<"\t" << 2*sin(x)*cos(x) << endl; }
برنامۀ مقدار x را در ستون اول، مقدار Sin2x را در ستون دوم و مقدار 2SinxCosx را در ستون سوم چاپ میكند. خروجی نشان میدهد که برای هر مقدار آزمایشی x، مقدار Sin2x با مقدار 2SinxCosx برابر است.
بیشتر توابع معروف ریاضی كه در ماشینحسابها هم وجود دارد در سرفایل <cmath> تعریف شده است. بعضی از این توابع در جدول زیر نشان داده شده:
نام تابع | شرح | مثال |
---|---|---|
acos(x) | کسینوس معکوسx (به رادیان) | acos(0.2) مقدار 1.36944 را برمیگرداند |
asin(x) | سینوس معکوس x (به رادیان) | asin(0.2) مقدار 0.201358 را برمیگرداند |
atan(x) | تانژانت معکوس x (به رادیان) | atan(0.2) مقدار 0.197396 را برمیگرداند |
ceil(x) | مقدار سقف x (گرد شده) | ceil(3.141593) مقدار 4.0 را برمیگرداند |
cos(x) | کسینوس x (به رادیان) | cos(2) مقدار -0.416147 را برمیگرداند |
exp(x) | تابع نمایی x (در پایه e) | exp(2) مقدار 7.38906 را برمیگرداند |
fabs(x) | قدر مطلق x | fabs(-2) مقدار 2.0 را برمیگرداند |
floor(x) | مقدار کف x (گرد شده) | floor(3.141593) مقدار 3.0 را برمیگرداند |
log(x) | لگاریتم طبیعی x (در پایه e) | log(2) مقدار 0.693147 را برمیگرداند |
log10(x) | لگاریتم عمومی x (در پایه 10) | log10(2) مقدار 0.30103 را برمیگرداند |
pow(x,p) | x به توان p | pow(2,3) مقدار 8.0 را برمیگرداند |
sin(x) | سینوس x (به رادیان) | sin(2) مقدار 0.909297 را برمیگرداند |
sqrt(x) | جذر x | sqrt(2) مقدار 1.41421 را برمیگرداند |
tan(x) | تانژانت x (به رادیان) | tan(2) مقدار -2.18504 را برمیگرداند |
توجه داشته باشید که هر تابع ریاضی یک مقدار از نوع double را برمیگرداند. اگر یك نوع صحیح به تابع فرستاده شود، قبل از این كه تابع آن را پردازش کند، مقدارش را به نوع double ارتقا میدهد.
فایل های سرآیند یا هدر در ++C
بعضی از سرفایلهای كتابخانۀ ++C استاندارد که کاربرد بیشتری دارند در جدول زیر آمده است:
نام سرفایل | شرح |
---|---|
assert | تابع |
ctype | توابعی را برای بررسی کاراکترها تعریف میکند |
cfloat | ثابتهای مربوط به اعداد ممیز شناور را تعریف میکند |
climits | محدودۀ اعداد صحیح را روی سیستم موجود تعریف میکند |
cmath | توابع ریاضی را تعریف میکند |
cstdio | توابعی را برای ورودی و خروجی استاندارد تعریف میکند |
cstdlib | توابع کاربردی را تعریف می کند |
cstring | توابعی را برای پردازش رشتهها تعریف میکند |
ctime | توابع تاریخ و ساعت را تعریف میکند |
این سرفایلها از كتابخانۀ C استاندارد گرفته شدهاند. استفاده از آنها شبیه استفاده از سرفایلهای ++C استاندارد (مانند <iostream> ) است. برای مثال اگر بخواهیم تابع اعداد تصادفی ()rand را از سرفایل <cstdlib> به كار ببریم، باید دستور پیشپردازندۀ زیر را به ابتدای فایل برنامۀ اصلی اضافه کنیم:
#include <cstdlib>
توابع ساخت كاربر در ++C
گرچه توابع بسیار متنوعی در کتابخانۀ ++C استاندارد وجود دارد ولی این توابع برای بیشتر وظایف برنامهنویسی كافی نیستند. علاوه بر این برنامهنویسان دوست دارند خودشان بتوانند توابعی را بسازند و استفاده نمایند.
تابع ()cube
یك مثال ساده از توابع ساخت كاربر:
#include<iostream> #include <cmath> using namespace std; int cube(int x) { // returns cube of x: return x*x*x; } int main() { cout << cube(2); }
این تابع، مكعب یك عدد صحیح ارسالی به آن را برمیگرداند. بنابراین فراخوانی (cube(2 مقدار 8 را برمیگرداند.
قسمت های تابع ساخت كاربر
یك تابع ساخت كاربر دو قسمت دارد:
1-عنوان 2- بدنه.
عنوان یك تابع به صورت زیر است:
(فهرست پارامترها) نام نوع بازگشتی
int cube(int x) { … بدنه تابع }
بدنۀ تابع، یك بلوك كد است كه در ادامۀ عنوان آن میآید. بدنه شامل دستوراتی است كه باید انجام شود تا نتیجۀ مورد نظر به دست آید. بدنه شامل دستور return است كه پاسخ نهایی را به مكان فراخوانی تابع برمیگرداند.
نوع بازگشتی تابع ()cube که در بالا تعریف شد، int است. نام آن cube میباشد و یک پارامتر از نوع int به نام x دارد. یعنی تابع ()cube یک مقدار از نوع int میگیرد و پاسخی از نوع int تحویل میدهد.
دستور return دو وظیفۀ عمده دارد. اول این که اجرای تابع را خاتمه میدهد و دوم این که مقدار نهایی را به برنامۀ فراخوان باز میگرداند. دستور return به شکل زیر استفاده میشود:
return expression;
به جای expression هر عبارتی قرار میگیرد که بتوان مقدار آن را به یک متغیر تخصیص داد. نوع آن عبارت باید با نوع بازگشتی تابع یکی باشد. عبارت int main که در همۀ برنامهها استفاده کردهایم یک تابع به نام «تابع اصلی» را تعریف میکند. نوع بازگشتی این تابع از نوع int است. نام آن main است و فهرست پارامترهای آن خالی است؛ یعنی هیچ پارامتری ندارد.
برنامۀ آزمون
وقتی یک تابع مورد نیاز را ایجاد کردید، فوراً باید آن تابع را با یک برنامۀ ساده امتحان کنید. چنین برنامهای برنامۀ آزمون نامیده میشود. برنامۀ آزمون یک برنامۀ موقتی است که باید «سریع و کثیف» باشد؛ یعنی: لازم نیست در آن تمام ظرافتهای برنامهنویسی – مثل پیغامهای خروجی، برچسبها و راهنماهای خوانا – را لحاظ کنید. تنها هدف این برنامه، امتحان کردن تابع و بررسی صحت کار آن است.
مثال: یك برنامۀ آزمون برای تابع ()cube – کد زیر شامل تابع ()cube و برنامۀ آزمون آن است:
#include<iostream> #include <cmath> using namespace std; int cube(int x) { // returns cube of x: return x*x*x; } int main() { // tests the cube() function: int i=5,n; while (i > 0) { cin >> n; cout << "\tcube(" << n << ") = " << cube(n) << endl; i--; } }
برنامۀ حاضر اعداد صحیح را از ورودی میگیرد و مكعب آنها را چاپ میكند این تا 5 مرحله تکرار می شود.
هر عدد صحیحی که خوانده میشود، با استفاده از کد (cube(n به تابع ()cube فرستاده میشود. مقدار بازگشتی از تابع، جایگزین عبارت (cube(n گشته و با استفاده از cout در خروجی چاپ میشود.
میتوان رابطۀ بین تابع ()main و تابع ()cube را شبیه این شکل تصور نمود:
مثال: یك برنامۀ آزمون برای تابع ()max – تابع زیر دو پارامتر دارد. این تابع از دو مقدار فرستاده شده به آن، مقدار بزرگتر را برمیگرداند:
#include <iostream> using namespace std; int max(int x, int y) { int z; z = (x > y) ? x : y ; return z; } int main() { int m, n; cin >> m >> n; cout << "\tmax(" << m << "," << n << ") = " << max(m,n) << endl; }
دستور return در ++C
توابع میتوانند بیش از یک دستور return داشته باشند. مثلا تابع ()max را مانند این نیز میتوانستیم بنویسیم:
int max(int x, int y) { // returns larger of the two given integers: if (x < y) return y; else return x; }
در این کد هر دستور return که زودتر اجرا شود مقدار مربوطهاش را بازگشت داده و تابع را خاتمه میدهد. دستور return نوعی دستور پرش است (شبیه دستور break ) زیرا اجرا را به بیرون از تابع هدایت میکند. اگرچه معمولا return در انتهای تابع قرار میگیرد، میتوان آن را در هر نقطۀ دیگری از تابع قرار داد.
اعلانها و تعاریف تابع در ++C
به دو روش می توان توابع را تعریف نمود:
- توابع قبل از تابع ()main به طور كامل با بدنه مربوطه آورده شوند.
- راه دیگری که بیشتر رواج دارد این گونه است که ابتدا تابع اعلان شود، سپس متن برنامۀ اصلی ()main بیاید، پس از آن تعریف کامل تابع قرار بگیرد.
اعلان تابع با تعریف تابع تفاوت دارد. اعلان تابع، فقط عنوان تابع است که یک سمیکولن در انتهای آن قرار دارد. تعریف تابع، متن کامل تابع است که هم شامل عنوان است و هم شامل بدنه. اعلان تابع شبیه اعلان متغیرهاست. یک متغیر قبل از این که به کار گرفته شود باید اعلان شود. تابع هم همین طور است با این فرق که متغیر را در هر جایی از برنامه میتوان اعلان کرد اما تابع را باید قبل از برنامۀ اصلی اعلان نمود.
در اعلان تابع فقط بیان میشود که نوع بازگشتی تابع چیست، نام تابع چیست و نوع پارامترهای تابع چیست. همینها برای کامپایلر کافی است تا بتواند کامپایل برنامه را آغاز کند. سپس در زمان اجرا به تعریف بدنۀ تابع نیز احتیاج میشود که این بدنه در انتهای برنامه و پس از تابع ()main قرار میگیرد.
فرق بین آرگومان و پارامتر در ++C
پارامترها متغیرهایی هستند که در فهرست پارامتر یک تابع نام برده میشوند. پارامترها متغیرهای محلی برای تابع محسوب میشوند؛ یعنی فقط در طول اجرای تابع وجود دارند. آرگومانها متغیرهایی هستند که از برنامۀ اصلی به تابع فرستاده میشوند.
مثال: تابع ()max با اعلان جدا از تعریف آن – این برنامه همان برنامۀ آزمون تابع ()max در مثال قبلی است. اما اینجا اعلان تابع بالای تابع اصلی ظاهر شده و تعریف تابع بعد از برنامۀ اصلی آمده است:
#include <iostream> using namespace std; int max(int,int); int main() { int m, n; cin >> m >> n; cout << "\tmax(" << m << "," << n << ") = " << max(m,n) << endl; } int max(int x, int y) { if (x < y) return y; else return x; }
توجه كنید كه پارامترهای x و y در بخش عنوان تعریف تابع آمدهاند (طبق معمول) ولی در اعلان تابع وجود ندارند.
كامپایل جداگانه توابع در ++C
اغلب این طور است که تعریف و بدنۀ توابع در فایلهای جداگانهای قرار میگیرد. این فایلها به طور مستقل کامپایل میشوند و سپس به برنامۀ اصلی که آن توابع را به کار میگیرد الصاق میشوند. توابع کتابخانۀ ++C استاندارد به همین شکل پیادهسازی شدهاند و هنگامی که یکی از آن توابع را در برنامههایتان به کار میبرید باید با دستور راهنمای پیشپردازنده، فایل آن توابع را به برنامهتان ضمیمه کنید. این کار چند مزیت دارد:
- اولین مزیت «مخفیسازی اطلاعات» است.
- مزیت دیگر این است که توابع مورد نیاز را میتوان قبل از این که برنامۀ اصلی نوشته شود، جداگانه آزمایش نمود.
- سومین مزیت این است که در هر زمانی به راحتی میتوان تعریف توابع را عوض کرد بدون این که لازم باشد برنامۀ اصلی تغییر یابد.
- چهارمین مزیت هم این است که میتوانید یک بار یک تابع را کامپایل و ذخیره کنید و از آن پس در برنامههای مختلفی از همان تابع استفاده ببرید.
تابع ()max را به خاطر بیاورید. برای این که این تابع را در فایل جداگانهای قرار دهیم، تعریف آن را در فایلی به نام max.cpp ذخیره میکنیم. فایل max.cpp شامل کد زیر است:
int max(int x, int y) { if (x < y) return y; else return x; }
حال كافی است عبارت: <include <test.cpp# را به اول برنامه اصلی وقبل از ()main اضافه كنیم:
#include <test.cpp> int main() { // tests the max() function: int m, n; cin >> m >> n; cout << "\tmax(" << m << "," << n << ") = " << max(m,n) << endl; }
نحوۀ کامپایل کردن فایلها و الصاق آنها به یکدیگر به نوع سیستم عامل و نوع کامپایلر بستگی دارد. در سیستم عامل ویندوز معمولا توابع را در فایلهایی از نوع DLL کامپایل و ذخیره میکنند و سپس این فایل را در برنامۀ اصلی احضار مینمایند. فایلهای DLL را به دو طریق ایستا و پویا میتوان مورد استفاده قرار داد. برای آشنایی بیشتر با فایلهای DLL به مرجع ویندوز و کامپایلرهای ++C مراجعه کنید.
متغیرهای محلی، توابع محلی در ++C
متغیر محلی، متغیری است که در داخل یک بلوک اعلان گردد. این گونه متغیرها فقط در داخل همان بلوکی که اعلان میشوند قابل دستیابی هستند. چون بدنۀ تابع، خودش یک بلوک است پس متغیرهای اعلان شده در یک تابع متغیرهای محلی برای آن تابع هستند. این متغیرها فقط تا وقتی که تابع در حال کار است وجود دارند. پارامترهای تابع نیز متغیرهای محلی محسوب میشوند.
مثال: تابع فاكتوریل – اعداد فاكتوریل را در مثال قبل تر دیدیم. فاكتوریل عدد صحیح 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 عنصری را کنار یکدیگر قرار داد. برای این محاسبه از رابطۀ زیر استفاده میشود:
این تابع، خود از تابع دیگری که همان تابع فاکتوریل است استفاده کرده است. شرط به کار رفته در دستور if برای محدود کردن حالتهای غیر ممکن استفاده شده است. در این حالتها، تابع مقدار 0 را برمیگرداند تا نشان دهد که یک ورودی اشتباه وجود داشته است.
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); }
تابع void در ++C
لازم نیست یك تابع حتما مقداری را برگرداند. در ++C برای مشخص کردن چنین توابعی از کلمۀ کلیدی void به عنوان نوع بازگشتی تابع استفاده میکنند یک تابع void تابعی است که هیچ مقدار بازگشتی ندارد. از آنجا كه یك تابع void مقداری را برنمیگرداند، نیازی به دستور return نیست ولی اگر قرار باشد این دستور را در تابع void قرار دهیم، باید آن را به شکل تنها استفاده کنیم بدون این که بعد از کلمۀ return هیچ چیز دیگری بیاید. در این حالت دستور return فقط تابع را خاتمه می دهد.
توابع بولی در ++C
در بسیاری از اوقات لازم است در برنامه، شرطی بررسی شود. اگر بررسی این شرط به دستورات زیادی نیاز داشته باشد، بهتر است که یک تابع این بررسی را انجام دهد. این کار مخصوصا هنگامی که از حلقهها استفاده میشود بسیار مفید است. توابع بولی فقط دو مقدار را برمیگردانند: true یا false .
اسم توابع بولی را معمولا به شکل سوالی انتخاب می کنند زیرا توابع بولی همیشه به یک سوال مفروض پاسخ بلی یا خیر میدهند.
#include <iostream> #include <cmath> using namespace std; 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 } int main() { cout << isPrime(10); }
توابع ورودی/خروجی (I/O) در ++C
بخشهایی از برنامه که به جزییات دست و پا گیر میپردازد و خیلی به هدف اصلی برنامه مربوط نیست را میتوان به توابع سپرد. در چنین شرایطی سودمندی توابع محسوستر میشود. فرض کنید نرمافزاری برای سیستم آموزشی دانشگاه طراحی کردهاید که سوابق تحصیلی دانشجویان را نگه میدارد. در این نرمافزار لازم است که سن دانشجو به عنوان یکی از اطلاعات پروندۀ دانشجو وارد شود. اگر وظیفۀ دریافت سن را به عهدۀ یک تابع بگذارید، میتوانید جزییاتی از قبیل کنترل ورودی معتبر، یافتن سن از روی تاریخ تولد و … را در این تابع پیادهسازی کنید بدون این که از مسیر برنامۀ اصلی منحرف شوید.
قبلا نمونهای از توابع خروجی را دیدیم. تابع ()PrintDate در مثال های قبلی هیچ چیزی به برنامۀ اصلی برنمیگرداند و فقط برای چاپ نتایج به کار میرود. این تابع نمونهای از توابع خروجی است؛ یعنی توابعی که فقط برای چاپ نتایج به کار میروند و هیچ مقدار بازگشتی ندارند.
توابع ورودی در ++C
توابع ورودی نیز به همین روش کار میکنند اما در جهت معکوس. یعنی توابع ورودی فقط برای دریافت ورودی و ارسال آن به برنامۀ اصلی به کار میروند و هیچ پارامتری ندارند. مثال بعد یک تابع ورودی را نشان میدهد.
مثال: تابعی برای دریافت سن كاربر – تابع سادۀ زیر، سن کاربر را درخواست میکند و مقدار دریافت شده را به برنامۀ اصلی میفرستد. این تابع تقریباً هوشمند است و هر عدد صحیح ورودی غیر منطقی را رد میکند و به طور مکرر درخواست ورودی معتبر میکند تا این که یک عدد صحیح در محدودۀ 7 تا 120 دریافت دارد:
#include <iostream> #include <cmath> using namespace std; 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 main() { // tests the age() function: int a = age(); cout << "\nYou are " << a << " years old.\n"; }
تا این لحظه تمام پارامترهایی كه در توابع دیدیم به طریق مقدار ارسال شدهاند. یعنی ابتدا مقدار متغیری که در فراخوانی تابع ذکر شده برآورد میشود و سپس این مقدار به پارامترهای محلی تابع فرستاده میشود. مثلا در فراخوانی (cube(x ابتدا مقدار x برآورد شده و سپس این مقدار به متغیر محلی n در تابع فرستاده میشود و پس از آن تابع کار خویش را آغاز میکند. در طی اجرای تابع ممکن است مقدار n تغییر کند اما چون n محلی است هیچ تغییری روی مقدار x نمیگذارد. پس خود x به تابع نمیرود بلکه مقدار آن درون تابع کپی میشود.
تغییر دادن این مقدار کپی شده درون تابع هیچ تاثیری بر x اصلی ندارد. به این ترتیب تابع میتواند مقدار x را بخواند اما نمیتواند مقدار x را تغییر دهد. به همین دلیل به x یک پارامتر «فقط خواندنی» میگویند. وقتی ارسال به وسیلۀ مقدار باشد، هنگام فراخوانی تابع میتوان از عبارات استفاده کرد. مثلا تابع ()cube را میتوان به صورت (cube(2*x-3 فراخوانی کرد یا به شکل ((cube(2*sqrt(x)-cube(3 فراخوانی نمود. در هر یک از این حالات، عبارت درون پرانتز به شکل یک مقدار تکی برآورد شده و حاصل آن مقدار به تابع فرستاده میشود.
ارسال به طریق ارجاع در ++C
ارسال به طریق مقدار call by value باعث میشود که متغیرهای برنامۀ اصلی از تغییرات ناخواسته در توابع مصون بمانند. اما گاهی اوقات عمداً میخواهیم این اتفاق رخ دهد. یعنی میخواهیم که تابع بتواند محتویات متغیر فرستاده شده به آن را دستکاری کند. در این حالت از ارسال به طریق ارجاع call by reference استفاده میکنیم.
برای این که مشخص کنیم یک پارامتر به طریق ارجاع ارسال میشود، علامت & را به نوع پارامتر در فهرست پارامترهای تابع اضافه میکنیم. این باعث میشود که تابع به جای این که یک کپی محلی از آن آرگومان ایجاد کند، خود آرگومان محلی را به کار بگیرد. به این ترتیب تابع هم میتواند مقدار آرگومان فرستاده شده را بخواند و هم میتواند مقدار آن را تغییر دهد. در این حالت آن پارامتر یک پارامتر «خواندنی-نوشتنی» خواهد بود. هر تغییری که روی پارامتر خواندنی-نوشتنی در تابع صورت گیرد به طور مستقیم روی متغیر برنامۀ اصلی اعمال میشود. به مثال زیر نگاه کنید.
مثال: تابع ()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
عملگر ارجاع در ++C
عملگر ارجاع & موجب میشود كه به جای x و y آرگومانهای ارسالی قرار بگیرند. برنامۀ آزمون و اجرای آزمایشی آن در زیر آمده است:
#include <iostream> using namespace std; void swap(float& x, float& y) { // exchanges the values of x and y: float temp = x; x = y; y = temp; } 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; }
وقتی فراخوانی (swap(a,b اجرا میشود، x به a اشاره میکند و y به b. سپس متغیر محلی temp اعلان میشود و مقدار x (که همان a است) درون آن قرار میگیرد. پس از آن مقدار y (که همان b است) درون x (یعنی a) قرار میگیرد و آنگاه مقدار temp درون y (یعنی b) قرار داده میشود. نتیجۀ نهایی این است که مقادیر a و b با یکدیگر جابجا می شوند. شکل زیر نشان میدهد که چطور این جابجایی رخ میدهد:
به اعلان تابع ()swap دقت کنید:
void swap(float&, float&)
این اعلان شامل عملگر ارجاع & برای هر پارامتر است. برنامهنویسان c عادت دارند که عملگر ارجاع & را به عنوان پیشوند نام متغیر استفاده کنند (مثل float &x) در ++C فرض می کنیم عملگر ارجاع & پسوند نوع است (مثل float& x) به هر حال کامپایلر هیچ فرقی بین این دو اعلان نمیگذارد و شکل نوشتن عملگر ارجاع کاملا اختیاری و سلیقهای است.
مثال ارسال به طریق مقدار و ارسال به طریق ارجاع در ++C
مثال زیر ارسال به طریق مقدار call by value و ارسال به طریق ارجاع call by reference را انجام می دهد. این برنامه، تفاوت بین ارسال به طریق مقدار و ارسال به طریق ارجاع را نشان میدهد:
#include <iostream> using namespace std; 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 دو پارامتر دارد که اولی به طریق مقدار و دومی به طریق ارجاع ارسال میشود. فراخوانی (f(a,b باعث میشود که a از طریق مقدار به x ارسال شود و b از طریق ارجاع به y فرستاده شود.
در جدول زیر خلاصۀ تفاوتهای بین ارسال از طریق مقدار و ارسال از طریق ارجاع آمده است.
ارسال از طریق ارجاع | ارسال از طریق مقدار |
int& x; |
int x; |
پارامتر x یک ارجاع است | پارامتر x یک متغیر محلی است |
x مترادف با آرگومان است | x یک کپی از آرگومان است |
میتواند محتویات آرگومان را تغییر دهد | تغییر محتویات آرگومان ممکن نیست |
آرگومان ارسال شده از طریق ارجاع فقط باید یک متغیر باشد | آرگومان ارسال شده از طریق مقدار میتواند یک ثابت، یک متغیر یا یک عبارت باشد |
آرگومان خواندنی-نوشتنی است | آرگومان فقط خواندنی است |
یكی از مواقعی كه پارامترهای ارجاع مورد نیاز هستند جایی است كه تابع باید بیش از یك مقدار را بازگرداند. دستور return فقط میتواند یك مقدار را برگرداند. بنابراین اگر باید بیش از یك مقدار برگشت داده شود، این كار را پارامترهای ارجاع انجام میدهند.
ارسال از طریق ارجاع ثابت در ++C
ارسال پارامترها به طریق ارجاع دو خاصیت مهم دارد:
- اول این که تابع میتواند روی آرگومان واقعی تغییراتی بدهد
- دوم این که از اشغال بیمورد حافظه جلوگیری میشود.
روش دیگری نیز برای ارسال آرگومان وجود دارد:
ارسال از طریق ارجاع ثابت. این روش مانند ارسال از طریق ارجاع است با این فرق که تابع نمیتواند محتویات پارامتر ارجاع را دستکاری نماید و فقط اجازۀ خواندن آن را دارد. برای این که پارامتری را از نوع ارجاع ثابت اعلان کنیم باید عبارت const را به ابتدای اعلان آن اضافه نماییم.
مثال: ارسال از طریق ارجاع ثابت – سه طریقه ارسال پارامتر در تابع زیر به کار رفته است:
void f(int x, int& y, const int& z) { x += z; y += z; cout << "x = " << x << ", y = " << y << ", z = " << z << endl; }
در تابع فوق اولین پارامتر یعنی x از طریق مقدار ارسال میشود، دومین پارامتر یعنی y از طریق ارجاع و سومین پارامتر نیز از طریق ارجاع ثابت. برنامۀ آزمون و یک اجرای آزمایشی از مثال قبل:
#include <iostream> using namespace std; 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; } void f(int x, int& y, const int& z) { x += z; y += z; cout << "x = " << x << ", y = " << y << ", z = " << z << endl; }
تابع فوق پارامترهای x و y را میتواند تغییر دهد ولی قادر نیست پارامتر z را تغییر دهد. تغییراتی که روی x صورت میگیرد اثری روی آرگومان a نخواهد داشت زیرا a از طریق مقدار به تابع ارسال شده. تغییراتی که روی y صورت میگیرد روی آرگومان b هم تاثیر میگذارد زیرا b از طریق ارجاع به تابع فرستاده شده. ارسال به طریق ارجاع ثابت بیشتر برای توابعی استفاده میشود که عناصر بزرگ را ویرایش میکنند مثل آرایهها یا نمونۀ کلاسها که در جلسههای بعدی توضیح آنها آمده است. عناصری که از انواع اصلی هستند (مثل int یا float) به طریق مقدار ارسال میشوند به شرطی که قرار نباشد تابع محتویات آنها را دستکاری کند.
توابع بیواسطه inline در ++C
تابعی که به شکل بیواسطه تعریف میشود، ظاهری شبیه به توابع معمولی دارد با این فرق که عبارت inline در اعلان و تعریف آن قید شده است. مثال زیر تابع ()cube به شکل بیواسطه یا inline است. این همان تابع ()cube مثال قبلی است:
inline int cube(int x) { // returns cube of x: return x*x*x; }
تنها تفاوت این است كه كلمۀ كلیدی inline در ابتدای عنوان تابع ذکر شده. این عبارت به كامپایلر می گوید كه در برنامه به جای (cube(n کد واقعی (n)*(n)*(n) را قرار دهد.استفاده از توابع بیواسطه میتواند اثرات منفی داشته باشد. مثلا اگر یک تابع بیواسطه دارای 40 خط کد باشد و این تابع در 26 نقطه مختلف از برنامۀ اصلی فراخوانی شود، هنگام کامپایل بیش از هزار خط کد به برنامۀ اصلی افزوده میشود. همچنین تابع بیواسطه می تواند قابلیت انتقال برنامۀ شما را روی سیستمهای مختلف کاهش دهد. به برنامۀ آزمون زیر نگاه کنید:
int main() { // tests the cube() function: cout << cube(4) << endl; int x, y; cin >> x; y = cube(2*x-3); }
این برنامه هنگام کامپایل به شکل زیر درمیآید، گویی اصلا تابعی وجود نداشته:
int main() { // tests the cube() function: cout << (4) * (4) * (4) << endl; int x, y; cin >> x; y = (2*x-3) * (2*x-3) * (2*x-3); }
وقتی كامپایلر کد واقعی تابع را جایگزین فراخوانی آن میکند، میگوییم که تابع بیواسطه، باز میشود.
درباره امین جلیل زاده رزین
پایه گذار و موسس وب سایت آموزشی پی استور، مدرس دانشگاه فنی و حرفه ای، برنامه نویس و تحلیل گر سیستم، پژوهشگر در حوزه الگوریتم های ابتکاری، فرا ابتکاری، یادگیری ماشین، شبکه و پایگاه داده. ایشان در زبان های برنامه نویسی متعدد، نظیر ++C، سی شارپ، PHP ،Java، متلب MATLAB و Python تسلط و سابقه تدریس فعال دارند.
تابعی که اعداد بین دو عدد را نشان دهد
فوق العاده بود ممنون
ممنون از لطف تون.