در این قسمت می‌خواهیم در مورد تابع در سی پلاس پلاس صحبت کنیم. بد نیست ابتدا در مورد این موضوع صحبت کنیم که چرا به تابع نیاز داریم؟
با یک مثال بحثمان را جلو می‌بریم. در مثال 4 از آموزش ساختار تکرار کد مربوط به محاسبه فاکتوریل یک عدد را نوشتیم. حال فرض کنید که مساله به این صورت باشد:
برنامه ای بنویسید که از ورودی n و k را دریافت کند و ترکیب k از n را محاسبه کند

دانشگاه برنامه نویسان جلسه 4 - تابع در سی پلاس پلاس

شما اگر الگوریتم محاسبه فاکتوریل را بلد باشید نوشتن ترکیب k از n سخت به نظر نمی‌رسد. کافی است که فاکتوریل اعداد n، k و (n-k) را کدنویسی کنید. اما همانطور که در ذهنتان کد این برنامه را مرور می‌کنید خواهید فهمید که شما الگوریتم فاکتوریل را باید سه بار (و برای سه عدد مختلف) بازنویسی کنید.اینجاست که شما به مفهوم تابع نیاز پیدا میکنید.شما می‌توانید الگوریتم مربوط به فاکتوریل را در تابعی با همین نام (مثلا تابع fact) کدنویسی کنید و این تابع به ازای ورودی های متفاوت فراخوانی کنید. مثلا fact(3) عدد 6 را محاسبه می‌کند و fact(5) عدد 120.

اگر به یاد داشته باشید قبلا از توابع pow و sqrt استفاده کرده‌ایم. در c++ توابع آماده ای وجود دارند که شما نیازی به دانستن نحوه پیاده سازی آنها ندارید. فقط کافیست الگوی تابع را بلد باشید به این معنی این  که نام تابع را بلد باشید، بدانید چه ورودی هایی دارند و خروجی آن به چه صورت است.

تعریف الگوی تابع در سی پلاس پلاس

برای تعریف و پیاده سازی تابع باید با الگوی تابع آشنا شوید. در الگوی تابع هر آنچه که در مورد کار کردن با یک تابع نیاز است بیان می‌شود. برای درک بهتر موضوع با مثال تابع فاکتوریل ادامه می‌دهیم. الگوی تابع فاکتوریل به صورت زیر است:

int fact(int x);

اطلاعاتی که از این الگو بدست می‌آید این است که تابعی به نام fact داریم که یک ورودی از نوع عدد صحیح (int) دریافت می‌کند، فاکتوریل آن را محاسبه می‌کند و خروجی که برمی‌گرداند از جنس عدد صحیح (int) است. پس الگوی تابع به صورت زیر است:

        (پارامترهای ورودی) نام تابع نوع برگشتی

برای درک بهتر موضوع الگوی توابع pow و sqrt را ببینید:

double pow(double x, double y);
double sqrt(double x);

همانطور که در الگوی تابع pow می‌بینید یک تابع می‌تواند بیشتر از یک ورودی داشته باشد.

پیاده سازی تابع:

برای پیاده سازی تابع ابتدا باید الگوی تابع را قبل از int main() نوشت. توجه داشته باشید که در انتهای الگو سیمی کالن فراموش نکنید. پیاده سازی تابع نیز باید بعد از int main(){  }  نوشت. بهتر است توضیحات بیشتر را در مثال زیر دنبال کنید:

مثال 1:  برنامه ای بنویسید که از ورودی n و k را دریافت کند و ترکیب k از n را محاسبه کند:

#include <iostream>
#include <cmath>

using namespace std;

int fact (int x);

int main()
{

	int n,k;
	cin>>n>>k;
	
	cout<<fact(n)/(fact(k) * fact(n-k));
				
   	return 0;
}
int fact (int x){
	int i,f=1;
	for(i=1;i<=x;i++)
		f = f*i;
		
	return f;	 
}

توضیح کد:
1- همانگونه که قبلا توضیح داده شد در خط 6 الگوی تابع معرفی شده است.
2- در خط 14 سه بار تابع fact فراخوانی شده است. در مورد فراخوانی تابع صحبت خواهد شد.
3- در خط 18 تا 24 پیاده‌سازی تابع صورت گرفته است. با دقت در نحوه پیاده سازی می‌بینید که در خط اول پیاده سازی همان الگوی تابع (که در خط 6 نوشته شده است) نوشته شده و به جای سیمی کالن، آکولاد باز و دستوارت تابع نوشته شده است.

