زبان برنامه نویسی سی/پارامترها و آرگومان‌ها

ویکی‎کتاب، کتابخانهٔ آزاد
پرش به ناوبری پرش به جستجو
Gnome-go-last.svg Gnome-go-first.svg

پارامتر ( parameter ) به مفهوم تغییر یک متغیر در ازای یک متغیر دیگر است ، در علوم ریاضی ( شامل خود ریاضیات و البته فیزیک و شیمی ) متغیر را به همراه یک علامت تساوی نوشته و در رو به روی آن پارامترهای آن را که به همراه نوع و مقدار نسبت آنها به هم و در نهایت متغیر سمت چپ تساوی است می‌نویسیم . اما در زبان‌های برنامه‌نویسی متداول و از جمله زبان C و خانواده آن این وابستگی ( پارامتر ) به شکل دیگری نوشته می‌شود . شما یک نوع داده را که قرار است بازگردانده شود می‌نویسید سپس نامی برای آن انتخاب می‌کنید و در مقابل جفت پرانتزهای باز و بسته آن ، نوع داده و نام پارامترها را می‌نویسید تا بعد از نوشته شدن تابع ؛ هر جا تابع احضار شد و داده‌هایی به عنوان آرگومان ( argument ) به آن فرستاده شدند ( پاس داده شدند pass ) داده‌ها را دریافت کرده و بر روی آن مقادیر پردازشی که در داخل بدنه تابع تعریف شده است را انجام دهد و سپس حاصل را بازگرداند

return-data-type-of-function function-name( data-type parameter-name-1, data-type parameter-name-2, ...)

{

//body of function

processing of data and parameters

return data-type-of-function;

}

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

#include <stdio.h>

void addOne ( int notadd, int * ptr )
{
	notadd++;
	(*ptr)++;

	return;
}

int main()
{
	int i = 10, j = 10;
	addOne( i, &j);

	printf("%d , %d", i, j);
	return 0;
}

در این مثال تابع addOne از نوع داده پوچ است بنابراین مجاز نیستیم تا در انتهای آن مقداری را بازگردانیم اما به صورت استاندارد نوشته‌ایم ;return که ۲ پارامتر دارید ، اولی notadd که از نوع صحیح است و دومی ptr که از نوع اشاره‌گر صحیح است . داخل بدنه تابع notadd توسط عملگر افزایش یک واحد افزایش می‌یابد و همین طور پارامتر ptr که اشاره‌گر است و به متغیری اشاره نمی‌کند اما به صورت مستقیم شده توسط عملگر افزایش ، یک واحد افزایش پیدا می‌کند ( دقت کنید که اگر پرانتزهای آن را نمی‌گذاشتیم تبدیل نمی‌شد به مقدار یک صحیح و فقط آدرس اشاره‌گر افزایش یافته و به جلو می‌رفت ) در تابع اصلی برنامه یعنی main دو متغیر به نام‌های i و j با مقدار ۱۰ تعریف کرده‌ایم . تابع addOne اولی را به صورت صحیح دریافت می‌کند و دومی را به صورت اشاره‌گر که به واسطه عملگر آدرس‌دهی متغیر j را به آن فرستاده‌ایم ( پاس داده‌ایم ) و در نهایت توسط تابع کتابخانه‌ای printf مقدار هر ۲ متغیر را در خروجی خط‌دستوری چاپ نموده‌ایم و چون تابع main مقداری را قرار نیست بازگرداند و در سیستم باقی بماند نوشته‌ایم return 0 تا به کامپایلر اعلام کنیم که خروجی‌ای ندارد و برنامه به پایان رسیده تا منابع اشغال شده توسط برنامه آزاد گردند . اما مقدار i و j با اینکه هر دو ۱۰ است اما در خروجی مقدار i مقدار ۱۰ بوده و مقدار j مقدار ۱۱ ، چون اولی در تابع addOne کپی شده است و در آرگومان i تغییری ایجاد نمی‌شود اما دومی به واسطه عملگر آدرس‌دهی ( & ) به تابع addOne فرستاده شده است که به جای پارامتر ptr قرار می‌گیرد و چون ptr اشاره‌گر است مقدار j را تغییر می‌دهد

حتماً به یاد دارید که برای اشاره کردن به یک اشاره‌گر باید یک اشاره‌گر به اشاره‌گر تعریف کنید . برای فرستادن یک اشاره‌گر به عنوان آرگومان به یک تابع نیز باید پارامتر آن را از نوع اشاره‌گر به اشاره‌گر تعریف کنید ( و البته برای اشاره به اشاره‌گر به اشاره‌گر یک اشاره‌گر به اشاره‌گر به اشاره‌گر تعریف می‌کنیم و همین طور الی آخر ) . مثال :

