1.7 تابع (function)

1.7 تابع (function)

1.7.1 تابع چیست؟ #

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

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

1.7.1.1 چرا از توابع در برنامه نویسی استفاده می‌کنیم؟ #

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

1.7.2 توابع در زبان گولنگ #

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

1.7.2.1 تعریف یک تابع #

1func function_name( [Parameter-list] ) [return_types] {
2
3   // body of the function
4
5}
  1. func - با این کلید واژه یک تابع تعریف می‌کنیم.
  2. function_name - نام تابع باید یکتا و در طول برنامه منحصر به فرد باشد.
  3. Parameter-list - پارامترهای ورودی در این قسمت تعریف می‌شوند. پارامترها اختیاری هستند یعنی ممکن است یک تابع هیچ پارامتری نداشته باشد.
  4. Return_type - نوع داده‌های بازگشتی را در این قسمت مشخص می‌کنیم و در برخی توابع عملیات موردنظر را بدون بازگرداندن مقداری انجام می‌دهند در این توابع نیازی به تعیین return_type نیست.
  5. Function Body - این قسمت شامل کدهایی است که نشان می‌دهد تابع چه کارهایی انجام می‌دهد.

1.7.2.1.1 مثال #

 1package main
 2
 3import "fmt"
 4
 5func plus(a int, b int) int {
 6	return a + b
 7}
 8
 9func main() {
10	fmt.Println(plus(4, 13))
11}
1$ go run func.go
217
  1. در اینجا یک تابع با نام plus تعریف کردیم که دو پارامتر a, b را با نوع داده int دریافت می‌کند و جمع این دو عدد را حساب می‌کند. توجه کنید که همیشه باید نوع داده را مشخص کنیم.
  2. بعد از تعریف پارامترهای ورودی و قبل از قرار دادن کد داخل براکت int را می‌بینید که نوع خروجی داده را مشخص می‌کند، یعنی خروجی این تابع باید از نوع int باشد.
  3. برای صدا زدن توابع کافی است نام تابع رو همراه با پرانتز باز و بسته تایپ کنید, برای مثال ()plus.

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

1func plus(a, b int)
2// or
3func name(a string, b,c int)

1.7.2.1.2 الگو دیگر تعریف تابع: #

 1package main
 2
 3import "fmt"
 4
 5func main() {
 6	plus := func (a int, b int) int {
 7		return a + b
 8	}
 9	fmt.Println(plus(3, 4))
10}
1$ go run main.go
27

1.7.3 قوانین نام گذاری تابع در گولنگ #

  • نام تابع باید با یک حرف شروع شود.
  • نام تابع فقط می‌تواند شامل حروف-عدد و underscores باشد.  (A-z0-9,  _ )
  • نام تابع به حروف کوچک و بزرگ حساس است.
  • در نام‌گذاری تابع از فاصله نمی‌توانیم استفاده کنیم.

‬1.7.4 توابع چند بازگشتی در گولنگ (Multiple results) #

همچنین در گولنگ توابع می‌توانند چندین مقادیر را برگردانند.

 1package main
 2
 3import "fmt"
 4
 5func vals() (int, int) {
 6    return 3, 7
 7}
 8
 9func main() {
10
11    a, b := vals()
12    fmt.Println(a)
13    fmt.Println(b)
14
15    _, c := vals()
16    fmt.Println(c)
17}
1$ go run main.go
23
37
47
  1. در کد بالا تابعی ساختیم با اسم vals که دو خروجی از نوع int دارد بنابراین نوع تعریف توابع چندبازگشتی متفاوت است و بصورت (data-type, data-type, … ) است.
  2. در مثال اول ما با کمک دو متغیر a,b دو خروجی تابع vals را دریافت کردیم.
  3. در مثال دوم ما با استفاده از _ blank identifier از دریافت یا استخراج خروجی اول صرف نظر کردیم و فقط خروجی دوم با متغیر c را دریافت کردیم.
در توابع چند بازگشتی خروجی آنها را باید با , از هم جدا کرد

1.7.4.1 مقادیر بازگشتی نام گذاری شده (Named Return Values) #

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

 1package main
 2
 3import "fmt"
 4
 5func split(sum int) (x, y int) {
 6	x = sum * 4 / 9
 7	y = sum - x
 8	return
 9}
10
11func main() {
12	fmt.Println(split(17))
13}
1$ go run main.go
27 10
  1. ما در این تابع مقدار بازگشتی را x, y از نوع int نام گذاری می‌کنیم.
  2. در اینجا یک دستور return بدون تعیین آرگومان بازگشتی، مقادیر نام گذاری شده را باز می‌گرداند، که به عنوان Naked return شناخته می‌شود.
  3. از naked return باید در توابع کوتاه مانند مثال بالا استفاده شود. استفاده آن در تابع طولانی منجر به کاهش خوانایی کد می شود.

1.7.5 توابع متنوع در گولنگ (Variadic Functions) #

