در زبان گو ساختار
کالکشنی از فیلدها با تایپهای مختلف است. شما با استفاده از ساختار
میتوانید یک مدل کلی از بدنه پروژه خود را تعریف کنید. برای نمونه ما در مثال زیر یک نمونه از ساختار
employee کارمند
را مثال زدیم تا شما کمی با مفهوم ساختار
آشنا شوید.
نکته: ساختار میتواند بصورت خالی جهت برخی اهداف ایجاد گردد. به مثال زیر دقت کنید:
1type sample struct {}
اگر میخواهید در مورد متودها اطلاعات کسب کنید به بخش متدها روی ساختار سر بزنید، هر چند توصیه میکنم اول این قسمت رو بخونید و تمرین کنید و بعد به قسمت متودها بروید.
برای ایجاد ساختار باید از کلمه کلیدی
type
اسم ساختار و در ادامه کلمه کلیدیstruct
استفاده کنید.سپس داخل بدنه ساختار فیلدها را تعریف کنید.
- فیلد name از نوع string
- فیلد age از نوع int
- فیلد salary از نوع int
ساختار را در زبان گو، با class در سایر زبانها مقایسه میکنند. هرچند زبان گو یک زبان شیگرا محسوب نمیشود.
2.2.1 تعریف تایپ struct #
به مثال زیر توجه کنید:
در مثال بالا ما ۲ تا فیلد برای ساختار
تعریف کردیم که هر دو فیلد از نوع float64
هستند.
2.2.2 ایجاد یک متغیر ساختار (struct) #
برای ایجاد یک متغیر ساختار میتوانید یک متغیر تعریف کنید و ساختار را به عنوان مقدار به آن بدهید. به مثال زیر توجه کنید:
1emp := employee{}
در مثال بالا ما یک متغیر با مقدار پیشفرض صفر ساختار employee تعریف کردیم.
زمانیکه یک متغیر ساختار خالی، مانند مثال بالا تعریف میکنید مقدار استفاده شده از حافظه 0 بایت است.
- ایجاد متغیر ساختار و مقدار دهی فیلدها در یک خط:
1emp := employee{name: "Sam", age: 31, salary: 2000}
- ایجاد متغیر ساختار و مقدار دهی فیلد در خطهای مختلف (این روش برای خوانایی و درک بهتر توصیه میشود) :
توجه کنید هیچ اجباری نیست که حتماً شما باید فیلدی را مقدار دهی کنید، شما میتوانید هر زمانیکه نیاز داشتید ساختار خودتان رو مقدار دهی کنید.
در مثال بالا ما فیلد 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}
- ایجاد متغیر ساختار و مقدار دهی فیلدها بدون نام فیلد:
شما میتوانید فیلدها را بدون اینکه نام فیلد را قرار دهید مقدار دهی کنید اما از نظر تکنیکی این کار توصیه نمیشود، دلیل این توصیه هم این است که اگر شما فیلدها رو به این روش مقدار دهی کنید، باید ترتیب رو در نظر بگیرید یعنی 1: باید نام باشد، 2: باید سن باشد، 3: باید درآمد باشد و اگر این ترتیب رعایت نشود شما دیتای اشتباهی خواهید داشت.
در مثال بالا ترتیب رعایت شده. به مثال زیر توجه کنید:
همانطور که در مثال بالا دیدین الان با ترتیب اشتباه سن کارمند و درآمدش جابه جا شدن و ما دیتای اشتباهی از کارمند خواهیم داشت.
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 fmt.Printf("Current name is: %s\n", emp.name)
16
17 //Assigning a new value to name field
18 emp.name = "John"
19 fmt.Printf("New name is: %s\n", emp.name)
20}
2.2.4 کار با اشارهگر (Pointer) در ساختار (struct) #
شما برای ایجاد یک struct از نوع اشارهگر میتوانید از دو حالت زیر استفاده کنید:
- با استفاده از عملگر
&
که اشاره به خانه حافظه دارد - با استفاده از تابع
new
2.2.4.1 ایجاد ساختار با استفاده از عملگر & #
برای اینکه بتوانید یک ساختار از نوع اشاره گر
ایجاد کنید میتوانید از عملگر &
استفاده کنید. به مثال زیر توجه کنید:
حتی شما میتوانید یک ساختار اشارهگر را مستقیماً ایجاد کنید این روش پیشنهاد میشود. به مثال زیر توجه کنید:
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}
2.2.4.2 ایجاد ساختار با استفاده تابع new #
1func new(Type) *Type
همینطور که در تعریف تابع new
هم میبینید، این تابع یک تایپ از ما میگیرد و مقدار دهی میکند، و در آخر هم تایپ را از نوع اشارهگر برای ما بر میگرداند.
با استفاده از تابع new
:
- شما یک ساختار ایجاد میکنید.
- سپس فیلدها، با مقدار پیشفرض صفر مقدار دهی اولیه میشوند.
- در نهایت ساختار شما از نوع اشارهگر بازگشت داده میشود.
به مثال زیر توجه کنید:
1empP := new(employee)
برای اینکه آدرس خانه حافظه ساختار، از نوع اشارهگر را ببینید کافی است با استفاده از p% اون ساختار رو چاپ کنید. به مثال زیر توجه کنید:
1fmt.Printf("Emp Pointer: %p\n", empP)
برای اینکه مقدار کلی فیلدها را ببینید کافی است با استفاده از v+% اون رو چاپ کنید. به مثال زیر توجه کنید:
1fmt.Printf("Emp Value: %+v\n", *empP)
در مثال زیر خروجی آنچه در بالا گفته شد رو قرار دادیم. لطفاً با دقت به مثال زیر نگاه کنید و در آخر هم مثالهای مشابهی رو برای خودتان بنویسید:
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) #
برای اینکه بتوانید یک متغیر ساختار struct
را چاپ کنید، از دو روش زیر میتوانید استفاده کنید. توجه کنید متغیر ساختار بصورت key/value هست.
- با استفاده از پکیج fmt
- با استفاده از پکیج json/encoding
2.2.5.1 چاپ با استفاده از fmt #
در پکیج fmt ما 2 تا تابع کاربردی جهت چاپ داریم که اکثر اوقات از این دو تابع استفاده میکنیم:
- تابع
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 - مقدار
value
هر کدام از فیلدهای ساختار را چاپ میکند. - %+v - مقدار هرکدام از فیلدها به همراه اسم فیلد
key-value
را چاپ میکند.
در مثال زیر ما با استفاده از از تابع 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، اعتبارسنجی مقادیر فیلدها و … به ما کمک میکند و یکی از کاربردیترین عناوین در ساختار هستند.
به مثال های زیر توجه کنید تا کارکرد تگ ها را متوجه شوید:
در این مثال، مقدار داخل متغیری که از نوع Employee است را تبدیل به json می کنیم و چاپ می کنیم.
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 //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}
خروجی :
حالا به ما می گویند که اول اسم فیلد ها در خروجی json با حرف بزرگ شروع نشود و حرف کوچک باشد. اولین چیزی که شاید به ذهن شما خطور کند این است که اسم فیلد ها را در ساختار تعریف شده با حروف کوچک شروع کنیم:
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 //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}
خروجی :
اما خروجی ما یک json خالی است. جرا؟ چون زمانی که اسم فیلد ها با حروف کوچک شروع شوند private هستند و از بیرون قابل دسترسی نیستند. به همین دلیل خروجی یک json خالی است.
برای حل این مشکل ما برای ساختار خودمان یک تگ json اضافه می کنیم و می گوییم اسم فیلد تو در json چیز دیگری است:
1package main
2
3import (
4 "encoding/json"
5 "fmt"
6 "log"
7)
8
9type employee struct {
10 Name string `json:"name"`
11 Age int `json:"age"`
12 Salary int `json:"salary"`
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}
خروجی :
فکر میکنم خروجی بالا کاملاً برای ما روشن کرد که دقیقاً اون تگهایی که قرار دادیم، برای ما چه کاری انجام دادند. بله کلید-keyهای ما را به اون نامهایی که در تگها نوشته بودیم تغییر دادند.
2.2.6.1 چند نمونه از کاربرد تگ ها #
تگ ها کاربرد های خیلی زیادی دارند که در بخش قرار است بعضی از آنها را بررسی کنیم.
می توانید با تگ (-) مشخص کنید که آن فیلد موقع سریالایز نادیده گرفته شود و نمایش داده نشود. مثال:
1package main
2
3import (
4 "encoding/json"
5 "fmt"
6 "log"
7)
8
9type employee struct {
10 Name string `json:"name"`
11 Age int `json:"-"`
12 Salary int `json:"salary"`
13}
14
15func main() {
16 emp := employee{Name: "Sam", 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}
خروجی :
با استفاده از تگ omitempty اگر آن فیلد مقداری نداشته باشد، نمایش داده نمی شود:
1package main
2
3import (
4 "encoding/json"
5 "fmt"
6 "log"
7)
8
9type employee struct {
10 Name string `json:"name,omitempty"`
11 Age int `json:"age,omitempty"`
12 Salary int `json:"salary,omitempty"`
13}
14
15func main() {
16 emp := employee{Age: 22, 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}
خروجی :
از دیگر کاربرد های تگ ها می توان به عملیات اعتبار سنجی اشاره کرد. برای مثال می توان چک کرد فیلد شماره موبایل از یازده رقم بیشتر و کمتر نباشد. همچنین در تعریف مدل های دیتابیس با استفاده از تگ ها ارتباط بین دیتابیس و مدل را می توانیم پیاده سازی کنیم و …
2.2.7 تعریف فیلد ناشناس در ساختار (struct) #
شما در ساختار struct
امکان تعریف فیلدهای ناشناس
را دارید و همینطور میتوانید فیلدهای ناشناس را نیز مقدار دهی کنید.
در کد زیر یک مثال ساده در خصوص تعریف و مقدار دهی فیلدهای ناشناس زدهایم:
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}
توجه داشته باشید زمانی که از فیلد های ناشناس استفاده می کنید، از هر دیتاتایپ فقط یکبار می توانید استفاده کنید:
1package main
2
3import (
4 "fmt"
5)
6
7type employee struct {
8 string // name
9 int // age
10 int // salary
11}
12
13func main() {
14 emp := employee{"alireza", 22, 10_000_000}
15
16 fmt.Printf("%+v", emp)
17}
1$ go run main.go
2# command-line-arguments
3./main.go:10:2: int redeclared
4 ./main.go:9:2: other declaration of int
5./main.go:14:33: too many values in struct literal of type employee
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}
توجه کنید شما طبق روش زیر میتوانید به فیلدهای تو در تو دسترسی داشته باشید:
بعضی مواقع بهتر است بصورت مستقیم به فیلد های درون ساختار تودرتو دسترسی داشته باشیم. به مثال زیر دقت کنید:
1package main
2
3type Product struct {
4 Name string
5 Price int
6}
7
8type Mobile struct {
9 Product Product
10 Ram int
11 SimCount int
12}
13
14func main() {
15 var mobile Mobile = Mobile{}
16 mobile.Product.Name = "Iphone 11"
17 mobile.Product.Price = 1000
18 mobile.Ram = 8
19 mobile.SimCount = 1
20}
همانطور که می بینید برای تعریف اسم موبایل باید بگوییم mobile.Product.Name که این زیاد جالب نیست. پس به این صورت ساختار Product را درون موبایل قرار می دهیم:
1package main
2
3type Product struct {
4 Name string
5 Price int
6}
7
8type Mobile struct {
9 Product
10 Ram int
11 SimCount int
12}
13
14func main() {
15 var mobile Mobile = Mobile{}
16 mobile.Name = "Iphone 11"
17 mobile.Price = 1000
18 mobile.Ram = 8
19 mobile.SimCount = 1
20}
الان بصورت مستقیم می توانیم به فیلد های درون Product دسترسی داشته باشیم.
2.2.9 تعریف یک ساختار عمومی یا خصوصی (Public/Private) #
در زبان گو، چیزی به عنوان کلمه کلیدی public یا private جهت تعیین وضعیت دسترسی struct
به بیرون وجود ندارد، در عوض کامپایلر گو بر اساس حرف بزرگ یا کوچک عنوان ساختار یا سایر تایپها، تشخیص میدهد تایپ شما عمومی است یا خصوصی. در صورتیکه شما حرف اول را کوچک قرار دهید تایپ شما بیرون از پکیج قابل دسترس نخواهد بود مثل مثالهای بالا و اگر حرف اول تایپ رو بزرگ قرار دهید، تایپ یا تابع شما بیرون از پکیج نیز در دسترس خواهد بود. مثال تابع fmt.Println
.
برای اطلاعات بیشتر بهتر است به بخش کپسوله سازی مراجعه کنید.
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}