آشنایی با توابع در سی‌پلاس‌پلاس

قسمت 9 از دوره C++

معرفی

تعریف توابع

توابع و آرایه‌ها

پروتوتایپ‌ها

مثال آخر

جمع بندی

معرفی
تعریف توابع
توابع و آرایه‌ها
پروتوتایپ‌ها
مثال آخر
جمع بندی

در این مقاله با مفهوم توابع در برنامه‌نویسی سی‌پلاس‌پلاس آشنا می‌شویم.

f(x)

توابع به نوعی بخش‌هایی از کد هستند که می‌توانند چندین بار طی اجرای برنامه شما فراخوانی و اجرا شوند و به همین طریق نیاز به تکرار و بازنویسی برخی کدها را کاهش دهند.

برای تعریف یک تابع اول باید برای آن یک اسم انتخاب کنیم همونطور که این کار را با متغیرها انجام می‌دادیم. برای مثال بگذارید یک تابع به اسم add تعریف کنیم که دو عدد a و b را با هم جمع می‌کند.

add(a, b) {
	return a + b;
}

return برای برگرداندن یک مقدار به جایی که تابع ما اجرا شده استفاده می‌شود و با استفاده از آن تابع ما می‌تواند یک خروجی تولید کند (کمی جلوتر عملکرد return را خواهیم دید)

اما کد بالا هنوز چند مشکل دارد که نیاز حل آن‌ها داریم. اولین مشکل این است که به کامپایلر اطلاع ندادیم که a و b از چه نوع داده‌ای هستند. مانند متغیرها که ما باید نوع آن‌ها را مشخص می‌کردیم در توابع هم باید نوع ورودی‌ها و خروجی تعیین شوند. می‌دانیم که این تابع می‌خواست دو عدد را با هم جمع بزند پس می‌تواند به هر دو ورودی (یا پارامتر) a و b نوع int بدهد، همچنین چون این تابع دو int را با هم جمع می‌کند قطعا حاصل و خروجی آن نیز int خواهد بود پس نوع خروجی که قبل نام تابع نوشته می‌شود را نیز معادل int می‌گذاریم.

این تابع می‌تواند با add(10, 20) اجرا شده و عملیاتی که عهده‌دار بوده را به انجام رساند و خروجی عملیات را نیز برگرداند.

یک مثال کامل که از این تابع استفاده کند و خروجی آن را چاپ کند را می‌توانید در زیر ببینید:

#include <iostream>

using namespace std;

int add(int a, int b) {
    return a + b;
}

int main() {
    cout << add(2, 3) << endl;
}
5

برنامه محاسبه معدل را به خاطر دارید؟ در مثال بعدی می‌خواهیم شرط صحیح بودن نمره را به یک تابع انتقال دهیم. اسم این تابع می‌تواند isValid باشد و خروجی آن bool خواهد بود که یک وضعیت منطقی درست یا غلط (درست بودن یا نبودن شرط ما) را نشان می‌دهد.

این کدی‌است که قبلا نوشته بودیم:

#include <iostream>

using namespace std;

int main() {
	int students;
	cin >> students;
	int scores[students];
	for (int i = 0; i < students; i++) {
		cout << "Enter student #" << i << " score ";
		cin >> scores[i];
	}
	int sum = 0;
	int corrupt = 0;
	for (int i = 0; i < students; i++) {
		if (scores[i] > 100 || scores[i] < 0) {
			corrupt++;
			continue;
		}
		sum += scores[i];
	}
	cout << sum / (students - corrupt) << endl;
	cout << "Corrupt numbers: " << corrupt << endl;
}

و این تابع isValid خواهد بود که بخش شرط را در کد بالا جایگزین می‌کند.

bool isValid(int score) {
    return score >= 0 && score <= 100;
}

این تابع نمره را به عنوان یک پارامتر دریافت می‌کند و درستی آن را مبنی بر بازه‌ای که برای آن تعریف شده، بر می‌گرداند. این تابع چک می‌کند که score بزرگ‌تر یا مساوی ۰ باشد و همزمان کوچکتر یا مساوی ۱۰۰ باشد. درواقع با استفاده از && یا and به معنی “و” ما توانستیم دو شرط را ترکیب کنیم. هر دو این شرط ها باید برقرار باشند تا خروجی این تابع true شود.

و کدی که از این تابع استفاده کند به شکل زیر در خواهد آمد:

#include <iostream>

using namespace std;
#include <iostream>

using namespace std;

bool isValid(int score) {
    return score >= 0 && score <= 100;
}

