پرش به محتوا

زبان برنامه نویسی سی/مطالب تکمیلی

ویکی‎کتاب، کتابخانهٔ آزاد

در این مبحث ، مطالب ناگفته در مورد تابع‌ها را که تکمیلی هستند بازگو می نمائیم . تابع بازگشتی ( Recursive Function ) در دانش رایانه و زبان برنامه‌نویسی سی C به تابعی گفته می شود که به صورت مستقیم خود را و یا به صورت غیر مستقیم تابع دیگری را فراخوانی می نماید که در شکل غیر مستقیم تابع دومی ، تابع اولی را فراخوانی می نماید که در هر دو صورت ، حتماً باید نقطه پایانی نیز داشته باشد تا تابع در جایی متوقف شود در غیر این صورت ، چه در بازگشت مستقیم چه در بازگشت غیر مستقیم ممکن است پشته سرریز شود ( Stack Overflow ) و یا بسته به سیستم عامل و کامپایلر و پیوند دهنده ( Linker ) یک نتیجه نامعین داشته باشد . تابع بازگشتی با فراخوانی خود و یا تابع دیگری که تابع اولی را فرا می خواند به شکل یک حلقه در می آید و باید در جایی پایان بیابد . تابع بازگشتی مستقیم پردازش‌هایی را بر روی داده‌ها انجام می دهد که در میان آنها پردازش به روی داده‌ها با فراخوانی خود تابع انجام می شود که با این کار ، برنامه به ابتدای تابع باز می گردد و مراحل را یک به یک انجام می دهد تا دوباره به فراخوانی تابع توسط خودش برسد و البته باید در جایی پایان پذیرد . تابع بازگشتی غیر مستقیم نیز در تابعی دیگر تابع اول را فراخوانی می کند که تابع اول نیز همان تابع را فراخوانی می کند که مرتب باید به پایان نزدیک شوند و در جایی پایان بابند . مثال برای تابع بازگشتی مستقیم ( Direct Recursive Function ) :

#include <stdio.h>

unsigned long long int factorial(unsigned int i)
{

	if(i <= 1) 
	{
		return 1;
	}
	return i * factorial(i - 1);
}

int main()
{
	printf("Enter a numner to get its factorial :\n");
	unsigned long long int num;
	scanf("%llu", &num);
	printf("Factorial is %llu\n", factorial(num));
	return 0;
}

تشریح : این برنامه فاکتوریل ( یا با تلفظ درست‌تر فاکتوریال ) عدد را برای شما محاسبه می نماید . در برنامه ، ابتدا فایل سرآیند stdio را ضمیمه نموده‌ایم تا از تابع کتابخانه‌ای scanf برای دریافت مقدار از کاربر در ورودی خط‌دستوری و تابع printf برای چاپ مقدار محصول در خروجی خط‌دستوری استفاده نمائیم . یک تابع از نوع خیلی بلند صحیح بدون علامت به نام factorial تعریف نموده‌ایم که یک پارامتر از نوع صحیح بدون علامت به نام i دارد ( دقت کنید که اعداد بزرگ حتی در نوع داده خیلی بلند هم ممکن است جا نشوند و اگر از حد بگذرد قابل پردازش نیست ) در تابع شرط کرده‌ایم که اگر عدد وارد شده که پارامتر تابع می باشد ، کوچک‌تر مساوی ۱ باشد مقدار ۱ را بازگرداند و اگر نه مقدار پارامتر ضرب در فاکتوریلِ مقدار پارامترِ منهای یکی را بازگرداند . فرض کنیم عدد ۴ توسط کاربر از ورودی خط‌دستوری وارد شود که در تابع اصلی mian آن را دریافت نموده‌ایم تا با فراخوانی تابع فاکتوریل آن را محاسبه نموده و نمایش دهیم . ۴ ضرب در فاکتوریل ۳ می شود که معلوم نیست مقدار آن چه قدر است و فاکتوریل ۳ ضرب در فاکتوریل ۲ می شود که باز هم معلوم نیست مقدار آن چه قدر است و فاکتوریل ۲ ضرب در فاکتوریل ۱ می شود که مقدار ۱ را دارد . پس ۱ ضرب در ۲ ضرب در ۳ و در نهایت ضرب در ۴ می شود که پاسخ آن می شود ۲۴

و اما مثالی برای تابع بازگشتی غیر مستقیم ( Indirect Recursive Function ) :

#include <stdio.h>

void fun1(int a)
{
	if (a > 0)
	{
		fun2(a - 1);
		printf("%d\n", a);
	}
	return;
}

void fun2(int b)
{
    if(b > 0)
	{
		fun1(b - 3);
		printf("%d\n", b);
	}
	return;
}

int main ()
{
	int num;
	printf("Enter a number :\n");
	scanf("%d", &num); 
	fun1(num);
	return 0;
}

