برنامههای واقعی و تجاری بسیار بزرگتر از برنامههایی هستند که در درسهای قبلی آموزش ++C بررسی کردیم. در این درس درمورد تابع در ++C و انواع آنها صحبت خواهیم کرد. برای این که برنامههای بزرگ قابل مدیریت باشند، برنامهنویسان این برنامهها را به زیربرنامههایی بخشبندی میکنند. این زیر برنامهها تابع یا Function نامیده میشوند. توابع را میتوان به طور جداگانه کامپایل و آزمایش نمود و در برنامههای مختلف دوباره از آنها استفاده کرد.
توابع کتابخانهای ++C استاندارد
کتابخانۀ ++C استاندارد مجموعهای است که شامل توابع از پیش تعریف شده و سایر عناصر برنامه و ریاضیات است. این توابع و عناصر از طریق «سرفایلها» یا هدرها قابل دستیابیاند. قبلا برخی از آنها را استفاده کردهایم: ثابت INT_MAX که در <climits> تعریف شده ، تابع ()sqrt که در <cmath> تعریف شده است و غیره.
تابع جذر () sqrt در تابع در ++C
ریشۀ دوم یک عدد مثبت، جذر آن عدد است. تابع مانند یک برنامه کامل، دارای روند ورودی – پردازش – خروجی است هرچند که پردازش، مرحلهای پنهان است. یعنی نمیدانیم که تابع روی عدد ۲ چه اعمالی انجام میدهد که ۴۱۴۲۱/۱ حاصل میشود.
مثال: برنامه سادۀ زیر، تابع از پیش تعریف شده جذر را به کار میگیرد:
#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 فراخوانی شده و مقدار ۳ به آن فرستاده میشود. تابع مذکور نیز حاصل ۱.۷۳۲۰۵ را به عنوان پاسخ برمیگرداند. این فرایند در نمودار زیر نشان داده شده است.
متغیرهای x و y در تابع ()main تعریف شدهاند. مقدار x که برابر با ۳ است به تابع ()sqrt فرستاده میشود و این تابع مقدار ۱.۷۳۲۰۵ را به تابع ()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) مقدار ۱.۳۶۹۴۴ را برمیگرداند |
asin(x) | سینوس معکوس x (به رادیان) | asin(0.2) مقدار ۰.۲۰۱۳۵۸ را برمیگرداند |
atan(x) | تانژانت معکوس x (به رادیان) | atan(0.2) مقدار ۰.۱۹۷۳۹۶ را برمیگرداند |
ceil(x) | مقدار سقف x (گرد شده) | ceil(3.141593) مقدار ۴.۰ را برمیگرداند |
cos(x) | کسینوس x (به رادیان) | cos(2) مقدار -۰.۴۱۶۱۴۷ را برمیگرداند |
exp(x) | تابع نمایی x (در پایه e) | exp(2) مقدار ۷.۳۸۹۰۶ را برمیگرداند |
fabs(x) | قدر مطلق x | fabs(-2) مقدار ۲.۰ را برمیگرداند |
floor(x) | مقدار کف x (گرد شده) | floor(3.141593) مقدار ۳.۰ را برمیگرداند |
log(x) | لگاریتم طبیعی x (در پایه e) | log(2) مقدار ۰.۶۹۳۱۴۷ را برمیگرداند |
log10(x) | لگاریتم عمومی x (در پایه ۱۰) | log10(2) مقدار ۰.۳۰۱۰۳ را برمیگرداند |
pow(x,p) | x به توان p | pow(2,3) مقدار ۸.۰ را برمیگرداند |
sin(x) | سینوس x (به رادیان) | sin(2) مقدار ۰.۹۰۹۲۹۷ را برمیگرداند |
sqrt(x) | جذر x | sqrt(2) مقدار ۱.۴۱۴۲۱ را برمیگرداند |
tan(x) | تانژانت x (به رادیان) | tan(2) مقدار -۲.۱۸۵۰۴ را برمیگرداند |
توجه داشته باشید که هر تابع ریاضی یک مقدار از نوع 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 مقدار ۸ را برمیگرداند.
قسمت های تابع ساخت کاربر
یک تابع ساخت کاربر دو قسمت دارد:
۱-عنوان ۲- بدنه.
عنوان یک تابع به صورت زیر است:
(فهرست پارامترها) نام نوع بازگشتی
int cube(int x) { … بدنه تابع }
بدنۀ تابع، یک بلوک کد است که در ادامۀ عنوان آن میآید. بدنه شامل دستوراتی است که باید انجام شود تا نتیجۀ مورد نظر به دست آید. بدنه شامل دستور return است که پاسخ نهایی را به مکان فراخوانی تابع برمیگرداند.
نوع بازگشتی تابع ()cube که در بالا تعریف شد، int است. نام آن cube میباشد و یک پارامتر از نوع int به نام x دارد. یعنی تابع ()cube یک مقدار از نوع int میگیرد و پاسخی از نوع int تحویل میدهد.
دستور return دو وظیفۀ عمده دارد. اول این که اجرای تابع را خاتمه میدهد و دوم این که مقدار نهایی را به برنامه فراخوان باز میگرداند. دستور return به شکل زیر استفاده میشود:
return expression;
به جای expression هر عبارتی قرار میگیرد که بتوان مقدار آن را به یک متغیر تخصیص داد. نوع آن عبارت باید با نوع بازگشتی تابع یکی باشد. عبارت int main که در همۀ برنامهها استفاده کردهایم یک تابع به نام «تابع اصلی» را تعریف میکند. نوع بازگشتی این تابع از نوع int است. نام آن main است و فهرست پارامترهای آن خالی است؛ یعنی هیچ پارامتری ندارد.
برنامه آزمون در مقاله تابع در ++C
وقتی یک تابع مورد نیاز را ایجاد کردید، فوراً باید آن تابع را با یک برنامه ساده امتحان کنید. چنین برنامهای برنامه آزمون نامیده میشود. برنامه آزمون یک برنامه موقتی است که باید «سریع و کثیف» باشد؛ یعنی: لازم نیست در آن تمام ظرافتهای برنامهنویسی – مثل پیغامهای خروجی، برچسبها و راهنماهای خوانا – را لحاظ کنید. تنها هدف این برنامه، امتحان کردن تابع و بررسی صحت کار آن است.
مثال: یک برنامه آزمون برای تابع ()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--; } }
برنامه حاضر اعداد صحیح را از ورودی میگیرد و مکعب آنها را چاپ میکند این تا ۵ مرحله تکرار می شود.
هر عدد صحیحی که خوانده میشود، با استفاده از کد (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 برای محدود کردن حالتهای غیر ممکن استفاده شده است. در این حالتها، تابع مقدار ۰ را برمیگرداند تا نشان دهد که یک ورودی اشتباه وجود داشته است.
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
توابع ورودی نیز به همین روش کار میکنند اما در جهت معکوس. یعنی توابع ورودی فقط برای دریافت ورودی و ارسال آن به برنامه اصلی به کار میروند و هیچ پارامتری ندارند. مثال بعد یک تابع ورودی را نشان میدهد.
مثال: تابعی برای دریافت سن کاربر – تابع ساده زیر، سن کاربر را درخواست میکند و مقدار دریافت شده را به برنامه اصلی میفرستد. این تابع تقریباً هوشمند است و هر عدد صحیح ورودی غیر منطقی را رد میکند و به طور مکرر درخواست ورودی معتبر میکند تا این که یک عدد صحیح در محدوده ۷ تا ۱۲۰ دریافت دارد:
#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) را قرار دهد.استفاده از توابع بیواسطه میتواند اثرات منفی داشته باشد. مثلا اگر یک تابع بیواسطه دارای ۴۰ خط کد باشد و این تابع در ۲۶ نقطه مختلف از برنامه اصلی فراخوانی شود، هنگام کامپایل بیش از هزار خط کد به برنامه اصلی افزوده میشود. همچنین تابع بیواسطه می تواند قابلیت انتقال برنامه شما را روی سیستمهای مختلف کاهش دهد. به برنامه آزمون زیر نگاه کنید:
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 مانند توابع جذر، مثلثاتی و فایلهای سرآیند صحبت کردیم. در درس بعدی با مقاله آموزش کامل آرایه ها در سی پلاس پلاس در خدمت شما عزیزان خواهیم بود.