int main() {
	int students;
	cin >> students;
	int scores[students];
	for (int i = 0; i < students; i++) {
		cout << "Enter student #" << i << " score ";
		cin >> scores[i];
	}
	int sum = 0;
	int corrupt = 0;
	for (int i = 0; i < students; i++) {
		if (!isValid(scores[i])) {
			corrupt++;
			continue;
		}
		sum += scores[i];
	}
	cout << sum / (students - corrupt) << endl;
	cout << "Corrupt numbers: " << corrupt << endl;
}
int main() {
    cout << "Hello, World!" << endl;
}

5
Enter student #0 score -80
Enter student #1 score 70
Enter student #2 score 60
Enter student #3 score 90
Enter student #4 score 120
73
Corrupt numbers: 2

با استفاده از عملگر ! یا همان not یا نقیض منطقی ما می‌توانیم مقدار true و false را برعکس کنیم. یعنی true به false تبدیل شده و false به true تبدیل می‌شود. در این کد این عملگر پشت تابع isValid قرار گرفته تا وضعیت آن را عکس کند و اگر نمره درست نبود شرط را اجرا کند.

فرض کنید بعدا تصمیم گرفتیم سیستم نمره ۰ تا ۱۰۰ را کنار گذاشته و از سیستم نمره ۰ تا ۲۰ استفاده کنیم، اگر در همه جای کد خود شرط را استفاده کرده بودیم مجبور بودیم همه آن شرط‌ها را تغییر دهیم. اما با استفاده از توابع ما فقط مجبور به تغییر محتوای تابع isValid خواهیم بود.

آخرین تمرین این بخش:

برنامه قسمت دوم، که تنها وظیفه‌اش سلام دادن به کاربر برنامه بود را به خاطر دارید؟ در این تمرین می‌خواهیم یک تابع برای نوشتن پیام سلام بنویسیم.

این کدی بود که در آن قسمت نوشتیم:

#include <iostream>
#include <string>

using namespace std;

int main() {
    string name;
    cin >> name;
    cout << "Hello, " << name < < endl;
}

و این تابعی خواهد بود که پیام سلام را چاپ می‌کند:

void sayHello(string name) {
    cout << "Hello, " << name;
}

همینطور که می‌بینید، نوع خروجی این تابع void به معنی تهی است. یعنی این تابع قرار نیست هیچ چیزی return کند و خروجی مشخصی ندارد. اما در عین حال این تابع از cout استفاده می‌کند تا خروجی‌ای پرینت کند.

و کد اصلی که از این تابع استفاده می‌کند یا driver code آن به شرح زیر خواهد بود:

#include <iostream>
#include <string>

using namespace std;

void sayHello(string name) {
    cout << "Hello, " << name;
}

int main() {
    string name;
    cin >> name;
    sayHello(name);
}
Radin
Hello, Radin

حتما تا به حال دقت کردید که هر بار که خواستیم یک آرایه را چاپ کنیم مجبور به نوشتن یک حلقه و شمارش تمام موارد موجود در آن شدیم.

int arr[] = { 1, 2, 3, 4, 5 };

for (int i = 0; i < 5; i++) {
    cout << arr[i] << endl;
}

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

void printArray(int arr[]) {
    for (int i = 0; i < 5; i++) {
        cout << arr[i] << endl;
    }
}

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

#include <iostream>

using namespace std;

void printArray(int arr[], int len) {
    for (int i = 0; i < len; i++) {
        cout << arr[i] << endl;
    }
}

int main() {
    int arr[] = { 1, 2, 3, 4, 5 };
    printArray(arr, 5);
}
1
2
3
4
5

تابع printArrayReversed

مثال بعدی که خواهیم نوشت تابعی است که یک آرایه دریافت و آن را به طور عکس پرینت می‌کند.

#include <iostream>

using namespace std;

void printArray(int arr[], int len) {
    for (int i = 0; i < len; i++) {
        cout << arr[i] << endl;
	}
}

void printArrayReversed(int arr[], int len) {
    for (int i = len - 1; i >= 0; i--) {
        cout << arr[i] << endl;
	}
}

int main() {
    int arr[] = { 1, 2, 3, 4, 5 };
    cout << "Elements in arr:" << endl;
    printArray(arr, 5);
    cout << "Elements in arr (in reverse):" << endl;
    printArrayReversed(arr, 5);
}
Elements in arr:
1
2
3
4
5
Elements in arr (in reverse):
5
4
3
2
1