تشریح : در ابتدای برنامه فایل سرآیند stdio را ضمیمه نموده‌ایم تا بتوانیم از توابع کتابخانه‌ای scanf و printf استفاده کنیم . سپس یک تابع از نوع داده پوچ به نام fun1 مخفف function 1 تعریف نموده‌ایم که یک پارامتر از نوع صحیح دارد و عدد را دریافت نموده در صورتی که بزرگ‌تر از ۰ باشد آن را چاپ می کند اما پیش از آن یک واحد از آن کم می کند و می فرستد به تابع بعدی که از نوع پوچ است و نام fun2 را دارد که یک متغیر از نوع صحیح را می پذیرد که آن را چاپ می کند اما قبل از آن با ۳ واحد تفریق می فرستد به fun1 پس اگر عددی را وارد کنیم ابتدا در stack ذخیره می شود تا حلقه کاهشی توابع fun1 و fun2 به پایان برسند ، و سپس در تابع اصلی برنامه main تابع fun1 فراخوانده شده و اعداد از کوچک‌ترین تا خود عدد در خروجی خط‌دستوری چاپ می شوند مثلاً اگر وارد کنیم ۱۲ ( به انگلیسی ) مقادیر زیر را دریافت خواهیم نمود :


3
4
7
8
11
12

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

#include <stdio.h>

