زبان برنامه نویسی سی/اعلان، تعریف و احضار تابع
در مبحث پیشین ، کلیت تابع نوشته شد ، در این مبحث به طور کامل به اعلان ، تعریف و احضار تابع پرداخته میشود . تابع در زبان برنامهنویسی سی C یک نوع داده است که دادههای دیگری را ( آرگومانها را ) دریافت میکند و جایگزین پارامترهای تعریف شده برای خود میکند تا آنها را مورد پردازش قرار دهد و خروجی و یا خروجیهایی را تحویل بدهد . البته برخی تابعها پارامتر ندارند و یا اینکه از نوع داده پوچ void هستند و خروجی نیز ندارند ( و فقط عمل خاصی را انجام داده و به پایان میرسند ) ولی به طور کلی ، تابع کمک میکند تا از زدن کد اضافی جلوگیری شود و هر بار که بخواهیم عملی را انجام دهیم و یا بر روی دادههایی پردازش کنیم ، دوباره عملیات را از نو ننویسیم ؛ بلکه تابع را تعریف کرده و با نوشتن نام تابع و دادههایی به عنوان آرگومان(ها) که جایگزین پارامتر(های) تابع میشوند ( که اصطلاحاً آن دادهها را به تابع میفرستیم ) دادهها مورد پردازش قرار گرفته و خروجی محصول به قسمت کد مربوطه تحویل داده شود . نمای کلّی تابع به صورت زیر است :
return-data-type function_name(data-type parameter_name1, data-type parameter_name2, ....)
{
data-processings;
return value;//for value
OR
return expression;//for expression
}
ابتدا نوع داده تابع مشخص میشود ( return-data-type ) سپس نام تابع نوشته میشود ( function-name ) که هر دوی آنها ضروری هستند سپس در مقابل نام تابع یک جفت پرانتز باز و بسته مینویسم که این نیز اجباری و ضروری میباشد سپس در صورتی که تابع پارامتر یا پارامترهایی دارد در داخل پرانتزها اعلان میشوند که برای این کار ابتدا نوع داده آنها ( data-type ) مشخص شده و سپس نام آنها ( parameter_name ) نوشته میشود ؛ پارامترها متغیرهایی هستند که بر روی آنها پردازش صورت میگیرد که داخل بدنه تابع یعنی داخل آکولادهای باز و بسته انجام میشود و خروجیِ پردازش ، توسط دستور return صادر میشود و به دادههایی نیز که بعداً در هنگام نوشتن نام تابع ( احضار و یا همان فراخوانی تابع ) به جای پارامترها نوشته میشوند تا آن دادهها را تابع ، پردازش کند آرگومان گفته میشود . دقت کنید که در صورتی که تابع شما پارامتری ندارد که بر روی آنها پردازشی صورت بگیرد بهتر است طبق استاندارد بنویسید retrun-data-type function-name (void) یعنی کلیدواژه نوع داده پوچ void را بدون نامی در داخل جفت پرانتز پارامترهای تابع بنویسید تا به کامپایلر بگوئید که تابع پارامتری ندارد و آرگومانی را نمیپذیرد
سپس داخل جفت آکولاد باز و بسته که بدنه تابع است ، پردازش را بر روی پارامترهای تابع با نام متغیرهای آنها انجام میدهیم که در صورتی که تابع پارامتری نداشته باشد فقط حکم یا حکمهایی را مینویسیم تا انجام شوند اما برای پردازش از دستورهای حلقه و شرطی و .... در کنار حکمها استفاده میکنیم تا پردازشهای لازم را بر روی پارامترها را انجام دهیم . سپس دستور return تعیین میکند که خروجی تابع چیست که باید نوع دادهای که برای دستور return نوشته میشود با نوع داده تابع یکی باشد . دقت کنید که با اجرای دستور return اجرای تابع پایان یافته و اجرای برنامه به بقیه کدهای خارج از تابع ، انتقال مییابد ؛ اما در صورتی که دستور return به همراه دستورهای شرطی نوشته شود میتوانید چند بار دستور return را بنویسید تا هر کدام از شرطها که برقرار بودند قطعه کد همان شرط که برفرار گردید به عنوان خروجی تابع صادر شود و برای دستور return نیز هم میتوانید از مقادیر استفاده کنید و هم نام متغیرهایی که مقدار دارند و هم از ترکیب آنها . افزون بر این ، شما میتوانید تابع را اعلان declaration کرده که به آن function prototype نیز گفته میشود ؛ یعنی میتوانید نوع داده تابع و نام تابع را نوشته و سپس پارامترهای آن را مشخص کنید که پارامترها باید نوع دادهشان مشخص باشد ولی میتوانند اسمی نداشته باشند تا در جای دیگری از متن منبع و برنامه آن را تعریف کرده و نام پارامترها را تعیین کنید ( و پارامترهای تابع تعریف شوند ) که روش اعلان و سپس تعریف تابع در جای دیگر برنامه روشی متدوال است
مثال :
#include <stdio.h>
int larger(int , int);
int main ()
{
int a = 100;
int b = 200;
int ret;
ret = larger(a, b);
printf( "Larger value is : %d\n", ret );
return 0;
}
int larger(int num1, int num2)
{
int result;
if (num1 > num2)
result = num1;
else
result = num2;
return result;
}
در این قطعه کد تابع larger به معنی « بزرگتر » ابتدا اعلان و prtotype شده که ۲ پارامتر صحیح int را میپذیرد و سپس در انتهای برنامه تعریف شده که پارامترهای صحیح آن num1 و num2 نام دارند و آنها را با هم مقایسه میکند که در صورتی که عدد اولی num1 بزرگتر بود آن را داخل result ذخیره میکند و اگر عدد دوم num2 بزرگتر بود عدد دوم را داخل result ذخیره میکند که در واقع مقایسه میکند که عدد اول بزرگتر است یا دومی و همان را به عنوان خروجی بازمیگرداند و تحویل میدهد ( که همان متغیر result است ) که در داخل تابع اصلی برنامه یعنی main دو متغیر به نامهای a و b با مقادیر ۱۰۰ و ۲۰۰ تعریف شدهاند و سپس متغیر ret اعلان شده که مقدار آن ، مقدار بازگشته احضار و فراخوانی تابع larger میباشد ( که تابع larger در مقابل آن و برای مقدار دهی به همراه دو متغیر a و b نوشته شده که به آن فرستاده شدهاند ) سپس در تابع کتابخانهای printf که در فایل سرآیند stdio.h تعریف شده ( که در ابتدای برنامه ، آن را به فایل برنامه خود ضمیمه نمودهایم ) نوشته شده مقدار بزرگتر ؟؟؟ میباشد که مقدار مجهول همان مقدار متغیر ret میباشد . همین قطعه کد را میتوانیم به صورت :
#include <stdio.h>
int larger(int num1, int num2)
{
int result;
if (num1 > num2)
result = num1;
else
result = num2;
return result;
}
int main ()
{
int a = 100;
int b = 200;
int ret;
ret = larger(a, b);
printf( "Larger value is : %d\n", ret );
return 0;
}
نیز بنویسم که دیگر تابع ، اعلان نشده تا در جای دیگر برنامه تعریف شود ، بلکه به یک باره تعریف شده است
کلاس ذخیره تابع ، میتواند static باشد که آن را یکتا میکند و از نگاه کامپایلر در فایلهای متن منبع دیگر غیر قابل دسترسی و رؤیت میباشد و با کلاس ذخیره static در متن منبعهای دیگر میتوان با همان اسم تابع ، تابعهای دیگری را تعریف نمود امّا این کار به مبتدیها توصیه نمیشود چون باعث گیج شدن میشود و ریسک زیادی برای ایجاد خطا دارد ؛ امّا به صورت پیش فرض ، تمام تابعها کلاس ذخیره extern دارند و کامپایلر در هنگام کامپایل پروژه ، به تمام تابعها دسترسی داشته و همه آنها برایش قابل رؤیت هستند . ننوشتن دستور return برای تابعی که نوع داده معین ( غیر پوچ ) دارد باعث به پایان نرسیدن تابع و هشدار دادن کامپایلر میشود که بدین شکل با فراخوانی تابع در متن منبع برنامه شما ، بعد از کامپایل و اجرا ، برنامه اجرایی به پایان نمیرسد و terminate نخواهد شد ( و میتوانید آن را در Process Monitor یا Process Manager سیستم عامل خود مشاهده کنید ) همچنین نوشتن دستور return بدون مقدار و موجودی و یا عبارتی ، در مقابل آن باعث میشود تا تابع مقداری ناشناس داشته و غیر قابل استفاده باشد ( به غیر از تابعهای نوع پوچ void که مقداری را باز نمیگردانند و میتوان در آنها دستور return را ننوشت ، اما توصیه میشود یک دستور ;return به همین صورت و بدون مقدار و عبارتی در مقابل آن نوشته شود و ما مجاز به برگردان مقداری از تابع پوچ void نیستیم )
پارامترها
پارامترها و آرگومانها را در مبحث بعدی به صورت کامل در مبحث مربوطه تشریح مینمایم امّا نکتهای وجود دارد که در همین مبحث نیز بدان اشارهای میکنم . تابع در هر هنگام فراخوانی ، آرگومانها را ( دادههای فرستاده شده به خود را ) کپی میکند و در متغیر دیگری در حافظه موقت ذخیره میکند و پردازش خود را بر روی آنها انجام میدهد ، بنابراین مقدار و موجودی آرگومان به خودی خود تغییری نمیکند . اگر بخواهید که تابع ، مقدار و موجودی دادهای را تغییر دهد باید پارامتر مورد نظر خود را به صورت اشارهگر تعریف کنید و در تابع نیز به صورت اشارهگر ، پردازش کنید و سپس در فراخوانی تابع ، داده خود را ( آرگومان ) با عملگر آدرسدهی ( امپرسند & ) به آن ارسال کنید . به مثال زیر دقت کنید :
#include <stdio.h>
void fun(int x)
{
x = 30;
}
int main(void)
{
int y = 20;
fun(y);
printf("y = %d", y);
return 0;
}
در این مثال مقدار پارامتر تابع fun باید ۳۰ بشود ، اما با فراخوانی fun که آرگومانِ y به آن فرستاده میشود ( با مقدار ۲۰ ) همان مقداری که آرگومان داشت ، یعنی ۲۰ ، باقی میماند و تغییری بر روی مقدار و موجودی y ایجاد نمیشود . پس برای اینکه مقدار داده و آرگومان را تغییر دهیم ، همان طور که گفته شد باید پارامتر x را از نوع اشارهگر تعریف نموده و پردازش نمائیم و سپس آرگومان و داده ارسال شده را نیز با عملگر آدرسدهی به تابع بفرستیم :
#include <stdio.h>
void fun(int *ptr)
{
*ptr = 30;
}
int main()
{
int y = 20;
fun(&y);
printf("y = %d", y);
return 0;
}
در مثال بالا ، پارامتر ptr یک اشارهگر صحیح میباشد و این اشارهگر مقدار ۳۰ میگیرد ( در بدنه تابع fun ) ؛ سپس در تابع اصلی برنامه یعنی main متغیر y مقدار ۲۰ دارد ( همانند مثال قبل ) اما این بار برای احضار و فراخوانی تابع fun آرگومان را با عملگر آدرسدهی به تابع ، فرستادهایم و مقدار y تغییر مییابد و میشود ۳۰
نکته : اگر نوع داده آرگومان ( که به تابع ارسال میگردد ) با نوع داده پارامتر یکی نباشد ، کامپایلر ، نوع داده آرگومان را با توجه به قوانین تبدیل کردن به نوع داده پارامتر تبدیل میکند ( رجوع کنید به تبدیل و جایگزینی دادهها در همین کتاب )
نکته : کلاس ذخیره پارامترها میتواند register باشد ولی نمیتواند auto یا static باشد ، چون جوزه آنها فرمال و داخل بدنه میباشد که با پایان یافتن بلوک از بین میروند و امکان باقی ماندن ندارند ( طبق استاندارد ) و این باعث ایجاد خطای مهلک در برنامه شما میشود
نکته : تعداد پارامترهای یک تابع میتواند نامعین و متغیر باشد که برای این کار باید فایل سرآیند stdarg.h را به برنامه خود ضمیمه کنید و در فهرست پارامترهای خود بنویسید (...,) مثل : function(int a, int b,...) که باید تابع پذیرای پردازش هر تعداد پارامتر که منابع سیستم اجازه بدهند باشد . همچنین در صورتی که در اعلان تابع خود ، یک تابع با پارامترهای معین بنویسید و یا پارامتری نداشته باشد و آن را خالی بگذارید ( بدون نوشتن void ) و سپس در تعریف تابع ، تعداد پارامترها را متغیر کنید ( با سه نقطه ... ) و یا در صورت پارامتر نداشتن در اعلان ، در تعریف برای آن پارامتر تعیین کنید ، کامپایلر معمولاً به شما هشدار نمیدهد ولی باعث ایجاد خطای حتمی میشود که پیدا کردن آن در پروژهها بسیار دشوار میشود . پس فراموش نکنید در صورتی که تابع شما پارامتر ندارد حتماً در پرانتزهای آن void را قید کنید و اگر تعداد پارامترهای متغیر دارد و میخواهید ابتدا آن را اعلان کنید ، در اعلان آن نیز سه نقطه را بنویسید ( در موضوع فایلهای سرآیند و مبحث stdarg به تفصیل به این موضوع خواهیم پرداخت )
اشارهای به مباحث تکمیلی
هر برنامهای که مینویسید تا تحت سیستم عامل اجرا شود باید یک تابع اصلی با نام main داشته باشد که در واقع گذرگاه اصلی برنامه شما میباشد و تمام کنترل برنامه تجت تابغ main میباشد . امّا در برنامهنویسی سطح پائین میتوانید تابع استاندارد کامپایلر خود را به عنوان تابع اصلی بنویسید که تقریباً ضروری می باشد
تابع از نوع داده inline بسیار سریعتر از تابعهای معمولی میباشد ، در صورتی که میخواهید تابع شما در پشته سیستم ذخیره شود ( در قسمت پائین توضیح داده شده که پشته سیستم چیست ) از کلیدواژه inline قبل از تعیین نوع داده پایه تابع خود ( نظیر int ، long یا double ) استفاده کنید تا در صورت تشحیص صلاحیت آن توسط کامپایلر در پشته ذخیره شده و سریعتر اجرا شود . همچنین اگر تابع را با نوع داده inline__ تعریف کنید ( که باید بدون نوع داده معینی نوشته شود ) تابع شما در کش CPU ذخیره میشود و بسیار سریعتر اجرا میشود چون کامپایلر برای آن نوع دادهای تعیین نمیکند و تابع شما نیز پذیرای آرگومانی نمیباشد ( که نباید برای آن پارامتری تعریف کنید ) که معمولاً مرسوم است برای نوشتن کدهای اسمبلی در داخل تابع در فایل متن منبع C استفاده می شود ( دقت کنید که مقدار کدهای زبان C نباید بیشتر از ۳ یا ۴ خط باشد و یا کدهای اسمبلی عملیات زیادی را انجام دهند ، چون در این صورت کامپایلر از ترجمه آن به کدبایتهای سیستم عامل یا ۰ و ۱ ماشین برای ذخیره آن در CPU اجتناب میکند ، پس بهتر است در صورت طولانی بودن کد خود آن را در چند تابع از نوع inline__ بنویسید ) . مثال :
__inline hello()
{
__asm
{
global _start
section .text
_start: mov rax, 1
mov rdi, 1
mov rsi, message
mov rdx, 13
syscall
mov rax, 60
xor rdi, rdi
syscall
section .data
message: db "Hello, World", 10
}
}
int main()
{
hello();
return 0;
}
این برنامه ساده به زبان اسمبلی سطح پائین ( که هر کلیدواژه نماینده یک عمل CPU میباشد ) در خروجی خط دستوری چاپ میکند Hello, World ( یعنی سلام دنیا ) فعلاً نیازی نیست تا آن را درک کنید و این قطعه کد مربوط به زبان اسمبلی میشود که خارج از زبان برنامهنویسی C میباشد و به استاندارد AT&T و برای Visual Studio نوشته شده است . اما در برنامهنویسی سطح پائین مانند نوشتن کرنل و میانافزارها ( Firmwares ) همانند شبهکرنلهای سیستم عامل ترکیبی ویندوز ( درایورها ) و یا نوشتن embeded softwares که برای IC ها و چیپستها و MOS ها ... استفاده میشوند ، درک اسمبلی ضروری میباشد ( که البته تا جای ممکن به این زبان کد زده نمیشود ، چون همان طور که تا حدودی در اینجا مشاهده میکنید نوشتن یک برنامه خیلی کوچک ، نیاز به نوشتن مقدار زیادی کد دارد )
در تعریف تابع میشود خود تابع را فراخوانی نمود که به این گونه تابعها ، تابع بازگشتی ( recursive ) گفته میشود که کمک بزرگی در برنامهنویسی حساب میشود امّا هر بار که تابع خودش را فراخوانی کند ، کامپایلر کدبایتهایی و یا دادههای دودوییای برای سیستم عامل یا رایانه ، ترجمه میکند و مینویسد تا به اجرا گذاشته شوند که در قسمتهایی از حافظه موقت ( RAM ) نوشته و ذخیره شوند که داخل کش ( نهانگاه ) واحد پردازنده مرکزی ( سیپییو CPU ) در یک مثبّت ( ثبت کننده register ) به آن قسمت از حافظه موقت که سریعتر در دسترس می باشند اشاره کند ( توسط اشارهگر ) که به آن قسمت از RAM پشته ( Stack ) گفته میشود و البته کامپایلر و کرنل و یا رایانه فقط تا تعداد و مقداری به شما اجازه این کار را میدهند که پشته ، جا برای ذخیره دادهها داشته باشد
مثال :
#include <stdio.h>
#include <math.h>
long double division(long double num1, long double num2)
{
long double count = 0.0, count2 = 0.0, remain = 0.0;
while(num1>0)
{
if(num1<num2)
break;
num1 = num1 - num2;
count++;
}
if(num1!=0)
remain = num1;
long double remain2 = remain;
while(remain2>0)
{
int c = 1000000;
long double one = 0.0;
while(c>1)
{
remain = remain2;
remain2 = 0.0;
for(int i = 0; i < 10; i++)
{
remain2 = remain2 + remain;
}
for(int k = 0; k < c; k++)
{
one = one + 0.0000001;
}
while(remain2>0)
{
if(remain2<num2)
break;
remain2 = remain2 - num2;
count2 = count2 + one;
}
one = 0.0;
c = division(c, 10);
}
if(remain2!=0)
break;
}
if(num1==0)
return count;
else
return (count + count2);
}
int main()
{
printf("Enter 2 numbers :\n");
long double a = 0.0 , b = 0.0;
scanf("%Lf%Lf", &a, &b);
if((b==0)&&((a>0)||(a<0)))
printf("Undefined\n");
else if((b==0)&&(a==0))
printf("Your result is : %Lf\n", division(a, b));
else if(((a>0)&&(b>0))||((a<0)&&(b<0)))
printf("Your result is : %Lf\n", division(fabs(a), fabs(b)));
else if((a>0)&&(b<0))
printf("Your result is : -%Lf\n", division(a, fabs(b)));
else
printf("Your result is : -%Lf\n", division(fabs(a), b));
return 0;
}
تشریح : این قطعه کد ، انقلابی محسوب میشود که تعریف تقسیم با استفاده از تفریق متوالی میباشد ( و ایده خود من در سال ۸۶ هجری مصادف با ۲۰۰۷ میلادی بوده است و تا پیش از اینکه در حساب اینستاگرام خود آن را عمومی مطرح کنم نیز بخش اعشاری آن فقط در دست عدّهای خاص که آن را از من دزدیده بودند قرار داشت ) در این برنامه ابتدا دو فایل سرآیند stdio و math را ضمیمه نمودهایم تا بتوانیم از تابعهای کتابخانهای printf برای چاپ نمودن خروجی در خروجی خطدستوری و scanf برای دریافت مقادیر از صفحه کلید کاربر و fabs به معنی قدر مطلق اعشاری ( مخفف float absolute value ) استفاده کنیم ( که البته میتوان به جای استفاده از تابع قدر مطلق ، برای اعداد منفی کد را اضافهتر نمود که کار بیهودهای میباشد و منفی تنها یک علامت میباشد که بهتر است با همان علامت منفی نوشته شود که در پایان خواهیم گفت که چگونه میتوان با نوشتن بر عکس همین قطعه کد برای اعداد منفی نیز تقسیم را با تفریق و جمع متوالی انجام داد ) در تابع main که تابع اصلی ما میباشد ابتدا در خروجی خطدستوری چاپ نموده : ۲ عدد را وارد کنید . سپس ۲ عدد با توانایی دریافت مقادیر اعشاری را از کاربر دریافت مینمائیم که سپس توسط تابع division مورد پردازش قرار میگیرند تا مقسوم ، تقسیم بر مقسوم علیه شود . تابع division از نوع داده اعشاری بلند ( بزرگ ) است و ۲ پارامتر با همین نوع داده دارد . سپس ۳ متغیر را برای شمارش قسمت صحیح تقسیم و دیگری را برای شمارش قسمت اعشاری آن و باقیمانده را از نوع داده اعشاری بلند تعریف نمودهایم ( با مقدار ۰٫۰ ) و در ادامه تا زمانی که مقسوم ( عدد اولی ) به صفر نرسیده از عدد اولی ، عدد دومی ( مقسوم علیه ) را کسر میکنیم که توسط count شمرده میشود که چند بار تفریق شده است ( که پاسخ تقسیم قسمت صحیح است ) که توسط حلقه while نوشته شده است و شرطی دارد که اگر مقسوم ، کوچکتر از مقسوم علیه شد ، حلقه را بشکند که آن را در ابتدا نوشتهایم تا اگر مقسوم ، کوچکتر از مقسوم علیه شد مقدار ۰ شود . سپس اگر مقسوم ۰ نشد ، مقدار آن در remain به معنی باقیمانده ، ذخیره میشود . و remain2 نیز مقدار باقیمانده را در مرحله بعد در خود ذخیره میکند ( تا آن را ۱۰ برابر کنیم ) سپس تا زمانی که باقیمانده remain2 به ۰ ( صفر ) نرسیده است باید قسمت اعشاری را نیز محاسبه کنیم که مقسوم علیه را مرتب از باقیمانده ۱۰ برابر شده ، تفریق مینمائیم . برای این کار نیز باید ابتدا مکان ارزشی دهم و سپس صدم و سپس هزارم و .... به ترتیب محاسبه شوند که ما تا میلیونیم محاسبه نمودهایم ( در صورتی که خواستید محاسبه سریعتر شود مقدار 0 های c و one را جایی که جمع میشود به یک تعداد کم کنید ) سپس حلقه بعدی تا زمانی که متغیر c به ۱ نرسیده ( که مقدار آن ۱۰۰۰۰۰۰ میباشد ) که در پایان حلقه نیز تقسیم بر ۱۰ میشود ( c = division(c, 10) که تابع ما بازگشتی است و خودش ، خودش را فراخوانی مینماید ) ادامه مییابد . باقیمانده reamin2 با حلقه for ده برابر میشود که در هر بار اجرای حلقه while بیرونی remain مقدار remian2 را در خود ذخیره میکند و سپس remain2 صفر میشود تا ۱۰ بار با remain جمع شود تا ۱۰ برابر شود و سپس در هر دفعه one با مقدار ۰٫۰۰۰۰۰۰۱ جمع میشود که ابتدا ۱۰۰۰۰۰۰ بار جمع میشود که میشود ۰٫۱ و سپس در اجرای دفعه بعدی حلقه ۱۰۰۰۰۰ بار که میشود ۰٫۰۱ و بعد ۰٫۰۰۱ و الی آخر تا ارزشهای مکانی کمتر که همگی محاسبه شوند و سپس داخلیترین حلقه while تا زمانی که باقیمانده به ۰ نرسیده از باقیمانده ، مقسوم علیه را کسر مینماید که در هر مرحله با one جمع میشود ( که ابتدا ۰٫۱ است و در مرحلههای بعد ۰٫۰۱ و ۰٫۰۰۱ و ۰٫۰۰۰۱ و الی آخر ) که در ابتدا نیز بررسی مینماید که باقیمانده کوچکتر از مقسوم علیه نباشد که در صورت کوچکتر بودن حلقه while داخلی میشکند و مقدار محاسبه تقسیم قسمت اعشاری ۰ خواهد شد ( برای آن ارزش مکانی ) و سپس one مقدار ۰ میگیرد تا مقدار بعدی آن محاسبه شود ( که باید ۰ بگیرد تا ارزش مکانی بعدی محاسبه شود ) و چون ممکن است باقیمانده هرگز به ۰ نرسد بعد از محاسبه ارزش مکانی میلیونیم تقسیم در صورتی که باز هم به مقدار ۰ نرسیدیم حلقه را میشکنیم . در پایان اگر عدد اوّل در تفریق متوالی به ۰ رسید ( که یعنی مقسوم بر مقسوم علیه بخش پذیر بود ) در آن صورت متغیر count که شمارش تعداد تفریق و پاسخ تقسیم صحیح میباشد بازگردانده میشود ( دقت کنید که نوع داده آن را اعشاری بلند تعریف نمودهایم چون نوع داده تابع ما اعشاری بلند یا بزرگ است ) و در غیر این صورت یعنی اگر بخش پذیر نبود مقدار تقسیم قسمت صحیح به علاوه مقدار تقسیم محاسبه شده قسمت اعشاری را باز میگرداند و تحویل میدهد . در قسمت انتهایی تابع mian اگر مقسوم علیه ۰ باشد مقدار متنی تعریفنشده ( Undefined ) را نشان میدهد ( چون عدد غیر صفر تقسیم بر ۰ تعریفنشده است و شما بینهایت بار هم که از عددی مانند ۲ صفر را کسر کنید باز هم چیزی از ۲ کم نمیشود و به عبارت دیگر این که چند صفر در ۲ جا میشوند تعریفنشده است و حتی بینهایت صفر هم باز هم ۰ هستند ) و در صورتی که هر دو عدد ۰ باشند تقسیم انجام شده و در صورتی که هر دو مثبت و یا هر دو منفی باشند تقسیم قدر مطلق آنها انجام میشود ( توسط تابع fabs و همچنین این نکته که تقسیم عدد منفی بر منفی ، مثبت میشود ) و در صورتی که مقسوم یا مقسوم علیه منفی باشند مقدار منفی تقسیم را باز میگرداند و در پایان return 0 تعیین میکند که تابع main با موققیت به پایان رسیده و بنابراین منابع اشغال شده در سیستم و سیستم عامل ، آزاد میشوند
نکته : در صورتی که نخواهیم از علامت منفی استفاده کنیم و بخواهیم مقدار منفی اعداد را حساب کنیم کد را باید بسیار توسعه دهیم که عملی پوچ و بیهوده است چون زمان بیشتری برای اجرای برنامه گرفته میشود و برنامه را کند میکند و از طرفی علامت منفی تنها یک علامت است و در محاسبات رایانهای و دیجیتالی نیز تنها با علامت گذاری نوشته و پردازش میشود ؛ اما در صورتی که بخواهیم آنها را محاسبه کنیم ابتدا باید شرط کنیم که اگر اولین عدد کوچکتر از صفر باشد و دومی بزرگتر از ۰ تا زمانی که عدد اولی کوچکتر از صفر است ( و نه بزرگتر ) عدد اولی به علاوه دومی شود و در قسمتهای دوم ( یعنی اعشاری ) نیز به همین شکل ولی در محاسبه one به جای جمع شدن با مقدار ۰٫۱ و ۰٫۰۱ باید آنها را تفریق نمود و همین طور الی آخر برای هر حالت که معکوسهای مختلف عملیات تقسیم با استفاده از تفریق هستند ( و این حالت ، حالت درست تقسیم است که در مقسوم ، چند مقسوم علیه جا میشوند و برای این کار آن قدر از مقسوم ، مقسوم علیه را کسر میکنیم تا به ۰ برسد که تعداد مراحل تفریق ، پاسخ ماست و در صورتی که باقیمانده داشت ، یک ممیز میگذاریم و باقیمانده را مرتب در هر مرحله ۱۰ برابر نموده و منهای مقسوم علیه میکنیم تا به ۰ برسیم ) ؛ همچنین در صورتی که با کلیدواژه register پارامترهای تابع و دیگر متغیرهای تابع را بر روی کش CPU ذخیره کنیم ، سرعت اجرای برنامه به شدّت افزایش مییابد
نکته : این تعریف میتواند جایگزین عمل تقسیم در CPU شود اما به شرطی که به زبان Assembly نوشته شود و به جای آنکه مقدار یک ده میلیونم را مرتب بزرگ کنیم ، مقادیر ۰٫۱ و ۰٫۰۱ و ... را به صورت دستی در هر مرحله وارد مینمائیم و در هنگام اسمبل شدن ( که همان کامپایل شدن است امّا برای زبان اسمبلی از اصطلاح اسمبل شدن استفاده میشود ) کد ترجمه شده ما بسیار کوچکتر میشود ( در مقایسه با زبان سی ) و در حال حاضر ، این کد ، عمل تقسیم را کمی کندتر از عمل تقسیم در CPU انجام میدهد اما با نوشته شدن آن در زبان C و Assembly ترکیب شده بسیار سریعتر از عمل تقسیم در CPU خواهد شد
مثال :
#include <stdio.h>
#include <math.h>
long double division(long double num1, long double num2)
{
long double count = 0.0, count2 = 0.0, remain = 0.0;
while (num1 > 0) {
if (num1 < num2)
break;
num1 = num1 - num2;
count++;
}
if (num1 != 0)
remain = num1;
long double remain2 = remain;
while (remain2 > 0)
{
int c = 1000000;
long double one = 0.0;
while (c > 1)
{
remain = remain2;
remain2 = 0.0;
for (int i = 0; i < 10; i++)
{
remain2 = remain2 + remain;
}
for (int k = 0; k < c; k++)
{
one = one + 0.0000001;
}
while (remain2 > 0)
{
if (remain2 < num2)
break;
remain2 = remain2 - num2;
count2 = count2 + one;
}
one = 0.0;
c = division(c, 10);
}
if (remain2 != 0)
break;
}
if (num1 == 0)
return count;
else
return (count + count2);
}
long double mult(long double num1, long double num2)
{
int integer1 = num1, integer2 = num2;
long double fraction1 = 0.0, fraction2 = 0.0;
fraction1 = num1 - integer1;
fraction2 = num2 - integer2;
int result1 = 0;
for (int i = 0; i < integer1; i++)
{
result1 = result1 + num2;
}
long double result2 = 0;
for (int j = 0; j < integer1; j++)
{
result2 = result2 + fraction2;
}
long double result3 = 0;
for (int k = 0; k < integer2; k++)
{
result3 = result3 + fraction1;
}
long double result4 = 0.0;
if (fraction2 > 0.0)
result4 = division(fraction1, (division(1, fraction2)));
else
result4 = 0.0;
long double result = result1 + result2 + result3 + result4;
return result;
}
long double powerinteger(long double num1, long double num2)
{
int integer = num2;
long double result = 1.0;
for (int j = 0; j < integer; j++)
{
result = mult(result, num1);
}
return result;
}
long double power(long double num1, long double num2)
{
int integer1 = num1;
long double fraction1 = num1 - integer1;
if(fraction1 != 0)
fraction1 = fraction1 + 0.01;
int integer2 = num2;
long double fraction2 = num2 - integer2;
if(fraction2 != 0)
fraction2 = fraction2 + 0.01;
fraction2 = mult(fraction2, 10);
long double powerednum = 0.0, assistant = 0.0, result = 1.01;
int assistant2 = 0;
powerednum = powerinteger(num1, fraction2);
assistant = powerednum;
while (assistant > 1)
{
int i = 0;
while ((assistant >= 1) && (i < 10))
{
assistant = division(assistant, result);
if (assistant <= 1.000001)
break;
i++;
}
if (assistant > 1.00001)
{
result = result + 0.01;
assistant = powerednum;
}
else
break;
}
if((num1>1)&&((fraction1>=0.1)||(fraction2>=0.1)))
{
return mult(result, (powerinteger(num1, num2)));
}
else if((num1>=1)&&((fraction1==0)&&(fraction2==0)))
{
return powerinteger(num1, num2);
}
else if((num1<1)&&(num2<1))
{
assistant2 = mult(fraction1, 10);
return division(power(assistant2, num2), power(10, num2));
}
else
return powerinteger(num1, num2);
}
long double check(long double num)
{
while(num>0)
{
num = num - 2;
if(num<2)
break;
}
if(num<1)
return 0;
else
return 1;
}
int main()
{
printf("Enter 2 numbers to get power :\n");
long double a = 0.0, b = 0.0;
scanf("%Lf%Lf", &a, &b);
if ((a > 0) && (b > 0))
printf("Your result is : %Lf\n", power(a, b));
else if ((a == 0) && (b > 0))
printf("0\n");
else if ((a == 0) && (b == 0))
printf("Indeterminable!\n");
else if (((a > 0) || (a < 0)) && (b == 0))
printf("1\n");
else if ((a > 0) && (b < 0))
printf("Your result is : %Lf\n", division(1, power(a, fabs(b))));
else if ((a < 0) && (b > 0))
{
if (check(b) == 0)
printf("Your result is : %Lf\n", power(fabs(a), b));
else
printf("Your result is : -%Lf\n", power(fabs(a), b));
}
else if ((a < 0) && (b < 0))
printf("Your result is : -%Lf\n", division(1, power(fabs(a), fabs(b))));
else
printf("Undefined!\n");
return 0;
}
نکته : این قطعه کد با استفاده از جمع و تفریق ، عمل به توان رساندن اعداد را به صورت کامل انجام میدهد ؛ فقط اندکی دقت پائینی دارد و کند است که برای دقت بالا باید به ۰ های result در تابع power اضافه نمود ، اما در آن صورت بسیار کند میشود ؛ در صورتی که از عملگرهای ضرب و تقسیم به جای توابع ضرب و تقسیم استفاده شود سرعت بالایی خواهد داشت و اگر تابع تقسیم را با ترکیب C و Assembly بنویسم تا در کش CPU ذخیره شود و در هر مرحله یک ده میلیونم مرتب جمع نشود تا به به مقدار ۰٫۱ و ۰٫۰۱ و ۰٫۰۰۱ و ... برسیم و مقادیر آن را در اسمبلی به صورت دستی در هر مرحله وارد کنیم ، کد ما سریعتر از عمل ضرب و تقسیم انجام خواهد شد ( عمل ضرب ، سریعتر از عمل ضرب در CPU تعریف شده است اما چون قسمت اعشاری آن وابسته به تقسیم میباشد ، سرعت آن کمی پائین میآید )
تشریح : این قطعه کد همان تابع division ( به معنای تقسیم ) را دارد به علاوه تابع mult که همان ضرب میباشد و دو پارامتر از نوع اعشاری بلند دارد که ابتدا به تعداد قسمت صحیح عدد اول ، قسمت صحیح دوم را با ۰ جمع میکند و سپس قسمت اعشار عدد دوم را به تعداد قسمت صحیح عدد اول با ۰ جمع میکند و سپس به تعداد قسمت صحیح عدد اول قسمت اعشار عدد دوم را با ۰ جمع میکند و در نهایت در صورتی که اعشار عدد اول موجود باشد ( ۰ نباشد ) آن را تقسیم بر ۱ تقسیم بر اعشار عدد دوم مینماید و در نهایت همه اینها را با هم جمع میکند تا حاصلضرب عدد اول در دوم به دست بیاید . سپس در تابع powerinteger عدد دوم اگر اعشار داشته باشد ، حذف میشود ( چون قابل ذخیره در داده صحیح int نیست ) و نتیجه یعنی result با مقداردهی اولیه ۰ تعریف شده است تا به تعداد قسمت صحیح عدد دوم که نمای توان میباشد ، عدد اول که پایه میباشد در آن ضرب شود که پاسخ توان اعداد به غیر از قسمت اعشار عدد دوم که نما است میباشد . در تابع power قسمت اعشار را ۰٫۰۱ اضافه میکنیم تا اگر سیستم بر اساس اعشار غیر دقیق ، مقدار قسمت اعشار نما را تغییر داد اصلاح شود و سپس آن را ضرب در ۱۰ میکنیم که یک دهم مقدار اعشار عدد وارد شده برای نما را به دست بیاوریم ( به دلیل محدودیت و نرسیدن به تابعهای کتابخانهای پیشرفتهتر ، دقت این برنامه فقط تا یک دهم میباشد چون مثلاً ۹ به توان ۲۱ عدد بسیار بزرگی میشود که اگر اعشار را ضرب در ۱۰۰ کنیم میتوانیم تا صدم اعشار نیز محاسبه نمائیم - و یا به همین نحو تا ارزشهای مکانی کمتر ) . دقت کنید که عدد به توان نمای اعشار به معنی عدد به توان آن عدد به صورت عدد صحیح به دست آمده از اعشار میباشد که سپس باید ریشه آن را با تعداد ارزشهای مکانی اعشاری ضرب در ۱۰ به دست آوریم . مثلاً ۹ به توان ۰٫۳ به معنی ۹ به توان ۳ و ریشه ۱۰ ـم آن است که شیوه محاسبه ریشه بر اساس تقسیم متوالی نیز کشف خودم بود که تا قبل از سال ۲۰۰۷ میلادی در هیچ جا موجود نبوده است ؛ برای محاسبه ریشه اعداد ، عدد زیر رادیکال باید به تعداد فرجه ، تقسیم بر عددی شود که در نهایت به مقدار ۱ برسیم . در اینجا نیز همین کار انجام شده است . ابتدا مقدار دهم اعشار را به دست آورده و سپس عدد به دست آمده را باید ۱۰ بار تقسیم بر مقداری ( result ) کنیم تا به ۱ برسیم ، اما چون ممکن است به ۱ نرسیم شرط کردهایم که اگر مقدار کوچکتر از ۱٫۰۰۰۰۰۱ شد حلقه تقسیم بشکند و در بیرون اگر مقدار از ۱٫۰۰۰۰۱ بزرگتر بود دوباره همان مقدار به توان رسیده را جایگذاری کند تا در نهایت به مقدار ۱ بسیار نزدیک شویم ؛ همچنین اگر عدد پایه کوچکتر از ۱ باشد آنگاه برای به دست آوردن توان اعشاری ( که همان ریشه عدد است ) قسمت اعشاری پایه را ده برابر کرده و قسمت اعشار آن را حذف مینمائیم ( دقت کنید که نوع داده assistant2 از نوع صحیح میباشد ) سپس آن را به توان مقدار نما رسانده و تقسیم بر ۱۰ به توانِ نما مینمائیم تا مقدار توان عدد پایه کوچکتر از ۱ به دست بیاید که البته برای این کار از خود تابع توان power استفاده نمودهایم که این تابع نیز ، تابع بازگشتی میباشد . در نهایت نیز در صورتی که عدد پایه ، بزرگتر از ۱ باشد و قسمت اعشار پایه یا نما بزرگتز از ۰٫۱ باشد خروجی تابع ، ضرب result در توان صحیح میباشد ( powerinteger ) و و در صورتی که پایه ، بزرگتر مساوی ۱ باشد و قسمت اعشار پایه و نما صفر باشد مقدار خروجی ، توان صحیح پایه به نمای صحیح میباشد که با فراخوانی تابع powerinteger و فرستادن اعداد ورودی به عنوان پایه و نما به آن ، به دست میآید و در صورتی که پایه کوچکتر از ۱ باشد پایه را ضرب در ۱۰ کرده و پاسخ همان تقسیم توان پایه ۱۰ برابر شده به توان ۱۰ به نمای وارد شده توسط کاربر خواهد بود ، در مرحله بعد نیز تابع check بررسی میکند که عددی که به آن فرستاده میشود زوج است یا فرد که برای این کار از عدد تا به مقدار ۰ نرسیدهایم ۲ واحد ۲ واحد کم میکنیم و اگر بعد از کاستن کوچکتر از ۲ شد حلقه میشکند تا دوباره منهای ۲ نشود که کوچکتر از ۰ شود سپس در صورتی که باقیمانده کوچکتر از ۱ باشد مقدار ۰ را باز میگرداند که یعنی عدد زوج است و در غیر این صورت مقدار ۱ را باز میگرداند که یعنی عدد فرد است . در تابع main نیز همانند مثال پیشین احتمالات مختلف را مورد بررسی قرار دادهایم . اگر هر دو عدد مثبت باشند که تابع power را محاسبه میکند ( و مقادیر a و b که دریافت شدهاند به تابع power فرستاده میشوند ) و اگر پایه ۰ بود و نما مثبت مقدار ۰ خروجی ما خواهد و اگر هر دو ۰ باشند مشخص نیست که پاسخ ۰ میشود یا ۱ پس در خروجی چاپ میشود !Indeterminable و اگر نما ۰ باشد ( و پایه غیر ۰ ) مقدار میشود ۱ و اگر پایه مثبت باشد و نما ، منفی حاصل تقسیم عدد ۱ بر توان مقدار پایه به نمای قدر مطلق نما میشود و اگر پایه منفی باشد و نما مثبت اگر نما زوج باشد پاسخ به توان رساندن عدد میباشد که مثبت میشود و اگر فرد باشد منفی همان توان و در غیر این صورت ( که هم نما هم پایه منفی باشند ) پاسخ ، منفی تقسیم مقدار ۱ به عدد به توان نما میباشد و در صورتی که هیچ کدام از اینها نباشد و پایه ۰ باشد و نما منفی ، پاسخ تعریف نشده است ( چون عدد غیر صفر تقسیم بر ۰ تعریف نشده است )
نکته : در استاندارد زبان سی C ، تابع نمیتواند یک آرایه را بازگرداند ، اما به کمک ترفندهایی مثلاً با استفاده از اشارهگرها میتوان آرایه را نیز به عنوان خروجی تابع ، بازگرداند که در مبحث « مطالب تکمیلی » نوشته خواهد شد
نکته : تابع را نمیتوان با کلیدواژه struct از نوع ساختمان تعریف نمود ، امّا میتوان ساختمانی ایجاد کرد و سپس با نام ساختمان یک تابع را از نوع داده ساختمان ایجاد نمود که بهتر است با استفاده از کلیدواژه typedef روی ساختمانی نام گذاشت تا نوع داده ما از نوع ساختمان تعریف شود و از روی نام ساختمانِ تعریف شده ، تابع را ایجاد نمود که این مطلب نیز در مبحث « مطالب تکمیلی » نوشته خواهد شد
نکته : طبق استاندارد سی ، تابع را نمیتوان در تابعی دیگر اعلان و یا تعریف نمود اما GCC از دیرباز از این قابلیت پشتیبانی می نماید