دقت کنید که این تابع از یک حلقه استفاده می‌کند که از len - 1 (در این مثال ۴ – که آخرین اندیس موجود در آرایه است) شروع و با کاهش i به ۰ (اولین اندیس موجود در آرایه) می‌رسد. عملگر مقایسه‌ای >= انتخاب شده تا تضمن کند که i به ۰ که نشان دهنده اولین مورد موجود در آرایه است می‌رسد.

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

فرض کنید تصمیم گرفتیم پارامترهای آرایه را با “,” تفکیک کنیم. فقط مجبور خواهیم بود محتوای printArray را تغییر دهیم و چون باقی کد از آن تابع استفاده می‌کنند، دیگر نیازی به تغییر باقی آن نخواهیم داشت

#include <iostream>

using namespace std;

void printArray(int arr[], int len) {
    for (int i = 0; i < len; i++) {
        cout << arr[i];
        cout << ", ";
	}
}

int main() {
    int arr[] = { 1, 2, 3, 4, 5 };
    printArray(arr, 5);
}
1, 2, 3, 4, 5, 

همینطور که می‌بینید این کد یک ویرگول (کاما) اضافه در آخر چاپ می‌کند. برای حل این مساله می‌توانیم چاپ شدن ویرگول را در یک بلوک شرط قرار دهیم و آن را فقط وقتی چاپ کنیم که i معادل len - 1 یا آخرین اندیس نباشد.

یک endl هم می‌تواند در آخر چاپ شود تا بعد از چاپ آرایه به خط بعد برویم و مشکل prompt در ترمینال را حل کنیم.

#include <iostream>

using namespace std;

void printArray(int arr[], int len) {
    for (int i = 0; i < len; i++) {
        cout << arr[i];
        if (i != len) {
            cout << ", ";
		}
	}
	cout << endl;
}

int main() {
    int arr[] = { 1, 2, 3, 4, 5 };
    printArray(arr, 5);
}
1, 2, 3, 4, 5

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

#include <iostream>

using namespace std;

int main() {
    int arr[] = { 1, 2, 3, 4, 5 };
    for (int i = 0; i < 3; i++) {
        printArray(arr, 5);
    }
}

void printArray(int arr[], int len) {
    for (int i = 0; i < len; i++) {
        cout << arr[i];
        if (i != len - 1) {
            cout << ", ";
        }
    }
    cout << endl;
}
error: 'printArray' was not declared in this scope

همینطور که می‌بیینید ارور دریافتی نشان می‌دهد که تابع printArray تعریف نشده، در حالی که ما این تابع را زیر بخش main تعریف کردیم. مشکل این است که کامپایلر زبان سی‌پلاس‌پلاس کد شما را از بالا به پایین بررسی می‌کند و وقتی هنوز به تعریف تابع printArray نرسیده نمی‌تواند به main اجازه دهد که از آن استفاده کند.
برای حل این مشکل، ما یک پروتوتایپ برای تابع تعریف می‌کنیم؛ prototype ها اطلاعاتی مانند نام تابع و ورودی‌ها و خروجی آن را دارند اما منطق و کد اصلی یا بدنه آن را حذف می‌کنند. به نوعی این پروتوتایپ‌ها یک قول به کامپایلر هستند که این توابع در ادامه تعریف خواهند شد.

#include <iostream>

using namespace std;

void printArray(int arr[], int len);

int main() {
    int arr[] = { 1, 2, 3, 4, 5 };
    for (int i = 0; i < 3; i++) {
        printArray(arr, 5);
    }
}

void printArray(int arr[], int len) {
    for (int i = 0; i < len; i++) {
        cout << arr[i];
        if (i != len - 1) {
            cout << ", ";
        }
    }
    cout << endl;
}
1, 2, 3, 4, 5

در پروتوتایپ‌ها نام ورودی‌ها نیز قابل حذف است. اما پیشنهاد می‌شود این کار انجام نشود چرا که نام پارامترها می‌تواند به شرح عملکرد آن ها کمک کند:

void printArray(int[], int);

بدون این نام‌ها برنامه‌نویس‌های دیگر مجبور خواهند بود تعریف اصلی تابع شما را پیدا کنند و بعد از آن‌جا عملکرد آن را بررسی کنند.

مثال آخر تابعی به نام max خواهد بود که یک آرایه به عنوان ورودی دریافت می‌کند و بزرگترین مقدار یا ماکسیموم در آن آرایه را بر می‌گرداند. این تابع باید یک آرایه به عنوان ورودی و یک عدد که طول آن آرایه خواهد بود دریافت کند. به طور کلی این تابع فقط باید اولین مورد در آرایه را به عنوان ماکسیموم در نظر بگیرد و بعد تمام موارد دیگر را با آن مقایسه کند، اگر هر کدام از آن‌ها از آن بزرگتر بودند می‌توانیم مقداری که قبلا به عنوان ماکسیموم فرض کرده بودیم را تغییر دهیم و معادل این مقدار ماکسیموم جدید قرار دهیم.

