در Go، یک struct دنبالهای از عناصر نامدار است که به آنها «فیلد» میگویند؛ هر فیلد یک اسم و یک نوع دارد.
اسم هر فیلد باید درون struct یکتا باشد.
میتوان structها را با «کلاسها» در الگوی برنامهنویسی شیءگرا مقایسه کرد.
struct
برای ساختن یک struct تازه، از «کلیدواژه»های type و struct استفاده میکنید و اسم و نوع فیلدها را بهصراحت تعریف میکنید.
برای مثال، اینجا structی به اسم Shape تعریف میکنیم که اسم یک شکل و اندازهاش را نگه میدارد:
type Shape struct {
name string
size int
}
اسم فیلدها در structها از قرارداد Go پیروی میکند: فیلدهایی که اسمشان با حرف کوچک شروع میشود، فقط برای کدهای همان بسته قابل مشاهدهاند، در حالی که فیلدهایی که اسمشان با حرف بزرگ شروع میشود، در بستههای دیگر هم قابل مشاهدهاند.
struct
پس از اینکه struct را تعریف کردید، باید یک نمونهی تازه بسازید و فیلدها را با اسم فیلدشان و به هر ترتیبی که میخواهید تعریف کنید:
s := Shape {
name: "Square",
size: 25,
}
برای خواندن یا تغییر فیلدهای یک نمونه، از نشانهگذاری . استفاده کنید:
// Update the name and the size
s.name = "Circle"
s.size = 35
fmt.Printf("name: %s size: %d\n", s.name, s.size)
// Output: name: Circle size: 35
فیلدهایی که مقدار اولیهای به آنها اختصاص داده نشود، مقدار صفر خود را خواهند داشت. برای مثال:
s := Shape{name: "Circle"}
fmt.Printf("name: %s size: %d\n", s.name, s.size)
// Output: name: Circle size: 0
میتوانید بدون استفاده از اسم فیلدها هم نمونهای از یک struct بسازید، به شرطی که فیلدها را به ترتیب تعریف کنید:
s := Shape {
"Oval",
20,
}
با این حال، این نحوهی نگارش کد شکننده در نظر گرفته میشود، چون با اضافه شدن یک فیلد میتواند از کار بیفتد، بهویژه وقتی فیلد تازه از نوع دیگری باشد.
در مثال زیر یک فیلد اضافه به Shape اضافه میکنیم:
type Shape struct {
name string
description string // new field 'description' added
size int
}
s := Shape{
"Circle",
20,
}
// Since the second field of the struct is now a string and not an int,
// the compiler will throw an error when compiling the program:
// Output: cannot use 20 (type untyped int) as type string in field value
// Output: too few values in Shape{...}
گاهی داشتن توابعی که به ما در ساختن نمونههای struct کمک میکنند میتواند خوشایند باشد.
طبق قرارداد، این توابع را معمولاً New مینامند یا اسمشان با New شروع میشود، اما چون فقط توابعی معمولی هستند، میتوانید هر اسمی که میخواهید برایشان انتخاب کنید.
ممکن است توابع سازنده در زبانهای دیگر را به یادتان بیاورند، اما در Go فقط توابعی معمولی هستند.
در مثال زیر، یکی از این توابع New برای ساختن نمونهی تازهای از Shape استفاده میشود و بهطور خودکار مقدار پیشفرضی برای size شکل تنظیم میکند:
func NewShape(name string) Shape {
return Shape{
name: name,
size: 100, //default-value for size is 100
}
}
NewShape("Triangle")
// => Shape { name: "Triangle", size: 100 }
استفاده از توابع New میتواند مزایای زیر را داشته باشد:
New اغلب در همان بستهی structهایی که مقداردهی اولیه میکنند تعریف میشوند، میتوانند حتی فیلدهای خصوصی struct را هم مقداردهی اولیه کننددر این تمرین، مسابقههایی بین انواع مختلف خودروهای کنترلی برگزار میکنید. هر خودرو سرعت و ویژگیهای مصرف باتری خودش را دارد.
خودروها با باتری کاملاً پر (۱۰۰٪) شروع میکنند. هر بار که خودرو را با کنترل از راه دور میرانید، به اندازهی سرعت خودرو متر میپیماید و درصد باقیماندهی باتری را به اندازهی مصرف باتریاش کاهش میدهد.
اگر شارژ باتری خودرو کمتر از درصد مصرف باتری آن باشد، دیگر نمیتوانید خودرو را برانید.
هر پیست مسابقه مسافت خودش را دارد. خودروها با بررسی اینکه آیا میتوانند بدون تمام شدن باتری پیست را به پایان برسانند آزمایش میشوند.
یک ساختار Car با فیلدهای زیر از نوع int تعریف کنید:
با تعریف تابع NewCar امکان ساخت یک خودروی کنترلی را فراهم کنید؛ این تابع سرعت خودرو بر حسب متر
و درصد مصرف باتری را به عنوان دو پارامتر خود (هر دو از نوع int) میگیرد و یک نمونه Car برمیگرداند:
speed := 5
batteryDrain := 2
car := NewCar(speed, batteryDrain)
// => Car{speed: 5, batteryDrain: 2, battery:100, distance: 0}
یک ساختار دیگر به نام Track با فیلد distance از نوع عدد صحیح تعریف کنید.
با تعریف تابع NewTrack امکان ساخت یک پیست مسابقه را فراهم کنید؛ این تابع مسافت پیست بر حسب متر را به عنوان تنها پارامتر خود (که از نوع int است) میگیرد:
distance := 800
track := NewTrack(distance)
// => Track{distance: 800}
تابع Drive را پیادهسازی کنید که تعداد مترهای پیمودهشده را بر اساس سرعت خودرو بهروزرسانی میکند و باتری را به اندازهی مصرف باتری کاهش میدهد.
اگر باتری برای یک بار رانندگی دیگر کافی نباشد، خودرو حرکت نمیکند:
speed := 5
batteryDrain := 2
car := NewCar(speed, batteryDrain)
car = Drive(car)
// => Car{speed: 5, batteryDrain: 2, battery: 98, distance: 5}
برای به پایان رساندن یک مسابقه، خودرو باید بتواند مسافت مسابقه را بپیماید.
یعنی نباید باتریاش پیش از عبور از خط پایان خالی شود.
تابع CanFinish را پیادهسازی کنید که یک نمونه Car و یک نمونه Track را به عنوان پارامترهایش میگیرد و اگر خودرو بتواند مسابقه را به پایان برساند true و در غیر این صورت false برمیگرداند.
فرض کنید هماکنون در خط شروع مسابقه هستید و موتور خودرو را برای مسابقه روشن میکنید. در نظر بگیرید که ممکن است باتری خودرو هنگام شروع مسابقه لزوماً کاملاً شارژ نباشد:
speed := 5
batteryDrain := 2
car := NewCar(speed, batteryDrain)
distance := 100
track := NewTrack(distance)
CanFinish(car, track)
// => true