Preface#
Introduction to Go#
- Open-sourced 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's autonomous driving, mini-programs
Tencent's Blue Whale, microservice 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 method
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
Identifier: alphanumeric underscore, 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 based on value
var (
a string
b int
c bool
d float32
)
Written outside functions as global variables
Local variable declaration 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, counts 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
)
Define 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 left shifted by 10 bits is 1024
Basic Data Types#
Integers are divided into the following two categories: by length: int8, int16, int32, int64
Corresponding unsigned integers: uint8, uint16, uint32, uint64
uint8 is byte, int16 is short, int64 is long
Special Integers#
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, to exit 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 of 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 based on 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 a pointer array,*[n]T
represents an array pointer.
Slices#
The limitation of arrays is their fixed length.
Slices are a variable-length sequence of elements of the same type. They are a layer of encapsulation based on array types. They are very flexible and support automatic resizing.
Slices are a reference type, and their 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 the original array elements will also change 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, same 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 value is 0
Empty Slice Check#
To check if a slice is empty, use len(s) == 0
instead of using 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 a length and capacity of 0 is necessarily nil
.
Assignment#
s1 := make([]int, 3) //[0 0 0]
s2 := s1 // Assign s1 directly 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 the capacity is sufficient, new elements can be added. When the underlying array cannot accommodate new elements, the slice will automatically resize according to a certain strategy, at which point the underlying array pointed to by the slice will change. The "expansion" operation often occurs during the call to 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 appends elements to the end of the slice and returns that slice. - The capacity of the slice
numSlice
expands automatically 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, check 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 double 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 increases 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, e.g., *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 based on 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; using the new function returns a pointer of 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, as these three types are reference types.
var b map[string]int
b = make(map[string]int, 10)
b["Shahe Nazha"] = 100
fmt.Println(b)
Maps#
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 Maps#
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])
}
}
Slice of Maps#
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)
}
Map with Slice as Value#
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 use the type
keyword to define a function type, formatted as follows:
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#
Defining a function inside another function
func main() {
// Save anonymous function to variable
add := func(x, y int) {
fmt.Println(x + y)
}
add(10, 20) // 30
// Self-executing function: An anonymous function defined and executed immediately
func(x, y int) {
fmt.Println(x + y)
}(10, 20)
}
Closures#
A closure refers to an entity formed by a function and its related reference environment combined. 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 following statement. When the function that defer
belongs to is about to return, the deferred statements are executed in reverse order of their definition, meaning 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 executed later 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 find length, for example, 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 append 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
mode 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 the program encounters a panic error, it can be recovered
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 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 new line
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 input 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, there are 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 a brand 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
By defining with the type
keyword, MyInt
is a new type that has the characteristics of int
.
Type Aliases#
Type alias rules: TypeAlias is just an alias for Type; essentially, TypeAlias and Type are the same type.
type TypeAlias = Type
The rune
and byte
types we encountered earlier are type aliases.
type byte = uint8
type rune = int32
Struct Definition#
Use the type
and struct
keywords to define a struct, formatted 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. The fields of a struct can only be used after instantiation.
Structs themselves are 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 that 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 corresponding zero values of their types. Initialization is the assignment of values during instantiation.
Using key-value pair 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; empty structs do not occupy space.
Constructor Functions#
Implementing constructor functions similar to other object-oriented languages, Go is oriented towards interface programming.