void max(int arr[], int len) {
    int max = arr[0];
    for (int i = 1; i < len; i++) {
        if (arr[i] > max) {
            max = arr[i];
        }
    }
}

این خروجی این تابع خواهد بود:

#include <iostream>

using namespace std;

void max(int[], int);

int main() {
    int arr[] = { 1, 2, 3, 4, 5 };
    cout << max(arr, 5) << endl;
}

void max(int arr[], int len) {
    int max = arr[0];
    for (int i = 1; i < len; i++) {
        if (arr[i] > max) {
            max = arr[i];
        }
    }
}
5

اگر به یاد داشته یاشید قبلا اشاره کردیم که main یک تابع است اما از آن پس از آن به عنوان “بخش” یاد کردیم تا از مفهومی که هنوز معرفی نکرده‌ایم استفاده نکنیم. اما اکنون که می‌دانید تابع چیست، می‌توانید به راحتی ببینید که main یک تابع خاص است که وقتی برنامه شما اجرا می‌شود فراخوانی می‌شود. نوع داده خروجی main، عدد(int) است که وضعیت خروج برنامه را تعیین می‌کند، وضعیت ۰ معادل “بدون خطا” و اعداد دیگر به عنوان نشانه‌ای برای خطاهای مختلف در نظر گرفته می‌شوند. اکثر برنامه‌نویسان از ۱ به عنوان خطای عمومی استفاده می‌کنند.
با وجود این که main باید یک int ریترن کند هیچ کدام از برنامه‌هایی که تا اینجا نوشتیم، این کار را انجام نداده، این به این خاطر ممکن شده که کامپایلرهای مدرن تر سی‌پلاس‌پلاس به طور پیش‌فرض مقدار ۰ را به عنوان خروجی تابع main در نظر می‌گیرند. این در حالی‌است که هر تابع دیگری که خروجی‌اش void نیست باید حتما یک مقدار ریترن کند.

مثال دیگری از توابعی که قبلا با آن‌ها کار کرده‌ایم مربوط به برنامه حدس عددی بود که قبلا نوشتیم. در آن برنامه از srand و rand و time استفاده کردیم که همه توابعی از standard library سی‌پلاس‌پلاس هستند و توسط برنامه‌نویسان دیگر در اختیار ما قرار گرفتند.
برنامه ما از time استفاده کرد تا زمان لحظه اجرا را دریافت کند، آن را به srand داد تا به عنوان seed و ورودی تابع رندوم استفاده شود و با استفاده از rand عدد رندوم را تولید کرد.

#include <iostream> 
#include <time.h>
using namespace std; 
int main() {
  // generate a random number from 1 to 100
  srand(time(0));
  int number = rand() % 100 + 1; 
  int guess = -1; 
  while (guess != number) {
	cout << "Enter your guess "; 
	cin >> guess; 
	if (guess == number) { 
	  cout << "You won" << endl; 
	} else if (guess > number) { 
	  cout << "Too High" << endl; 
	} else { // is smaller
	  cout << "Too Low" << endl; 
	} 
  } 
}
Enter your guess 50
Too High
Enter your guess 30
Too Low
Enter your guess 40
Too Low
Enter your guess 45
Too Low
Enter your guess 47
Too High
Enter your guess 46
You won

در این مقاله با توابع و کاربردهای آن‌ها در زبان سی‌پلاس‌پلاس آشنا شدیم. مثال‌های این مقاله را تست کنید و گزینه زیر را برای علامت زدن آن به عنوان تکمیل شده بزنید. ممنون از این که تا اینجای مقاله با ما همراه بودید و امیدوارم شما را در قسمت بعد هم ملاقات کنم.

وارد شوید تا پیشرفت خود را ثبت کنید

وارد شوید تا پروژه‌هایی که تکمیل می‌کنید را علامت گذاری کنید و فرایند یادگیری خود را ثبت کنید

دیدگاهتان را بنویسید

برای نوشتن نظر٬ اول باید وارد شوید

مرحله بعد

معرفی

اطلاعات شما با موفقیت ثبت شد. کارشناسان ما در اسرع وقت با شما تماس خواهند گرفت.

از شکیبایی شما متشکریم.