The third Youth Training Camp of Byte is a back-end special session, and the class has started. I'm happily taking notes!
The class detailed the basic syntax of Go, along with some summaries from my own reading of the Go programming Bible, leading to this article. I feel there are many commonalities with JS and C/C++.
Content sourced from: The Go Programming Language and the third Youth Training Camp course.
Course source code wangkechun/go-by-example
Introduction to Go Language and Installation#
What is Go Language#
- High performance, high concurrency
- Rich standard library
- Complete toolchain
- Static linking
- Fast compilation
- Cross-platform
- Garbage collection
In summary, it balances the performance of C/C++ while having the simplicity and completeness of standard libraries found in languages like Python.
Installation#
- Visit https://go.dev/, click Download, download the installation package for your platform, and install it.
- If you cannot access the above website, you can alternatively visit https://studygolang.com/dl to download and install.
- If accessing GitHub is slow, it is recommended to configure the Go mod proxy. Refer to the description in https://goproxy.cn/ for configuration, which can greatly speed up the download of third-party dependencies.
Recommended IDEs#
- Install Go plugin for vscode
- GoLand A new IDE from JetBrains series, dddd
You can easily log in to experience the course's example project on GitHub Dashboard — Gitpod (So good, I'm crying)
Basic Data Types#
Integer#
Similar to C++, integers are divided into signed and unsigned types. Signed integers include:
- int8, int16, int32, and int64
- Corresponding to 8-bit, 16-bit, 32-bit, and 64-bit signed integers
- uint8, uint16, uint32, and uint64 correspond to unsigned integers
- Additionally, there are two types corresponding to the specific CPU platform's machine word size:
int
anduint
, whereint
is the most widely used numeric type. Both types have the same size: 32 or 64 bits.- Different compilers may produce different sizes even on the same hardware platform.
- The Unicode character type
rune
is equivalent to int32, typically used to represent a Unicode code point. These two names can be used interchangeably. byte
is an equivalent type ofuint8
, generally used to emphasize that the value is raw data rather than a small integer.- The
uintptr
type does not specify a specific bit size but is sufficient to hold a pointer. It is only needed in low-level programming, especially where Go interacts with C language libraries or operating system interfaces. We will see similar examples in the unsafe package section in Chapter 13.
You can print numbers in binary format using the %b
parameter of the Printf
function, and control the output base format with %d
, %o
, or %x
, which is similar to formatted output in C.
var x uint8 = 1<<1 | 1<<5
fmt.Printf("%08b\n", x) // "00100010", the set {1, 5}
o := 0666
fmt.Printf("%d %[1]o %#[1]o\n", o) // "438 666 0666"
x := int64(0xdeadbeef)
fmt.Printf("%d %[1]x %#[1]x %#[1]X\n", x)
// Output:
// 3735928559 deadbeef 0xdeadbeef 0XDEADBEEF
ascii := 'a'
unicode := '国'
newline := '\n'
fmt.Printf("%d %[1]c %[1]q\n", ascii) // "97 a 'a'"
fmt.Printf("%d %[1]c %[1]q\n", unicode) // "22269 国 '国'"
In the above example, when the Printf
format string contains multiple %
parameters, it will include the corresponding number of additional operands. However, the [1]
modifier after %
tells the Printf
function to use the first operand again.
- The
#
modifier after%
tellsPrintf
to generate0
,0x
, or0X
prefixes when outputting with%o
,%x
, or%X
. - Characters can be printed using the
%c
parameter, or using the%q
parameter to print characters with single quotes.
The built-in len
function returns a signed int
, which can handle reverse loops as shown in the example below.
medals := []string{"gold", "silver", "bronze"}
for i := len(medals) - 1; i >= 0; i-- {
fmt.Println(medals[i]) // "bronze", "silver", "gold"
}
Floating Point#
Go has floating point types float32
and float64
.
Their range limits can be found in the math package.
- The constant
math.MaxFloat32
represents the maximum value thatfloat32
can represent, approximately3.4e38
; the corresponding constantmath.MaxFloat64
is approximately1.8e308
. The minimum values they can represent are approximately1.4e-45
and4.9e-324
, respectively. - Using the
%g
parameter of thePrintf
function to print floating-point numbers will adopt a more compact representation and provide sufficient precision. However, for tabular data, using%e
(with exponent) or%f
may be more appropriate. All three printing formats can specify printing width and control printing precision.
for x := 0; x < 8; x++ {
fmt.Printf("x = %d e^x = %8.3f\n", x, math.Exp(float64(x)))
}
// x = 0 e^x = 1.000
// x = 1 e^x = 2.718
// x = 2 e^x = 7.389
// x = 3 e^x = 20.086
// x = 4 e^x = 54.598
// x = 5 e^x = 148.413
// x = 6 e^x = 403.429
// x = 7 e^x = 1096.633
In addition to providing a large number of commonly used mathematical functions, the math package also provides the creation and testing of special values defined in the IEEE754 floating-point standard: positive infinity and negative infinity Inf -Inf
, used to represent numbers that are too large to fit and results of division by zero; and NaN
(Not a Number), generally used to represent invalid division operation results, such as 0/0 or Sqrt(-1).
var z float64
fmt.Println(z, -z, 1/z, -1/z, z/z) // "0 -0 +Inf -Inf NaN"
- Go's
NaN
is similar to JS in that it is not equal to any number, including itself, and can be tested usingmath.IsNaN
to check if a number is NaN.
nan := math.NaN()
fmt.Println(nan == nan, nan < nan, nan > nan) // "false false false"
Complex Numbers#
Go provides two precision complex number types: complex64
and complex128
, corresponding to float32
and float64
floating-point precision, respectively. The built-in complex
function is used to construct complex numbers, while the built-in real
and imag
functions return the real part and imaginary part of the complex number, respectively.
var x complex128 = complex(1, 2) // 1+2i
var y complex128 = complex(3, 4) // 3+4i
fmt.Println(x*y) // "(-5+10i)"
fmt.Println(real(x*y)) // "-5"
fmt.Println(imag(x*y)) // "10"
If a floating-point literal or a decimal integer literal is followed by an i
, such as 3.141592i
or 2i
, it will form a complex number's imaginary part, with the complex number's real part being 0:
fmt.Println(1i * 1i) // "(-1+0i)", i^2 = -1
A complex constant can be normally added to another ordinary numeric constant.
fmt.Println(1i * 1i) // "(-1+0i)", i^2 = -1
The math/cmplx package provides many functions for complex number processing, such as the square root function and power function for complex numbers.
fmt.Println(cmplx.Sqrt(-1)) // "(0+1i)"
Boolean#
true
or false
, nothing much to say about this.
String#
The string type in Go, string
, is an immutable string, similar to JS but different from C++.
Immutability means that it is safe for two strings to share the same underlying data, making copying any length of string inexpensive. Similarly, an operation on a string
s
and its corresponding substring slices[7:]
can safely share the same memory, so string slice operations are also inexpensive. In both cases, there is no need to allocate new memory.
The i
-th byte in a string is not necessarily the i
-th character of the string, as non-ASCII characters in UTF8 encoding may require two or more bytes.
s[i:j]
generates a new string starting from the i
-th byte of the original string s
to the j
-th byte (excluding j
itself). The new string generated will contain j-i
bytes.
- Both
i
andj
can be omitted; when omitted,0
is used as the starting position, andlen(s)
is used as the ending position.
fmt.Println(s[0:5]) // "hello"
fmt.Println(s[:5]) // "hello"
fmt.Println(s[7:]) // "world"
fmt.Println(s[:]) // "hello, world"
The +
operator concatenates two strings to construct a new string:
fmt.Println("goodbye" + s[5:]) // "goodbye, world"
String comparison is done by comparing byte by byte, and the comparison result is in the natural encoding order of the strings.
Go source files are always encoded in UTF8, and Go's text strings are also processed in UTF8, so we can write Unicode code points in string literals.
A raw string literal is as follows, using backticks instead of double quotes.
const GoUsage = `Go is a tool for managing Go source code.
Usage:
go command [arguments]
...`
In a raw string literal, there are no escape operations; all content is taken literally, including backspaces and newlines, so a raw string literal in a program can span multiple lines.
- It is not possible to write a backtick directly inside a raw string literal; it can be escaped using octal or hexadecimal or concatenated with a string constant using + "`".
- The only special handling is that it will remove carriage returns to ensure that the values are the same across all platforms, including those systems that also include carriage returns in text files.
Windows systems will include both carriage returns and newlines in text files.
Here are some string methods:
package main
import (
"fmt"
"strings"
)
func main() {
a := "hello"
fmt.Println(strings.Contains(a, "ll")) // true
fmt.Println(strings.Count(a, "l")) // 2
fmt.Println(strings.HasPrefix(a, "he")) // true
fmt.Println(strings.HasSuffix(a, "llo")) // true
fmt.Println(strings.Index(a, "ll")) // 2
fmt.Println(strings.Join([]string{"he", "llo"}, "-")) // he-llo
fmt.Println(strings.Repeat(a, 2)) // hellohello
fmt.Println(strings.Replace(a, "e", "E", -1)) // hEllo
fmt.Println(strings.Split("a-b-c", "-")) // [a b c]
fmt.Println(strings.ToLower(a)) // hello
fmt.Println(strings.ToUpper(a)) // HELLO
fmt.Println(len(a)) // 5
b := "你好"
fmt.Println(len(b)) // 6
}
In Go, you can easily use
%v
to print variables of any type, without needing to distinguish between numeric strings, and you can use%+v
to print detailed results, while%#v
provides even more detail.
package main
import "fmt"
type point struct {
x, y int
}
func main() {
s := "hello"
n := 123
p := point{1, 2}
fmt.Println(s, n) // hello 123
fmt.Println(p) // {1 2}
fmt.Printf("s=%v\n", s) // s=hello
fmt.Printf("n=%v\n", n) // n=123
fmt.Printf("p=%v\n", p) // p={1 2}
fmt.Printf("p=%+v\n", p) // p={x:1 y:2}
fmt.Printf("p=%#v\n", p) // p=main.point{x:1, y:2}
f := 3.141592653
fmt.Println(f) // 3.141592653
fmt.Printf("%.2f\n", f) // 3.14
}
String and Number Conversion#
In Go, conversions between strings and numeric types are handled in the strconv
package, which is an abbreviation for string convert. You can use ParseInt
or ParseFloat
to parse a string. You can also use Atoi
to convert a decimal string to a number. You can use Itoa
to convert a number to a string.
- If the input is invalid, these functions will return an error except for Itoa.
package main
import (
"fmt"
"strconv"
)
func main() {
f, _ := strconv.ParseFloat("1.234", 64)
fmt.Println(f) // 1.234
n, _ := strconv.ParseInt("111", 10, 64)
fmt.Println(n) // 111
n, _ = strconv.ParseInt("0x1000", 0, 64)
fmt.Println(n) // 4096
n2, _ := strconv.Atoi("123")
fmt.Println(n2) // 123
n2, err := strconv.Atoi("AAA")
fmt.Println(n2, err) // 0 strconv.Atoi: parsing "AAA": invalid syntax
n3 := strconv.Itoa(123) // This does not return an error
fmt.Println(n3) // 123
}
Constants#
Like constants in other languages, the value of a constant cannot be modified and must be initialized. When declaring constants in bulk, all except the first initialization expression can be omitted; if omitted, the previous constant's initialization expression is used, as shown below:
const pi = 3.14159 // approximately; math.Pi is a better approximation
const (
e = 2.71828182845904523536028747135266249775724709369995957496696763
pi = 3.14159265358979323846264338327950288419716939937510582097494459
)
const (
a = 1
b
c = 2
d
)
iota
Constant Generator#
Similar to the enumeration type
Enum
in C/C++!!
Constant declarations can use the iota
constant generator for initialization, which is used to generate a set of constants initialized with similar rules without having to write the initialization expression on each line. In a const
declaration statement, iota
will be set to 0
on the line of the first declared constant, and then incremented by one on each line with a constant declaration.
type Weekday int
const (
Sunday Weekday = iota
Monday
Tuesday
Wednesday
Thursday
Friday
Saturday
)
// Sunday corresponds to 0
// Monday corresponds to 1
// ....
// Saturday corresponds to 6
You can also combine complex expressions with iota
, as shown in the example: each constant corresponds to the expression 1 << iota
, which is consecutive powers of 2.
type Flags uint
const (
FlagUp Flags = 1 << iota // is up
FlagBroadcast // supports broadcast access capability
FlagLoopback // is a loopback interface
FlagPointToPoint // belongs to a point-to-point link
FlagMulticast // supports multicast access capability
)
fmt.Println(FlagUp, FlagBroadcast, FlagLoopback, FlagPointToPoint, FlagMulticast)
// 1 2 4 8 16
Untyped Constants#
Many constants do not have a specific underlying type. The Go compiler provides arithmetic operations with higher precision than the underlying types for these untyped numeric constants; you can think of it as having at least 256 bits of arithmetic precision. There are six types of untyped constants: untyped boolean, untyped integer, untyped character, untyped floating-point, untyped complex, and untyped string.
Only constants can be untyped. When an untyped constant is assigned to a variable, the untyped constant will be implicitly converted to the corresponding type if the conversion is valid.
- For variables declared without explicit types (including short variable declarations), the form of the constant will implicitly determine the default type of the variable,
- Untyped integer constants convert to
int
, which has an uncertain memory size, while untyped floating-point and complex constants convert to memory size definedfloat64
andcomplex128
.
- Untyped integer constants convert to
Program Structure#
https://books.studygolang.com/gopl-zh/ch2/ch2.html
Declaration and Variables#
var#
The general syntax is as follows:
var variableName type = expression
If the type is omitted, it is automatically inferred from the expression. If the expression is empty, the variable is initialized with zero value (therefore, in Go, there are no uninitialized variables).
Type | Zero Value |
---|---|
Numeric | 0 |
Boolean | false |
String | "" |
Aggregate types like arrays or structs | nil |
You can declare a group of variables in a single declaration statement, or declare and initialize a group of variables with a set of initialization expressions.
var i, j, k int // int int int
var b, f, s = true, 2.3, "hello" // bool float64 string
A group of variables can also be initialized by calling a function that returns multiple return values:
var f, err = os.Open(name) // os.Open returns a file and an error
Short Variable Declaration :=
#
Variables are declared in the form name := expression
, with the variable's type automatically inferred from the expression.
- Due to its concise and flexible nature, short variable declarations are widely used for most local variable declarations and initializations.
- The
var
form of declaration statements is often used where explicit variable types need to be specified, or where the initial value is irrelevant because the variable will be reassigned later.
i := 100 // int
i, j := 0, 1 // int int
var boiling float64 = 100 // a float64
var names []string
var err error
- The short variable declaration statement will only perform assignment for variables that have already been declared in the same lexical scope.
- If the variable is declared in an outer lexical scope, the short variable declaration statement will redeclare a new variable in the current lexical scope.
Pointers#
Similar to C, you can take the address with the &
operator and dereference with *
.
x := 1
p := &x // p, of type *int, points to x
fmt.Println(*p) // "1"
*p = 2 // equivalent to x = 2
fmt.Println(x) // "2"
The zero value of any pointer type is nil
.
- If
p
points to a valid variable, thenp != nil
tests true. - Two pointers are equal only if they point to the same variable or are all
nil
.
new
Function#
new(T)
creates an anonymous variable of type T
, initialized to the zero value of type T, and returns the variable's address, with the returned pointer type being *T
.
- In Go,
new
is a predefined function, not a keyword! So it can be redefined.
p := new(int) // p, *int type, points to an anonymous int variable
fmt.Println(*p) // "0"
*p = 2 // Set the value of the int anonymous variable to 2
fmt.Println(*p) // "2"
Increment/Decrement Operations#
The increment statement i++
adds 1 to i
; this is equivalent to i += 1
and i = i + 1
. The corresponding decrement operation is i--
, which subtracts 1 from i
. They are statements, unlike in other C-like languages where they are expressions.
- Therefore,
j = i++
is illegal, and both++
and--
can only be placed after the variable name, making--i
also illegal.
Type type
#
Similar to an enhanced version of typeof
in C++, the form is as follows:
type typeName underlyingType
For example, two types are declared: Celsius
and Fahrenheit
, corresponding to different temperature units.
- The underlying data type determines its internal structure and representation.
- Although they have the same underlying type
float64
, they are different data types, so they cannot be compared or mixed in an expression. - Type conversion does not change the value itself but changes its semantics.
import "fmt"
type Celsius float64 // Celsius temperature
type Fahrenheit float64 // Fahrenheit temperature
const (
AbsoluteZeroC Celsius = -273.15 // Absolute zero
FreezingC Celsius = 0 // Freezing point temperature
BoilingC Celsius = 100 // Boiling water temperature
)
func CToF(c Celsius) Fahrenheit { return Fahrenheit(c*9/5 + 32) }
func FToC(f Fahrenheit) Celsius { return Celsius((f - 32) * 5 / 9) }
The comparison operators ==
and <
can also be used to compare a named type variable with another variable of the same type or values of unnamed types with the same underlying type. However, if two values have different types, they cannot be compared directly:
var c Celsius
var f Fahrenheit
fmt.Println(c == 0) // "true"
fmt.Println(f >= 0) // "true"
fmt.Println(c == f) // compile error: type mismatch
fmt.Println(c == Celsius(f)) // "true"! Type conversion does not change the value.
Named types can also define new behaviors for the values of that type. These behaviors are represented as a set of functions associated with that type, which we call the method set of the type (detailed in Chapter 6).
In the following declaration statement, the parameter c
of type Celsius appears before the function name, indicating that a method named String is declared for the Celsius type, which returns a string representation of the object c
with the °C temperature unit:
func (c Celsius) String() string { return fmt.Sprintf("%g°C", c) }
Many types define a String method because when using the printing methods of the fmt package, the result returned by the String method of that type will be prioritized for printing.
c := FToC(212.0)
fmt.Println(c.String()) // "100°C"
fmt.Printf("%v\n", c) // "100°C"; no need to call String explicitly
fmt.Printf("%s\n", c) // "100°C"
fmt.Println(c) // "100°C"
fmt.Printf("%g\n", c) // "100"; does not call String
fmt.Println(float64(c)) // "100"; does not call String
Looping for
#
Command Line Arguments · The Go Programming Language
In Go, there are no while or do while loops, only one for
loop.
The syntax is as follows:
for initialization; condition; post {
// zero or more statements
}
The three parts of the for loop do not need to be surrounded by parentheses. Braces are mandatory, and the left brace must be on the same line as the post statement.
- The
initialization
statement is optional and is executed before the loop starts. Ifinitialization
exists, it must be a simple statement (simple statement), such as a short variable declaration, increment statement, assignment statement, or function call. - The
condition
is a boolean expression whose value is evaluated at the start of each iteration of the loop. If it istrue
, the loop body statements are executed. - The
post
statement is executed after each execution of the loop body, and then thecondition
is evaluated again. When the value ofcondition
isfalse
, the loop ends.
Each of the three parts of the for loop can be omitted. If both initialization
and post
are omitted, it becomes a while loop. Semicolons can also be omitted; if all three parts are omitted, it becomes an infinite loop, which can be exited using break
:
i := 1
for {
fmt.Println("loop")
break
}
for j := 7; j < 9; j++ {
fmt.Println(j)
}
for n := 0; n < 5; n++ {
if n%2 == 0 {
continue
}
fmt.Println(n)
}
for i <= 3 {
fmt.Println(i)
i = i + 1
}
Branching Structure#
if else#
The if
in Go is similar to Python, with no parentheses, but must be followed by braces.
if 7%2 == 0 {
fmt.Println("7 is even")
} else {
fmt.Println("7 is odd")
}
if 8%4 == 0 {
fmt.Println("8 is divisible by 4")
}
if num := 9; num < 0 {
fmt.Println(num, "is negative")
} else if num < 10 {
fmt.Println(num, "has 1 digit")
} else {
fmt.Println(num, "has multiple digits")
}
switch#
The switch
structure in Go is similar to C++. However, there are many differences:
- The variable name after switch should not have parentheses.
- In C++, if you do not add
break
to switch cases, it will continue to run all cases below. In Go, you do not need to addbreak
. - The switch in Go is more powerful; it can use any variable type and can even replace any if else statements.
You can omit any variable after switch and write conditional branches in the case. This makes the code logic clearer compared to using multiple if else statements.
package main
import (
"fmt"
"time"
)
func main() {
a := 2
switch a {
case 1:
fmt.Println("one")
case 2:
fmt.Println("two")
case 3:
fmt.Println("three")
case 4, 5:
fmt.Println("four or five")
default:
fmt.Println("other")
}
t := time.Now()
switch {
case t.Hour() < 12:
fmt.Println("It's before noon")
default:
fmt.Println("It's after noon")
}
}
Process Information#
In Go, we can use os.argv
to get the command line arguments specified when executing the program. For example, when we compile a binary file, command
, followed by abcd
to start, the output will be that os.argv
will be a slice of length 5
, with the first member representing the name of the binary itself. We can use os.Getenv
to read environment variables. exec
package main
import (
"fmt"
"os"
"os/exec"
)
func main() {
// go run example/20-env/main.go a b c d
fmt.Println(os.Args) // [/var/folders/8p/n34xxfnx38dg8bv_x8l62t_m0000gn/T/go-build3406981276/b001/exe/main a b c d]
fmt.Println(os.Getenv("PATH")) // /usr/local/go/bin...
fmt.Println(os.Setenv("AA", "BB"))
buf, err := exec.Command("grep", "127.0.0.1", "/etc/hosts").CombinedOutput()
if err != nil {
panic(err)
}
fmt.Println(string(buf)) // 127.0.0.1 localhost
}
Composite Data Types#
Arrays#
An array is a sequence of fixed length consisting of elements of a specific type. An array can consist of zero or more elements. Because the length of the array is fixed, arrays are rarely used directly in Go. The corresponding type to an array is Slice
, which is a dynamic sequence that can grow and shrink, and slice functionality is more flexible, but to understand how slices work, you need to first understand arrays.
package main
import "fmt"
func main() {
var a [5]int
a[4] = 100
fmt.Println("get:", a[2])
fmt.Println("len:", len(a))
b := [5]int{1, 2, 3, 4, 5}
fmt.Println(b)
var twoD [2][3]int
for i := 0; i < 2; i++ {
for j := 0; j < 3; j++ {
twoD[i][j] = i + j
}
}
fmt.Println("2d: ", twoD)
}
Slices#
Slices differ from arrays in that they can change length arbitrarily and have richer operations.
- Use
make
to create a slice, and you can access values like an array. - Use
append
to add elements. Note that the usage of append is similar toconcat
in JS, returning a new array, and the result of append is assigned to the original array. - Slices can also be dynamically initialized with a specified length.
len(s)
- Slices support slicing operations like Python, for example,
s[2:5]
represents extracting elements from the second to the fifth position, excluding the fifth element. However, unlike Python, negative indexing is not supported here.
package main
import "fmt"
func main() {
s := make([]string, 3)
s[0] = "a"
s[1] = "b"
s[2] = "c"
fmt.Println("get:", s[2]) // c
fmt.Println("len:", len(s)) // 3
s = append(s, "d")
s = append(s, "e", "f")
fmt.Println(s) // [a b c d e f]
c := make([]string, len(s))
copy(c, s)
fmt.Println(c) // [a b c d e f]
fmt.Println(s[2:5]) // [c d e]
fmt.Println(s[:5]) // [a b c d e]
fmt.Println(s[2:]) // [c d e f]
good := []string{"g", "o", "o", "d"}
fmt.Println(good) // [g o o d]
}
Maps#
map
is the most frequently used data structure in practical use.
- You can create an empty
map
usingmake
, which requires two types: the type ofkey
and the type ofvalue
.map[string]int
indicates that thekey
type isstring
and thevalue
type isint
.
- The value retrieval and insertion in
map
are similar to STL maps in C++, and can be done directly.m[key]
m[key] = value
. - You can use
delete
to remove key-value pairs from it. - In Go,
map
is completely unordered, and the output during traversal will not be in alphabetical order or insertion order, but rather in random order.
package main
import "fmt"
func main() {
m := make(map[string]int)
m["one"] = 1
m["two"] = 2
fmt.Println(m) // map[one:1 two:2]
fmt.Println(len(m)) // 2
fmt.Println(m["one"]) // 1
fmt.Println(m["unknow"]) // 0
r, ok := m["unknow"]
fmt.Println(r, ok) // 0 false
delete(m, "one")
m2 := map[string]int{"one": 1, "two": 2}
var m3 = map[string]int{"one": 1, "two": 2}
fmt.Println(m2, m3)
}
Range#
For a slice
or a map
, we can use range
to quickly iterate, making the code more concise. When iterating over arrays, it will return two values: the first is the index, and the second is the value at that position. If we do not need the index, we can use the underscore _
to ignore it.
Go does not allow the use of unused local variables (local variables), as this will lead to compilation errors. The
blank identifier
(blank identifier), i.e.,_
(the underscore), can be used to discard unnecessary loop indices while retaining the element values.
package main
import "fmt"
func main() {
nums := []int{2, 3, 4}
sum := 0
for i, num := range nums {
sum += num
if num == 2 {
fmt.Println("index:", i, "num:", num) // index: 0 num: 2
}
}
fmt.Println(sum) // 9
m := map[string]string{"a": "A", "b": "B"}
for k, v := range m {
fmt.Println(k, v) // b 8; a A
}
for k := range m {
fmt.Println("key: ", k) // key: a; key: b
}
for _, v := range m {
fmt.Println("value:", v) // value: A; value: B
}
}
Structs#
A struct is a collection of fields with types. For example, the user
struct contains two fields, name
and password
.
- You can initialize a struct variable using the struct name, passing initial values for each field.
- You can also specify initial values using key-value pairs, allowing you to initialize only a portion of the fields.
- Structs also support pointers, allowing direct modification of the struct, which can avoid some overhead of copying large structs in certain situations.
package main
import "fmt"
type user struct {
name string
password string
}
func main() {
a := user{name: "wang", password: "1024"}
b := user{"wang", "1024"}
c := user{name: "wang"}
c.password = "1024"
var d user
d.name = "wang"
d.password = "1024"
fmt.Println(a, b, c, d) // {wang 1024} {wang 1024} {wang 1024} {wang 1024}
fmt.Println(checkPassword(a, "haha")) // false
fmt.Println(checkPassword2(&a, "haha")) // false
}
func checkPassword(u user, password string) bool {
return u.password == password
}
func checkPassword2(u *user, password string) bool {
return u.password == password
}
JSON#
JSON operations in Go are very simple.
- For an existing struct, as long as the first letter of each field is uppercase, meaning it is public, the struct can be serialized into a JSON string using
JSON.marshaler
.
The
JSON.marshaler
returns the serialized value and error, as shown in the example
The default serialized string will have fields starting with uppercase letters. You can modify the output JSON field names using json tags and other syntax.
- The serialized string can be deserialized back into an empty variable using
JSON.unmarshaler
.
package main
import (
"encoding/json"
"fmt"
)
type userInfo struct {
Name string
Age int `json:"age"`
Hobby []string
}
func main() {
a := userInfo{Name: "wang", Age: 18, Hobby: []string{"Golang", "TypeScript"}}
buf, err := json.Marshal(a)
if err != nil {
panic(err)
}
fmt.Println(buf) // [123 34 78 97...]
fmt.Println(string(buf)) // {"Name":"wang","age":18,"Hobby":["Golang","TypeScript"]}
buf, err = json.MarshalIndent(a, "", "\t")
if err != nil {
panic(err)
}
fmt.Println(string(buf))
var b userInfo
err = json.Unmarshal(buf, &b)
if err != nil {
panic(err)
}
fmt.Printf("%#v\n", b) // main.userInfo{Name:"wang", Age:18, Hobby:[]string{"Golang", "TypeScript"}}
}
Time Handling#
In Go, the most commonly used function is time.Now()
to get the current time. You can also use time.Date
to construct a time with a timezone, and there are many methods to get the year, month, day, hour, minute, and second of this time point.
- You can use the
Sub
method to subtract two times and get a time duration. - The duration can also tell you how many hours, minutes, and seconds it has.
- When interacting with certain systems, we often use timestamps. You can use
.Unix
to get the timestamp.time.Format
time.Parse
package main
import (
"fmt"
"time"
)
func main() {
now := time.Now()
fmt.Println(now) // 2022-05-07 13:12:03.7190528 +0800 CST m=+0.004990401
t := time.Date(2022, 5, 7, 13, 25, 36, 0, time.UTC)
t2 := time.Date(2022, 8, 12, 12, 30, 36, 0, time.UTC)
fmt.Println(t) // 2022-05-07 13:25:36 +0000 UTC
fmt.Println(t.Year(), t.Month(), t.Day(), t.Hour(), t.Minute()) // 2022 March 27 1 25
fmt.Println(t.Format("2006-01-02 15:04:05")) // 2022-05-07 13:25:36
diff := t2.Sub(t)
fmt.Println(diff) // 2327h5m0s
fmt.Println(diff.Minutes(), diff.Seconds()) // 139625 8.3775e+06
t3, err := time.Parse("2006-01-02 15:04:05", "2022-05-07 13:25:36")
if err != nil {
panic(err)
}
fmt.Println(t3 == t) // true
fmt.Println(now.Unix()) // 1651900531
}
Functions#
Unlike many other languages, the variable types of function parameters in Go are postfixed. Go natively supports returning multiple values.
- In practical business logic code, almost all functions return two values: the first is the return value, and the second is an error message. As shown in the example,
exists
.
package main
import "fmt"
func add(a int, b int) int {
return a + b
}
func add2(a, b int) int {
return a + b
}
func exists(m map[string]string, k string) (v string, ok bool) {
v, ok = m[k]
return v, ok
}
func main() {
res := add(1, 2)
fmt.Println(res) // 3
v, ok := exists(map[string]string{"a": "A"}, "a")
fmt.Println(v, ok) // A True
}
Error Handling#
Error handling in Go is done by using a separate return value to pass the error information.
- By adding an
error
to the return value type of a function, it indicates that this function may return an error. Therefore, in the function implementation, you need toreturn
two values simultaneously. - When an error occurs, you can
return nil
and anerror
. If there is no error, return the original result andnil
.
package main
import (
"errors"
"fmt"
)
type user struct {
name string
password string
}
func findUser(users []user, name string) (v *user, err error) {
for _, u := range users {
if u.name == name {
return &u, nil
}
}
return nil, errors.New("not found")
}
func main() {
u, err := findUser([]user{{"wang", "1024"}}, "wang")
if err != nil {
fmt.Println(err)
return
}
fmt.Println(u.name) // wang
if u, err := findUser([]user{{"wang", "1024"}}, "li"); err != nil {
fmt.Println(err) // not found
return
} else {
fmt.Println(u.name)
}
}
Recommended Tools#
Several code generation tools mentioned in class:
After-Class Exercises#
- Modify the final code of the first example guessing game to use fmt.Scanf to simplify the implementation.
package main
import (
"fmt"
"math/rand"
"time"
)
func main() {
maxNum := 100
rand.Seed(time.Now().UnixNano())
secretNumber := rand.Intn(maxNum)
// fmt.Println("The secret number is ", secretNumber)
fmt.Println("Please input your guess")
//reader := bufio.NewReader(os.Stdin)
for {
//input, err := reader.ReadString('\n')
var guess int
_, err := fmt.Scanf("%d", &guess)
fmt.Scanf("%*c") // consume newline
if err != nil {
fmt.Println("An error occurred while reading input. Please try again", err)
continue
}
//input = strings.TrimSuffix(input, "\n")
if err != nil {
fmt.Println("Invalid input. Please enter an integer value")
continue
}
fmt.Println("Your guess is", guess)
if guess > secretNumber {
fmt.Println("Your guess is bigger than the secret number. Please try again")
} else if guess < secretNumber {
fmt.Println("Your guess is smaller than the secret number. Please try again")
} else {
fmt.Println("Correct, you Legend!")
break
}
}
}
- Modify the final code of the second example command line dictionary to add support for another translation engine.
- Based on the previous step, modify the code to implement parallel requests to two translation engines to improve response speed.
Summary and Insights#
The class detailed the basic syntax of Go, along with some summaries from my own reading of the Go programming Bible, leading to this article. I feel there are many commonalities with JS and C/C++.
Content sourced from: The Go Programming Language and the third Youth Training Camp course.