نگاهی دقیق تر به پیاده سازی و فراخوانی تابع
بیایید نگاهی دقیق تر به این مثال داشته باشیم. در خط 6 الگوی تابع معرفی شده است. در خط 12 از ورودی n و k دریافت می‌شود و در خطا 14 سه بار تابع fact فراخوانی می‌شود. فراخوانی تابع به این معنی است که یک (یا چند) ورودی مشخص به تابع ارسال میکنیم. تابع بر اساس ورودی دستورات را اجرا می‌کند و در نهایت مقدار محاسبه شده را برمیگرداند. مثلا فرض کنید n=5 باشد. در فراخوانی fact(n) (در خط 14) عدد 5 به تابع فاکتوریل در 18 ارسال می‌شود. تابع fact در خط 18، عدد 5 را در متغیر x دریافت می‌کند، فاکتوریل آن را در f ذخیره می‌کند و در نهایت مقدار f (که 120 است) را به جایی برمیگرداند که این تابع فراخوانی شده است. (یعنی fact(n) در خط 14) همین فرایند برای fact(k) و fact(n-k) نیز تکرار می‌شود.

دانشگاه برنامه نویسان جلسه 4 - تابع در سی پلاس پلاس

نکات:
1- تعداد و نوع پارامترها در الگوی تابع، فراخوانی تابع و پیاده سازی تابع باید مثل هم باشند. در تصویر بالا این موارد با هایلایت صورتی رنگ مشخص شده اند.
2- در پیاده‌سازی تابع نوع برگشتی و مقداری که return می‌شود باید از یک نوع باشد. این مورد در تصویر بالا با هایلایت سبز رنگ مشخص شده است. نوع برگشتی تابع fact از جنس int است و متغیر f که برگشت داده می‌شود نیز از جنس int است.
3- اگر تابع نیاز به برگشت مقداری نداشته باشد نوع برگشتی را void در نظر میگیریم. این نوعِ برگشتی را در مثالهای بعدی خواهید دید.

یوتیوب

مثال 2- برنامه‌ای بنویسید که اعداد اول کوچکتر از 100 را چاپ کند.

در مثال 8 ساختار تکرار الگوریتم تشخیص عدد اول به صورت کامل توضیح داده شده است. در این مثال باید تابعی بنویسیم که یک عدد دریافت کنید و تشخیص دهد اول است یا خیر. خروجی این تابع از جنس bool است زیرا برای هر عدد دریافتی مشخص خواهد کرد که عدد اول است یا خیر، پس خروجی یا true است یا false.

#include <iostream>
#include <cmath>
using namespace std;

bool isPrime(int n);

int main()
{
	for (int i=2; i<100; i++)
		if (isPrime(i))
			cout<<i<<" ";

	return 0;
}

bool isPrime(int n)
{
	for (int i=2; i<=sqrt(n);i++)
		if (n%i==0)
			return false;
	return true;
}


توضیحات کد:
1- در خط 5 الگوی تابع نوشته شده است. این الگو نشان می‌دهد که نام تابع isPrime است، یک ورودی از جنس int دریافت می‌کند و در نهایت یا true و یا false برمیگرداند. به این معنی که عدد ورودی یا اول است یا خیر.
2- در خط 9 تا 11 همه اعداد 2 تا 99 تولید می‌شود و با چنانچه فراخوانی تابع isPime(i) مقدار true برگرداند به این معنی است که i اول است و بنابراین در خروجی چاپ می‌شود.
3- در خط 16 تا 22 پیاده‌سازی تابع isPrime نوشته شده است که کمی با روشی که قبلا حل شده فرق دارد. در این تابع عدد n دریافت می‌شود و چنانچه تشخیص دهد عدد اول است مقدار false را برمیگرداند (خط 20) به این نکته توجه داشته باشید پس از اجرای خط 20 مقدار false برگشت داده می‌شود (به کجا؟ جاییکه فراخوانی شده، یعنی خط 10) و کلا از پیاده سازی تابع fact خارج می‌شویم. یعنی بقیه دستورات تابع اجرا نمی‌شود.
4- طبق قضیه ای در ریاضی برای تشخیص اول بودن یک عدد نیاز نیست همه اعداد کوچکتر از عدد را بررسی کنیم. اعداد کوچکتر از رادیکال آن عدد بررسی شود کفایت می‌کند.

