2.2 ساختار (struct)

2.2 ساختار (struct)

در زبان گو ساختار به کالکشنی از فیلدها با تایپ های مختلف می گویند. شما با استفاده ساختار می توانید ساختار یا مدل کلی از بدنه پروژه خود را بنویسید. برای نمونه ما یک ساختار employee مثال زدیم تا با ساختار آشنا شوید.

1type employee struct {
2    name   string
3    age    int
4    salary int
5}

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

1type sample struct {}

متدها روی ساختار

  • برای ایجاد ساختار باید از کلمه کلیدی type استفاده کنید.

  • داخل بدنه ساختار فیلدها را تعریف کنید.

    • فیلد name از نوع string
    • فیلد age از نوع int
    • فیلد salary از نوع int
ساختار زبان گو را با class در سایر زبان ها مقایسه می کنند.

2.2.1 تعریف تایپ struct #

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

1type point struct {
2    x float64
3    y float64
4}

در بالا ما ۲ تا فیلد برای ساختار تعریف کردیم که هر دو فیلد از نوع float64 می باشند.

2.2.2 ایجاد یک متغیر ساختار (struct) #

برای ایجاد یک متغیر ساختار می توانید خیلی ساده تعریف کنید و خانه ای از حافظه را برای متغیر در نظر گرفته شود.

1emp := employee{}

در بالا یک متغیر با مقدار پیش فرض صفر ساختار employee تعریف کردیم.

زمانیکه یک متغیر ساختار تعریف می کنید مقدار استفاده از حافظه 0 بایت می باشد.
  • ایجاد متغیر ساختار و مقدار دهی فیلدها در یک خط :
1emp := employee{name: "Sam", age: 31, salary: 2000}
  • ایجاد متغیر ساختار و مقدار دهی فیلد در خط های مختلف (توصیه می شود) :
1emp := employee{
2   name:   "Sam",
3   age:    31,
4   salary: 2000,
5}

توجه کنید هیچ اجباری نیست حتما فیلدی را مقدار دهی کنید چون می توانید در جاهای دیگر یا هر زمانیکه نیاز داشتید مقدار دهی کنید.

1emp := employee{
2   name: "Sam",
3   age: 31,
4}

در بالا ما فیلد salary را مقدار دهی نکردیم و بطور پیش فرض کامپایلر با توجه به تایپ فیلد مقدار پیش فرض صفر اون تایپ را در نظر میگیرد.

 1package main
 2
 3import "fmt"
 4
 5type employee struct {
 6    name   string
 7    age    int
 8    salary int
 9}
10
11func main() {
12    emp1 := employee{}
13    fmt.Printf("Emp1: %+v\n", emp1)
14
15    emp2 := employee{name: "Sam", age: 31, salary: 2000}
16    fmt.Printf("Emp2: %+v\n", emp2)
17
18    emp3 := employee{
19        name:   "Sam",
20        age:    31,
21        salary: 2000,
22    }
23    fmt.Printf("Emp3: %+v\n", emp3)
24
25    emp4 := employee{
26        name: "Sam",
27        age:  31,
28    }
29    fmt.Printf("Emp4: %+v\n", emp4)
30}
1$ go run main.go
2Emp1: {name: age:0 salary:0}
3Emp2: {name:Sam age:31 salary:2000}
4Emp3: {name:Sam age:31 salary:2000}
5Emp4: {name:Sam age:31 salary:0}
  • ایجاد متغیر ساختار و مقدار دهی فیلدها بدون نام فیلد :

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

1emp := employee{"Sam", 31, 2000}
 1package main
 2
 3import "fmt"
 4
 5type employee struct {
 6    name   string
 7    age    int
 8    salary int
 9}
10
11func main() {
12    emp := employee{"Sam", 31, 2000}
13    fmt.Printf("Emp: %+v\n", emp)
14}
1$ go run main.go
2Emp2: {name:Sam age:31 salary:2000}

2.2.3 دسترسی و تنظیم فیلدهای ساختار (struct) #

