تابع در ++C

برای این که برنامههای بزرگ قابل مدیریت باشن، برنامهنویسان این برنامهها رو به زیربرنامههایی بخشبندی میکنن. به این زیربرنامهها تابع یا Function میگن. توابع رو میتونیم به طور جداگانه کامپایل و آزمایش کنیم و داخل برنامههای مختلف دوباره از اونها استفاده کنیم.
توابع كتابخانهای ++C استاندارد
كتابخانۀ ++C استاندارد مجموعهای هست که شامل توابع از پیش تعریف شده و سایر عناصر برنامه هست. این توابع و عناصر از طریق «سرفایلها» یا هدرها قابل دستیابی هستن که قبلا بعضی از اونها رو استفاده كردیم مثل: ثابت INT_MAX که در <climits> تعریف شده ، تابع ()sqrt که در <cmath> تعریف شده و… .
تابع جذر()sqrt
ریشۀ دوم یك عدد مثبت، جذر اون عدد هست. تابع مانند یک برنامۀ کامل، دارای روند ورودی – پردازش – خروجی هست هرچند که پردازشش مرحلهای پنهان هستش. یعنی نمیدونیم که تابع روی عدد 2 چه اعمالی رو انجام میده که 41421/1 جوابش میشه.
برای اجرای یك تابع مثل تابع ()sqrt کافی هست که اسم این تابع به صورت یک متغیر در دستورالعمل مورد نظر استفاده بشه که به این کار فراخوانی تابع یا احضار تابع میگن. بنابراین وقتی كد (sqrt(x اجرا بشه، تابع ()sqrt فراخوانی میشه. عبارت x داخل پرانتز رو آرگومان یا پارامتر واقعی فراخوانی مگن بهش که در چنین حالتی میگیم كه x توسط مقدار به تابع فرستاده میشه. مثلا وقتی x=3 هست، با اجرای کد (sqrt(x تابع ()sqrt فراخوانی میشه و مقدار 3 به اون فرستاده میشه. که این تابع حاصل 1.73205 رو به عنوان پاسخ برمیگردونه.
مثلا داخل تصویر بالا متغیرهای x و y داخل تابع ()main تعریف شده. مقدار x که برابر با 3 هست به تابع ()sqrt فرستاده میشه و این تابع مقدار 1.73205 رو به تابع()mainبرمیگردونه. جعبهای كه تابع ()sqrt رو نشان میده به رنگ تیره هست، به این معنا كه فرایند داخلی و نحوۀ کارش قابل دیدن نیست.
توابع مثلثاتی
برنامه زیر از سرفایل <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++ استاندارد که کاربرد بیشتری دارن عبارت اند از:
نام سرفایل | شرح |
assert | تابع رو تعریف میکنه |
ctype | توابعی رو برای بررسی کاراکترها تعریف میکنه |
cfloat | ثابتهای مربوط به اعداد ممیز شناور رو تعریف میکنه |
climits | محدودۀ اعداد صحیح رو روی سیستم موجود تعریف میکنه |
cmath | توابع ریاضی رو تعریف میکنه |
cstdio | توابعی رو برای ورودی و خروجی استاندارد تعریف میکنه |
cstdlib | توابع کاربردی رو تعریف می کنه |
cstring | توابعی رو برای پردازش رشتهها تعریف میکنه |
ctime | توابع تاریخ و ساعت رو تعریف میکنه |
این سرفایلها از كتابخانۀ C استاندارد گرفته شده. استفاده از اونها شبیه استفاده از سرفایلهای استاندارد C++ مثل <iostream> هست. برای مثال اگه بخوایم تابع اعداد تصادفی ()rand رو از سرفایل <cstdlib> به كار ببریم، باید دستور پیشپردازندۀ زیر رو به ابتدای فایل برنامۀ اصلی اضافه کنیم:
#include <cstdlib>
توابع ساخت كاربر
توابع بسیار متنوعی داخل کتابخانۀ 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 مرحله تکرار بشه.
هر عدد صحیحی که خونده میشه، با استفاده از کد (n)cube به تابع ()cube فرستاده میشه. مقدار بازگشتی از تابع، جایگزین عبارت (n)cube گشته و با استفاده از 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
توابع میتونن بیش از یک دستور 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 در انتهای تابع قرار میگیره، میتونیم اون رو در هر نقطۀ دیگری از تابع قرار بدیم.
اعلانها و تعاریف تابع
به دو روش می تونیم توابع رو تعریف کنیم:
- توابع قبل از تابع ()main به طور كامل با بدنه مربوطه آورده بشن.
- راه دیگه ای که بیشتر رواج داره این گونه هست که ابتدا تابع اعلان بشه، سپس متن برنامۀ اصلی ()main بیاد، پس از اون تعریف کامل تابع قرار بگیره.
اعلان تابع با تعریف تابع فرق داره. اعلان تابع، فقط عنوان تابع هست که یک سمیکولن در انتهای آن قرار داره. تعریف تابع، متن کامل تابع هست که هم شامل عنوان هست و هم شامل بدنه. اعلان تابع شبیه اعلان متغیرهاست. یک متغیر قبل از این که به کار گرفته بشه باید اعلان کرد. تابع هم همین طور هست با این فرق که متغیر رو در هر جایی از برنامه میتونیم اعلان کنیم اما تابع رو باید قبل از برنامۀ اصلی اعلان کرد.
در اعلان تابع فقط بیان میشه که نوع بازگشتی تابع چیه، اسم تابع چیه و نوع پارامترهای تابع چیه. همینها برای کامپایلر کافی هست تا بتونه کامپایل برنامه رو آغاز کنه. سپس در زمان اجرا به تعریف بدنۀ تابع نیز احتیاج میشه که این بدنه در انتهای برنامه و پس از تابع ()main قرار میگیره.
فرق بین آرگومان و پارامتر
پارامترها متغیرهایی هستن که در فهرست پارامتر یک تابع نام برده میشن. پارامترها متغیرهای محلی برای تابع محسوب میشن؛ یعنی فقط در طول اجرای تابع وجود دارن. آرگومانها متغیرهایی هستن که از برنامۀ اصلی به تابع فرستاده میشون.
مثال: تابع ()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++ استاندارد به همین شکل پیادهسازی شدهاند و هنگامی که یکی از اون توابع رو در برنامههاتون به کار میبرید باید با دستور راهنمای پیشپردازنده، فایل اون توابع رو به برنامهتون ضمیمه کنید. این کار چند مزیت داره:
- اولین مزیت مخفیسازی اطلاعات هست.
- مزیت دیگه این هست که توابع مورد نیاز رو میتونیم قبل از این که برنامۀ اصلی نوشته بشه، جداگانه آزمایش کنیم.
- سومین مزیت این هست که در هر زمانی به راحتی میتونیم تعریف توابع رو عوض کنیم بدون این که لازم باشه برنامۀ اصلی تغییرکنه.
- چهارمین مزیت هم این هست که میتونیم یک بار یک تابع رو کامپایل و ذخیره کنیم و از این به بعد در برنامههای مختلفی از همان تابع استفاده کنیم.
تابع ()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 رو به دو طریق ایستا و پویا میتونیم مورد استفاده قرار بدیم.
متغیرهای محلی، توابع محلی
متغیر محلی، متغیری هست که در داخل یک بلوک اعلان میشه. این گونه متغیرها فقط در داخل همان بلوکی که اعلان میشن قابل دستیابی هستن. چون بدنۀ تابع، خودش یک بلوک هست پس متغیرهای اعلان شده در یک تابع متغیرهای محلی برای اون تابع هستن. این متغیرها فقط تا وقتی که تابع در حال کار هست وجود دارن. پارامترهای تابع هم متغیرهای محلی محسوب میشن.
مثال: تابع فاكتوریل – اعداد فاكتوریل رو در مثال قبل تر دیدیم. فاكتوریل عدد صحیح 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 هم محلی هست چون درون بدنۀ تابع اعلان شده هست.
همان گونه که متغیرها میتونن محلی باشن، توابع هم میتونن محلی باشن. یک تابع محلی تابعی هست که درون یک تابع دیگر به کار بره. با استفاده از چند تابع ساده و ترکیب اونها میتونیم توابع پیچیدهتری بسازیم.
این تابع، خودش از تابع دیگه که همان تابع فاکتوریل هست استفاده کرده. شرط به کار رفته داخل دستور 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++ برای مشخص کردن همچین توابعی از کلمۀ کلیدی void به عنوان نوع بازگشتی تابع استفاده میکنن یک تابع void تابعی هست که هیچ مقدار بازگشتی نداره و از اونجا كه یك تابع void مقداری رو برنمیگردونه، نیازی به دستور return نیست ولی اگه قرار باشه این دستور رو در تابع void قرار بدیم، باید اون رو به شکل تنها استفاده کنیم بدون این که بعد از کلمۀ return هیچ چیز دیگه بیاد. در این حالت دستور return فقط تابع رو خاتمه میده.
توابع بولی
خبلی از اوقات لازم هست که داخل برنامه، شرطی بررسی بشه. اگه بررسی این شرط به دستورات زیادی نیاز داشته باشه، بهتر هست که یک تابع این بررسی رو انجام بده. این کار مخصوصا هنگامی که از حلقهها استفاده میشه بسیار مفید هست. توابع بولی فقط دو مقدار رو برمیگردونن: 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)
بخشهایی از برنامه که به جزییات دست و پا گیر میپردازد و خیلی به هدف اصلی برنامه مربوط نیست رو میتونیم به توابع بسپریم. در چنین شرایطه سودمندی توابع محسوستر میشن. فرض کنید نرمافزاری برای سیستم آموزشی دانشگاه طراحی کردید که سوابق تحصیلی دانشجویان رو نگه میداره. در این نرمافزار لازم هست که سن دانشجو به عنوان یکی از اطلاعات پروندۀ دانشجو وارد بشه. اگه وظیفۀ دریافت سن رو به عهدۀ یک تابع بگذارید، میتوانید جزییاتی از قبیل کنترل ورودی معتبر، یافتن سن از روی تاریخ تولد و … رو در این تابع پیادهسازی کنید بدون این که از مسیر برنامۀ اصلی منحرف بشید.
تابع () PrintDate در مثال های قبلی هیچ چیزی به برنامۀ اصلی برنمیگردوند و فقط برای چاپ نتایج به کار میرفت. این تابع نمونهای از توابع خروجی هست؛ یعنی توابعی که فقط برای چاپ نتایج به کار میرن و هیچ مقدار بازگشتی ندارن.
توابع ورودی
توابع ورودی هم به همین روش کار میکنن اما در جهت معکوس. یعنی توابع ورودی فقط برای دریافت ورودی و ارسال اون به برنامۀ اصلی به کار میرن و هیچ پارامتری ندارن.
مثال: تابعی برای دریافت سن كاربر – تابع سادۀ زیر، سن کاربر رو ازش میگیره و مقدار دریافت شده رو به برنامۀ اصلی میفرسته. این تابع تقریباً هوشمند هست و هر عدد صحیح ورودی غیر منطقی رو رد میکنه و به طور مکرر درخواست ورودی معتبر میکنه تا این که یک عدد صحیح در محدودۀ 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 فراخوانی کنیم. در هر یک از این حالتها، عبارت درون پرانتز به شکل یک مقدار تکی برآورد میشه و حاصل اون مقدار به تابع فرستاده میشه.
ارسال به طریق ارجاع
ارسال به طریق مقدار 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
عملگر ارجاع
عملگر ارجاع & موجب میشه كه به جای 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) به هر حال کامپایلر هیچ فرقی بین این دو اعلان نمیگذاره و شکل نوشتن عملگر ارجاع کاملا اختیاری و سلیقهای هست.
ارسال به طریق مقدار و ارسال به طریق ارجاع
مثال زیر ارسال به طریق مقدار 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 فقط میتونه یك مقدار رو برگردونه. بنابراین اگر باید بیش از یك مقدار برگشت داده شه، این كار رو پارامترهای ارجاع انجام میدن.
ارسال از طریق ارجاع ثابت
ارسال پارامترها به طریق ارجاع دو خاصیت مهم داره:
- اول این که تابع میتونه روی آرگومان واقعی تغییراتی بده.
- دوم این که از اشغال بیمورد حافظه جلوگیری میشه.
روش دیگه هم برای ارسال آرگومان وجود داره:
ارسال از طریق ارجاع ثابت. این روش مانند ارسال از طریق ارجاع هست با این فرق که تابع نمیتونه محتویات پارامتر ارجاع رو دستکاری کنه و فقط اجازۀ خوندن اون رو داره. برای این که پارامتری رو از نوع ارجاع ثابت اعلان کنیم باید عبارت 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
تابعی که به شکل بیواسطه تعریف میشه، ظاهری شبیه به توابع معمولی داره با این فرق که عبارت 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);
}
وقتی كامپایلر کد واقعی تابع رو جایگزین فراخوانی اون میکنه، میگوییم که تابع بیواسطه، باز میشه.
دیدگاهتان را بنویسید