طرح‌چه

نوع داده struct | گولنگ به زبان ساده

5 ماه پیش

نوع داده struct | گولنگ به زبان ساده
با نوع داده struct آشنا میشیم و یاد میگیریم چطور همانند زبان های برنامه نویسی شئ گرا با استفاده از struct اشیا را مدلسازی کنیم.

در زبان Golang، نوع داده‌ی struct یک نوع داده‌ی ترکیبی (composite type) است که برای گروه‌بندی مجموعه‌ای از مقادیر مرتبط با هم تحت یک نام استفاده می‌شود. این مقادیر که فیلدهای (fields) یک struct نامیده می‌شوند، می‌توانند انواع داده‌ای مختلفی داشته باشند.

تعریف struct

ساختار کلی یک struct به شکل زیر است:

type name struct {
	field1 type1
	field2 type2
	field3 type3
}

برای مثال میتونیم یک struct با نام Person تعریف کنیم که دارای دو فیلد Age و Name است:

package main

import "fmt"

type Person struct {
	Name string
	Age  int
}

func main() {
	var x Person
	
	fmt.Println(x)
	fmt.Println(x.Name, x.Age)
	
	x.Name = "John Doe"
	x.Age = 45
	
	fmt.Println(x)
	fmt.Println(x.Name, x.Age)
}

در مثال بالا ابتدا یک struct با نام Person تعریف کردیم که دارای دو فیلد با نام های Name و Age است. یک متغیر با نام x از نوع Person تعریف کردیم و یکبار مقدار صفر (zero value) آن را چاپ کردیم و سپس هرکدام از فیلد ها را جداگانه مقداردهی و چاپ کردیم.

مقدار صفر (zero value) یک struct برابر با مقدار صفر فیلدهای آن خواهد بود. متغیر بالا را همچنین میتونیم به صورت زیر مقدار دهی کنیم:

package main

import "fmt"

type Person struct {
	Name string
	Age  int
}

func main() {
	x := Person{
		Name: "John Doe",
		Age: 45,
	}

	fmt.Println(x)
	fmt.Println(x.Name, x.Age)
}

یک struct را میتوان بدون تخصیص دادن نام ایجاد کرد, به این حالت anonymouse struct میگویند. کافیست تعریف struct و تخصیص آن به یک متغیر را همزمان انجام دهیم.

package main

import "fmt"

func main() {
	x := struct {
		Name string
		Age  int
	}{
		Name: "John Doe",
		Age:  45,
	}

	fmt.Println(x)
	fmt.Println(x.Name, x.Age)
}

دقت کنید anonymose struct قابلیت استفاده مجدد ندارد بدین معنی که اگر بخواهیم متغیر دیگری را با همین دیتا تایپ تعریف کنیم باید دوباره ساختار struct را تکرار کنیم!

استفاده از Struct تو در تو (Nested Struct)

به دو صورت میتوان از struct به صورت تودرتو استفاده کرد:

  • داشتن یک یا چند فیلد از نوع struct
  • قراردادن یک struct درون یک struct دیگر بدون مشخص کردن فیلد (embeding)

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

package main

import "fmt"

type UsersCollection struct {
	Items []User
	Count int
}

type User struct {
	Name string
	Age  int
}

func main() {
	user1 := User{
		Name: "John Doe",
		Age:  21,
	}

	user2 := User{
		Name: "James Bond",
		Age:  44,
	}

	collection := UsersCollection{
		Items: []User{
			user1, user2,
		},
		Count: 3,
	}

	fmt.Println(collection)
	fmt.Println(collection.Items)
	fmt.Println(collection.Count)

	for i, item := range collection.Items {
		fmt.Printf("%d- %s %d\n", i, item.Name, item.Age)
	}
}

مِتُد (method)

در گولنگ شما میتونید به هر type که تعریف کرده اید مِتُد اضافه کنید و struct هم به همین صورت هست. مثال قبل رو با استفاده از متد میتوان به صورت زیر بازنویسی کرد:

package main

import "fmt"

type UsersCollection struct {
	Items []User
	Count int
}

func (c UsersCollection) Print() {
	for i, item := range c.Items {
		fmt.Printf("%d- %s %d\n", i, item.Name, item.Age)
	}
}

type User struct {
	Name string
	Age  int
}

func main() {
	user1 := User{
		Name: "John Doe",
		Age:  21,
	}

	user2 := User{
		Name: "James Bond",
		Age:  44,
	}

	collection := UsersCollection{
		Items: []User{
			user1, user2,
		},
		Count: 3,
	}

	collection.Print()
}

در مثال بالا یک مِتُد با نام Print به UsersCollection اضافه کردیم. این متد آیتم های این struct را در صفحه چاپ میکنم.

یک مِتُد با استفاده از آرگومان گیرنده (receiver argument) میتونه به نوع داده ای که متد بهش تعلق داره دسترسی پیدا کنه. آرگومان گیرنده در واقع متغیری از نوع داده ای است که مِتُد بهش تعلق داره. در صورتی که گیرنده (receiver) را به صورت اشاره گر (pointer) تعریف کنیم, میتونیم داده های struct را از طریق مِتُد تغییر بدیم.

package main

import "fmt"

type Person struct {
	Name string
	Age int
}

func (p *Person) IncreaseAge() {
	p.Age += 1
}

func (p Person) Greet() {
	fmt.Printf("Hi, My name is %s\n", p.Name)
}

func main() {
	p := Person{Name:"Mahdi", Age: 31}
	
	p.Greet() // Hi, My name is Mahdi
	p.IncreaseAge()
	
	fmt.Println(p.Age) // 32
}

در مِتُد IncreaseAge از نوع pointer برای آرگومان گیرنده (reciever) استفاده کردیم تا بتوانیم Age را تغییر دهیم.

هر متد را میتوان درون متغیر ذخیره کرد و فراخوانی کرد:

package main

import "fmt"

type Person struct {
	Name string
	Age int
}

func (p *Person) IncreaseAge() {
	p.Age += 1
}

func (p Person) Greet() {
	fmt.Printf("Hi, My name is %s\n", p.Name)
}

func main() {
	p := Person{Name:"Mahdi", Age: 31}
	
	q := p.Greet
	
	q() // Hi, My bane is Mahdi
}

همچنین متدهای یک دیتا تایپ را میتوان به یک متغیر نسبت داد و سپس, مِتُدِ درون آن متغیر را با receiver هایی از دیتا تایپ یکسان فراخوانی کرد. 

package main

import "fmt"

type Person struct {
	Name string
	Age int
}

func (p *Person) IncreaseAge() {
	p.Age += 1
}

func (p Person) Greet() {
	fmt.Printf("Hi, My name is %s\n", p.Name)
}

func main() {
	p := Person{Name:"Mahdi", Age: 31}
	q := Person{Name:"Armin", Age: 39}

	greet := Person.Greet
	
	greet(p) // Hi, My name is Mahdi
	greet(q) // Hi, My name is Armin
}

قسمت قبل: تابع (function) | گولنگ به زبان ساده

قسمت بعد: اینترفیس (interface) | گولنگ به زبان ساده


دیدگاه ها