زمانیکه شما یک متغیر ساختار تعریف می کنید می توانید خیلی آسان با استفاده از همان متغیر به فیلدهای ساختار دسترسی پیدا کنید و مقدار هر کدام از فیلدها را تغییر دهید.

 1package main
 2
 3import "fmt"
 4
 5type employee struct {
 6    name   string
 7    age    int
 8    salary int
 9}
10
11func main() {
12    emp := employee{name: "Sam", age: 31, salary: 2000}
13
14    //Accessing a struct field
15    n := emp.name
16    fmt.Printf("Current name is: %s\n", n)
17
18    //Assigning a new value
19    emp.name = "John"
20    fmt.Printf("New name is: %s\n", emp.name)
21}
1$ go run main.go
2Current name is: Sam
3New name is: John

2.2.4 کار با اشاره گر (Pointer) در ساختار (struct) #

شما برای ایجاد یک struct از نوع اشاره گر از دو حالت زیر می توانید استفاده کنید :

  • با استفاده از عملگر & که اشاره به خانه حافظه دارد
  • با استفاده از تابع new

2.2.4.1 ایجاد ساختار با استفاده عملگر & #

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

1emp := employee{name: "Sam", age: 31, salary: 2000}
2empP := &emp

حتی یک ساختار اشاره گر می تواند مستقیما ایجاد شود :

1empP := &employee{name: "Sam", age: 31, salary: 2000}

به کد زیر توجه کنید :

 1package main
 2
 3import "fmt"
 4
 5type employee struct {
 6    name   string
 7    age    int
 8    salary int
 9}
10
11func main() {
12    emp := employee{name: "Sam", age: 31, salary: 2000}
13    empP := &emp
14    fmt.Printf("Emp: %+v\n", empP)
15    empP = &employee{name: "John", age: 30, salary: 3000}
16    fmt.Printf("Emp: %+v\n", empP)
17}
1$ go run main.go
2Emp: &{name:Sam age:31 salary:2000}
3Emp: &{name:John age:30 salary:3000}

2.2.4.2 ایجاد ساختار با استفاده تابع new #

1func new(Type) *Type

با استفاده از تابع new :

  • شما یک ساختار ایجاد می کنید.
  • سپس فیلدها با مقدار پیش فرض صفر مقدار دهی اولیه می شوند.
  • در نهایت ساختار شما از نوع اشاره گر بازگشت داده می شود.
1empP := new(employee)

برای اینکه آدرس خانه حافظه ساختار از نوع اشاره گر را ببینید کافیه :

1fmt.Printf("Emp Pointer: %p\n", empP)

برای اینکه مقدار هر یک از فیلدها را ببینید کافیه :

1fmt.Printf("Emp Value: %+v\n", *empP)
1$ go run main.go
2Emp Value: {name: age:0 salary:0}

در زیر یک کد نمونه قرار دادیم به عنوان مثال :

 1package main
 2
 3import "fmt"
 4
 5type employee struct {
 6    name   string
 7    age    int
 8    salary int
 9}
10
11func main() {
12    empP := new(employee)
13    fmt.Printf("Emp Pointer Address: %p\n", empP)
14    fmt.Printf("Emp Pointer: %+v\n", empP)
15    fmt.Printf("Emp Value: %+v\n", *empP)
16}
1$ go run main.go
2Emp Pointer Address: 0xc000130000
3Emp Pointer: &{name: age:0 salary:0}
4Emp Value: {name: age:0 salary:0}

2.2.5 چاپ یک متغیر ساختار (struct) #

برای اینکه بتوانید یک متغیر را چاپ کنید از دو روش زیر می توانید استفاده کنید و توجه کنید متغیر ساختار بصورت key/value هستش.

  • با استفاده از پکیج fmt
  • چاپ متغیر ساختار با استفاده از پکیج json/encoding

2.2.5.1 چاپ با استفاده از fmt #

در پکیج fmt ۲ تا تابع کاربردی جهت چاپ وجود دارد که اکثر اوقات استفاده می کنیم :

  • تابع Println ورودی را با فرمت پیش فرض چاپ و در نهایت خط جدید در نظر میگیرد.
  • تابع Printf ورودی را با فرمت مشخص شده چاپ می کند و دست شما برای تعیین فرمت باز می باشد.

در زیر ما یک نمونه از employee ایجاد کردیم :

1emp := employee{name: "Sam", age: 31, salary: 2000}

حال به شیوه های زیر با استفاده از تابع Printf ساختار را چاپ کردیم :

1fmt.Printf("%v", emp)  -  {Sam 31 2000}
1fmt.Printf("%+v", emp) - {name:Sam age:31 salary:2000}
  • %v - مقدار هر کدام از فیلدهای ساختار چاپ می کند.
  • %+v - مقدار هرکدام از فیلدها به همراه اسم فیلد چاپ می کند.

در زیر با استفاده از از تابع Println ساختار را چاپ کردیم :

1fmt.Println(emp) - {Sam 31 2000}

در نهایت کد زیر یک مثال کلی از چاپ با استفاده از پکیج fmt می باشد :

 1package main
 2
 3import "fmt"
 4
 5type employee struct {
 6    name   string
 7    age    int
 8    salary int
 9}
10
11func main() {
12    emp := employee{name: "Sam", age: 31, salary: 2000}
13    fmt.Printf("Emp: %v\n", emp)
14    fmt.Printf("Emp: %+v\n", emp)
15    fmt.Printf("Emp: %#v\n", emp)
16    fmt.Println(emp)
17}
1$ go run main.go
2Emp: {Sam 31 2000}
3Emp: {name:Sam age:31 salary:2000}
4Emp: main.employee{name:"Sam", age:31, salary:2000}
5{Sam 31 2000}

2.2.5.2 چاپ ساختار با استفاده از پکیج JSON #

در این روش ما با استفاده از ۲ تابع Marshal و MarshalIndent پکیج json ساختار را encode می کنیم. و در نهایت خروجی encode شده را چاپ می کنیم.

  • Marshal - در این تابع به عنوان ورودی ساختار را پاس می دهیم و در نهایت ۲ خروجی از نوع بایت و خطا دریافت می کنیم.
1Marshal(v interface{}) ([]byte, error)
  • MarhsalIndent - با استفاده از این تابع ۳ تا ورودی پاس می دهیم به ترتیب ساختار, پیشوند و indent و در نهایت ۲ خروجی از نوع بایت و خطا دریافت می کنیم.
1MarshalIndent(v interface{}, prefix, indent string) ([]byte, error)

حالا با استفاده از توابع فوق یک کد نمونه مثال می زنیم چطور از این توابع استفاده کنید :

 1package main
 2
 3import (
 4    "encoding/json"
 5    "fmt"
 6    "log"
 7)
 8
 9type employee struct {
10    Name   string
11    Age    int
12    salary int
13}
14
15func main() {
16    emp := employee{Name: "Sam", Age: 31, salary: 2000}
17    //Marshal
18    empJSON, err := json.Marshal(emp)
19    if err != nil {
20        log.Fatalf(err.Error())
21    }
22    fmt.Printf("Marshal funnction output %s\n", string(empJSON))
23
24    //MarshalIndent
25    empJSON, err = json.MarshalIndent(emp, "", "  ")
26    if err != nil {
27        log.Fatalf(err.Error())
28    }
29    fmt.Printf("MarshalIndent funnction output %s\n", string(empJSON))
30}
1$ go run main.go
2Marshal funnction output {"Name":"Sam","Age":31}
3
4MarshalIndent funnction output {
5  "Name": "Sam",
6  "Age": 31
7}
برای اطلاعات بیشتر در خصوص پکیج json می توانید به بخش آموزش کار با jsonمراجعه کنید.

2.2.6 کار با تگ ها در ساختار (struct) #

در ساختار زبان گو شما امکان اضافه کردن metadata به هر یک از فیلدها را دارید که ما به عنوان تگ می شناسیم که تگ ها برای انجام یکسری عملیات خاص نظیر encode/decode, اعتبارسنجی مقادیر فیلدها و … کمک می کند و یکی از کاربردی ترین عناوین در ساختار می باشد.

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

1type strutName struct{
2   fieldName type `key:"value" key2:"value2"`
3}
1type employee struct {
2    Name   string `json:"n"`
3    Age    int    `json:"a"`
4    Salary int    `json:"s"`
5}