مثال 3: برنامه ای‌ بنویسید که دو نقطه در صفحه مختصات از ورودی دریافت کند و فاصله این دو نقطه را چاپ کند.

برای دریافت دو نقطه به 4 ورودی نیاز داریم. هر نقطه یک x و یک y دارد. بدیهی است که همه برنامه ها را می‌توان بدون استفاده از تابع پیاده سازی کرد ولی توجه داشته باشید که استفاده از تابع موجب کاهش پیچیدگی و کدنویسی و همچنین افزایش خوانایی برنامه می‌شود.

#include <iostream>
#include <cmath>
using namespace std;

double distance (double x1, double y1, double x2, double y2);

int main()
{	double x1,y1,x2,y2;
	cin>>x1>>y1>>x2>>y2;
	cout<<distance(x1,y1,x2,y2)<<endl;

	return 0;
}

double distance (double x1, double y1, double x2, double y2)
{
	double d=sqrt(pow(x1-x2,2)+pow(y1-y2,2));
	
	return d;
}


مثال 4: فلوچارت برنامه‌ای بنویسید که 1398مین عدد اول را چاپ کند.

#include <iostream>
#include <cmath>
using namespace std;
 
bool isPrime(int n);
 
int main()
{
	int i=2,counter = 0;
	while(counter<1398){
		if(isPrime(i))
			counter++;
		i++;	
	}
	
	cout<<i-1;
 
	return 0;
}
 
bool isPrime(int n)
{
	for (int i=2; i<=sqrt(n);i++)
		if (n%i==0)
			return false;
	return true;
}

توضیحات کد:
در این مثال هم از تابع isPrime استفاده شده است. یکی از مزایای کار با توابع همین است. یکبار می‌نویسید و بارها استفاده می‌کنید!
با استفاده از متغیر i یکی یکی اعداد تولید می‌شوند و در خط 11 چک می‌شوند که آیا عدد اول است یا خیر. اگر اول بود به counter یک واحد اضافه می‌شود. بنابراین تا زمانی که counter به 1398 نرسد ساختار تکرار باید تکرار شود. نکته مهم این است که شما در نهایت i-1 را در خروجی چاپ می‌کنید (خط 16) چرا؟ زیرا وقتی که در خط 11 ، 1398 امین عدد اول پیدا می‌شود (که همان i است) و یک واحد به counter اضافه میشود، مقدار counter برابر 1398 می‌شود و در خط 13 یک واحد به i اضافه می‌شود. بنابراین بعد از خروج از ساختار تکرار باید i-1 چاپ شود.

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

#include <iostream>
using namespace std;
void convert(int s);

int main()
{
	int seconds;
	cin >> seconds;
	convert(seconds);
	return 0;
}

void convert(int s)
{
	int second, h, m;
	second = s % 60;
	h = s / 3600;
	m = (s / 60) % 60;
	cout << "Time is: " << h << ":" << m << ":" << second;
}

توضیح کد:
همانطور که در الگوی تابع convert میبینید نوع برگشتی این تابع از نوع void است. وقتی که یک تابع مقداری را برنگرداند (یعنی دستور return نداشته باشد) نوع بازگشتی را از جنس void تعریف می‌کنیم.

ارسال پارامتر به تابع

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

#include <iostream>
using namespace std;
void plus(int a,int b);

int main()
{
	int a=5,b=5;
	plus(a,b);
	cout<<a<<b;
	
	return 0;
}

void plus(int a,int b)
{
	a++;
	b++;
}

در تابع plus، از ورودی a و b را دریافت می‌کند و به هر کدام یک واحد اضافه می‌شود. با فرض مقدار اولیه 5 برای a و b (خط 7) و فراخوانی تابع در خط 8 انتظار بی جایی است که فکر کنید در خط 9 مقدار 6 برای a و b چاپ شود. چرا؟ چون ما از روش ارسال پارامتر با مقدار استفاده کردیم. یعنی در ارسال پارامتر به تابع plus یک کپی از a و b به این تابع ارسال می‌شود و هر تغییری که بر روی این کپی انجام شود هیچ تاثیری بر روی مقادیر a و b موجود در int main ندارد. در مبحث اشاره گرها و با معرفی ارسال پارامتر با آدرس بیشتر توضیح می‌دهیم.

اگه دوست دارید به آموزش کامل سی پلاس پلاس دسترسی داشته باشید میتونید از لینک زیر این دوره رو دریافت کنید: