تو این مقاله در مورد تعریف و نحوه استفاده از توابع در زبان C مواردی ارائه میشه.

تا الان تنها تابعی که داخل زبان C باهاش آشنا شدیم، تابع main هست. گفتیم که این تابع نقطه شروع اجرای برنامست! یکم با این تابع آشنا بشیم و بحث اصلی رو شروع کنیم...
تابع main یه تابع خاصه که وقتی برنامه اجرا میشه، کامپایلر از اونجا شروع میکنه به اجرای کد. به عبارت دیگه، وقتی برنامت رو اجرا میکنی سیستمعامل مستقیم میره سراغ تابع main و کدی که داخلش نوشتی رو اجرا میکنه.
میتونی توابع دیگهایی غیر از main تعریف کنی و این توابع میتونن بیرون از main باشن. این کار به چند دلیل انجام میشه:
سازماندهی کد: اگه بخوای همهی کدت رو داخل main بنویسی، وقتی برنامت بزرگ میشه، خیلی شلوغ و نامرتب میشه. با تعریف توابع جداگونه، میتونی کدت رو تمیز و ماژولار کنی.
استفاده مجدد: توابع جداگونه رو میتونی چندبار تو برنامت (یا حتی تو برنامههای دیگه) استفاده کنی.
فرض کن میخوای یه برنامه بنویسی که یه عدد رو بگیره و بگه زوجه یا فرد. میتونی این کارو فقط تو main انجام بدی ولی اگه بخوای این چک کردن رو چندبار استفاده کنی، بهتره یه تابع جدا بنویسی. کد:
#include <stdio.h> int is_even(int number) { if (number % 2 == 0) { return 1; // 1 یعنی زوج } else { return 0; // 0 یعنی فرد } } int main() { int num = 4; if (is_even(num)) { printf("عدد %d زوجه!\n", num); } else { printf("عدد %d فرده!\n", num); } return 0; }
(الان کاری نداریم که تابع چطور ساخته میشه! فقط میخوام موضوع main رو درک کنید)
درنتیجه، من میتونم مواردی رو (مثل توابع) بیرون از تابع main ایجاد کنم. تابع main خاصه چون وقتی برنامه اجرا میشه سیستمعامل خودش main رو صدا میزنه. اما توابع دیگهایی که بیرون از main تعریف میکنی، خود به خود اجرا نمیشن مگر اینکه یه جایی (معمولا از داخل main یا یه تابع دیگه که از main شروع شده) صداشون کنی.
حالا بریم سراغ موضوع اصلی...
تابع (Function): تابع یه تیکه کد جدا و مستقله که یه کار مشخص رو انجام میده. تو هر وقت به اون کار نیاز داشتی، فقط باید تابع رو صدا بزنی. مزایای اصلی استفاده از function:
جلوگیری از تکرار کد: اگه یه کاری رو قراره 10 بار انجام بدی، کدش رو یه بار تو یه تابع مینویسی و 10 بار اون تابع رو صدا میزنی.
سازماندهی و خوانایی: برنامت به بخشهای کوچیک و قابل فهم تقسیم میشه. به جای یه کد هزار خطی درهم برهم، چندتا تابع با اسمهای معنیدار داری.
اشکالزدایی راحتتر: اگه یه جای کار بلنگه، میدونی مشکل احتمالا توی کدوم function هست و مستقیم میری سراغ همون.
شکل کلی تعریف تابع:
Return_Type function_name(parameters) { بدنه تابع }
نوع برگشتی (Return Type): کلمهای که قبل از اسم تابع میاد میگه که تابع ما بعد از اینکه کارش تموم شد، چه نوع مقداری رو به ما پس میده (return میکنه) حالت های مختلف:
int: عدد صحیح برمیگردونه.
void: هیچ چیزی برنمیگردونه.
char و float و double و struct و... هم داریم.
اسم تابع (Function Name): اسمی که ما برای تابع انتخاب میکنیم. بهتره یه اسم با معنی باشه که بگه این تابع چیکار میکنه.
پارامترها (parameters): متغیرهایی که داخل پرانتز قرار دارن. ورودیهای تابع هستن، یعنی اطلاعاتی که ما به تابع میدیم تا کارش رو با اونا انجام بده. تو کد پایین ما دوتا عدد صحیح بهش میدیم (باید Data Type هر پارامتری که برای یک تابع تعریف میکنی رو مشخص کنی)
بدنهی تابع: جایی که عملیات اصلی انجام میشه.
مثال:
int add(int a, int b) { return a + b; }
نحوه صدا زدن تابع (Call کردن تابع): تابع رو توی main (یا توابع دیگه که اونا داخل main صدا زنده میشن) میتونی صدا بزنی:
#include <stdio.h> int add(int a, int b) { return a + b; } int main() { int result = add(3, 4); printf("%d", result); }
تابعی که هیچ چیزی برنمیگردونه:
#include <stdio.h> void greet() { printf("Hello"); } int main() { greet(); }
خروجی: Hello
تابعی بدون پارامتر ولی با خروجی:
#include <stdio.h> char tf() { return 'x'; } int main() { char ch = tf(); printf("%c\n", ch); // یا printf("%c", tf()); }
خروجی: (دوتا x)
تابعی با پارامتر ولی بدون خروجی:
#include <stdio.h> void sayHi(char name[]) { printf("Hello %s", name); } int main() { sayHi("Amirhosein"); }
چرا این کد خطا نداد با اینکه توی تابع sayHi گفتم ورودیش یه []char name هست ولی نگفتم چندتا خونه باید داشته باشه؟ چرا C نگفت مثلا باید بنویسی char name[10] یا یه چیزی شبیه به اون؟
وقتی توی پارامتر تابع بنویسی []char name یعنی:
من نمیخوام خود آرایه رو بفرستی، فقط آدرس شروعش رو بده بهم.
یعنی چی آدرس شروعش؟
فرض کن یه کیف داری. وقتی این کیف رو میخوای به کسی نشون بدی، کل کیف رو نمیدی دستش. فقط میگی: کیف من روی میز اون گوشست، برو ببینش.
یعنی فقط آدرس کیف رو میدی، نه خود کیف رو.
درنتیجه، وقتی میگی:
sayHi("Amirhosein");
در واقع C خودش رشته Amirhosein رو تو حافظه میذاره و آدرسش رو میده به تابع. و چون رشتهها همیشه تهشون یه 0\ دارن، تابع میفهمه کجا تموم میشن.
نکته. اگه تابع قراره دادهایی رو برگشت بده، باید از return استفاده کنم. آیا فقط از یک return میشه استفاده کرد؟ خیر. بسته به برنامه میتونم چندین return قرار بدم. کد:
#include <stdio.h> int tf(int number) { if (number % 2 == 0) { return 1; } return 0; } int main() { if (tf(5)) { printf("Zoje"); } else { printf("Fard"); } }
نکته. توابع باید بالای main تعریف بشن، اگه تابعی رو پایین main تعریف کنم و بخوام داخل main صداش بزنم، خطا میده! چرا که اون تابع هنوز شناخته نشده و دارم تلاش میکنم صداش بزنم. مثال:
int main() { if (tf(5)) { printf("Zoje"); } else { printf("Fard"); } } int tf(int number) { if (number % 2 == 0) { return 1; } return 0; }
حالا اگه به هر دلیلی بخوام تابع رو پایین main تعریف کنم، راه حل چیه؟
به این کد دقت کن:
#include <stdio.h> int tf (int); int main() { if (tf(5)) { printf("Zoje"); } else { printf("Fard"); } } int tf (int number) { if (number % 2 == 0) { return 1; } return 0; }
بالای main برای C مشخص کردم که یه نوع تابع وجود داره به اسم tf که نوع int رو قراره return کنه و یه دونه int هم قراره به عنوان پارامتر دریافت کنه و در آخر ; قرار دادم. بعد اومدم پایین main تابع رو کامل تر تعریف کردم و بدنه تابع رو هم نوشتم. با این روش خطا بهم نمیده.
نکته. وقتی داخل یه تابع (مثلا main یا هر تابع دیگه) یه متغیر تعریف میکنی، به اون میگن متغیر محلی (Local Variable). ویژگیهای متغیرهای محلی (Local Variables):
- فقط توی همون تابع شناخته میشن
- وقتی تابع اجرا میشه، متغیر ساخته میشه و وقتی تابع تموم میشه، متغیر از بین میره (یعنی متغیرهای محلی عمرشون فقط تو زمان اجرای تابع هست)
- دو تا متغیر با اسم یکسان میتونن تو توابع مختلف باشن (چون هر تابع محدوده خودشو داره)
- متغیرهای داخل بلوک {} باز هم محلی هستن (حتی اگه تو یه حلقه یا شرط داخل یه تابع بنویسی) مثال:
int main() { if (1) { int x = 5; printf("%d\n", x); } printf("%d\n", x); }
تو خط 7 خطا دارم چون متغیری تحت عنوان x وجود نداره (اون x چون داخل {} ساخته شده بود، بازهم یه متغیر محلی به حساب میومد و بیرون از اون نمیشه ازش استفاده کرد (حتی با اینکه داخل یک تابع هستن))
(پیشنهاد میکنم مقاله (مفهوم Scope و Namespace ها در پایتون) رو مطالعه کنید که به درک این مقاله کمک میکنه)
درکنار Local Variables یا متغیرهای محلی، Global Variables یا متغیرهای سراسری هم داریم! وقتی بیرون از هر تابعی (حتی بیرون main) یه متغیر تعریف کنی، اون میشه متغیر global. یعنی همهی توابع تو کل برنامه میتونن بهش دسترسی داشته باشن (هم میتونن مقدارش رو بخونن و هم مقدارش رو تغییر بدن) مثال:
#include <stdio.h> int x = 10; // Global Variables void sayHi() { printf("x inside sayHi = %d\n", x); } int main() { printf("x inside main = %d\n", x); sayHi(); return 0; }
خروجی:
x inside main = 10 x inside sayHi = 10
به کد زیر دقت کن:
void tf() { int count = 0; count++; printf("count = %d\n", count); } int main() { tf(); tf(); tf(); tf(); }
خروجی:
count = 1 count = 1 count = 1 count = 1
هر بار که تابع tf اجرا میشه، متغیر count از نو ساخته میشه و دوباره مقدارش میشه 0.
الان متغیر رو با static ایجاد میکنم:
#include <stdio.h> void tf() { static int count = 0; count++; printf("count = %d\n", count); } int main() { tf(); tf(); tf(); tf(); }
خروجی:
count = 1 count = 2 count = 3 count = 4
کلمه static قبل از متغیر، یعنی این متغیر:
فقط همون اولین بار ساخته بشه.
مقدارش بین اجرای تابعها حفظ بشه.
فقط توی همون تابع قابل استفادست (local scope داره) اما تا آخر برنامه تو حافظه باقی میمونه.
تابع بازگشتی چیه؟ تابع بازگشتی، تابعیه که خودش رو صدا میزنه!
تصور کن یه عروسک بزرگ داری. وقتی بازش میکنی، داخلش یه نسخه کوچیکتر از همون عروسک وجود داره. دوباره اون رو باز میکنی و باز هم یه نسخه کوچیکتر داخلشه. این کار رو ادامه میدی تا به کوچیک ترین عروسک برسی که دیگه باز نمیشه. یک تابع بازگشتی همینطوری کار میکنه. برای حل یک مسئله بزرگ، خودش رو با یک نسخه کوچیکتر و سادهتر از همون مسئله صدا میزنه. این کار رو اونقدر تکرار میکنه تا به سادهترین حالت ممکن برسه که جوابش مشخصه و دیگه نیازی به صدا زدن دوباره خودش نداره.

