In Go, struct is an aggregate type used for defining and encapsulating data. It allows combining fields of different types. Structs can be seen as custom data types similar to classes in other languages, but they do not support inheritance. Methods are functions associated with a specific type (often a struct) and can be called using an instance of that type.
Structs are defined using the type and struct keywords. Here's an example of a simple struct definition:
type User struct { Username string Email string SignInCount int IsActive bool }
Structs can be initialized in various ways.
user1 := User{ Username: "alice", Email: "[email protected]", SignInCount: 1, IsActive: true, }
If some fields are not specified, they are initialized to their zero values for the respective types.
user2 := User{ Username: "bob", }
In this example, Email will be initialized to an empty string (""), SignInCount to 0, and IsActive to false.
A struct can also be initialized using a pointer.
user3 := &User{ Username: "charlie", Email: "[email protected]", }
In Go, structs are not only for storing data but can also have methods defined for them. This enables structs to encapsulate behavior related to their data. Below is a detailed explanation of struct methods and behavior.
Methods are defined using a receiver, which is the first parameter of the method and specifies the type the method belongs to. The receiver can be either a value receiver or a pointer receiver.
A value receiver creates a copy of the struct when the method is called, so modifications to fields do not affect the original struct.
type User struct { Username string Email string } func (u User) PrintInfo() { fmt.Printf("Username: %s, Email: %s\n", u.Username, u.Email) }
A pointer receiver allows the method to modify the original struct fields directly.
func (u *User) UpdateEmail(newEmail string) { u.Email = newEmail }
In Go, all methods of a struct form its method set. The method set for a value receiver includes all methods with value receivers, while the method set for a pointer receiver includes all methods with both pointer and value receivers.
Struct methods are often used with interfaces to achieve polymorphism. When defining an interface, you specify the methods a struct must implement.
type UserInfo interface { PrintInfo() } // User implements the UserInfo interface func (u User) PrintInfo() { fmt.Printf("Username: %s, Email: %s\n", u.Username, u.Email) } func ShowInfo(ui UserInfo) { ui.PrintInfo() }
In Go, memory alignment for structs is designed to improve access efficiency. Different data types have specific alignment requirements, and the compiler may insert padding bytes between struct fields to meet these requirements.
Memory alignment means that data in memory must be located at addresses that are multiples of certain values. The size of a data type determines its alignment requirement. For example, int32 requires alignment to 4 bytes, and int64 requires alignment to 8 bytes.
Efficient memory access is critical for CPU performance. If a variable is not properly aligned, the CPU may need multiple memory accesses to read or write data, leading to performance degradation. By aligning data, the compiler ensures efficient memory access.
Example:
package main import ( "fmt" "unsafe" ) type Example struct { a int8 // 1 byte b int32 // 4 bytes c int8 // 1 byte } func main() { fmt.Println(unsafe.Sizeof(Example{})) }
Output: 12
Analysis:
You can rearrange struct fields to minimize padding and reduce memory usage.
type Optimized struct { b int32 // 4 bytes a int8 // 1 byte c int8 // 1 byte }
Output: 8
In this optimized version, b is placed first, aligning it to 4 bytes. a and c are placed consecutively, making the total size 8 bytes, which is more compact than the unoptimized version.
In Go, nested structs and composition are powerful tools for code reuse and organizing complex data. Nested structs allow a struct to include another struct as a field, enabling the creation of complex data models. Composition, on the other hand, creates new structs by including other structs, facilitating code reuse.
Nested structs enable one struct to include another struct as a field. This makes data structures more flexible and organized. Here's an example of a nested struct:
package main import "fmt" // Define the Address struct type Address struct { City string Country string } // Define the User struct, which includes the Address struct type User struct { Username string Email string Address Address // Nested struct } func main() { // Initialize the nested struct user := User{ Username: "alice", Email: "[email protected]", Address: Address{ City: "New York", Country: "USA", }, } // Access fields of the nested struct fmt.Printf("User: %s, Email: %s, City: %s, Country: %s\n", user.Username, user.Email, user.Address.City, user.Address.Country) }
Composition allows multiple structs to be combined into a new struct, enabling code reuse. In composition, a struct can include multiple other structs as fields. This helps build more complex models and share common fields or methods. Here's an example of struct composition:
package main import "fmt" // Define the Address struct type Address struct { City string Country string } // Define the Profile struct type Profile struct { Age int Bio string } // Define the User struct, which composes Address and Profile type User struct { Username string Email string Address Address // Composes the Address struct Profile Profile // Composes the Profile struct } func main() { // Initialize the composed struct user := User{ Username: "bob", Email: "[email protected]", Address: Address{ City: "New York", Country: "USA", }, Profile: Profile{ Age: 25, Bio: "A software developer.", }, } // Access fields of the composed struct fmt.Printf("User: %s, Email: %s, City: %s, Age: %d, Bio: %s\n", user.Username, user.Email, user.Address.City, user.Profile.Age, user.Profile.Bio) }
Nested structs and composition are powerful features in Go that help organize and manage complex data structures. When designing data models, using nested structs and composition appropriately can make your code clearer and more maintainable.
An empty struct in Go is a struct with no fields.
An empty struct occupies zero bytes of memory. However, its memory address may or may not be equal under different circumstances. When memory escape occurs, the addresses are equal, pointing to runtime.zerobase.
// empty_struct.go type Empty struct{} //go:linkname zerobase runtime.zerobase var zerobase uintptr // Using the go:linkname directive to link zerobase to runtime.zerobase func main() { a := Empty{} b := struct{}{} fmt.Println(unsafe.Sizeof(a) == 0) // true fmt.Println(unsafe.Sizeof(b) == 0) // true fmt.Printf("%p\n", &a) // 0x590d00 fmt.Printf("%p\n", &b) // 0x590d00 fmt.Printf("%p\n", &zerobase) // 0x590d00 c := new(Empty) d := new(Empty) // Forces c and d to escape fmt.Sprint(c, d) println(c) // 0x590d00 println(d) // 0x590d00 fmt.Println(c == d) // true e := new(Empty) f := new(Empty) println(e) // 0xc00008ef47 println(f) // 0xc00008ef47 fmt.Println(e == f) // false }
From the output, variables a, b, and zerobase share the same address, all pointing to the global variable runtime.zerobase (runtime/malloc.go).
Regarding escape scenarios:
This behavior is intentional in Go. When empty struct variables do not escape, their pointers are unequal. After escaping, the pointers become equal.
An empty struct itself occupies no space, but when embedded in another struct, it might consume space depending on its position:
type s1 struct { a struct{} } type s2 struct { _ struct{} } type s3 struct { a struct{} b byte } type s4 struct { a struct{} b int64 } type s5 struct { a byte b struct{} c int64 } type s6 struct { a byte b struct{} } type s7 struct { a int64 b struct{} } type s8 struct { a struct{} b struct{} } func main() { fmt.Println(unsafe.Sizeof(s1{})) // 0 fmt.Println(unsafe.Sizeof(s2{})) // 0 fmt.Println(unsafe.Sizeof(s3{})) // 1 fmt.Println(unsafe.Sizeof(s4{})) // 8 fmt.Println(unsafe.Sizeof(s5{})) // 16 fmt.Println(unsafe.Sizeof(s6{})) // 2 fmt.Println(unsafe.Sizeof(s7{})) // 16 fmt.Println(unsafe.Sizeof(s8{})) // 0 }
When empty structs are elements of arrays or slices:
var a [10]int fmt.Println(unsafe.Sizeof(a)) // 80 var b [10]struct{} fmt.Println(unsafe.Sizeof(b)) // 0 var c = make([]struct{}, 10) fmt.Println(unsafe.Sizeof(c)) // 24, the size of the slice header
The zero-size property of empty structs allows them to be used for various purposes without extra memory overhead.
type MustKeyedStruct struct { Name string Age int _ struct{} } func main() { person := MustKeyedStruct{Name: "hello", Age: 10} fmt.Println(person) person2 := MustKeyedStruct{"hello", 10} // Compilation error: too few values in MustKeyedStruct{...} fmt.Println(person2) }
package main import ( "fmt" ) type Set struct { items map[interface{}]emptyItem } type emptyItem struct{} var itemExists = emptyItem{} func NewSet() *Set { return &Set{items: make(map[interface{}]emptyItem)} } func (set *Set) Add(item interface{}) { set.items[item] = itemExists } func (set *Set) Remove(item interface{}) { delete(set.items, item) } func (set *Set) Contains(item interface{}) bool { _, contains := set.items[item] return contains } func (set *Set) Size() int { return len(set.items) } func main() { set := NewSet() set.Add("hello") set.Add("world") fmt.Println(set.Contains("hello")) fmt.Println(set.Contains("Hello")) fmt.Println(set.Size()) }
Sometimes, the content of the data transmitted through a channel is irrelevant, serving only as a signal. For instance, empty structs can be used in semaphore implementations:
var empty = struct{}{} type Semaphore chan struct{} func (s Semaphore) P(n int) { for i := 0; i
We are Leapcell, your top choice for deploying Go projects to the cloud.
Leapcell is the Next-Gen Serverless Platform for Web Hosting, Async Tasks, and Redis:
- Multi-Language Support
Explore more in the Documentation!
Follow us on X: @LeapcellHQ
Read on our blog
부인 성명: 제공된 모든 리소스는 부분적으로 인터넷에서 가져온 것입니다. 귀하의 저작권이나 기타 권리 및 이익이 침해된 경우 자세한 이유를 설명하고 저작권 또는 권리 및 이익에 대한 증거를 제공한 후 이메일([email protected])로 보내주십시오. 최대한 빨리 처리해 드리겠습니다.
Copyright© 2022 湘ICP备2022001581号-3