تگ json به شما برای تعیین نام key داخل json کمک می کند و شما می توانید عنوان هر یک از فیلدهای بدنه json را یک نام اختصاصی قرار دهید و با استفاده از اون نام اختصاصی عملیات encode/decode در json انجام دهید.

 1package main
 2
 3import (
 4    "encoding/json"
 5    "fmt"
 6    "log"
 7)
 8
 9type employee struct {
10    Name   string `json:"n"`
11    Age    int    `json:"a"`
12    Salary int    `json:"s"`
13}
14
15func main() {
16    emp := employee{Name: "Sam", Age: 31, Salary: 2000}
17    //Converting to jsonn
18    empJSON, err := json.MarshalIndent(emp, '', '  ')
19    if err != nil {
20        log.Fatalf(err.Error())
21    }
22    fmt.Println(string(empJSON))
23}
1$ go run main.go
2{
3  "n": "Sam",
4  "a": 31,
5  "s": 2000
6}

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

2.2.7 تعریف فیلد ناشناس در ساختار (struct) #

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

1type employee struct {
2    string
3    age    int
4    salary int
5}

در زیر یک مثال ساده در خصوص فیلد ناشناس زدیم :

 1package main
 2
 3import "fmt"
 4
 5type employee struct {
 6    string
 7    age    int
 8    salary int
 9}
10
11func main() {
12    emp := employee{string: "Sam", age: 31, salary: 2000}
13    //Accessing a struct field
14    n := emp.string
15    fmt.Printf("Current name is: %s\n", n)
16    //Assigning a new value
17    emp.string = "John"
18    fmt.Printf("New name is: %s\n", emp.string)
19}
1$ go run main.go
2Current name is: Sam
3New name is: John

2.2.8 تعریف ساختار تو در تو (nested) #

یکی دیگر از امکانات ساختار در زبان گو بحث ساختار تو در تو می باشد. که در زیر مثالی که زدیم ساختار address را داخل employee قرار دادیم :

 1package main
 2
 3import "fmt"
 4
 5type employee struct {
 6    name    string
 7    age     int
 8    salary  int
 9    address address
10}
11
12type address struct {
13    city    string
14    country string
15}
16
17func main() {
18    address := address{city: "London", country: "UK"}
19    emp := employee{name: "Sam", age: 31, salary: 2000, address: address}
20    fmt.Printf("City: %s\n", emp.address.city)
21    fmt.Printf("Country: %s\n", emp.address.country)
22}
1$ go run main.go
2City: London
3Country: UK

توجه کنید شما طبق روش زیر می توانید به فیلدهای تو در تو دسترسی داشته باشید :

1emp.address.city
2emp.address.country

2.2.9 تعریف یک ساختار عمومی یا خصوصی (Public/Private) #

در زبان گو چیزی به عنوان کلمه کلیدی public یا private جهت تعیین وضعیت دسترسی به بیرون ندارد ولی در عوض کامپایلر گو براساس حرف بزرگ یا کوچک عنوان ساختار یا سایر تایپ ها تشخیص می دهد تایپ شما عمومی است یا خصوصی و در صورتیکه شما حرف اول را کوچک قرار دهید به کامپایلر دارید میگید این تایپ از بیرون این پکیج دسترسی ندارد.

1type Person struct {
2    Name string
3    age  int
4}
5
6type company struct {
7    Name string
8}
برای اطلاعات بیشتر بهتره به بخش کپسوله سازی مراجعه کنید.

2.2.10 مقایسه ساختارها #

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

  • boolean
  • numeric
  • string
  • pointer
  • channel
  • interface types
  • structs
  • array

اما ۳ تایپ زیر امکان مقایسه را از بین می برد :

  • Slice
  • Map
  • Function
 1package main
 2
 3import "fmt"
 4
 5type employee struct {
 6    name   string
 7    age    int
 8    salary int
 9}
10
11func main() {
12    emp1 := employee{name: "Sam", age: 31, salary: 2000}
13    emp2 := employee{name: "Sam", age: 31, salary: 2000}
14    if emp1 == emp2 {
15        fmt.Println("emp1 annd emp2 are equal")
16    } else {
17        fmt.Println("emp1 annd emp2 are not equal")
18    }
19}
1$ go run main.go
2emp1 annd emp2 are equal