int * addself(int num)
{
	int * a = &num;
	*a = num + *a;
	
	return (int *) 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 main()
{

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

در این مثال تابع addself به معنی « خود را اضافه کن » یک اشاره‌گر از نوع صحیح است که یک پارامتر صحیح به نام num دارد . در بدنه تابع یک اشاره‌گر از نوع صحیح به نام a تعریف نموده‌ایم که با عملگر آدرس‌دهی مقدار num را می گیرد . سپس با روش غیر مستقیم کردن num را به a اضافه نموده‌ایم . سپس برای اینکه خروجی را بازگردانیم a را با تبدیل نمودن ( cast کردن ) با دستور return بازگردانده‌ایم . سپس تابع بعدی یعنی multself به معنی « در خود ضرب کن » را با یک پارامتر از نوع تابع صحیح اشاره‌گر که پارامتری از نوع صحیح دارد و یک پارامتر دیگر از نوع صحیح به نام num2 تعریف نموده‌ایم . در بدنه تابع دوم متغیر صحیح res را با مقدار فراخوانی تابع adding که پارامتر اشاره‌گر تابع multself می باشد و دریافت پارامتر num2 که بر روی آن پردازش خود را اعمال خواهد نمود تعریف کرده‌ایم . سپس با حلقه for به تعداد num2 منهای ۲ تا res را که مقدار اضافه شده num2 را دارد با خود جمع نموده‌ایم ( دقت کنید که با عمل جمعِ عدد با خود عدد ، در تابع پیشین ، عدد با خودش جمع شده است و باید ۲ بار از تعداد جمع بعدی برای ضرب عدد در خودش بکاهیم ) تابع multself مقدار res را باز می گرداند . در تابع اصلی برنامه main به عنوان گذرگاه و کنترل کننده برنامه یک متغیر از نوع صحیح به نام anum به معنی « یک عدد » اعلان نموده‌ایم که در خط بعدی با تابع کتابخانه‌ای scanf که در فایل سرآیند stdio تعریف شده است و در ابتدای برنامه خود آن را ضمیمه نموده‌ایم ، مقدار anum را از کاربر از ورودی خط‌دستوری دریافت می نمائیم . سپس تابع کتابخانه‌ای printf با فرمت کننده دسیمال d% مقدار خروجی را چاپ می کند که مقدار آن تابع multself است که به عنوان آرگومان addself را دریافت می کند که آرگومان بعدی را که anum است دریافت می نماید و پس از پردازش ، مقدار محصول را در خروجی خط‌دستوری چاپ خواهد نمود . در پایان با دستور ;return 0 تابع با موفقیت به پایان می رسد و منابع اشغال شده سیستم آزاد می شوند

تابع از نوع داده ساختمان

شما می توانید ساختمانی را با نام و برچسبی بسازید و با آن نام ، تابعی را تعریف کنید و یا با استفاده از کلیدواژه typedef نام ساختمان را یک نوع داده جدید و جایگزین تعریف کنید و با آن نام ، تابعی را تعریف نمائید . به مثال زیر توجه کنید :

#include<stdio.h>

struct mystruct
{
	int a, b , c , d;
};

struct mystruct func(int f, int s, int t, int fourth)
{
	struct mystruct res;
	res.a = f;
	res.b = s + f;
	res.c = t + s;
	res.d = fourth + t;
	return res;
}

int main(void)
{
	struct mystruct sample;
	printf("Enter 4 numbers :\n");
	scanf("%d%d%d%d", &sample.a, &sample.b, &sample.c, &sample.d);
	sample = func(sample.a, sample.b, sample.c, sample.d);
	printf("The result is : %d and %d and %d and %d\n", sample.a, sample.b, sample.c, sample.d);
	
	return 0;
}

در قطعه کد بالا یک ساختمان با برچسب mystruct به معنی ساختمان من با ۴ عضو تعریف نموده‌ایم که هر ۴ عضو متغیرهایی از نوع صحیح هستند . سپس یک تابع از نوع ساختمانی با برچسب mystruct و نام تابع func تعریف کرده‌ایم ( که در واقع func یک نمونه از mystruct است ) که چهار پارامتر از نوع صحیح به نام‌های f مخفف first و به معنی اولی و s مخفف second و به معنی دومی و t مخفف third به معنی سومی و fourth به معنی چهارمی دارد . داخل بدنه تابع یک نمونه از ساختمان mystruct با نام res ساخته‌ایم که عضو a آن برابر با پارامتر اول و عضو b آن برابر جمع پارامتر دوم و پارامتر اول است و عضو c آن برابر جمع پارامتر سوم و پارامتر دوم است و عضو d آن برابر با مجموع پارامتر چهارم با پارامتر سوم است . سپس تابع ساختمان res را باز می گرداند . در تابع اصلی برنامه که پارامتری ندارد و داخل آن نوشته‌ایم void یک نمونه از ساختمان mystruct با نام sample به معنی نمونه ( ولی نه نمونه به معنی instance ) ساخته‌ایم که بعد از آن ، در خروجی خط‌دستوری چاپ کرده‌ایم چهار عدد را وارد کنید . سپس اعضای ساختمان sample را دریافت نموده‌ایم و sample برابر با تابع func با پارامترهای اعضای ساختمان sample است . سپس با تابع کتابخانه‌ای printf چاپ کرده‌ایم که نتیجه جمع مقادیر پارامترهای تابع مقادیر رو به رو می باشد که اعضای ساختمان sample را فرستاده‌ایم . در پایان با ;return 0 منابع اشغال شده در سیستم را آزاد می کنیم

تابع از نوع داده آرایه

اگر از مبحث آرایه به یاد داشته باشید ، آرایه‌ها توسط اشاره‌گرها تعریف می شوند و می توانند به صورتی دیگری توسط اشاره‌گر نوشته شوند ، مثلاً [2]i را می توان به شکل (i+2)* نیز نوشت . در مورد تابعی که نوع داده اشاره‌گر را بازگرداند نکته مهم این است که آرایه‌ای را که می خواهید بازگردانید با کلاس ذخیره ایستا static تعریف کنید . در زبان سی C تابع نمی تواند یک متغیر اشاره‌گر محلی را بازگرداند ، اما اگر ایستا باشد آنگاه بازگردانده خواهد شد . نکته کوچک دیگر اینکه باید تابع خود را از نوع اشاره‌گر تعریف کنید که بدیهی است ( چون همان طور که گفته شد ، آرایه‌ها توسط اشاره‌گرها در کامپایلر تعریف می شوند ) به مثال زیر دقت کنید :

#include <stdio.h>

int * func(void)
{
	
	int i = 0;
	static int myarr[5];
	for(int j =0; j <5; j++)
		{
			*(myarr+i) = j;
		printf("Index %d is %d\n", i, j);
		i++;
		}
	
	return myarr;
}

int main(void)
{
	
	func();
	
	return 0;
}

ابتدا فایل سرآیند stdio.h را ضمیمه برنامه خود نموده‌ایم تا از تابع printf استفاده کنیم . سپس یک تابع اشاره‌گر از نوع صحیح با نام func تعریف نموده‌ایم که پارامتری ندارد . سپس یک متغیر صحیح با نام i و مقدار 0 تعریف نموده‌ایم . پس از آن یک متغیر اشاره‌گر از نوع آرایه با کلاس ذخیره ایستا static با ۵ عنصر با نام mayarr یعنی آرایه من ، اعلان نموده‌ایم . و در مرحله بعد توسط حلقه for از جایی که متغیر j از نوع صحیح با مقدار 0 باشد تا جایی که کوچک‌تر از ۵ باشد با حکم printf مقدار myarr+i که 0 بود را چاپ می کند و در هر مرحله به آن اضافه خواهد شد و حکم ++i را تکرار کند . بدین شکل از جایی که مقدار i و j هر دو 0 هستند تا جایی که مقدار هر دو 4 است در خروجی خط‌دستوری چاپ خواهد شد ( فراموش نکنید که عنصرهای آرایه از 0 شروع می شوند و اندیس اول آن 0 است ) . سپس در پایانِ تابع func مقدار myarr بازگردانده می شود . در تابع اصلی برنامه main تابع func را فراخوانده و مقدار 0 را بازگردانده‌ایم تا برنامه با موفقیت به پایان رسیده و terminate شود و مبابع اشغال شده توسط برنامه آزاد شوند