هر تابع بازگشتی باید دو قانون اصلی رو رعایت کنه. اگه یکی از اینا نباشه، برنامه یا کار نمیکنه یا توی یه حلقه بینهایت گیر میکنه و کرش میکنه.
1 - شرط توقف (Base Case): شرط توقف همون کوچیکترین عروسکه که دیگه باز نمیشه. این شرط به تابع میگه که: مسئله به قدر کافی ساده شده، دیگه خودت رو صدا نزن و این جواب نهایی رو برگردون. اگه شرط توقف وجود نداشته باشه، تابع تا ابد خودش رو صدا میزنه تا اینکه حافظه کامپیوتر پر بشه و برنامه با خطای Stack Overflow کرش کنه.
2 - گام بازگشتی (Recursive Step): این همون بخشیه که تابع، خودش رو دوباره صدا میزنه. اما نکته کلیدی اینه که باید مسئله رو به شکلی تغییر بده که به شرط توقف نزدیکتر بشه. یعنی هر بار عروسک رو که باز میکنی، باید به عروسک کوچکتری برسی، نه یه عروسک هماندازه یا بزرگتر.
برای مثال توابع بازگشتی، میخوام مثال فاکتوریل رو بهتون توضیح بدم! اما قبلش، فاکتوریل چیه؟
فاکتوریل یه عملیات ریاضی خیلی سادس که با علامت تعجب (!) نشون داده میشه. فاکتوریلِ یه عدد، یعنی اون عدد رو در تمام عددهای صحیحِ قبل از خودش (تا 1) ضرب کنیم. مثال فاکتوریل عدد 4:
4! = 4 × 3 × 2 × 1 = 24
فاکتوریل عدد 5:
5! = 5 × 4 × 3 × 2 × 1 = 120
به کد زیر دقت کن:
#include <stdio.h> int factorial(int number) { int result = 1; while (number > 1) { result *= number; number--; } return result; } int main() { printf("%d", factorial(5)); }
تو این کد با استفاده از حلقه و بدون استفاده از توابع بازگشتی فاکتوریل رو بدست اوردم.
تو کد زیر با استفاده از توابع بازگشتی اینکارو انجام میدم:
#include <stdio.h> unsigned long long int factorial(int number) { if (number == 1) { return 1; } else { return number * factorial(number - 1); } } int main() { printf("%d", factorial(5)); }
تحلیل کد رو نمیرم. چرا؟ چون توابع بازگشتی چی بشه بخواد استفاده بشه و بیشتر از این (فعلا) لازم نیست براش وقت بزاریم.