تا

تابع در Go

1 تمرین

درباره‌ی تابع

تابع به شما امکان می‌دهد کد را در قالب یک واحد قابل استفاده‌ی مجدد گروه‌بندی کنید. هر تابع از کلیدواژه‌ی func، اسم تابع، و فهرستی جداشده با کاما از صفر یا چند پارامتر و نوع آن‌ها در پرانتز تشکیل می‌شود.

پارامترهای تابع

نوع همه‌ی پارامترها باید صریحاً مشخص شود؛ برای پارامترها استنتاج نوع وجود ندارد. پارامترها مقدار پیش‌فرض ندارند، بنابراین همه‌ی پارامترهای تابع الزامی‌اند.

import "fmt"

// No parameters
func PrintHello() {
    fmt.Println("Hello")
}

// One parameter
func PrintHelloName(name string) {
  fmt.Println("Hello " + name)
}

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

import "fmt"

func PrintGreetingName(greeting, name string) {
  fmt.Println(greeting + " " + name)
}

پارامترها در برابر آرگومان‌ها

بیایید به‌سرعت دو اصطلاحی را بررسی کنیم که اغلب با هم اشتباه گرفته می‌شوند: parameters و arguments. پارامترهای تابع همان اسم‌هایی هستند که در امضای تابع تعریف می‌شوند، مانند greeting و name در تابع PrintGreetingName در بالا. آرگومان‌های تابع مقادیر مشخصی هستند که هنگام فراخوانی تابع به پارامترهای آن داده می‌شوند. برای نمونه، در مثال زیر، "Hello" و "Katrina" آرگومان‌هایی هستند که به پارامترهای greeting و name داده می‌شوند:

PrintGreetingName("Hello", "Katrina")

مقادیر بازگشتی

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

func Hello(name string) string {
  return "Hello " + name
}

func HelloAndGoodbye(name string) (string, string) {
  return "Hello " + name, "Goodbye " + name
}

فراخوانی توابع

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

import "fmt"

// No parameters, no return value
func PrintHello() {
    fmt.Println("Hello")
}
// Called like this:
PrintHello()

// One parameter, one return value
func Hello(name string) string {
  return "Hello " + name
}
// Called like this:
greeting := Hello("Dave")

// Multiple parameters, multiple return values
func SumAndMultiply(a, b int) (int, int) {
    return a+b, a*b
}
// Called like this:
aplusb, atimesb := SumAndMultiply(a, b)

مقادیر بازگشتی نام‌دار و بازگشت لخت

مقادیر بازگشتی نیز مانند پارامترها می‌توانند به‌صورت اختیاری نام‌دار باشند. اگر از مقادیر بازگشتی نام‌دار استفاده شود، دستور return بدون آرگومان همان مقادیر را بازمی‌گرداند. به این کار بازگشت «لخت» می‌گویند.

func SumAndMultiplyThenMinus(a, b, c int) (sum, mult int) {
    sum, mult = a+b, a*b
    sum -= c
    mult -= c
    return
}

انتقال با مقدار در برابر انتقال با ارجاع

روشن کردن مفهوم انتقال با مقدار و انتقال با ارجاع نیز اهمیت دارد.

اول بیایید انتقال با مقدار را روشن کنیم. در مثال زیر، وقتی متغیر val را به تابع MultiplyByTwo می‌دهیم، یک نسخه از val را داده‌ایم. به همین دلیل، مقدار newVal به 4 به‌روزرسانی شده است، اما متغیر اصلی val هنوز 2 است. در پشت‌صحنه، Go اساساً یک نسخه از مقدار اصلی می‌سازد تا فقط همین نسخه، که به آن v می‌گویند، توسط تابع تغییر کند.

val := 2
func MultiplyByTwo(v int) int {
    v = v * 2
    return v
}
newVal := MultiplyByTwo(val)
// newval is 4, val is still 2 because only a copy of its value was passed into the function

به بیان دقیق، در Go همه‌ی آرگومان‌ها با مقدار منتقل می‌شوند، یعنی از مقدار یا داده‌ای که به تابع داده می‌شود یک نسخه ساخته می‌شود. اما اگر نمی‌خواهید از داده‌ای که به تابع داده می‌شود نسخه بسازید و می‌خواهید آن داده را درون تابع تغییر دهید، باید از pointers به عنوان آرگومان استفاده کنید، یعنی همان انتقال با ارجاع.

اشاره‌گرها

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

فعلاً همین‌قدر کافی است که بدانید انواع اشاره‌گر را می‌توان از * جلوی نوع در امضای تابع تشخیص داد.

func HandlePointers(x, y *int) {
    // Some logic to handle integer values referenced by pointers x and y
}

اگر مفهوم pointer گیج‌کننده به نظر می‌رسد، نگران نباشید. بخش جداگانه‌ای در ادامه‌ی برنامه‌ی درسی داریم که به شما کمک می‌کند بر اشاره‌گرها مسلط شوید.

استثناها

توجه داشته باشید که slices و maps استثناهایی بر قاعده‌ی بالا هستند. وقتی یک slice یا map را به عنوان آرگومان به تابعی می‌دهیم، با آن‌ها مانند انواع اشاره‌گر رفتار می‌شود، هرچند * صریحی در نوع وجود ندارد. یعنی اگر یک اسلایس یا مپ را به تابع بدهیم و داده‌ی زیربنایی آن را تغییر دهیم، تغییرات روی اسلایس یا مپ اصلی بازتاب می‌یابد.

انواع دیگر توابع

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

ویرایش از طریق GitHub این پیوند در پنجره یا زبانه‌ی جدیدی باز می‌شود

تابع را یاد بگیرید