#include <stdio.h>

void addOne ( int ** ptr )
{
	(**ptr)++;

	return;
}

int main()
{
	int i = 10, * ptr2 = &i;
	addOne( &ptr2 );

	printf("%d", *ptr2);
	return 0;
}

در مثال بالا ( که همانند مثال پیشین است ، می‌خواهیم مقدار متغیر i را تغییر بدهیم که اشاره‌گر ptr2 به آن اشاره می‌کند و اشاره‌گر ptr2 را به تابع addOne فرستاده‌ایم که یک پارامتر از نوع اشاره‌گر به اشاره‌گر دارد و مقدار آن را توسط عملگر افزایش ، یک واحد افزایش می‌دهد ، بنابراین با فراخوانی تابع addOne پارامتر ptr به ptr2 اشاره می‌کند که به i اشاره می‌کند و مقدار آن را تغییر می‌دهد و در خروجی خط‌دستوری می‌توانیم مقدار ۱۱ را مشاهده نمائیم

همچنین برای فرستادن یک تابع به عنوان آرگومان به تابعی دیگر باید تابعی که می‌خواهد تابع دیگر را دریافت کند پارامتری از نوع اشاره‌گر تابع داشته باشد تا بتوان تابع دیگر را با عملگر آدرس‌دهی ( امپرسند & ) به آن فرستاد . دقت کنید که برای تعریف هر تابع به عنوان پارامتر باید نوع داده را بنویسید در مقابل آن یک جفت پرانتز باز و بسته که داخل آن یک عملگر اشاره‌گر ( استریسک * ) به همراه نام یک تابع به عنوان پارامتر نوشته و در مقابل آن یک جفت پرانتز باز و بسته دیگر بنویسید که دارای نوع داده همسان با تابعی که می‌خواهد دریافت کند باشد به علاوه نام پارامتر دل به خواه . به مثال زیر دقت کنید :

#include <stdio.h>

int take(void)
{
	int a;
	scanf("%d", &a);
	
	return a;
}

int not(int (*func)(void))
{
	if ((*func)() == 0)
	 return 1;
	else
	 return 0;
}

int main()
{
	printf("%d", not(&take));
	
	return 0;
}

در مثال بالا یک تابع با نام take داریم که از نوع صحیح می‌باشد و پارامتری را پذیرا نیست ( که نوشته‌ایم void ) در داخل آن یک متغیر با نام a اعلان شده است که در خط بعدی توسط مقداری که کاربر وارد می‌کند تعریف می‌شود ( که این کار را توسط تابع کتابخانه‌ای scanf انجام داده‌ایم که اول نوع داده مشخص می‌شود سپس توسط عملگر آدرس‌دهی متغیر مورد نظر را دریافت می‌نماید که در مبحث فایل سرآیند stdio مفصلاً آن را تعریف نموده و تشریح خواهیم کرد ) سپس تابع not از نوع صحیح را تعریف نموده‌ایم که یک پارامتر از نوع داده تابع از نوع صحیح و بدون پارامتر ( به همراه کلیدواژه void ) را می‌پذیرد . که اگر پارامتر که یک تابع می‌باشد که فراخوانی خواهد شد مقدار بازگردانده شده آن 0 باشد مقدار 1 را باز می‌گرداند و در غیر این صورت مقدار 0 را ( یعنی اگر مقداری وجود نداشته باشد آن را موجود می‌کند و اگر موجود باشد آن را 0 و ناموجود می‌کند ) در انتها نیز تابع اصلی برنامه مقدار تابع not را که تابع take را دریافت می‌کند در خروجی خط‌دستوری چاپ نموده‌ایم که مقداری را از کاربر خواهد گرفت و در صورتی که 0 باشد 1 را چاپ می‌کند و اگر عددی دیگر باشد 0 را چاپ می‌کند و در پایان نیز منابع سیستم را آزاد نموده‌ایم ( با دستور ;0 return )

نکته : در کامپایلرهایی با استانداردهای قدیمی شما فقط مجاز به فرستادن یک تابع به تابعی دیگر با پارامتری از نوع اشاره‌گر هستید ( البته همیشه به همراه تعداد و نوع پارامترهای تابعی که قرار است دریافت شود ) اما در کامپایلرهایی با استانداردهای جدید شما مجاز به فرستادن تابع به تابعی دیگر بدون پارامتر اشاره‌گر نیز هستید ؛ اما بهتر است همواره از روش فوق که نوشته گردید استفاده کنید چون هم با همه استانداردها سازگار است و هم روش صحیح فرستادن یک تابع به تابعی دیگر است ( و شما همچنان باید تعداد و نوع پارامترهای تابعی که قرار است فرستاده شود را به ترتیب و درست اعلان کنید )

یک مثال دیگر :

#include <stdio.h>

int sum(int firstsummand, int secondsummand)
{
   return firstsummand + secondsummand;
}

int sub(int minuend, int subtrahend)
{
	return minuend - subtrahend;
}

int mul(int multiplier, int multiplicand)
{
   return multiplier * multiplicand;
}

int div(int dividend, int divisor)
{
   return dividend / divisor;
}

int calc(int (*execute)(int, int), int a, int b)
{
   return (*execute)(a, b);
}

int main()
{

   printf("%d , %d , %d , %d", calc(&sum, 10, 12), calc(&sub, 9, 3), calc(&mul, 3, 10), calc(&div, 10, 2));
   
   return 0;
}

در این مثال ۴ تابع صحیح تعریف کرده‌ایم که هر کدام ۲ پارامتر صحیح می‌پذیرند ، اولی پارامترهای خود را جمع می‌کند ، دومی تفریق ، سومی ضرب می‌کند و چهارمی تقسیم . سپس یک تابع با نام calc تعریف نموده‌ایم که ۳ پارامتر دارد ، اولی یک تابع از نوع صحیح که ۲ پارامتر از نوع صحیح دارد و دومی یک صحیح با نام a و سومی یک صحیح دیگر با نام b که داخل بدنه تابع calc تابع پارامتر به اجرا گذاشته می‌شود ( execute ) و ۲ پارامتر a و b را پردازش می‌کند . سپس در تابع اصلی برنامه main در خروجی خط‌دستوری ۴ مقدار صحیح را چاپ نموده‌ایم که اولی فراخوانی تابع calc با دریافت تابع sum به جای پارامتر execute و مقادیر ۱۰ و ۱۲ است ( که البته تابع با عملگر آدرس‌دهی دریافت شده است چون پارامتر تابع اشاره‌گر است ) و دومی فراخوانی تابع calc با دریافت تابع sub و مقادیر ۹ و ۳ و سومی دریافت تابع mul و مقادیر ۳ و ۱۰ و چهارمی که آخری است فراخوانی تابع calc که تابع div را که تقسیم است به عنوان پارامتر از نوع اشاره‌گر دریافت می‌کند که با عملگر آدرس‌دهی آن را دریافت کرده است و مقادیر ۱۰ و ۲ را به تابع div می‌فزستد تا ۱۰ به ۲ تقسیم شود و در پایان منابع اشغال شده سیسنم را آزاد نموده‌ایم و در خروجی خط‌دستوری می‌توانید مقادیر ۲۲ و ۶ و ۳۰ و ۵ را مشاهده کنید که خروجی پردازش‌های هر کدام از تابع‌های جمع ، تفریق ، ضرب و تقسیم هستند

مثال :

#include <stdio.h>

int addself(int num)
{
	int a = num;
	a = num + a;
	
	return a;
}

int multself(int (*adding)(int num), int num2)
{
	int res = (*adding)(num2);
	for(int i = 0; i < ( num2 - 2); i++)
	{
		res = res + num2;
	}
	
	return res;
}

int exponentiation(int (*mult)(int (*a)(int ), int ), int (*add)(int ), int num)
{
	int result = (*mult)((*add), num);
	int assistant = result, assistant2 = 0;
	for(int i = 0; i < num; i++)
	{
		assistant = result + assistant2;
		assistant2 = assistant;
	}
	result = assistant;
	
	return result;
}

int main()
{

	int anum;
	scanf("%d", &anum);
	printf("%d", exponentiation(&multself, &addself, anum));
   
   return 0;
}

در این مثال یک تابع با نام addself به معنی اضافه کردن به خود را تعریف نموده‌ایم که یک پارامتر با نام num دارد که از نوع صحیح است . داخل تابع addself متغیر صحیح a را تعریف نموده‌ایم که مقدار num را که بعداً قرار است یک آرگومان به آن فرستاده شود را دریافت می‌نماید . سپس a مقدار num به علاوه خود a که مقدار num را می‌گیرد می‌کند ( یعنی عدد را با خودش جمع می‌کند ) و خروجی تابع addself مقدار a می‌باشد . سپس یک تابع با نام multself تعریف کرده‌ایم که یعنی عدد را ضرب در خود می‌کند که تابع دیگر از نوع صحیح به عنوان پارامتر دارد که باید آن را به عنوان اشاره‌گر تعریف می‌نمودیم ( که این کار را کرده‌ایم ) که آن تابع متغیری از نوع صحیح را به عنوان پارامتر دارد ( تابع adding ) و دیگر پارامتر تابع multslef پارامتر num2 از نوع صحیح می‌باشد . داخل تابع multself متغیر res مخفف result به معنی نتیجه مقداری که دارد از فراخوانی پارامتر تابعی که به عنوان آرگومان به تابع multself فرستاده شود به دست می‌آید و بعد به تعداد num2 منهای ۲ تا res با num2 جمع می‌شود و در res ذخیره می‌شود که عددی را که به آن فرستاده شود داخل تابعی دیگر که به آن فرستاده شود می‌گذارد و سپس نتیجه را به تعداد عدد آرگومان منهای ۲ بار با خودش جمع می‌کند که می‌شود ضرب هر عدد در خودش . در پایان تابع multself مقدار res به عنوان خروجی بازگردانده می‌شود . سپس تابعی به نام exponentation داریم به معنی به نما رساندن یا همان توان ( که توان ۳ هر عدد را محاسبه می‌کند ) . در تابع exponentiation یک پارامتر از نوع تابع اشاره‌گر به تابع اشاره‌گری دیگر با نام mult وجود دارد که یک تابع اشاره‌گر را می‌پذیرد و یک عدد صحیح را و تابع اشاره‌گر خود یک عدد صحیح دیگر را می‌پذیرد ، پارامتر دیگر تابع exponentation تابع اشاره‌گری دیگر است که قرار است داخل تابع mult آن را فراخوانی کند و یک پارامتر دیگر از نوع صحیح با نام num که متغیر محلی result ( چون داخل تابع اعلان و تعریف شده است ، محلی می‌باشد ؛ در صورت فراموشی به کلاس‌های ذخیره در بخش داده‌ها مراجعه نمائید ) مقدار تابعی اشاره‌گر را که تابع اشاره‌گری دیگر را که پارامترهای تابع هستند دریافت می‌نماید که بعداً می‌توانیم تابعی را به داخل تابعی دیگر به عنوان آرگومان در کنار یک عدد به آن بفرستیم . سپس ۲ متغیر کمکی با نام‌های assitant و assitant2 تعریف نموده‌ایم که اولی مقدار result را به خود می‌گیرد و دومی مقدار « ۰ » را . سپس در حلقه for به تعداد num که پارامتر تابع exponentation می‌باشد و بعداً که احضار شود هر مقداری به آن فرستاده شود را دریافت می‌نماید ، assistant مقدار result را با assistant2 جمع می‌کند که assistnat2 مقدار اولیه 0 دارد بنابراین result با 0 جمع می‌شود و همان result می‌شود و سپس assistant2 مقدار assistant را می‌گیرد و در دفعه بعدی حلقه assistant2 مقدار قبلی assitant را با result جمع می‌نماید که در assitant ذخیره می‌شود و ادامه پیدا می‌کند تا جایی که مقدار شمارنده حلقه for به num منهای یکی برسد که آخرین اجرای حلقه خواهد بود ( چون شرط شده است i < num که اگر i مساوی num شود شرط برقرار نیست ) و سپس مقدار assistant به داخل متغیر result انتقال پیدا می‌کند و result مقدار assistant را می‌گیرد و به عنوان خروجی تابع تغیین شده است که تابع exponentation آن را به عنوان خروجی تحویل می‌دهد و عمل آن جمع حاضل ضرب یک عدد در خودش به تعداد خود با « ۰ » می‌باشد که عدد به توان ۳ می‌رسد . در پایان برنامه تابع اصلی برنامه main یک متغیر با نام anum یعنی یک عدد ( a number ) دارد که اعلان شده است و مقدار آن توسط تابع کتابخانه‌ای scanf از کاربر دریافت می‌شود که یک عدد از نوع صحیح است و سپس در تابع printf مقدار صحیحی قرار است که چاپ شود و جایگزین آن مقدار ، تابع exponentation می‌باشد که تابع multself را دریافت کرده و در داخل multself تابع addself را قرار می‌دهد و مقدار عددی anum را می‌گیرد که در واقع آن را داخل addself گذاشته و سپس خروجی آن به multself می‌فرستد و خروجی هر دو را به تابع exponentation می‌فرستد که مثلاً اگر عدد ۵ را وارد کنیم در خروجی خط‌دستوری مقدار ۱۲۵ را دریافت خواهیم نمود