یکی از قابلیت‌های گو وجود توابع متنوع است. منظور از توابع متنوع توابعی هستند که بدون محدودیت پارامتر دریافت می‌کنند (این نکته رو در نظر بگیرین که نباید تایپ ورودی‌ها با یکدیگر فرق کند، برای مثال همه باید int باشند). ‍‍

 1package main
 2
 3import "fmt"
 4
 5func sum(nums ...int) {
 6    fmt.Print(nums, " ")
 7    total := 0
 8
 9    for _, num := range nums {
10        total += num
11    }
12    fmt.Println(total)
13}
14
15func main() {
16
17    sum(1, 2)
18    sum(1, 2, 3)
19
20    nums := []int{1, 2, 3, 4}
21    sum(nums...)
22}
1$ go run variadic-functions.go 
2[1 2] 3
3[1 2 3] 6
4[1 2 3 4] 10
  1. در اینجا تابعی تعریف کردیم که تعداد دلخواهی از آرگومان‌ها از نوع int را به کمک … (بهش میگن Ellipsis) که قبل از نوع داده قرار گرفته به داخل تابع منتقل می‌کند.
  2. برای صدا زدن این توابع می‌توان به روش sum(num1, num2, …) عمل کرد.
  3. اگر شما داده‌ای با نوع slice دارید می‌توانید آن را به کمک اپراتور …(Ellipsis) به صورت sum(nums…) به داخل تابع انتقال بدید.

1.7.6 توابع ناشناس در گولنگ (Anonymous Functions) #

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

 1package main
 2
 3import "fmt"
 4
 5func main() {
 6
 7  // anonymous function
 8  var sum = func(n1, n2 int) int {
 9    sum := n1 + n2
10  
11    return sum
12  } 
13
14  // function call
15  result := sum(5, 3)
16
17  fmt.Println("Sum is:", result)
18
19}
1$ go run main.go
2Sum is: 8
  1. از آنجایی که توابع ناشناس نامی ندارد ما در بعضی اوقات آن‌ها را به یک متغیر اختصاص می‌دهیم سپس از نام متغیر برای فراخوانی تابع استفاده می‌کنیم. در این مثال می‌بینید که ما از sum برای فراخوانی تابع استفاده می‌کنیم.
  2. مانند توابع معمولی ما می‌توانیم برای تابع ناشناس پارامتر تعریف کنیم و همچنین مقداری را از تابع برگردانیم در این مثال تابع دو مقدار با نوع داده int دریافت می‌کنید و یک خروجی با نوع int دارد.
  3. تابع ناشناس را می‌توان برای عملکردهایی که نیازی به نام‌گذاری ندارند و معمولا برای استفاده کوتاه مدت هست، استفاده کرد.

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

 1package main  
 2  
 3import "fmt"  
 4  
 5func add10AndSum(num1 int, num2 int, sum func(n1, n2 int) int) {  
 6   result := sum(num1+10, num2+10)  
 7   fmt.Println("Sum by adding 10 is:", result)  
 8}  
 9  
10func main() {  
11   add10AndSum(5, 3, func(n1, n2 int) int {  
12      sum := n1 + n2  
13  
14      return sum  
15   })  
16}
1$ go run main.go
2Sum by adding 10 is: 28

1.7.7 توابع از پیش تعریف شده (Built-in Function) #

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

https://book.gofarsi.ir/chapter-1/go-builtins/


1.7.7 کلوژر(Function closure) #

یک نوع دیگری از anonymous function ها در زبان گولنگ، کلوژر ها هستند. به بیان ساده زمانی که یک فانکشن درون خودش، متغیر هایی که اسکوپ آنها خارج از اسکوپ خودش قرار دارد استفاده کند. کلوژر میگوییم.

 1package main
 2
 3import "fmt"
 4
 5func main() {
 6	number := 1
 7
 8	func() {
 9		fmt.Println(number * 2)
10	}()
11}

در مثال بالا ما یک anonymous function داریم که درون خودش از متغیر اسکوپی که خارج از خودش قرار دارد استفاده کرده است. به این عمل کلوژر می گوییم.

زمانی که از کلوژر ها استفاده می کنید. برخی مواقع بهتر است به‌جای اینکه به‌صورت مستقیم به متغیر اسکوپ خارجی دسترسی داشته باشید، در پارامتر های ورودی مقدار را دریافت کنید. به مثال زیر دقت کنید(در این مثال از concurrency استفاده کردیم. اگر آشنایی ندارید با مطالعه مقدمه ای از فصل سوم مثال را متوجه می شوید)

 1package main
 2
 3import (
 4	"fmt"
 5	"time"
 6)
 7
 8func main() {
 9	for i := 0; i < 10; i++ {
10		go func() {
11			fmt.Println(i)
12		}()
13	}
14
15	time.Sleep(time.Second * 1)
16}

خروجی کد برخلاف چیزی که تصور می کنیم به این شکل است:

 1$ go run main.go
 210
 310
 410
 510
 610
 710
 810
 910
1010
1110

این اتفاق به این دلیل می افتد چون کلوژر ها به‌صورت مستقیم به مقدار اسکوپ بالایی خود دسترسی دارند. قبل از اینکه گوروتین ها مقدار را چاپ کنند حلقه به انتها می رسد و مقدار i برابر با 10 می شود. برای همین در خروجی همه گوروتین ها مقدار 10 چاپ می شود. برای حل این مشکل مقدار i را در پارامتر فانکشن دریافت می کنیم:

 1package main
 2
 3import (
 4	"fmt"
 5	"time"
 6)
 7
 8func main() {
 9	for i := 0; i < 10; i++ {
10		go func(num int) {
11			fmt.Println(num)
12		}(i)
13	}
14
15	time.Sleep(time.Second * 1)
16}

پس زمانی که از کلوژر ها استفاده می کنید به این نکات دقت کنید.