زبان برنامه نویسی سی/پارامترها و آرگومانها
پارامتر ( 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 میفرستد که مثلاً اگر عدد ۵ را وارد کنیم در خروجی خطدستوری مقدار ۱۲۵ را دریافت خواهیم نمود
امّا ممکن است بخواهید پارامتر خود را از نوع آرایه تعریف کنید تا چند یا چندین آرگومان را به آن بفرستید تا مورد پردازش قرار بگیرند و سپس خروجی مطلوب را دریافت کنید . برای این کار میتوانید داخل کروشههای آرایه برای اندیس آرایه تعداد عنصرها را بنویسید تا به همان تعداد به آن آرگومان بفرستید یا اینکه اندیسی ننویسید ولی حتماً تعداد معینی از عنصرها را مورد پردازش قرار داده و به همان تعداد نیز به آن آرگومان بفرستید . به مثال زیر دقت کنید :
#include<stdio.h>
long double getAverage(long double nums[])
{
long double sum = 0.0;
for(int i = 0; i <6; i++)
{
sum += nums[i];
}
long double result = sum/6;
return result;
}
int main(void)
{
long double numbers[6];
for(int i = 0; i < 6; i++)
{
scanf("%Lf", &numbers[i]);
}
long double res = getAverage(numbers);
printf("The Average is %Lf", res);
return 0;
}
در این مثال یک تابع از نوع اعشاری بلند ( با دقت بالا ) به نام getAverage به معنی « معدل را به دست بیاور » تعریف نموده ایم که خروجی آن از نوع اعشاری بلند است و یک پارامتر از نوع اعشاری بلند که البته آرایه نیز هست ( بدون اندیس ) به نام nums به معنی اعداد برای آن تعریف نمودهایم . داخل بدنه تابع یک متغیر از نوع اعشاری بلند به نام sum با مقدار 0 تعریف نموده ایم که به معنی مجموع می باشد . سپس توسط حلقه for شش بار sum با nums که دریافت می شود جمع می شود که اندیس nums همان i است که از 0 شروع می شود و یک واحد یک واحد افزایش می یابد تا به 5 برسد که می شود ۶ بار . سپس یک متغیر اعشاری بلند با نام result تعریف کرده ایم که مقدار آن تقسیم مقدار sum ( مجموع ) به مقدار ۶ است که میانگین و معدل اعداد وارد شده به عنوان آرگومان به تابع می باشد و در پایان تابع getAverage مقدار result را باز می گرداند . در قسمت بعد نیز در تابع اصلی برنامه ( main ) که پارامتری ندارد و به جای آن کلیدواژه void را نوشته ایم ، یک متغیر اعشاری بلند با نام numbers از نوع آرایه با اندیس ۶ تعریف نموده ایم . سپس یک حلقه for شش بار از خروجی خطدستوری کاربر توسط صفحه کلید ( Keyboard ) مقادیر عددی را دریافت می کند . و بعد از آن نیز یک متغیر اعشاری بلند به نام res مخفف result به معنی نتیجه تعریف نموده ایم که مقدار آن با فراخوانی تابع getAverage که آرگومان numbers را دریافت می کند و به دست می آید می باشد که سپس با تابع کتابخانهای printf چاپ کرده ایم که میانگین ، مقدار رو به رو میباشد که مقدار همان مقدار res و میانگین ۶ عدد وارد شده توسط کاربر در ورودی خطدستوری می باشد . در پایان نیز چون برنامه با موفقیت به اتمام رسیده و باید بسته شود نوشته ایم ;return 0 تا کامپایلر برنامه را خاتمه داده و منابع اشغال شده در سیستم را آزاد کند . در مورد پارامترها و آرگومانهای آرایهای چند بعدی نیز به همین شکل و فقط با حلقههای دیگر چندگانه و تو در تو آنها را دریافت کرده و مورد پردازش قرار می دهیم و به همان تعداد نیز به آن آرگومان می فرستیم
حال برنامه بالا را به جای پارامتر آرایهای با پارامتری از نوع ساختمان مینویسیم تا نحوه نوشتن پارامتر ساختمان را نیز فرا بگیرید . مثال :
#include<stdio.h>
struct lessons
{
double math;
double physics;
double chemistry;
double biology;
};
double getAverage(struct lessons firstinst)
{
double average = ((firstinst.math + firstinst.physics + firstinst.chemistry + firstinst.biology)/4);
return average;
}
int main(void)
{
struct lessons secondinst;
printf("Enter degrees :\n");
scanf("%lf%lf%lf%lf", &secondinst.math, &secondinst.physics, &secondinst.chemistry, &secondinst.biology);
printf("The Average is %lf", getAverage(secondinst));
return 0;
}
ابتدا فایل سرآیند stdio را که شامل توابع کتابخانهای printf و scanf برای چاپ خطدستوری و دریافت خطدستوری میباشد ضمیمه نموده ایم تا از آنها در برنامه خود استفاده نمائیم . سپس یک ساختمان با نام lessons به معنی دروس تعریف نموده ایم که شامل ۴ متغیر از نوع اعشاری زیاد ( double ) میباشد تا مطمئن شویم که مقادیر اعشاری با مکان ارزشی کمتر از بین نمی روند ( نیازی به اعشار خیلی زیاد و بلند یعنی long double نیست چون دروس نهایتاً دو رقم اعشار دارند ) که اولی math ریاضی می باشد و بعدی physics فیزیک و بعد از آن chemistry که همان شیمی می باشد و آخری biology یعنی زیستشناسی می باشد . در قسمت بعد یک تابع از نوع اعشاری زیاد و دوبله با نام getAverage به معنی « معدل را به دست بیاور » را تعریف نموده ایم که یک پارامتر از نوع ساختمان و با اسم ساختمان خودمان که همان lessons می باشد دارد که یک نمونه instance از آن به نام firstinst ساخته ایم . داخل بدنه تابع ، متغیر اعشاری دوبله با نام average هر ۴ متغیر ساختمان دروس را با هم جمع می کند و تقسیم بر ۴ می کند و مقدار آن را در خود جای می دهد . تابع مقدار average را باز می گرداند . در تابع اصلی برنامه یعنی main با ساختمان lessons یک نمونه دیگر از ساختمان خود به نام secondinst به معنی نمونه دوم ساخته ایم تا مقادیر خود را دریافت نموده و به عنوان آرگومان به تابع getAverage بفرستیم . تابع کتابخانهای printf چاپ می کند نمرات را وارد کنید ؛ سپس تابع کتابخانهای scanf چهار مقدار اعشاری دوبله را دریافت میکند که هر کدام مقادیر ریاضی و فیزیک و شیمی و زیستشناسی هستند که البته هنوز به فصل فایلهای سرآیند و مبحث stdio نرسیدهایم اما تابع scanf توسط اشارهگرها نوشته می شود و توسط عملگر آدرسدهی مقادیر را دریافت می نماید . در خط بعدی در خروجی خطدستوری چاپ کردهایم مقدار معدل نمرات مقدار رو به رو می باشد که مقدار ، همان مقدار تابع getAverage می باشد که به آن نمونه ساختمانی secondinst را فرستادهایم در پایان نیز منابع اشغال شده توسط برنامه را آزاد کرده ایم