در زبان گو ساختار
کالکشنی از فیلدها با تایپهای مختلف است. شما با استفاده از ساختار
میتوانید یک مدل کلی از بدنه پروژه خود را تعریف کنید. برای نمونه ما در مثال زیر یک نمونه از ساختار
employee کارمند
را مثال زدیم تا شما کمی با مفهوم ساختار
آشنا شوید.
type employee struct {
name string
age int
salary int
}
نکته: ساختار میتواند بصورت خالی جهت برخی اهداف ایجاد گردد. به مثال زیر دقت کنید:
type sample struct {}
اگر میخواهید در مورد متودها اطلاعات کسب کنید به بخش متدها روی ساختار سر بزنید، هر چند توصیه میکنم اول این قسمت رو بخونید و تمرین کنید و بعد به قسمت متودها بروید.
برای ایجاد ساختار باید از کلمه کلیدی
type
اسم ساختار و در ادامه کلمه کلیدیstruct
استفاده کنید.سپس داخل بدنه ساختار فیلدها را تعریف کنید.
- فیلد name از نوع string
- فیلد age از نوع int
- فیلد salary از نوع int
ساختار را در زبان گو، با class در سایر زبانها مقایسه میکنند. هرچند زبان گو یک زبان شیگرا محسوب نمیشود.
2.2.1 تعریف تایپ struct #
به مثال زیر توجه کنید:
type point struct {
x float64
y float64
}
در مثال بالا ما ۲ تا فیلد برای ساختار
تعریف کردیم که هر دو فیلد از نوع float64
هستند.
2.2.2 ایجاد یک متغیر ساختار (struct) #
برای ایجاد یک متغیر ساختار میتوانید یک متغیر تعریف کنید و ساختار را به عنوان مقدار به آن بدهید. به مثال زیر توجه کنید:
emp := employee{}
در مثال بالا ما یک متغیر با مقدار پیشفرض صفر ساختار employee تعریف کردیم.
زمانیکه یک متغیر ساختار خالی، مانند مثال بالا تعریف میکنید مقدار استفاده شده از حافظه 0 بایت است.
- ایجاد متغیر ساختار و مقدار دهی فیلدها در یک خط:
emp := employee{name: "Sam", age: 31, salary: 2000}
- ایجاد متغیر ساختار و مقدار دهی فیلد در خطهای مختلف (این روش برای خوانایی و درک بهتر توصیه میشود) :
emp := employee{
name: "Sam",
age: 31,
salary: 2000,
}
توجه کنید هیچ اجباری نیست که حتماً شما باید فیلدی را مقدار دهی کنید، شما میتوانید هر زمانیکه نیاز داشتید ساختار خودتان رو مقدار دهی کنید.
emp := employee{
name: "Sam",
age: 31,
}
در مثال بالا ما فیلد salary را مقدار دهی نکردیم. کامپایلر بطور پیشفرض با توجه به تایپ فیلد، مقدار پیشفرض صفر را برای اون تایپ در نظر میگیرد. در ادامه به مثالی که از نحوه ایجاد ساختارها زدیم، توجه کنید:
package main
import "fmt"
type employee struct {
name string
age int
salary int
}
func main() {
emp1 := employee{}
fmt.Printf("Emp1: %+v\n", emp1)
emp2 := employee{name: "Sam", age: 31, salary: 2000}
fmt.Printf("Emp2: %+v\n", emp2)
emp3 := employee{
name: "Sam",
age: 31,
salary: 2000,
}
fmt.Printf("Emp3: %+v\n", emp3)
emp4 := employee{
name: "Sam",
age: 31,
}
fmt.Printf("Emp4: %+v\n", emp4)
}
- ایجاد متغیر ساختار و مقدار دهی فیلدها بدون نام فیلد:
شما میتوانید فیلدها را بدون اینکه نام فیلد را قرار دهید مقدار دهی کنید اما از نظر تکنیکی این کار توصیه نمیشود، دلیل این توصیه هم این است که اگر شما فیلدها رو به این روش مقدار دهی کنید، باید ترتیب رو در نظر بگیرید یعنی 1: باید نام باشد، 2: باید سن باشد، 3: باید درآمد باشد و اگر این ترتیب رعایت نشود شما دیتای اشتباهی خواهید داشت.
emp := employee{"Sam", 31, 2000}
{Sam 31 2000} // حروجی
در مثال بالا ترتیب رعایت شده. به مثال زیر توجه کنید:
emp := employee{"Sam", 2000, 31}
{Sam 2000 31} // حروجی
همانطور که در مثال بالا دیدین الان با ترتیب اشتباه سن کارمند و درآمدش جابه جا شدن و ما دیتای اشتباهی از کارمند خواهیم داشت.
2.2.3 دسترسی و تنظیم فیلدهای ساختار (struct) #
زمانیکه شما یک متغیر ساختار تعریف میکنید، میتوانید خیلی آسان با استفاده از همان متغیر به فیلدهای ساختار دسترسی پیدا کنید و مقدار هر کدام از فیلدها را تغییر دهید. به مثال زیر توجه کنید:
package main
import "fmt"
type employee struct {
name string
age int
salary int
}
func main() {
emp := employee{name: "Sam", age: 31, salary: 2000}
//Accessing a struct field
fmt.Printf("Current name is: %s\n", emp.name)
//Assigning a new value to name field
emp.name = "John"
fmt.Printf("New name is: %s\n", emp.name)
}
2.2.4 کار با اشارهگر (Pointer) در ساختار (struct) #
شما برای ایجاد یک struct از نوع اشارهگر میتوانید از دو حالت زیر استفاده کنید:
- با استفاده از عملگر
&
که اشاره به خانه حافظه دارد - با استفاده از تابع
new
2.2.4.1 ایجاد ساختار با استفاده از عملگر & #
برای اینکه بتوانید یک ساختار از نوع اشاره گر
ایجاد کنید میتوانید از عملگر &
استفاده کنید. به مثال زیر توجه کنید:
emp := employee{name: "Sam", age: 31, salary: 2000}
empP := &emp
حتی شما میتوانید یک ساختار اشارهگر را مستقیماً ایجاد کنید این روش پیشنهاد میشود. به مثال زیر توجه کنید:
empP := &employee{name: "Sam", age: 31, salary: 2000}
در مثال زیر هر دو روش رو برای شما توضیح دادیم. با دقت به کد و خروجی کد نگاه کنید:
package main
import "fmt"
type employee struct {
name string
age int
salary int
}
func main() {
emp := employee{name: "Sam", age: 31, salary: 2000}
empP := &emp
fmt.Printf("Emp: %+v\n", empP)
empP = &employee{name: "John", age: 30, salary: 3000}
fmt.Printf("Emp: %+v\n", empP)
}
2.2.4.2 ایجاد ساختار با استفاده تابع new #
func new(Type) *Type
همینطور که در تعریف تابع new
هم میبینید، این تابع یک تایپ از ما میگیرد و مقدار دهی میکند، و در آخر هم تایپ را از نوع اشارهگر برای ما بر میگرداند.
با استفاده از تابع new
:
- شما یک ساختار ایجاد میکنید.
- سپس فیلدها، با مقدار پیشفرض صفر مقدار دهی اولیه میشوند.
- در نهایت ساختار شما از نوع اشارهگر بازگشت داده میشود.
به مثال زیر توجه کنید:
empP := new(employee)
برای اینکه آدرس خانه حافظه ساختار، از نوع اشارهگر را ببینید کافی است با استفاده از p% اون ساختار رو چاپ کنید. به مثال زیر توجه کنید:
fmt.Printf("Emp Pointer: %p\n", empP)
برای اینکه مقدار کلی فیلدها را ببینید کافی است با استفاده از v+% اون رو چاپ کنید. به مثال زیر توجه کنید:
fmt.Printf("Emp Value: %+v\n", *empP)
در مثال زیر خروجی آنچه در بالا گفته شد رو قرار دادیم. لطفاً با دقت به مثال زیر نگاه کنید و در آخر هم مثالهای مشابهی رو برای خودتان بنویسید:
package main
import "fmt"
type employee struct {
name string
age int
salary int
}
func main() {
empP := new(employee)
fmt.Printf("Emp Pointer Address: %p\n", empP)
fmt.Printf("Emp Pointer: %+v\n", empP)
fmt.Printf("Emp Value: %+v\n", *empP)
}
2.2.5 چاپ یک متغیر ساختار (struct) #
برای اینکه بتوانید یک متغیر ساختار struct
را چاپ کنید، از دو روش زیر میتوانید استفاده کنید. توجه کنید متغیر ساختار بصورت key/value هست.
- با استفاده از پکیج fmt
- با استفاده از پکیج json/encoding
2.2.5.1 چاپ با استفاده از fmt #
در پکیج fmt ما 2 تا تابع کاربردی جهت چاپ داریم که اکثر اوقات از این دو تابع استفاده میکنیم:
- تابع
Println
ورودی را با فرمت پیشفرض چاپ میکند. - تابع
Printf
ورودی را با فرمت مشخص شده چاپ میکندفرمت رو خود ما مشخص میکنیم
.
در مثال زیر ما یک نمونه از ساختار employee را ایجاد کردیم:
emp := employee{name: "Sam", age: 31, salary: 2000}
حال با استفاده از تابع Printf
ساختار را با فرمت دلخواه خودمان چاپ کردیم:
fmt.Printf("%v", emp) - {Sam 31 2000}
fmt.Printf("%+v", emp) - {name:Sam age:31 salary:2000}
- %v - مقدار
value
هر کدام از فیلدهای ساختار را چاپ میکند. - %+v - مقدار هرکدام از فیلدها به همراه اسم فیلد
key-value
را چاپ میکند.
در مثال زیر ما با استفاده از از تابع Println
ساختار را چاپ کردیم:
fmt.Println(emp) - {Sam 31 2000}
در نهایت کد زیر یک مثال کلی از چاپ با استفاده از پکیج fmt است:
package main
import "fmt"
type employee struct {
name string
age int
salary int
}
func main() {
emp := employee{name: "Sam", age: 31, salary: 2000}
fmt.Printf("Emp: %v\n", emp)
fmt.Printf("Emp: %+v\n", emp)
fmt.Printf("Emp: %#v\n", emp)
fmt.Println(emp)
}
2.2.5.2 چاپ ساختار با استفاده از پکیج JSON #
در این روش ما با استفاده از ۲ تابع Marshal و MarshalIndent پکیج json، ساختار را encode میکنیم و در نهایت خروجی encode شده را چاپ میکنیم.
- Marshal - در این تابع ما به عنوان ورودی، ساختار را پاس میدهیم و در نهایت ۲ خروجی از نوع بایت و خطا دریافت میکنیم.
Marshal(v interface{}) ([]byte, error)
- MarhsalIndent - در این تابع ما ۳ تا ورودی به تابع میفرستیم, به ترتیب ساختار، پیشوند و indent و در نهایت ۲ خروجی از نوع بایت و خطا دریافت میکنیم.
MarshalIndent(v interface{}, prefix, indent string) ([]byte, error)
حالا با استفاده از توابع فوق یک کد نمونه مثال میزنیم و به شما یاد میدیم که چطور از این توابع استفاده کنید. به مثال زیر دقت کنید:
package main
import (
"encoding/json"
"fmt"
"log"
)
type employee struct {
Name string
Age int
salary int
}
func main() {
emp := employee{Name: "Sam", Age: 31, salary: 2000}
//Marshal
empJSON, err := json.Marshal(emp)
if err != nil {
log.Fatalf(err.Error())
}
fmt.Printf("Marshal funnction output %s\n", string(empJSON))
//MarshalIndent
empJSON, err = json.MarshalIndent(emp, "", " ")
if err != nil {
log.Fatalf(err.Error())
}
fmt.Printf("MarshalIndent funnction output %s\n", string(empJSON))
}
برای اطلاعات بیشتر در خصوص پکیج json میتوانید به بخش آموزش کار با json مراجعه کنید.
2.2.6 کار با تگ ها در ساختار (struct) #
ساختار زبان گو، به شما امکان اضافه کردن metadata به هر یک از فیلدها را میدهد و ما این قابلیت را به عنوان تگ میشناسیم. تگها برای انجام یکسری عملیات خاص نظیر encode/decode، اعتبارسنجی مقادیر فیلدها و … به ما کمک میکند و یکی از کاربردیترین عناوین در ساختار هستند.
به مثال های زیر توجه کنید تا کارکرد تگ ها را متوجه شوید:
type strutName struct{
fieldName type `key:"value" key2:"value2"`
}
type employee struct {
Name string
Age int
Salary int
}
در این مثال، مقدار داخل متغیری که از نوع Employee است را تبدیل به json می کنیم و چاپ می کنیم.
package main
import (
"encoding/json"
"fmt"
"log"
)
type employee struct {
Name string
Age int
Salary int
}
func main() {
emp := employee{Name: "Sam", Age: 31, Salary: 2000}
//Converting to jsonn
empJSON, err := json.MarshalIndent(emp, "", " ")
if err != nil {
log.Fatalf(err.Error())
}
fmt.Println(string(empJSON))
}
حالا به ما می گویند که اول اسم فیلد ها در خروجی json با حرف بزرگ شروع نشود و حرف کوچک باشد. اولین چیزی که شاید به ذهن شما خطور کند این است که اسم فیلد ها را در ساختار تعریف شده با حروف کوچک شروع کنیم:
package main
import (
"encoding/json"
"fmt"
"log"
)
type employee struct {
name string
age int
salary int
}
func main() {
emp := employee{name: "Sam", age: 31, salary: 2000}
//Converting to jsonn
empJSON, err := json.MarshalIndent(emp, "", " ")
if err != nil {
log.Fatalf(err.Error())
}
fmt.Println(string(empJSON))
}
اما خروجی ما یک json خالی است. جرا؟ چون زمانی که اسم فیلد ها با حروف کوچک شروع شوند private هستند و از بیرون قابل دسترسی نیستند. به همین دلیل خروجی یک json خالی است.
برای حل این مشکل ما برای ساختار خودمان یک تگ json اضافه می کنیم و می گوییم اسم فیلد تو در json چیز دیگری است:
package main
import (
"encoding/json"
"fmt"
"log"
)
type employee struct {
Name string `json:"name"`
Age int `json:"age"`
Salary int `json:"salary"`
}
func main() {
emp := employee{Name: "Sam", Age: 31, Salary: 2000}
//Converting to jsonn
empJSON, err := json.MarshalIndent(emp, "", " ")
if err != nil {
log.Fatalf(err.Error())
}
fmt.Println(string(empJSON))
}
فکر میکنم خروجی بالا کاملاً برای ما روشن کرد که دقیقاً اون تگهایی که قرار دادیم، برای ما چه کاری انجام دادند. بله کلید-keyهای ما را به اون نامهایی که در تگها نوشته بودیم تغییر دادند.
2.2.6.1 چند نمونه از کاربرد تگ ها #
تگ ها کاربرد های خیلی زیادی دارند که در بخش قرار است بعضی از آنها را بررسی کنیم.
می توانید با تگ (-) مشخص کنید که آن فیلد موقع سریالایز نادیده گرفته شود و نمایش داده نشود. مثال:
package main
import (
"encoding/json"
"fmt"
"log"
)
type employee struct {
Name string `json:"name"`
Age int `json:"-"`
Salary int `json:"salary"`
}
func main() {
emp := employee{Name: "Sam", Salary: 2000}
//Converting to jsonn
empJSON, err := json.MarshalIndent(emp, "", " ")
if err != nil {
log.Fatalf(err.Error())
}
fmt.Println(string(empJSON))
}
با استفاده از تگ omitempty اگر آن فیلد مقداری نداشته باشد، نمایش داده نمی شود:
package main
import (
"encoding/json"
"fmt"
"log"
)
type employee struct {
Name string `json:"name,omitempty"`
Age int `json:"age,omitempty"`
Salary int `json:"salary,omitempty"`
}
func main() {
emp := employee{Age: 22, Salary: 2000}
//Converting to jsonn
empJSON, err := json.MarshalIndent(emp, "", " ")
if err != nil {
log.Fatalf(err.Error())
}
fmt.Println(string(empJSON))
}
از دیگر کاربرد های تگ ها می توان به عملیات اعتبار سنجی اشاره کرد. برای مثال می توان چک کرد فیلد شماره موبایل از یازده رقم بیشتر و کمتر نباشد. همچنین در تعریف مدل های دیتابیس با استفاده از تگ ها ارتباط بین دیتابیس و مدل را می توانیم پیاده سازی کنیم و …
2.2.7 تعریف فیلد ناشناس در ساختار (struct) #
شما در ساختار struct
امکان تعریف فیلدهای ناشناس
را دارید و همینطور میتوانید فیلدهای ناشناس را نیز مقدار دهی کنید.
type employee struct {
string
age int
salary int
}
در کد زیر یک مثال ساده در خصوص تعریف و مقدار دهی فیلدهای ناشناس زدهایم:
package main
import "fmt"
type employee struct {
string
age int
salary int
}
func main() {
emp := employee{string: "Sam", age: 31, salary: 2000}
//Accessing a struct field
n := emp.string
fmt.Printf("Current name is: %s\n", n)
//Assigning a new value
emp.string = "John"
fmt.Printf("New name is: %s\n", emp.string)
}
توجه داشته باشید زمانی که از فیلد های ناشناس استفاده می کنید، از هر دیتاتایپ فقط یکبار می توانید استفاده کنید:
package main
import (
"fmt"
)
type employee struct {
string // name
int // age
int // salary
}
func main() {
emp := employee{"alireza", 22, 10_000_000}
fmt.Printf("%+v", emp)
}
2.2.8 تعریف ساختار تو در تو (nested) #
یکی دیگر از امکانات ساختار در زبان گو بحث ساختار تو در تو است. در مثالی که در ادامه زدیم ساختار address را داخل employee قرار دادیم:
package main
import "fmt"
type employee struct {
name string
age int
salary int
address address
}
type address struct {
city string
country string
}
func main() {
address := address{city: "London", country: "UK"}
emp := employee{name: "Sam", age: 31, salary: 2000, address: address}
fmt.Printf("City: %s\n", emp.address.city)
fmt.Printf("Country: %s\n", emp.address.country)
}
توجه کنید شما طبق روش زیر میتوانید به فیلدهای تو در تو دسترسی داشته باشید:
emp.address.city emp.address.country
بعضی مواقع بهتر است بصورت مستقیم به فیلد های درون ساختار تودرتو دسترسی داشته باشیم. به مثال زیر دقت کنید:
package main
type Product struct {
Name string
Price int
}
type Mobile struct {
Product Product
Ram int
SimCount int
}
func main() {
var mobile Mobile = Mobile{}
mobile.Product.Name = "Iphone 11"
mobile.Product.Price = 1000
mobile.Ram = 8
mobile.SimCount = 1
}
همانطور که می بینید برای تعریف اسم موبایل باید بگوییم mobile.Product.Name که این زیاد جالب نیست. پس به این صورت ساختار Product را درون موبایل قرار می دهیم:
package main
type Product struct {
Name string
Price int
}
type Mobile struct {
Product
Ram int
SimCount int
}
func main() {
var mobile Mobile = Mobile{}
mobile.Name = "Iphone 11"
mobile.Price = 1000
mobile.Ram = 8
mobile.SimCount = 1
}
الان بصورت مستقیم می توانیم به فیلد های درون Product دسترسی داشته باشیم.
2.2.9 تعریف یک ساختار عمومی یا خصوصی (Public/Private) #
در زبان گو، چیزی به عنوان کلمه کلیدی public یا private جهت تعیین وضعیت دسترسی struct
به بیرون وجود ندارد، در عوض کامپایلر گو بر اساس حرف بزرگ یا کوچک عنوان ساختار یا سایر تایپها، تشخیص میدهد تایپ شما عمومی است یا خصوصی. در صورتیکه شما حرف اول را کوچک قرار دهید تایپ شما بیرون از پکیج قابل دسترس نخواهد بود مثل مثالهای بالا و اگر حرف اول تایپ رو بزرگ قرار دهید، تایپ یا تابع شما بیرون از پکیج نیز در دسترس خواهد بود. مثال تابع fmt.Println
.
type Person struct {
Name string
age int
}
type company struct {
Name string
}
برای اطلاعات بیشتر بهتر است به بخش کپسوله سازی مراجعه کنید.
2.2.10 مقایسه ساختارها #
شما در زبان گو میتوانید ساختارها را بر اساس عنوان فیلد، تایپ و مقدارشان مقایسه کنید. اما باید توجه کنید ساختارها فقط براساس تایپهایی که در ادامه معرفی کردیم, امکان مقایسه را خواهند داشت:
- boolean
- numeric
- string
- pointer
- channel
- interface types
- structs
- array
و اما ۳ تایپ زیر امکان مقایسه را به شما نمیدهند:
- Slice
- Map
- Function
package main
import "fmt"
type employee struct {
name string
age int
salary int
}
func main() {
emp1 := employee{name: "Sam", age: 31, salary: 2000}
emp2 := employee{name: "Sam", age: 31, salary: 2000}
if emp1 == emp2 {
fmt.Println("emp1 annd emp2 are equal")
} else {
fmt.Println("emp1 annd emp2 are not equal")
}
}