Preface#
Introduction to Go#
- Open source by Google
- Compiled language
- The C language of the 21st century
In 2005, multi-core processors emerged, while other languages were born in the single-core era. Go was designed with multi-core concurrency in mind.
Features:
- Simple syntax (only 25 keywords, simpler than Python, built-in formatting, easy to read)
- High development efficiency
- Good execution performance (close to Java)
Development:
Baidu autonomous driving, mini-programs
Tencent Blue Whale, microservices framework
Zhihu was initially written in Python, but later could not handle the load and was restructured in Go, saving 80% of resources.
Course Introduction#
8 weeks of basics
3 practical projects
Go Project Structure#
Individual developers
Popular methods
Helloworld#
go build
Compiles to exe on Windows, executable file on macOS
go install
Install is equivalent to building and then moving to bin
go run
Runs the script
Supports cross-platform cross-compilation
// wincmd SET, macos export
export CGO_ENABLED=0 // Disable CGO
export GOOS=linux // Set target platform linux, windows, darwin
export GOARCH=amd64// Target processor architecture is amd64
go build
export CGO_ENABLED=0 GOOS=linux GOARCH=amd64
go build
Variables and Constants#
Statements cannot be written outside functions
Identifiers: alphanumeric underscores, cannot start with a number
Keywords and reserved words are not recommended for variable names
Variables#
Initialization#
Numbers default to 0
, strings default to empty, booleans default to false
, slices, functions, pointers default to nil
.
var variableName type = expression
var name string = "Q1mi"
var age int = 18
var name, age = "Q1mi", 20 // Type inferred from value
var (
a string
b int
c bool
d float32
)
Declared outside functions as global variables
Local variables declared inside functions can be abbreviated as
n := 10
m := 200
fmt.Println(m, n)
Note: In Golang, non-global variable declarations must be used, otherwise compilation will fail!
fmt.Print()
fmt.Printf()
fmt.Println() // New line
Automatically formats when saved
Naming Rules#
var studentName string
Golang uses camelCase naming
Anonymous Variables#
Receive with a short underscore, do not occupy namespace, do not allocate memory
x, _ = foo()
_, y = foo()
Constants#
const pi = 3.14
iota#
Constant counter, increments with each new line of constant declaration, note it is one line
const (
n1 = iota //0
n2 //1
n3 //2
n4 //3
)
const (
n1 = iota //0
n2 //1
_ //2 but discarded
n3 //3
)
Defining Magnitudes#
const (
_ = iota
KB = 1 << (10 * iota)
MB = 1 << (10 * iota)
GB = 1 << (10 * iota)
TB = 1 << (10 * iota)
PB = 1 << (10 * iota)
)
<< Left shift operator, binary 1 shifted left 10 bits is 1024
Basic Data Types#
Integer types are divided into the following two categories: by length: int8, int16, int32, int64
Corresponding unsigned integer types: uint8, uint16, uint32, uint64
uint8 is byte, int16 is short, int64 is long
Special Integer Types#
uint int
will be determined by the system as 32 or 64
uintptr
pointer, stores memory addresses
Number Systems#
Golang cannot directly define binary numbers, octal and hexadecimal are both possible
// Decimal
var a int = 10
fmt.Printf("%d \n", a) // 10
fmt.Printf("%b \n", a) // 1010 Placeholder %b indicates binary
// Octal Starts with 0
var b int = 077
fmt.Printf("%o \n", b) // 77
// Hexadecimal Starts with 0x
var c int = 0xff
fmt.Printf("%x \n", c) // ff
fmt.Printf("%X \n", c) // FF
fmt.Printf("%T \n", c) // Output type
fmt.Printf("%v \n", c) // Output variable value, any type
Floating Point Numbers#
In golang, decimals default to float64
math.MaxFloat64 // maximum value of float64
Boolean#
Defaults to false, no conversion allowed
Strings#
Only double quotes are allowed, single quotes are for characters
Escape | Meaning |
---|---|
\r | Return to the start |
\n | New line (same column) |
\t | Tab |
// Escape path in Windows
s := "D:\\Documents\\A"
// Backticks output as is, multi-line string
s := `
asda
asd
`
s := "D:\Documents\A"
len(str)
ss := s1 + s2
ret := strings.Split(s3, "\\")
ret = strings.Contains(s3, "abcd")
ret = strings.HasPrefix(s3, "abcd")
ret = strings.HasSuffix(s3, "abcd")
ret = strings.Index(s3, "c")
ret = strings.LastIndex(s3, "c")
ret = strings.Join(a, b)
English characters are byte
, other languages such as Chinese characters are rune
, which is actually int32
, occupying 3 bytes
String traversal
for _, char := range str {
fmt.Printf("%c", char)
}
Strings cannot be modified directly, they must be converted to other types for processing
s3 := []rune(s2) // Slice
s3[0] = 'e' // Modify
s4 := string(s3)
Control Flow#
if#
if expression1 {
branch1
} else if expression2 {
branch2
} else{
branch3
}
// Local variable score only effective in if, reducing memory usage
if score := 65; score >= 90 {
fmt.Println("A")
} else if score > 75 {
fmt.Println("B")
} else {
fmt.Println("C")
}
for#
Golang only has for
for initialization; condition; ending {
loop body statements
}
for i := 0; i < 10; i++ {
fmt.Println(i)
}
Initialization and ending statements can be omitted, equivalent to while
i := 0
for i < 10 {
fmt.Println(i)
i++
}
Infinite loop
for {
loop body statements
}
Exit the loop forcibly through break
, goto
, return
, panic
statements
Traversal#
for range
traverses arrays, slices, strings, maps, and channels
for i,v := range s{
fmt.Println(i, v)
}
- Arrays, slices, strings return index and value.
- Maps return key and value.
- Channels only return values in the channel.
switch#
finger := 3
switch finger {
case 1:
fmt.Println("Thumb")
fallthrough
case 2:
fmt.Println("Index Finger")
case 3:
fmt.Println("Middle Finger")
case 4:
fmt.Println("Ring Finger")
case 5:
fmt.Println("Little Finger")
default:
fmt.Println("Invalid input!")
}
The fallthrough
syntax can execute the next case of the satisfied condition, designed for compatibility with case in C language
switch n := 7; n {
case 1, 3, 5, 7, 9:
fmt.Println("Odd")
case 2, 4, 6, 8:
fmt.Println("Even")
default:
fmt.Println(n)
}
goto#
The goto
statement performs an unconditional jump between code via labels. The goto
statement can help quickly exit loops and avoid repeated exits. Using goto
in Go can simplify some code implementations. For example, when exiting a double nested for loop
var breakFlag bool
for i := 0; i < 10; i++ {
for j := 0; j < 10; j++ {
if j == 2 {
// Set exit label
breakFlag = true
break
}
fmt.Printf("%v-%v\n", i, j)
}
// Outer for loop judgment
if breakFlag {
break
}
}
Simplified to
for i := 0; i < 10; i++ {
for j := 0; j < 10; j++ {
if j == 2 {
// Set exit label
goto breakTag
}
fmt.Printf("%v-%v\n", i, j)
}
}
return
// Label
breakTag:
fmt.Println("End for loop")
Operators#
++
(increment) and --
(decrement) are separate statements in Go, not operators.
// Logical operations
&&
||
!
// Bitwise operations
&
|
^
<<
>>
// Assignment
+=
-=
<<=
Arrays#
Initialization#
Arrays are determined at declaration, members can be modified during use, but the size of the array cannot change
var a [3]int
var a [3]int
var b [4]int
a = b // Cannot do this, as a and b are different types
Arrays can be accessed via indices, starting from 0
, the last element index is: len-1
, accessing out of bounds (index outside the valid range) triggers an out-of-bounds panic
var testArray [3]int // Array will initialize to zero value of int type
var numArray = [3]int{1, 2} // Initialize using specified initial values
var cityArray = [3]string{"Beijing", "Shanghai", "Shenzhen"} // Initialize using specified initial values
var numArray = [...]int{1, 2} // Infer array length from values
var cityArray = [...]string{"Beijing", "Shanghai", "Shenzhen"}
a := [...]int{1: 1, 3: 5} // Initialize with specified index
fmt.Println(a) // [0 1 0 5]
for index, value := range a {
fmt.Println(index, value)
}
Multi-dimensional Arrays#
a := [3][2]string{
{"Beijing", "Shanghai"},
{"Guangzhou", "Shenzhen"},
{"Chengdu", "Chongqing"},
}
Only the first layer of multi-dimensional arrays can use
...
to let the compiler infer the array length
Arrays are value types, assignment and parameter passing will copy the entire array. Therefore, changing the value of the copy will not change the original value.
- Arrays support “==“ and “!=” operators, as memory is always initialized.
[n]*T
represents an array of pointers,*[n]T
represents a pointer to an array.
Slices#
The limitation of arrays is that their length is fixed.
A slice is a variable-length sequence of elements of the same type. It is a layer of encapsulation based on the array type. It is very flexible and supports automatic expansion.
A slice is a reference type, and its internal structure contains address
, length
, and capacity
. Slices are generally used for quick operations on a block of data.
Initialization#
var a = []string // Declare a string slice
var b = []int{} // Declare an integer slice and initialize
var c = []bool{false, true} // Declare a boolean slice and initialize
var d = []bool{false, true} // Declare a boolean slice and initialize
Slices are not empty when pointing to values.
a1 := [...]int{1, 3, 5, 7, 9, 11, 13}
s3 := a1[0:4] // Left inclusive, right exclusive, slice from index 0 to 3
len(s3) // 4 Slice length
cap(s3) // 7 Capacity = length from slice point to end of original array
a[2:] // Equivalent to a[2:len(a)]
a[:3] // Equivalent to a[0:3]
a[:] // Equivalent to a[0:len(a)]
Modifying original array elements also changes the slice, as it is a reference type.
a[low : high : max]
a := [5]int{1, 2, 3, 4, 5}
t := a[1:3:5] // t:[2 3] len(t):2 cap(t):4
Constructing with a simple slice expression a[low: high]
results in a slice of the same type, length, and elements. Additionally, it sets the capacity of the resulting slice to max-low
. In a complete slice expression, only the first index value (low) can be omitted; it defaults to 0.
make()#
Dynamically create a slice
make([]T, size, cap)
a := make([]int, 2, 10) // Initialized to 0
Empty Slice Check#
To check if a slice is empty, use len(s) == 0
rather than s == nil
.
Slices cannot be compared; we cannot use the ==
operator to determine if two slices contain all equal elements. The only valid comparison operation for slices is with nil
. A nil
slice has no underlying array, and both its length and capacity are 0. However, we cannot say that a slice with length and capacity both 0 is necessarily nil
.
Assignment#
s1 := make([]int, 3) //[0 0 0]
s2 := s1 // Directly assign s1 to s2, s1 and s2 share an underlying array
s2[0] = 100
fmt.Println(s1) //[100 0 0]
fmt.Println(s2) //[100 0 0]
append()#
var s []int
s = append(s, 1) // [1]
s = append(s, 2, 3, 4) // [1 2 3 4]
s2 := []int{5, 6, 7}
s = append(s, s2...) // [1 2 3 4 5 6 7]
A zero-value slice declared with var can be used directly in the
append()
function without initialization
var s []int
s = append(s, 1, 2, 3)
Each slice points to an underlying array, and if that array has enough capacity, new elements can be added. When the underlying array cannot accommodate new elements, the slice will automatically expand according to a certain strategy, at which point the underlying array pointed to by the slice will change. The "expansion" operation often occurs when calling the append()
function, so we usually need to receive the return value of the append function with the original variable.
func main() {
// append() adds elements and expands the slice
var numSlice []int
for i := 0; i < 10; i++ {
numSlice = append(numSlice, i)
fmt.Printf("%v len:%d cap:%d ptr:%p\n", numSlice, len(numSlice), cap(numSlice), numSlice)
}
}
Output
[0] len:1 cap:1 ptr:0xc0000a8000
[0 1] len:2 cap:2 ptr:0xc0000a8040
[0 1 2] len:3 cap:4 ptr:0xc0000b2020
[0 1 2 3] len:4 cap:4 ptr:0xc0000b2020
[0 1 2 3 4] len:5 cap:8 ptr:0xc0000b6000
[0 1 2 3 4 5] len:6 cap:8 ptr:0xc0000b6000
[0 1 2 3 4 5 6] len:7 cap:8 ptr:0xc0000b6000
[0 1 2 3 4 5 6 7] len:8 cap:8 ptr:0xc0000b6000
[0 1 2 3 4 5 6 7 8] len:9 cap:16 ptr:0xc0000b8000
[0 1 2 3 4 5 6 7 8 9] len:10 cap:16 ptr:0xc0000b8000
From the above results, we can see:
- The
append()
function adds elements to the end of the slice and returns that slice. - The capacity of the slice
numSlice
expands according to the rules of 1, 2, 4, 8, 16, doubling each time.
$GOROOT/src/runtime/slice.go
source code:
newcap := old.cap
doublecap := newcap + newcap
if cap > doublecap {
newcap = cap
} else {
if old.len < 1024 {
newcap = doublecap
} else {
// Check 0 < newcap to detect overflow
// and prevent an infinite loop.
for 0 < newcap && newcap < cap {
newcap += newcap / 4
}
// Set newcap to the requested cap when
// the newcap calculation overflowed.
if newcap <= 0 {
newcap = cap
}
}
}
- First, it checks if the newly requested capacity (cap) is greater than twice the old capacity (old.cap), then the final capacity (newcap) is the newly requested capacity (cap).
- Otherwise, if the old slice's length is less than 1024, the final capacity (newcap) is twice the old capacity (old.cap), i.e., (newcap=doublecap).
- Otherwise, if the old slice length is greater than or equal to 1024, the final capacity (newcap) starts from the old capacity (old.cap) and loops to increase by 1/4 of the original until the final capacity (newcap) is greater than or equal to the newly requested capacity (cap), i.e., (newcap >= cap).
- If the final capacity (cap) calculation overflows, then the final capacity (cap) is the newly requested capacity (cap).
Chinese strings are 3*2^n
copy()#
Slices are reference types, so a and b actually point to the same memory address. Modifying b will also change the value of a.
The built-in copy()
function in Go can quickly copy data from one slice to another slice space.
a := []int{1, 2, 3, 4, 5}
c := make([]int, 5, 5)
copy(c, a) // Use copy() function to copy elements from slice a to slice c
fmt.Println(a) //[1 2 3 4 5]
fmt.Println(c) //[1 2 3 4 5]
c[0] = 1000
fmt.Println(a) //[1 2 3 4 5]
fmt.Println(c) //[1000 2 3 4 5]
Deleting Elements#
a = append(a[:index], a[index+1:]...)
a := []int{30, 31, 32, 33, 34, 35, 36, 37}
// To delete the element at index 2
a = append(a[:2], a[3:]...)
fmt.Println(a) //[30 31 33 34 35 36 37]
// The underlying array length remains unchanged, elements shift left, right filled by the rightmost element
Sorting slices
sort.Ints(a[:])
Pointers#
ptr := &v // v's type is T, output pointer type *T, such as *string *int
a := 10
b := &a
fmt.Printf("a:%d ptr:%p\n", a, &a) // a:10 ptr:0xc00001a078
fmt.Printf("b:%p type:%T\n", b, b) // b:0xc00001a078 type:*int
fmt.Println(&b) // 0xc00000e018
c := *b // Pointer dereferencing (getting value from memory via pointer)
fmt.Printf("type of c:%T\n", c)
fmt.Printf("value of c:%v\n", c)
& and * are complementary
func modify1(x int) {
x = 100
}
func modify2(x *int) {
*x = 100
}
func main() {
a := 10
modify1(a)
fmt.Println(a) // 10
modify2(&a)
fmt.Println(a) // 100
}
new and make#
The new function is not commonly used; it returns a pointer to a type, and the value corresponding to that pointer is the zero value of that type.
a := new(int)
b := new(bool)
fmt.Printf("%T\n", a) // *int
fmt.Printf("%T\n", b) // *bool
fmt.Println(*a) // 0
fmt.Println(*b) // false
Make is also used for memory allocation, but unlike new, it is only used for creating memory for slices, maps, and channels, and it returns the type itself rather than its pointer type, because these three types are reference types.
var b map[string]int
b = make(map[string]int, 10)
b["Shahe Nazha"] = 100
fmt.Println(b)
Map#
The mapping container provided by Go is map
, which is implemented using a hash table
, similar to Python's dictionary.
A map is an unordered data structure based on key-value
, and maps in Go are reference types that must be initialized before use.
The default initial value of a map type variable is nil, and memory must be allocated using the make() function.
map[KeyType]ValueType
scoreMap := make(map[string]int, 8) // Must initialize to avoid dynamic expansion!
scoreMap["Zhang San"] = 90
scoreMap["Xiao Ming"] = 100
fmt.Println(scoreMap)
fmt.Println(scoreMap["Xiao Ming"])
fmt.Printf("type of a:%T\n", scoreMap)
userInfo := map[string]string{
"username": "Shahe Little Prince",
"password": "123456",
}
Check if Key Exists#
value, ok := map[key] // ok returns a bool indicating whether the key exists
v, ok := scoreMap["Zhang San"]
if ok {
fmt.Println(v)
} else {
fmt.Println("No such person found")
}
Traversing Map#
for k, v := range scoreMap {
fmt.Println(k, v)
}
for k := range scoreMap {
fmt.Println(k)
}
for _, v := range scoreMap {
fmt.Println(v)
}
Note: The order of elements when traversing a map is unrelated to the order in which key-value pairs were added
Deleting Key-Value Pairs#
delete(map, key)
Traverse in Specified Order#
func main() {
rand.Seed(time.Now().UnixNano()) // Initialize random seed
var scoreMap = make(map[string]int, 200)
for i := 0; i < 100; i++ {
key := fmt.Sprintf("stu%02d", i) // Generate strings starting with stu
value := rand.Intn(100) // Generate random integers from 0 to 99
scoreMap[key] = value
}
// Extract all keys from the map into a slice
var keys = make([]string, 0, 200)
for key := range scoreMap {
keys = append(keys, key)
}
// Sort the slice
sort.Strings(keys)
// Traverse the map according to the sorted keys
for _, key := range keys {
fmt.Println(key, scoreMap[key])
}
}
Elements as Map Types in Slices#
var mapSlice = make([]map[string]string, 3) // Slice initialization, each element is a map
for index, value := range mapSlice {
fmt.Printf("index:%d value:%v\n", index, value)
}
fmt.Println("after init")
// Initialize map elements in the slice
mapSlice[0] = make(map[string]string, 10)
mapSlice[0]["name"] = "Little Prince"
mapSlice[0]["password"] = "123456"
mapSlice[0]["address"] = "Shahe"
for index, value := range mapSlice {
fmt.Printf("index:%d value:%v\n", index, value)
}
Values as Slice Types in Maps#
func main() {
var sliceMap = make(map[string][]string, 3)
fmt.Println(sliceMap)
fmt.Println("after init")
key := "China"
value, ok := sliceMap[key]
if !ok {
value = make([]string, 0, 2)
}
value = append(value, "Beijing", "Shanghai")
sliceMap[key] = value
fmt.Println(sliceMap)
}
Functions#
func functionName(parameter type) returnType {
function body
}
func intSum(x int, y int) int {
return x + y
}
Parameter Type Simplification#
func intSum(x, y int) int {
return x + y
}
Variable Parameters#
func intSum2(x ...int) int {
fmt.Println(x) // x is a slice
sum := 0
for _, v := range x {
sum = sum + v
}
return sum
}
Return Values#
// Named returns
func calc(x, y int) (sum, sub int) {
sum = x + y
sub = x - y
return
}
// Slice
func someFunc(x string) []int {
if x == "" {
return nil // No need to return []int{}
}
...
}
If a local variable and a global variable have the same name, the local variable takes precedence.
Function Types and Variables#
We can define a function type using the type
keyword, in the following format:
type calculation func(int, int) int
The above statement defines a calculation
type, which is a function type that takes two int parameters and returns an int return value.
func main() {
var c calculation // Declare a variable c of type calculation
c = add // Assign add to c
fmt.Printf("type of c:%T\n", c) // type of c:main.calculation
fmt.Println(c(1, 2)) // Call c like calling add
f := add // Assign function add to variable f
fmt.Printf("type of f:%T\n", f) // type of f:func(int, int) int
fmt.Println(f(10, 20)) // Call f like calling add
}
Functions as Parameters and Return Values#
func add(x, y int) int {
return x + y
}
func calc(x, y int, op func(int, int) int) int {
return op(x, y)
}
func main() {
ret2 := calc(10, 20, add)
fmt.Println(ret2) //30
}
func do(s string) (func(int, int) int, error) {
switch s {
case "+":
return add, nil
case "-":
return sub, nil
default:
err := errors.New("Unrecognized operator")
return nil, err
}
}
Anonymous Functions#
Define a function inside another function
func main() {
// Save anonymous function to variable
add := func(x, y int) {
fmt.Println(x + y)
}
add(10, 20) // Call anonymous function via variable
// Self-executing function: define an anonymous function and execute it immediately
func(x, y int) {
fmt.Println(x + y)
}(10, 20)
}
Closures#
A closure refers to an entity formed by a function and its associated reference environment. In simple terms, closure = function + reference environment
func adder() func(int) int {
var x int
return func(y int) int {
x += y
return x
}
}
func main() {
var f = adder()
fmt.Println(f(10)) //10
fmt.Println(f(20)) //30
fmt.Println(f(30)) //60
f1 := adder()
fmt.Println(f1(40)) //40
fmt.Println(f1(50)) //90
}
defer#
The defer
statement delays the execution of the statement that follows it. When the function to which defer
belongs is about to return, the delayed statements are executed in reverse order of their definition, meaning that the first deferred statement is executed last, and the last deferred statement is executed first.
func main() {
fmt.Println("start")
defer fmt.Println(1)
defer fmt.Println(2)
defer fmt.Println(3)
fmt.Println("end")
}
/*
start
end
3
2
1
*/
// Interview question: When registering a function to be delayed with defer, all parameters of that function must have their values determined
func calc(index string, a, b int) int {
ret := a + b
fmt.Println(index, a, b, ret)
return ret
}
func main() {
x := 1
y := 2
defer calc("AA", x, calc("A", x, y))
x = 10
defer calc("BB", x, calc("B", x, y))
y = 20
}
/*
A 1 2 3 //defer calc("AA", 1, 3)
B 10 2 12 //defer calc("BB", 10, 12)
BB 10 12 22
AA 1 3 4
*/
Built-in Functions#
Built-in Function | Description |
---|---|
close | Mainly used to close channels |
len | Used to calculate length, such as for string, array, slice, map, channel |
new | Used to allocate memory, mainly for value types like int, struct. Returns a pointer |
make | Used to allocate memory, mainly for reference types like chan, map, slice |
append | Used to add elements to arrays, slices |
panic and recover | Used for error handling |
Currently (Go1.12), Go does not have an exception mechanism, but uses the panic/recover
model to handle errors. panic
can be triggered anywhere, but recover
is only effective in functions called by defer
.
func funcA() {
fmt.Println("func A")
}
func funcB() {
defer func() {
err := recover()
// If a panic error occurs in the program, it can be recovered through recover
if err != nil {
fmt.Println("recover in B")
}
}()
panic("panic in B")
}
func funcC() {
fmt.Println("func C")
}
func main() {
funcA()
funcB()
funcC()
}
recover()
must be used withdefer
.defer
must be defined before statements that may triggerpanic
.
fmt Standard Library#
The fmt package implements formatted I/O similar to C language's printf and scanf. It is mainly divided into two parts: outputting content and obtaining input content.
Print#
func main() {
fmt.Print("Print this message in the terminal.") // No newline
name := "Shahe Little Prince"
fmt.Printf("I am: %s\n", name)
fmt.Println("Print a separate line in the terminal")
}
FPrint#
The Fprint
series of functions will output content to a variable of type io.Writer
interface, which we usually use to write content to files.
// Write content to standard output
fmt.Fprintln(os.Stdout, "Write content to standard output")
fileObj, err := os.OpenFile("./xx.txt", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644)
if err != nil {
fmt.Println("Error opening file, err:", err)
return
}
name := "Shahe Little Prince"
// Write content to the opened file handle
fmt.Fprintf(fileObj, "Write information to the file: %s", name)
Any type that satisfies the
io.Writer
interface supports writing
Sprint#
The Sprint
series of functions generates and returns a string from the passed data.
s3 := fmt.Sprintln("Shahe Little Prince")
Errorf#
e := errors.New("Original error e")
w := fmt.Errorf("Wrapped an error %w", e)
Scan#
fmt.Scan(&name, &age, &married)
fmt.Scanf("1:%s 2:%d 3:%t", &name, &age, &married)
fmt.Scanln(&name, &age, &married)
Also includes Fscan, Sscan
bufio.NewReader#
func bufioDemo() {
reader := bufio.NewReader(os.Stdin) // Generate read object from standard input
fmt.Print("Please enter content:")
text, _ := reader.ReadString('\n') // Read until newline, spaces are also read
text = strings.TrimSpace(text)
fmt.Printf("%#v\n", text)
}
Structs#
Go does not have the concept of "classes" and does not support "class" inheritance and other object-oriented concepts. Go achieves higher extensibility and flexibility through struct embedding combined with interfaces.
Custom Types#
A custom type defines an entirely new type. We can define it based on built-in basic types or through struct definitions.
// Define MyInt as an int type
type MyInt int
Through the definition with the type
keyword, MyInt
is a new type that has the characteristics of int
.
Type Aliases#
Type aliasing states: TypeAlias is just an alias for Type; essentially, TypeAlias and Type are the same type.
type TypeAlias = Type
We have previously seen that rune
and byte
are type aliases.
type byte = uint8
type rune = int32
Struct Definition#
Use the type
and struct
keywords to define a struct, the specific code format is as follows:
type TypeName struct {
FieldName FieldType
FieldName FieldType
…
}
type person struct {
name string
city string
age int8
}
type person1 struct {
name, city string
age int8
}
Where:
- TypeName: Identifies the name of the custom struct, which cannot be duplicated within the same package.
- FieldName: Represents the field names of the struct. Field names within a struct must be unique.
- FieldType: Represents the specific type of the struct field.
Instantiation#
Memory is only allocated when a struct is instantiated. Fields of a struct can only be used after instantiation.
A struct itself is also a type, and we can use the var
keyword to declare a struct type just like we do with built-in types.
var structInstance structType
Basic instantiation
type person struct {
name string
city string
age int8
}
func main() {
var p1 person
p1.name = "Shahe Nazha"
p1.city = "Beijing"
p1.age = 18
fmt.Printf("p1=%v\n", p1) //p1={Shahe Nazha Beijing 18}
fmt.Printf("p1=%#v\n", p1) //p1=main.person{name:"Shahe Nazha", city:"Beijing", age:18}
}
Anonymous structs are used for temporary data structures.
func main() {
var user struct{Name string; Age int}
user.Name = "Little Prince"
user.Age = 18
fmt.Printf("%#v\n", user)
}
Pointer type structs, using new to allocate addresses.
var p2 = new(person)
// Using & to take the address of the struct is equivalent to instantiating the struct type with new
p3 := &person{}
fmt.Printf("%T\n", p2) //*main.person
fmt.Printf("p2=%#v\n", p2) //p2=&main.person{name:"", city:"", age:0}
// Supports directly using . to access struct members on struct pointers
p2.name = "Little Prince"
p2.age = 28
p2.city = "Shanghai"
fmt.Printf("p2=%#v\n", p2) //p2=&main.person{name:"Little Prince", city:"Shanghai", age:28}
Initialization#
Structs that are not initialized have their member variables set to the zero value corresponding to their type. Initialization is the assignment of values during instantiation.
Using key-value pairs for initialization
p5 := person{
name: "Little Prince",
city: "Beijing",
age: 18,
}
Initializing a struct pointer
p6 := &person{
name: "Little Prince",
city: "Beijing",
age: 18,
}
Using list initialization
p8 := &person{
"Shahe Nazha",
"Beijing",
28,
}
Memory Layout#
Structs occupy a contiguous block of memory, and empty structs do not occupy space.
Constructor Functions#
Implementing constructor functions similar to those in other object-oriented languages, Go is oriented towards interface programming.