Summary of quickly learning GO language

Note: This blog will share the summary of my initial learning of GO. I hope that everyone can quickly master the basic programming capabilities of GO through this blog in a short time. If there are any mistakes, please leave a message to correct me. Thank you!

1. Preliminary understanding of Go language

(1) Main issues and goals of the birth of Go language

  1. Multi-core hardware architecture: With the development of computer hardware, multi-core processors have become mainstream, making parallel computing common. However, traditional programming languages ​​may face difficulties in handling multi-core parallelism because they lack suitable native support. The Go language makes concurrent programming easier by introducing lightweight goroutine and channel mechanisms. Developers can easily create thousands of concurrently executing coroutines without worrying about the complexity of thread management.

  2. Ultra-large-scale distributed computing clusters: With the rise of cloud computing and distributed systems, it has become more and more common to build and maintain ultra-large-scale distributed computing clusters. These clusters need to be able to efficiently handle large volumes of requests, data sharing, and coordination. The concurrency features and channel mechanism of the Go language make it easier to write distributed systems. Developers can use coroutines and channels to handle concurrent tasks, message passing, and coordination work.

  3. The increase in development scale and update speed caused by the Web model: The rise of Web applications has brought unprecedented development scale and continuous update requirements. Traditional programming languages ​​may face issues such as maintainability, performance, and development efficiency when developing large-scale web applications. Through its concise syntax, efficient compilation speed, and concurrency support, the Go language enables developers to iterate and deploy web applications more quickly, and can also better handle highly concurrent network requests.

Taken together, when the Go language was born, it did focus on solving technical challenges such as multi-core hardware architecture, ultra-large-scale distributed computing clusters, and development scale and speed in Web mode. One of its design goals is to provide a programming language that adapts to the needs of modern software development so that developers can better deal with these challenges.

(2) Typical representatives of Go language applications

Go language has been widely used in current application development. Many well-known companies and projects use Go language to build various types of applications. The following are some representative products and projects that use Go language as the core development language:

These are just a few examples of Go language applications. There are actually many other projects and products using Go language to build high-performance, reliable and easy-to-maintain applications. This shows that the Go language plays an important role in modern application development, especially in the fields of distributed systems, cloud computing and high-performance applications.

(3) Misunderstandings among Java, C++, and C programmers when learning to write Go

When programmers of Java, C++, C and other programming languages ​​start to learn to write Go language, they may encounter some misunderstandings because Go is different from these traditional languages ​​in some aspects. Here are some common misconceptions:

  1. Overuse of traditional concurrency models: Traditional programming languages ​​such as Java, C++, and C usually use threads and locks to handle concurrency, but in Go, using goroutines and channels is a better way. . Programmers new to Go may continue to use traditional concurrency models without taking full advantage of Go's lightweight coroutines and channels, thereby losing Go's concurrency advantages.

  2. Excessive use of pointers: Languages ​​such as C and C++ emphasize the use of pointers, but the Go language avoids excessive pointer operations during design. Programmers new to Go may overuse pointers, causing their code to become complex. In Go, try to avoid using pointers unless you really need to modify the value.

  3. Ignore error handling: Go encourages handling errors explicitly rather than simply ignoring them. This is different from the convention of some other languages, where errors are often ignored or simply thrown. Programmers new to Go may overlook error handling, leaving potential problems undetected.

  4. Overuse of global variables: Global variables can be a common practice in languages ​​like C and C++. However, in Go, the use of global variables is considered bad practice. Go encourages the use of local variables and passing parameters to pass data to avoid introducing unnecessary coupling and side effects.

  5. Not familiar with slices and maps: Slices and maps in Go are powerful data structures, but may not be familiar to programmers in other languages. It's important to learn how to use slices and maps correctly because they are used extensively in Go for collections and data processing.

  6. Wrong Go style: Every language has its own unique coding style and conventions. Programmers new to Go may apply coding styles from other languages ​​to their Go code, which may make the code difficult to read and understand. It is important to understand and follow Go's coding conventions.

In order to avoid these misunderstandings, programmers who learn Go should invest time to understand the core concepts of the Go language, including concurrency models, error handling, data structures, etc., and at the same time actively participate in the Go community and read Go's official documentation and sample codes in order to better Adapt to Go's design philosophy and best practices.

2. Environment preparation (explained using Mac)

(1) Environment settings

Setting up a Go language development environment on macOS is very simple, you can follow the following steps:

  1. Install using Homebrew: This is the most convenient method if you use the Homebrew package manager. Open a terminal and run the following command to install the Go language:

    brew install go
  2. Manual installation: If you wish to install the Go language manually, you can follow these steps:

    a. Visit the official website to download the installation package `goX.XXdarwin-amd64.pkg

    b. Double-click the downloaded installation package and follow the instructions to run the installation program. Just follow the default settings, the installation path is usually /usr/local/go.

  3. Set environment variables: Once the installation is complete, you need to add the binary path of the Go language to the PATH environment variable in your terminal configuration file. This allows you to run Go commands directly in the terminal.

    a. Open a terminal and edit your terminal configuration file using a text editor such as nano, vim, or any editor you like. For example:

    nano ~/.bash_profile

    b. Add the following lines to the file (adjust to your installation path), then save and exit the editor:

    export PATH=$PATH:/usr/local/go/bin

    c. To make the configuration take effect, you can run the following command or restart the terminal:

    source ~/.bash_profile
  4. Verify installation: Open a terminal and enter the following command to verify that Go has been installed correctly:

    go version

    If you see the Go version number, the installation is successful.

(2) IDE selection instructions

I personally use GoLand. After downloading it directly from the official website, you can buy the cracked version online. I won’t go into details here!

3. Go language program learning

Create your own project directory/Users/zyf/zyfcodes/go/go-learning and create a new src directory.

(1) The first written in Go language

Create the chapter1/hello directory under the src directory, create a new hello.go file, and write the code as follows:

package main

import (
	"fmt"
	"os"
)

/**
 * @author zhangyanfeng
 * @description 第一个godaima
 * @date 2023/8/20  23:45
 * @param
 * @return
 **/
func main() {
	if len(os.Args) > 1 {
		fmt.Println("Hello World", os.Args[1])
	}
}

This code is a simple Go language program that accepts command line parameters and prints out a "Hello World" message with parameters. Here is a line-by-line analysis of the code:

  1. package main: Declare that this file belongs to the package named "main", which is the entry package name of a Go program.

  2. import ("fmt" "os"): Two standard library packages are introduced, "fmt" for formatting output, and "os" for interacting with the operating system.

  3. func main() { ... }: This is the entry function of the program, it will be called first when the program is running.

  4. if len(os.Args) > 1 { ... }: This conditional statement checks whether the number of command line parameters is greater than 1, that is, whether there are parameters passed to the program. os.Argsis a string slice that contains all command line parameters. The first parameter is the name of the program.

  5. fmt.Println("Hello World", os.Args[1]): If any parameters are passed to the program, this line of code will be executed. It uses fmt.Printlnthe function to print a message os.Args[1]consisting of the string "Hello World" and os.Args[1]representing the first argument passed to the program.

To sum up, this code covers the following knowledge points:

  1. Package import and use of the standard library: Import the "fmt" and "os" packages through importthe keyword, and then use the functions and types provided by these packages in your code.

  2. Obtaining command line parameters: Use os.Argsto obtain command line parameters.

  3. Conditional statements: Use ifconditional statements to determine whether any command line parameters are passed to the program.

  4. String operations: Use string concatenation operations to concatenate "Hello World" with command line parameters.

  5. Formatted output: Use fmt.Printlnthe function to output messages to standard output.

Note: If no parameters are passed to the program, this code will not print any message. If multiple parameters are passed, the code only uses the first parameter and ignores the others.

Execute "go run hello.go ZYF" in this directory, and the running result is "Hello World ZYF".

(2) Learning to write basic program structures

Create chapter2 in the src directory

1.Variables

Prerequisite: Create variables in the chapter2 directory. The learning summary is as follows:

  1. Variable declaration: Use varkeywords to declare a variable, for example: var x int.
  2. Type inference: You can use :=operators for variable declaration and assignment, and Go will automatically infer the variable type based on the value on the right, for example: y := 5.
  3. Variable assignment: Use assignment operators =to assign values ​​to variables, for example: x = 10.
  4. Multi-variable declaration: Multiple variables can be declared at the same time, for example: var a, b, c int.
  5. Variable initialization: Variables can be initialized when declared, for example: var name string = "John".
  6. Zero value: Uninitialized variables will be assigned zero value, numeric type is 0, Boolean type is Boolean type false, string type is empty string, etc.
  7. Short variable declaration: Inside a function, you can use short variable declaration, for example: count := 10.

Create a new fib_test.go, background: simple and practical Fibonacci sequence for practice

package variables

import "testing"

func TestFibList(t *testing.T) {
	a := 1
	b := 1
	t.Log(a)
	for i := 0; i < 5; i++ {
		t.Log(" ", b)
		tmp := a
		a = b
		b = tmp + a
	}
}

func TestExchange(t *testing.T) {
	a := 1
	b := 2
	// tmp := a
	// a = b
	// b = tmp
	a, b = b, a
	t.Log(a, b)
}

The knowledge points involved in the code are explained one by one below:

  1. package variables: declares a package named "variables", which is a package name used for testing.

  2. import "testing": Imported the Go language testing framework "testing" package for writing and running test functions.

  3. func TestFibList(t *testing.T) { ... }: Defines a test function "TestFibList", which is used to test the Fibonacci sequence generation logic. This is the standard naming for a test function, starting with "Test", followed by the name of the function being tested.

    • aInside the test function, two integer variables and are declared band initialized to 1, which are the first two numbers of the Fibonacci sequence.
    • Use t.Log(a)print variable avalue to test log.
    • Use a loop to generate the first 5 numbers of the Fibonacci sequence, each iteration prints the bvalue of to the test log and updates the values ​​of aand bto generate the next number.
  4. func TestExchange(t *testing.T) { ... }: Another test function "TestExchange" is defined, which is used to test the logic of variable exchange.

    • Inside the test function, two integer variables aand are declared band initialized to 1 and 2 respectively.
    • The use of comments shows a way of writing variable exchange (through intermediate variables), but it is actually commented out. Then use a, b = b, athis line of code to implement the exchange of aand b, which is a unique exchange method in the Go language and does not require additional intermediate variables.
    • Use to t.Log(a, b)print the swapped variable values ​​to the test log.

2.Constant

Prerequisite: Create a constant in the chapter2 directory. The learning summary is as follows:

  1. Constant declaration: Use constkeywords to declare a constant, for example: const pi = 3.14159.
  2. Constant assignment: The value of a constant must be assigned when it is declared. Once assigned, it cannot be modified.
  3. Enumeration constants: An enumeration can be simulated using a set of constants, for example:
    const (
        Monday = 1
        Tuesday = 2
        // ...
    )
  4. Type specification: The type of the constant can also be specified, for example: const speed int = 300000.
  5. Constant expressions: Constants can be evaluated using expressions, for example: const secondsInHour = 60 * 60.
  6. Untyped constants: Constants can be untyped, with the type automatically inferred based on context. For example, const x = 5an integer type will be inferred.

Create a new constant_test.go and write the code as follows:

package constant

import "testing"

const (
	Monday = 1 + iota
	Tuesday
	Wednesday
)

const (
	Readable = 1 << iota
	Writable
	Executable
)

func TestConstant1(t *testing.T) {
	t.Log(Monday, Tuesday)
}

func TestConstant2(t *testing.T) {
	a := 1 //0001
	t.Log(a&Readable == Readable, a&Writable == Writable, a&Executable == Executable)
}

The knowledge points involved in the code are explained one by one below:

  1. package constant: declares a package named "constant", which is a package name used for testing.

  2. import "testing": Imported the Go language testing framework "testing" package for writing and running test functions.

  3. const (...): Two constant blocks are defined.

    • In the first constant block, a constant generator is used iotato define a series of constants starting from 1 and increasing. In this example, Mondayis assigned a value of 1, Tuesdayis assigned a value of 2, and Wednesdayis assigned a value of 3. iotaIt is incremented each time it is used in a constant block, so subsequent constants will be incremented in sequence.

    • In the second constant block, is used iotato define a series of bitwise left-shifted constants. In this example, Readableis assigned the value 1, Writableis assigned the value 2 (10 in binary), and Executableis assigned the value 4 (100 in binary). In bitwise operations, the left shift operation can move a binary number to the left by a specified number of digits.

  4. func TestConstant1(t *testing.T) { ... }: Defines a test function "TestConstant1" for testing the constants defined in the first constant block.

    • Use to t.Log(Monday, Tuesday)print the values ​​of constants Mondayand Tuesdayto the test log.
  5. func TestConstant2(t *testing.T) { ... }: Defines another test function "TestConstant2" for testing bit operations and the use of constants.

    • Inside the test function, an integer variable is declared aand initialized to 1, which is 0001 in binary.
    • Use bitwise operations and bitwise AND operations to check awhether a variable has the Readable, Writableand Executableproperties. For example, a&Readable == Readablethe expression checks awhether the binary representation of contains Readablethe flag bit.
    • Use to t.Log()print the results of the three expressions to the test log.

3.Data type

Prerequisite: Create type in the chapter2 directory. The learning summary is as follows:

Description of main data types

The Go language has a rich set of built-in data types that are used to represent different types of values ​​and data. The following is a summary analysis of some major data types in Go language:

  1. Integer Types: Go language provides integer types of different sizes, such as int, int8, int16, int32and int64. Unsigned integer types include uint, uint8, uint16, uint32and uint64. The size of the integer type depends on the computer's architecture, such as 32-bit or 64-bit.

  2. Floating-Point Types: Go language provides float32two float64floating-point types, corresponding to single-precision and double-precision floating-point numbers respectively.

  3. Complex Types: Go language provides complex64two complex128complex types, corresponding to complex numbers composed of two floating point numbers.

  4. Boolean Type: The Boolean type is used to represent true ( true) and false ( false) values ​​and is used for conditional judgment and logical operations.

  5. String Type: String type represents a sequence of characters. Strings are immutable and can be defined using double quotes "or backticks .`

  6. Character type (Rune Type): Character type runeis used to represent Unicode characters, which is an alias of int32. Single quotes are usually used 'to represent characters, such as 'A'.

  7. Array Types: An array is a collection of elements of the same type with a fixed size. When declaring an array, you need to specify the element type and size.

  8. Slice Types: A slice is a layer of encapsulation of an array and a variable sequence of dynamic length. Slices don't store elements, they just reference a portion of the underlying array.

  9. Map Types: Maps are unordered collections of key-value pairs used to store and retrieve data. Keys and values ​​can be of any type, but keys must be comparable.

  10. Struct Types: A structure is a user-defined composite data type that can contain fields of different types. Each field has a name and type.

  11. Interface Types: An interface is an abstract type that defines a set of methods. The set of methods of a type that implements an interface implements the interface.

  12. Function Types: Function types represent the signature of a function, including parameter and return value types. Functions can be passed as parameters and returned.

  13. Channel Types: Channels are a mechanism for communication and synchronization between coroutines. Channels have send and receive operations.

  14. Pointer Types: Pointer types represent the memory address of variables. The value of a variable can be directly accessed and modified through a pointer.

The data types of Go language have clear syntax and semantics and support rich built-in functions. Proper selection and use of different data types can improve program efficiency and readability.

Specific code expansion analysis

package main

import "fmt"

type Person struct {
	FirstName string
	LastName  string
	Age       int
}

type Shape interface {
	Area() float64
}

type Circle struct {
	Radius float64
}

func (c Circle) Area() float64 {
	return 3.14 * c.Radius * c.Radius
}

func add(a, b int) int {
	return a + b
}

func subtract(a, b int) int {
	return a - b
}

type Operation func(int, int) int

func main() {
	fmt.Println("整数类型(Integer Types)")
	var x int = 10
	var y int64 = 100

	fmt.Println(x)
	fmt.Println(y)

	fmt.Println("浮点数类型(Floating-Point Types)")
	var a float32 = 3.14
	var b float64 = 3.14159265359

	fmt.Println(a)
	fmt.Println(b)

	fmt.Println("布尔类型(Boolean Type)")
	var isTrue bool = true
	var isFalse bool = false

	fmt.Println(isTrue)
	fmt.Println(isFalse)

	fmt.Println("字符串类型(String Type)")
	str1 := "Hello, "
	str2 := "Go!"

	concatenated := str1 + str2
	fmt.Println(concatenated)

	fmt.Println("切片类型(Slice Types)")
	numbers := []int{1, 2, 3, 4, 5}
	fmt.Println(numbers)

	// 修改切片元素
	numbers[0] = 10
	fmt.Println(numbers)

	// 切片操作
	subSlice := numbers[1:4]
	fmt.Println(subSlice)

	fmt.Println("映射类型(Map Types)")
	ages := map[string]int{
		"Alice": 25,
		"Bob":   30,
		"Eve":   28,
	}

	fmt.Println(ages)
	fmt.Println("Alice's age:", ages["Alice"])

	// 添加新的键值对
	ages["Charlie"] = 22
	fmt.Println(ages)

	fmt.Println("结构体类型(Struct Types)")
	person := Person{
		FirstName: "John",
		LastName:  "Doe",
		Age:       30,
	}

	fmt.Println(person)
	fmt.Println("Name:", person.FirstName, person.LastName)

	fmt.Println("接口类型(Interface Types)")
	var shape Shape
	circle := Circle{Radius: 5}

	shape = circle
	fmt.Println("Circle Area:", shape.Area())

	fmt.Println("函数类型(Function Types)")
	var op Operation
	op = add
	result := op(10, 5)
	fmt.Println("Addition:", result)

	op = subtract
	result = op(10, 5)
	fmt.Println("Subtraction:", result)

	fmt.Println("通道类型(Channel Types)")
	messages := make(chan string)

	go func() {
		messages <- "Hello, Go!"
	}()

	msg := <-messages
	fmt.Println(msg)

	fmt.Println("指针类型(Pointer Types)")
	x = 10
	var ptr *int
	ptr = &x

	fmt.Println("Value of x:", x)
	fmt.Println("Value stored in pointer:", *ptr)

	*ptr = 20
	fmt.Println("Updated value of x:", x)
}

The knowledge points involved in the code are explained one by one below:

  1. type Person struct { ... }: Defines a structure type Personto represent a person's information, including FirstName, LastNameand Agefields.

  2. type Shape interface { ... }: Defines an interface type Shapethat requires the implementation of a method Area()that returns a float64type.

  3. type Circle struct { ... }: Defines a structure type Circlerepresenting the radius of a circle.

    func (c Circle) Area() float64 { ... }: CircleImplements the method Shapeof the interface for the type Area(), which is used to calculate the area of ​​a circle.
  4. func add(a, b int) int { ... }: Defines a function addthat performs integer addition operations.

  5. func subtract(a, b int) int { ... }: Defines a function subtractthat performs integer subtraction.

  6. type Operation func(int, int) int: Defines a function type Operationthat accepts two integer parameters and returns an integer result.

  7. main() { ... }: Entry function of the program.

  • Several different types of variables are defined, including integers, floats, booleans, strings, slices, maps, structures, interfaces, functions, channels, and pointer types.
  • Demonstrates the initialization, assignment, access and basic operations of different types of variables.
  • Extract partial slices using the slice operation.
  • Demonstrates the use of mappings, including adding new key-value pairs and accessing key-value pairs.
  • Demonstrates the definition and initialization of structures, and accesses structure fields.
  • Demonstrates the use of interfaces, Circleassigning types to Shapetype variables, and calling interface methods.
  • Demonstrates the definition and use of function types, assigns different functions to Operationtype variables, and calls them.
  • Use channels to implement concurrent communication, sending and receiving messages in goroutines through anonymous functions.
  • Demonstrates the use of pointers, including operations such as creating pointer variables and modifying the value of variables through pointers.

Type conversion instructions in Go language

The Go language supports type conversion, but there are some rules and restrictions to be aware of. Type conversion is used to convert a value of one data type to another data type for use in a different context. Here is some important information about type conversion in Go language:

  1. Conversion between basic types: Conversion between basic data types is possible, but attention must be paid to type compatibility and possible data loss. For example, a conversion from intto float64is safe, but a conversion float64from to intmay result in the decimal part being truncated.

  2. Explicit casts: In Go, casts are used to explicitly specify the conversion of one value to another type. The syntax is: destinationType(expression). For example: float64(10).

  3. Conversion between incompatible types: The compiler does not automatically convert incompatible types. For example, you cannot directly convert a stringtype to inta type.

  4. Type alias conversion: If there is a type alias (Type Alias), you need to pay attention to the compatibility of the alias when converting.

Here are some examples to demonstrate type conversion:

package main

import "fmt"

func main() {
	// 显式类型转换
	var x int = 10
	var y float64 = float64(x)
	fmt.Println(y)

	// 类型别名的转换
	type Celsius float64
	type Fahrenheit float64
	c := Celsius(25)
	f := Fahrenheit(c*9/5 + 32)
	fmt.Println(f)
}

4.Operator

Prerequisite: Create an operator in the chapter2 directory. The learning summary is as follows:

In fact, this part is similar to other languages. I personally feel that there is nothing to review and consolidate. The Go language supports a variety of operators for performing various arithmetic, logical and comparison operations.

regular operators

The following are some common operators and their usage and knowledge points in Go:

Arithmetic Operators:

  • +:addition
  • -:Subtraction
  • *:multiplication
  • /:division
  • %: Modulo (remainder)

Assignment Operators:

  • =: Assignment
  • +=: Addition assignment
  • -=: subtraction assignment
  • *=:Multiplication assignment
  • /=: Division assignment
  • %=: Modulo assignment

Logical Operators:

  • &&: Logical AND (AND)
  • ||: Logical OR (OR)
  • !: Logical NOT (NOT)

Comparison Operators:

  • ==:equal
  • !=:not equal to
  • <: less than
  • >:more than the
  • <=: less than or equal to
  • >=:greater or equal to

Bitwise Operators:

  • &: Bitwise AND (AND)
  • |: Bitwise OR (OR)
  • ^: Bitwise exclusive OR (XOR)
  • <<: Shift left
  • >>:Move right

Other operators:

  • &:Address operator
  • *: pointer operator
  • ++:increment operator
  • --: Decrement operator

When using operators, there are a few things to consider:

  • The operands of an operator must match the operator's expected type.
  • Some operators have higher precedence and require the use of parentheses to clarify precedence.
  • The operands of operators can be variables, constants, expressions, etc.

Create a new operator_test.go. Here are some examples to show the use of operators:

package operator

import (
	"fmt"
	"testing"
)

const (
	Readable = 1 << iota
	Writable
	Executable
)

func TestOperatorBasic(t *testing.T) {
	// 算术运算符
	a := 10
	b := 5
	fmt.Println("Sum:", a+b)
	fmt.Println("Difference:", a-b)
	fmt.Println("Product:", a*b)
	fmt.Println("Quotient:", a/b)
	fmt.Println("Remainder:", a%b)

	// 逻辑运算符
	x := true
	y := false
	fmt.Println("AND:", x && y)
	fmt.Println("OR:", x || y)
	fmt.Println("NOT:", !x)

	// 比较运算符
	fmt.Println("Equal:", a == b)
	fmt.Println("Not Equal:", a != b)
	fmt.Println("Greater Than:", a > b)
	fmt.Println("Less Than:", a < b)
	fmt.Println("Greater Than or Equal:", a >= b)
	fmt.Println("Less Than or Equal:", a <= b)
}

func TestCompareArray(t *testing.T) {
	a := [...]int{1, 2, 3, 4}
	b := [...]int{1, 3, 2, 4}
	//	c := [...]int{1, 2, 3, 4, 5}
	d := [...]int{1, 2, 3, 4}
	t.Log(a == b)
	//t.Log(a == c)
	t.Log(a == d)
}

func TestBitClear(t *testing.T) {
	a := 7 //0111
	a = a &^ Readable
	a = a &^ Executable
	t.Log(a&Readable == Readable, a&Writable == Writable, a&Executable == Executable)
}

The knowledge points involved in the code are explained one by one below:

  1. const (...): Defines three constants Readable, sum, Writableand Executableuses displacement operations to generate different values.

  2. func TestOperatorBasic(t *testing.T) { ... }: Defines a test function "TestOperatorBasic" for testing the use of basic operators.

    • Arithmetic Operators: Shows addition, subtraction, multiplication, division and remainder operations.
    • Logical Operators: Demonstrates the logical AND, logical OR and logical NOT operations.
    • Comparison Operators: Shows the equal, not equal, greater than, less than, greater than or equal to, and less than or equal to operations.
  3. func TestCompareArray(t *testing.T) { ... }: Defines a test function "TestCompareArray" for testing array comparison.

    • aTwo integer arrays and are declared b, as well as another array dwhere the contents of array aand array dare identical.
    • Use the comparison operator ==to check whether arrays aand bare equal, and whether arrays aand dare equal.
  4. func TestBitClear(t *testing.T) { ... }: Defines a test function "TestBitClear" for testing bit clearing operations.

    • Declare an integer variable aand initialize it to 7, i.e. the binary representation 0111.
    • Use the bit clear operation &^to clear the and bits ain .ReadableExecutable
    • Use the bitwise AND operation &to check awhether has the Readable, Writableand Executableproperties.

Bitwise clear operator &^

In the Go language, &^it is the Bit Clear Operator. It is used to clear the bits at certain positions, that is, set the bits at the specified position to 0. &^Operators are very useful when dealing with binary bit operations.

&^Operators perform the following operations:

  1. For each bit, if the corresponding bit of the right-hand operand is 0, the result bit is the same as the left-hand operand.
  2. For each bit, if the corresponding bit in the right-hand operand is 1, the result bit is forced to 0.

This means that &^the operator is used to "clear" a specific bit of the left operand so that the corresponding bit of the right operand is unaffected. Write a code to verify:

func TestOther(t *testing.T) {
	var a uint8 = 0b11001100 // 二进制表示,十进制为 204
	var b uint8 = 0b00110011 // 二进制表示,十进制为 51

	result := a &^ b

	fmt.Printf("a: %08b\n", a)               // 输出:11001100
	fmt.Printf("b: %08b\n", b)               // 输出:00110011
	fmt.Printf("Result: %08b\n", result)     // 输出:11000000
	fmt.Println("Result (Decimal):", result) // 输出:192
}

5. Conditional Statements

Prerequisite: Create condition in the chapter2 directory. The learning summary is as follows:

ifstatement

ifStatements are used to decide whether to execute a certain piece of code based on a condition. Its basic syntax is as follows:

if condition {
    // 代码块
} else if anotherCondition {
    // 代码块
} else {
    // 代码块
}

switchstatement

switchStatements are used to execute different branches of code based on different values ​​of an expression. Unlike other languages, Go switchcan automatically match the first branch that satisfies the condition without using breaka statement. Its syntax is as follows:

switch expression {
case value1:
    // 代码块
case value2:
    // 代码块
default:
    // 代码块
}

Create condition_test.go for verification analysis. The specific code is as follows:

package condition

import (
	"fmt"
	"testing"
)

func TestConditionIf(t *testing.T) {
	age := 18

	if age < 18 {
		fmt.Println("You are a minor.")
	} else if age >= 18 && age < 60 {
		fmt.Println("You are an adult.")
	} else {
		fmt.Println("You are a senior citizen.")
	}
}

func TestConditionSwitch(t *testing.T) {
	dayOfWeek := 3

	switch dayOfWeek {
	case 1:
		fmt.Println("Monday")
	case 2:
		fmt.Println("Tuesday")
	case 3:
		fmt.Println("Wednesday")
	case 4:
		fmt.Println("Thursday")
	case 5:
		fmt.Println("Friday")
	default:
		fmt.Println("Weekend")
	}
}

func TestSwitchMultiCase(t *testing.T) {
	for i := 0; i < 5; i++ {
		switch i {
		case 0, 2:
			t.Logf("%d is Even", i)
		case 1, 3:
			t.Logf("%d is Odd", i)
		default:
			t.Logf("%d is not 0-3", i)
		}
	}
}

func TestSwitchCaseCondition(t *testing.T) {
	for i := 0; i < 5; i++ {
		switch {
		case i%2 == 0:
			t.Logf("%d is Even", i)
		case i%2 == 1:
			t.Logf("%d is Odd", i)
		default:
			t.Logf("%d is unknow", i)
		}
	}
}

The content of each test function is explained one by one below:

  1. func TestConditionIf(t *testing.T) { ... }: Test ifthe use of statements.

    According to different circumstances of age, it is judged whether it is a minor, an adult or an elderly person through ifthe, else ifand branches.else
  2. func TestConditionSwitch(t *testing.T) { ... }: Test switchthe use of statements. According to dayOfWeekthe value of , use switchthe statement to output the corresponding day of the week.

  3. func TestSwitchMultiCase(t *testing.T) { ... }: Test the case where switchthe statement has multiple casevalues. Use switchthe statement to determine the parity of each number and output the corresponding information.

  4. func TestSwitchCaseCondition(t *testing.T) { ... }: Test switchthe conditional expression in the statement. Use switchthe statement to determine the parity of a number by taking the remainder of the number and output the corresponding information.

These test functions demonstrate different uses of conditional statements in Go language, including condition-based branch judgment and caseprocessing of multiple values, as well as switchthe use of conditional expressions in statements.

6. Loop Statements

Prerequisite: Create a loop in the chapter2 directory. The learning summary is as follows:

forcycle

forLoops are used to repeatedly execute blocks of code and support initialization statements, loop conditions, and statements after the loop. Its basic form is as follows:

for initialization; condition; post {
    // 代码块
}

In the initialization statement, you initialize the loop variables, then use conditions within the loop body to control the loop, and finally postperform increment or decrement operations in the statement.

forSimplified form of loop

Go language loops can also be simplified to only the loop condition part, similar to loops forin other languages :while

for condition {
    // 代码块
}

rangecycle

rangeLoops are used to iterate over iterable data structures such as arrays, slices, maps, and strings. It returns the index and value of each iteration. Example:

for index, value := range iterable {
    // 使用 index 和 value
}

Create loop_test.go for verification analysis. The specific code is as follows:

package loop

import (
	"fmt"
	"testing"
)

func TestLoopFor(t *testing.T) {
	for i := 1; i <= 5; i++ {
		fmt.Println("Iteration:", i)
	}
}

func TestLoopForBasic(t *testing.T) {
	i := 1
	for i <= 5 {
		fmt.Println("Iteration:", i)
		i++
	}
}

func TestLoopForRange(t *testing.T) {
	numbers := []int{1, 2, 3, 4, 5}

	for index, value := range numbers {
		fmt.Printf("Index: %d, Value: %d\n", index, value)
	}
}

func TestLoopForUnLimit(t *testing.T) {
	i := 1
	for {
		fmt.Println("Iteration:", i)
		i++
		if i > 5 {
			break
		}
	}
}

The content of each test function is explained one by one below:

  1. func TestLoopFor(t *testing.T) { ... }: Test basic forloops. Using fora loop, iterate from 1 to 5 to output the number of loop iterations.

  2. func TestLoopForBasic(t *testing.T) { ... }: Test a loop without initialization statements for. Using fora loop, iterate from 1 to 5 to output the number of loop iterations, but there is no initialization statement declared at the head of the loop.

  3. func TestLoopForRange(t *testing.T) { ... }:Test using for rangeiterative slicing. Define an integer slice numbers, use for rangea loop to iterate over each element in the slice, and output the index and value of the element.

  4. func TestLoopForUnLimit(t *testing.T) { ... }: Test infinite loops and breakstatements. Use an infinite loop and breakstatement to determine whether to terminate the loop inside the loop body, and iexit the loop when it is greater than 5.

These test functions demonstrate forthe usage of different types of loops in the Go language, including standard counting loops, loops without initialization statements, traversing slices, and infinite loops and loop termination conditions.

7. Jump Statements

Prerequisite: Create a jump in the chapter2 directory. The learning summary is as follows:

The Go language also supports several jump statements for controlling flow in loops and conditions:

  • break: Jump out of the loop.
  • continue: Skip this loop iteration and continue with the next iteration.
  • goto: Jump directly to the specified tag in the code (not recommended) .

Create jump_test.go for verification analysis. The specific code is as follows:

package jump

import (
	"fmt"
	"testing"
)

func TestJumpBreak(t *testing.T) {
	for i := 1; i <= 5; i++ {
		if i == 3 {
			break
		}
		fmt.Println("Iteration:", i)
	}
}

func TestJumpContinue(t *testing.T) {
	for i := 1; i <= 5; i++ {
		if i == 3 {
			continue
		}
		fmt.Println("Iteration:", i)
	}
}

func TestJumpGoto(t *testing.T) {
	i := 1

start:
	fmt.Println("Iteration:", i)
	i++
	if i <= 5 {
		goto start
	}
}

The content of each test function is explained one by one below:

  1. func TestJumpBreak(t *testing.T) { ... }: Test breakthe use of statements. Use fora loop to iterate from 1 to 5, but iwhen the iteration variable equals 3, use breakthe statement to terminate the loop.

  2. func TestJumpContinue(t *testing.T) { ... }: Test continuethe use of statements. Use forthe loop to iterate from 1 to 5, but when the iteration variable iequals 3, use continuethe statement to skip this iteration and continue to the next iteration.

  3. func TestJumpGoto(t *testing.T) { ... }: Test gotothe use of statements. An infinite loop is implemented using gotothe statement, which uses labels startand goto startto jump to the starting position of the loop inside the loop body. The termination condition of the loop is when iis greater than 5.

These test functions demonstrate loop control jump statements in the Go language, including statements for terminating loops break, skipping the current iteration continue, and infinite loops goto.

(3) Commonly used collections and strings

Create chapter3 in the src directory. In the Go language, a set is a data structure that stores a set of values. Commonly used collection types include arrays, slices, maps, and channels.

1.Array

Prerequisite: Create an array in the chapter3 directory. The learning summary is as follows:

An array in Go language is a collection of fixed-length, same-type elements. The following is a summary and application of knowledge about Go arrays:

Characteristics of arrays

  • The length of an array is specified at declaration time and cannot be changed after creation.
  • Arrays are value types, and when an array is assigned to a new variable or passed as a parameter, a new copy is created.
  • Arrays are stored continuously in memory and support random access.

Array declaration and initialization

var arrayName [size]dataType
  • arrayName: The name of the array.
  • size: The length of the array, must be a constant expression.
  • dataType: The type of elements stored in the array.

How to initialize an array

// 使用指定的值初始化数组
var arr = [5]int{1, 2, 3, 4, 5}

// 根据索引初始化数组
var arr [5]int
arr[0] = 10
arr[1] = 20

// 部分初始化
var arr = [5]int{1, 2}

// 自动推断数组长度
arr := [...]int{1, 2, 3, 4, 5}

Array access and traversal

// 访问单个元素
value := arr[index]

// 遍历数组
for index, value := range arr {
    fmt.Printf("Index: %d, Value: %d\n", index, value)
}

Array as function parameter

A copy of the array is created when the function parameter is passed, so modifications to the array within the function will not affect the original array. If you need to modify the original array within the function, you can pass a pointer to the array.

func modifyArray(arr [5]int) {
    arr[0] = 100
}

func modifyArrayByPointer(arr *[5]int) {
    arr[0] = 100
}

Multidimensional Arrays

Go language supports multi-dimensional arrays, such as two-dimensional arrays and three-dimensional arrays. The initialization and access of multidimensional arrays are similar to those of one-dimensional arrays, except that multiple indexes need to be specified.

var matrix [3][3]int = [3][3]int{
    {1, 2, 3},
    {4, 5, 6},
    {7, 8, 9},
}

Arrays are very useful when storing a fixed number of elements of the same type, but due to their fixed length limitations, slices are usually more commonly used in actual development, which have the characteristics of dynamic length. Slices can be added, deleted, and reallocated as needed, making them more flexible.

Create array_test.go for verification analysis. The specific code is as follows:

package array

import "testing"

func TestArrayInit(t *testing.T) {
	var arr [3]int
	arr1 := [4]int{1, 2, 3, 4}
	arr3 := [...]int{1, 3, 4, 5}
	arr1[1] = 5
	t.Log(arr[1], arr[2])
	t.Log(arr1, arr3)
}

func TestArrayTravel(t *testing.T) {
	arr3 := [...]int{1, 3, 4, 5}
	for i := 0; i < len(arr3); i++ {
		t.Log(arr3[i])
	}
	for _, e := range arr3 {
		t.Log(e)
	}
}

func TestArraySection(t *testing.T) {
	arr3 := [...]int{1, 2, 3, 4, 5}
	arr3_sec := arr3[:]
	t.Log(arr3_sec)
}

The content of each test function is explained one by one below:

  1. func TestArrayInit(t *testing.T) { ... }: Test the initialization of the array.

    • Use different ways to initialize arrays arr, arr1and arr3.
    • Modify arr1the second element of 5.
    • Use to t.Log()output the element values ​​and contents of different arrays.
  2. func TestArrayTravel(t *testing.T) { ... }: Test array traversal.

    • Use forto loop through the array arr3and output the value of each element separately.
    • Use for rangeto loop through the array arr3and also output the value of each element.
  3. func TestArraySection(t *testing.T) { ... }: Test the use of array slicing.

    • Creates an array slice arr3_secbased on the entire array arr3.
    • Use t.Log()output array slice arr3_seccontents.

2. Slice

Prerequisite: Create slice in the chapter3 directory. The learning summary is as follows:

Slice in Go language is a layer of encapsulation of arrays, providing a more flexible dynamic length sequence. The following is a summary of knowledge and applications about slicing:

Characteristics of slices

  • A slice is a reference type, it does not store data, it just refers to a part of the underlying array.
  • Slices are of dynamic length and can be expanded or reduced as needed.
  • Slices are indexable and can be cut by slice index.

Declaration and initialization of slices

var sliceName []elementType

How to initialize slices

// 声明切片并初始化
var slice = []int{1, 2, 3, 4, 5}

// 使用 make 函数创建切片
var slice = make([]int, 5) // 创建长度为 5 的 int 类型切片

// 使用切片切割已有数组或切片
newSlice := oldSlice[startIndex:endIndex] // 包括 startIndex,但不包括 endIndex

Built-in functions and operations for slicing

  • len(slice): Returns the length of the slice.
  • cap(slice): Returns the capacity of the slice, which is the length of the underlying array.
  • append(slice, element):Append elements to the end of the slice and return the new slice.
  • copy(destination, source): Copies elements from the source slice to the target slice.

Slice traversal

for index, value := range slice {
    // 使用 index 和 value
}

Slices as function parameters

When a slice is passed as a parameter to a function, modifications to the slice within the function will affect the original slice.

func modifySlice(s []int) {
    s[0] = 100
}

func main() {
    numbers := []int{1, 2, 3, 4, 5}
    modifySlice(numbers)
    fmt.Println(numbers) // 输出:[100 2 3 4 5]
}

Slicing is widely used in Go language to handle dynamic data sets, such as collections, lists, queues, etc. It provides convenient ways to manage elements while avoiding the limitations of fixed arrays. In practical applications, slices are often used to store and process variable-length data.

Create slice_test.go for verification analysis. The specific code is as follows:

package slice

import (
	"fmt"
	"testing"
)

func TestSlice(t *testing.T) {
	// 声明和初始化切片
	numbers := []int{1, 2, 3, 4, 5}
	fmt.Println("Original Slice:", numbers)

	// 使用 make 函数创建切片
	slice := make([]int, 3)
	fmt.Println("Initial Make Slice:", slice)

	// 添加元素到切片
	slice = append(slice, 10)
	slice = append(slice, 20, 30)
	fmt.Println("After Append:", slice)

	// 复制切片
	copySlice := make([]int, len(slice))
	copy(copySlice, slice)
	fmt.Println("Copied Slice:", copySlice)

	// 切片切割
	subSlice := numbers[1:3]
	fmt.Println("Subslice:", subSlice)

	// 修改切片的值会影响底层数组和其他切片
	subSlice[0] = 100
	fmt.Println("Modified Subslice:", subSlice)
	fmt.Println("Original Slice:", numbers)
	fmt.Println("Copied Slice:", copySlice)

	// 遍历切片
	for index, value := range slice {
		fmt.Printf("Index: %d, Value: %d\n", index, value)
	}
}

The content of each test function is explained one by one below:

func TestSlice(t *testing.T) { ... }: Test the basic operations of slicing.

  • Declare and initialize the slice numbers, and output the initial slice content.
  • Use makethe function to create 3a slice with an initial capacity of sliceand output the initial slice content.
  • Use a function to add elements appendto a slice .slice
  • Use copythe function to copy a slice sliceto a new slice copySlice.
  • Use Slice numbersto make slice cuts and create sub-slices subSlice.
  • subSliceThe first element of modification is 100, output the modified slice and the original slice, as well as the copied slice.
  • Use for rangea loop to loop through the slice slice, outputting the index and value of each element.

This test function shows various operations of slices in Go language, including slice creation, adding elements, copying slices, cutting slices, modifying slice elements, etc.

3.Map

Prerequisite: Create a map in the chapter3 directory. The learning summary is as follows:

A map in Go language is an unordered collection of key-value pairs, also known as an associative array or dictionary. The following is a summary of knowledge and applications about mapping:

Mapping characteristics

  • A map is used to store a set of key-value pairs, where each key is unique.
  • The map is unordered and the order of key-value pairs is not guaranteed.
  • Keys can be of any comparable type, and values ​​can be of any type.
  • Maps are reference types that can be assigned and passed to functions.

Declaration and initialization of mapping

var mapName map[keyType]valueType

How to initialize mapping

// 声明和初始化映射
var ages = map[string]int{
    "Alice": 25,
    "Bob":   30,
    "Eve":   28,
}

// 使用 make 函数创建映射
var ages = make(map[string]int)

Mapping operations

  • Add key-value pairs:ages["Charlie"] = 35
  • Delete key-value pairs:delete(ages, "Eve")
  • Get value:value := ages["Alice"]

Mapping traversal

for key, value := range ages {
    fmt.Printf("Name: %s, Age: %d\n", key, value)
}

Mapping as function parameter

When a mapping is passed as a parameter to a function, modifications to the mapping within the function will affect the original mapping.

func modifyMap(m map[string]int) {
    m["Alice"] = 30
}

func main() {
    ages := map[string]int{
        "Alice": 25,
        "Bob":   30,
    }
    modifyMap(ages)
    fmt.Println(ages) // 输出:map[Alice:30 Bob:30]
}

Map is a very commonly used data structure in Go language for storing and retrieving data. It is very useful when storing a set of associated key-value pairs, such as the correspondence between name and age, the correspondence between words and definitions, etc. In practical applications, mapping is an important tool for processing and storing key-value data.

Create map_test.go for verification analysis. The specific code is as follows:

package my_map

import (
	"fmt"
	"testing"
)

func TestBasic(t *testing.T) {
	// 声明和初始化映射
	ages := map[string]int{
		"Alice": 25,
		"Bob":   30,
		"Eve":   28,
	}
	fmt.Println("Original Map:", ages)

	// 添加新的键值对
	ages["Charlie"] = 35
	fmt.Println("After Adding:", ages)

	// 修改已有键的值
	ages["Bob"] = 31
	fmt.Println("After Modification:", ages)

	// 删除键值对
	delete(ages, "Eve")
	fmt.Println("After Deletion:", ages)

	// 获取值和检查键是否存在
	age, exists := ages["Alice"]
	if exists {
		fmt.Println("Alice's Age:", age)
	} else {
		fmt.Println("Alice not found")
	}

	// 遍历映射
	for name, age := range ages {
		fmt.Printf("Name: %s, Age: %d\n", name, age)
	}
}

type Student struct {
	Name  string
	Age   int
	Grade string
}

func TestComplex(t *testing.T) {
	// 声明和初始化映射,用于存储学生信息和成绩
	studentScores := make(map[string]int)
	studentInfo := make(map[string]Student)

	// 添加学生信息和成绩
	studentInfo["Alice"] = Student{Name: "Alice", Age: 18, Grade: "A"}
	studentScores["Alice"] = 95

	studentInfo["Bob"] = Student{Name: "Bob", Age: 19, Grade: "B"}
	studentScores["Bob"] = 85

	// 查找学生信息和成绩
	aliceInfo := studentInfo["Alice"]
	aliceScore := studentScores["Alice"]
	fmt.Printf("Name: %s, Age: %d, Grade: %s, Score: %d\n", aliceInfo.Name, aliceInfo.Age, aliceInfo.Grade, aliceScore)

	// 遍历学生信息和成绩
	for name, info := range studentInfo {
		score, exists := studentScores[name]
		if exists {
			fmt.Printf("Name: %s, Age: %d, Grade: %s, Score: %d\n", info.Name, info.Age, info.Grade, score)
		} else {
			fmt.Printf("No score available for %s\n", name)
		}
	}
}

The content of each test function is explained one by one below:

  1. func TestBasic(t *testing.T) { ... }: Basic operations for testing mapping.

    • Declare and initialize the map agesto store key-value pairs of person's name and age.
    • Output the initial mapping content.
    • Use ages["Charlie"]to add new key-value pairs.
    • Use to ages["Bob"]modify the value of an existing key.
    • Use deletethe function to delete key-value pairs.
    • Use age, existsto get the value and check if the key exists.
    • Use for rangea loop to traverse the map and output the information for each key-value pair.
  2. type Student struct { ... }: Defines a Studentstructure named to store student information.

  3. func TestComplex(t *testing.T) { ... }: Test mapping operations containing complex values.

    • Declare and initialize two mappings, studentScoresused to store student scores and studentInfostudent information.
    • Add student information and scores to the mapping.
    • Use studentInfo["Alice"]to get student information and use studentScores["Alice"]to get student scores.
    • Use for rangea loop to loop through the map and output each student's information and score.

These test functions demonstrate various operations of mapping in the Go language, including creating, adding, modifying, deleting key-value pairs, checking whether a key exists, and traversing mapped key-value pairs.

4. Implement Set

Prerequisite: Create a set in the chapter3 directory. The learning summary is as follows:

In the Go language, although the standard library does not provide a built-in Set type, you can use a variety of ways to implement the Set function. The following are several common ways to implement Set:

Use slices

Create set_slice_test.go exercise

Use slices to store elements and check whether the elements exist by traversing the slice. This is a simple implementation that works well for small collections.

package set

import (
	"fmt"
	"testing"
)

type IntSet struct {
	elements []int
}

func (s *IntSet) Add(element int) {
	if !s.Contains(element) {
		s.elements = append(s.elements, element)
	}
}

func (s *IntSet) Contains(element int) bool {
	for _, e := range s.elements {
		if e == element {
			return true
		}
	}
	return false
}

func TestSet(t *testing.T) {
	set := IntSet{}
	set.Add(1)
	set.Add(2)
	set.Add(3)
	set.Add(2) // Adding duplicate, should be ignored

	fmt.Println("Set:", set.elements) // Output: [1 2 3]
}

Use mapping

Create set_map_test.go exercise

Use a map to store elements. The keys of the map represent the elements of the collection, and the values ​​can be of any type. This implementation is faster and suitable for large collections because the mapping lookup complexity is O(1) .

package set

import (
	"fmt"
	"testing"
)

type Set map[int]bool

func (s Set) Add(element int) {
	s[element] = true
}

func (s Set) Contains(element int) bool {
	return s[element]
}

func TestSetMap(t *testing.T) {
	set := make(Set)
	set.Add(1)
	set.Add(2)
	set.Add(3)
	set.Add(2) // Adding duplicate, should be ignored

	fmt.Println("Set:", set) // Output: map[1:true 2:true 3:true]
}

Use third-party libraries

Create set_third_test.go exercise

In order to avoid implementing it yourself, you can use some third-party libraries, for example github.com/deckarep/golang-set, which provide richer Set functions.

Add a proxy: go env -w GOPROXY=https://goproxy.io,direct

Then install the package: go get github.com/deckarep/golang-set

package set

import (
	"fmt"
	"github.com/deckarep/golang-set"
	"testing"
)

func TestSetThird(t *testing.T) {
	intSet := mapset.NewSet()
	intSet.Add(1)
	intSet.Add(2)
	intSet.Add(3)
	intSet.Add(2) // Adding duplicate, will be ignored

	fmt.Println("Set:", intSet) // Output: Set: Set{1, 2, 3}
}

The above are several ways to implement Set. You can choose the appropriate implementation method based on your needs and performance considerations. Third-party libraries can provide more functionality and performance optimizations for large-scale data collections.

5. String

Prerequisite: Create string in the chapter3 directory. The learning summary is as follows:

Declaration and initialization of strings

In the Go language, a string is composed of a series of characters. You can use double quotes "or backticks ``` to declare and initialize the string.

package main

import "fmt"

func main() {
    str1 := "Hello, World!"   // 使用双引号声明
    str2 := `Go Programming` // 使用反引号声明

    fmt.Println(str1) // Output: Hello, World!
    fmt.Println(str2) // Output: Go Programming
}

length of string

Use the built-in function len()to get the length of a string, that is, the number of characters in the string.

package main

import "fmt"

func main() {
    str := "Hello, 世界!"
    length := len(str)
    fmt.Println("String Length:", length) // Output: String Length: 9
}

Indexing and slicing of strings

Characters in a string can be accessed by index, which starts at 0. You can use the slicing operation to obtain substrings of a string.

package main

import "fmt"

func main() {
    str := "Hello, World!"

    // 获取第一个字符
    firstChar := str[0]
    fmt.Println("First Character:", string(firstChar)) // Output: First Character: H

    // 获取子串
    substring := str[7:12]
    fmt.Println("Substring:", substring) // Output: Substring: World
}

String concatenation

Use +the operator to concatenate two strings into a new string. In addition, strings.Jointhe function is used to concatenate string slices into a new string, which can be used to splice multiple strings.

Finally, using byte buffering allows for efficient string concatenation without making redundant string copies.

package main

import (
    "fmt"
    "strings"
    "bytes"
)

func main() {
    str1 := "Hello, "
    str2 := "World!"
    
    result := str1 + str2
    fmt.Println("Concatenated String:", result) // Output: Concatenated String: Hello, World!

    strSlice := []string{"Hello", " ", "World!"}
    result := strings.Join(strSlice, "")
    fmt.Println(result) // Output: Hello World!

    var buffer bytes.Buffer
    
    buffer.WriteString(str1)
    buffer.WriteString(str2)
    
    result := buffer.String()
    fmt.Println(result) // Output: Hello, World!
}

multiline string

Use backticks ``` to create multiline strings.

package main

import "fmt"

func main() {
    multiLineStr := `
        This is a
        multi-line
        string.
    `
    fmt.Println(multiLineStr)
}

String iteration

Use for rangea loop to iterate through each character of the string.

package main

import "fmt"

func main() {
    str := "Go语言"
    
    for _, char := range str {
        fmt.Printf("%c ", char) // Output: G o 语 言
    }
}

Conversion between string and byte array

In Go language, strings and byte arrays can be converted to and from each other.

package main

import "fmt"

func main() {
    str := "Hello"
    bytes := []byte(str) // 转换为字节数组
    strAgain := string(bytes) // 字节数组转换为字符串

    fmt.Println("Bytes:", bytes)       // Output: Bytes: [72 101 108 108 111]
    fmt.Println("String Again:", strAgain) // Output: String Again: Hello
}

String comparison

Strings can be compared using the ==and !=operators. Of course there are other function types that are directly applicable: strings.Comparefunctions that compare two strings and return an integer based on the comparison result.

You can also use custom comparison functions to compare strings and define comparison logic according to your own needs.

package main

import (
    "fmt"
    "strings"
)


func customCompare(str1, str2 string) bool {
    // 自定义比较逻辑
    return str1 == str2
}

func main() {
    str1 := "Hello"
    str2 := "World"

    if str1 == str2 {
        fmt.Println("Strings are equal")
    } else {
        fmt.Println("Strings are not equal") // Output: Strings are not equal
    }


    result := strings.Compare(str1, str2)
    if result == 0 {
        fmt.Println("Strings are equal")
    } else if result < 0 {
        fmt.Println("str1 is less than str2")
    } else {
        fmt.Println("str1 is greater than str2") // Output: str1 is less than str2
    }

    if customCompare(str1, str2) {
        fmt.Println("Strings are equal")
    } else {
        fmt.Println("Strings are not equal") // Output: Strings are not equal
    }
}

These basic concepts and operations can help you better understand and use strings in the Go language. Be aware of the immutability of strings, as well as conversions and comparisons with other data types.

Create string_test.go exercise

package string

import (
	"strconv"
	"strings"
	"testing"
)

func TestString(t *testing.T) {
	var s string
	t.Log(s) //初始化为默认零值“”
	s = "hello"
	t.Log(len(s))
	//s[1] = '3' //string是不可变的byte slice
	//s = "\xE4\xB8\xA5" //可以存储任何二进制数据
	s = "\xE4\xBA\xBB\xFF"
	t.Log(s)
	t.Log(len(s))
	s = "中"
	t.Log(len(s)) //是byte数

	c := []rune(s)
	t.Log(len(c))
	//	t.Log("rune size:", unsafe.Sizeof(c[0]))
	t.Logf("中 unicode %x", c[0])
	t.Logf("中 UTF8 %x", s)
}

func TestStringToRune(t *testing.T) {
	s := "中华人民共和国"
	for _, c := range s {
		t.Logf("%[1]c %[1]x", c)
	}
}

func TestStringFn(t *testing.T) {
	s := "A,B,C"
	parts := strings.Split(s, ",")
	for _, part := range parts {
		t.Log(part)
	}
	t.Log(strings.Join(parts, "-"))
}

func TestConv(t *testing.T) {
	s := strconv.Itoa(10)
	t.Log("str" + s)
	if i, err := strconv.Atoi("10"); err == nil {
		t.Log(10 + i)
	}
}

The content of each test function is explained one by one below:

  1. func TestString(t *testing.T) { ... }: Basic operations for testing strings.

    • Declare a string variable sthat outputs its default value of zero.
    • Assign the string value to "hello" and output the length of the string.
    • Trying to modify a character in the string will result in an error because strings are immutable.
    • Use strings to store binary data and Unicode encoding.
    • Use a string to store a Chinese character and output its length.
    • Convert the string to runetype slice, output the slice length and Unicode and UTF-8 encoding of Chinese characters.
  2. func TestStringToRune(t *testing.T) { ... }: Test string to runeconversion.

    Declare a string containing Chinese characters s, rangeconvert the string to runetype through traversal and output it.
  3. func TestStringFn(t *testing.T) { ... }: Test string-related functions.

    Declare a string containing comma delimiters s, use strings.Splitthe function to split the string and output each part. Use strings.Jointhe function to merge the split parts into a new string and output it.
  4. func TestConv(t *testing.T) { ... }: Test conversion of strings to other types.

    • Use strconv.Itoato convert an integer to a string.
    • Concatenate strings and integers and output the result.
    • Use strconv.Atoito convert strings to integers, perform addition, and handle error conditions.

These test functions demonstrate various operations on strings in the Go language, including string length, UTF-8 encoding, runetype conversion, string splitting and merging, and conversion of strings to other types.

(4) Function

Create chapter 4 in the src directory. In the Go language, a function is a code block used to perform a specific task and can be called multiple times. The following are some important knowledge points about Go language functions:

1. Function declaration

In Go, a function declaration funcbegins with the keyword followed by the function name, parameter list, return value, and function body.

func functionName(parameters) returnType {
    // 函数体
    // 可以包含多个语句
    return returnValue
}

2. Function parameters

Functions can have zero or more parameters, which consist of parameter names and parameter types. Use commas to separate parameters.

func greet(name string) {
    fmt.Printf("Hello, %s!\n", name)
}

3. Multiple return values

Go language functions can return multiple values. Return values ​​are enclosed in parentheses and separated by commas.

func divide(a, b float64) (float64, error) {
    if b == 0 {
        return 0, errors.New("division by zero")
    }
    return a / b, nil
}

4. Name the return value

Functions can declare named return values, and these names can be used directly for assignment within the function body. Finally, there is no need to explicitly use returnthe keyword.

func divide(a, b float64) (result float64, err error) {
    if b == 0 {
        err = errors.New("division by zero")
        return
    }
    result = a / b
    return
}

5. Variable number of parameters

The Go language supports the use of ...syntax to represent a variable number of parameters. These parameters are used as slices within the function body.

func sum(numbers ...int) int {
    total := 0
    for _, num := range numbers {
        total += num
    }
    return total
}

6. Function as parameter

In Go language, functions can be passed as parameters to other functions.

func applyFunction(fn func(int, int) int, a, b int) int {
    return fn(a, b)
}

func add(a, b int) int {
    return a + b
}

func main() {
    result := applyFunction(add, 3, 4)
    fmt.Println(result) // Output: 7
}

7. Anonymous functions and closures

The Go language supports anonymous functions, also known as closures. These functions can be defined inside other functions and access variables from the outer function.

func main() {
    x := 5
    fn := func() {
        fmt.Println(x) // 闭包访问外部变量
    }
    fn() // Output: 5
}

8.defer statement

deferStatements are used to delay the execution of functions, usually to perform some cleanup operations before the function returns.

func main() {
    defer fmt.Println("World")
    fmt.Println("Hello")
}

The above are some basic knowledge points about Go language functions. Functions play a very important role in Go, used to organize code, implement functional modularization, and improve code maintainability.

Verification 1: Basic use case verification

Create a new basic under chapter 4 and create func_basic_test.go practice

package basic

import (
	"errors"
	"fmt"
	"testing"
)

// 普通函数
func greet(name string) {
	fmt.Printf("Hello, %s!\n", name)
}

// 多返回值函数
func divide(a, b int) (int, error) {
	if b == 0 {
		return 0, errors.New("division by zero")
	}
	return a / b, nil
}

// 命名返回值函数
func divideNamed(a, b int) (result int, err error) {
	if b == 0 {
		err = errors.New("division by zero")
		return
	}
	result = a / b
	return
}

// 可变数量的参数函数
func sum(numbers ...int) int {
	total := 0
	for _, num := range numbers {
		total += num
	}
	return total
}

// 函数作为参数
func applyFunction(fn func(int, int) int, a, b int) int {
	return fn(a, b)
}

// 匿名函数和闭包
func closureExample() {
	x := 5
	fn := func() {
		fmt.Println(x)
	}
	fn() // Output: 5
}

// defer语句
func deferExample() {
	defer fmt.Println("World")
	fmt.Println("Hello") // Output: Hello World
}

func TestBasic(t *testing.T) {
	greet("Alice") // Output: Hello, Alice!

	q, err := divide(10, 2)
	if err != nil {
		fmt.Println("Error:", err)
	} else {
		fmt.Println("Quotient:", q) // Output: Quotient: 5
	}

	qNamed, errNamed := divideNamed(10, 0)
	if errNamed != nil {
		fmt.Println("Error:", errNamed) // Output: Error: division by zero
	} else {
		fmt.Println("Quotient:", qNamed)
	}

	total := sum(1, 2, 3, 4, 5)
	fmt.Println("Sum:", total) // Output: Sum: 15

	addResult := applyFunction(func(a, b int) int {
		return a + b
	}, 3, 4)
	fmt.Println("Addition:", addResult) // Output: Addition: 7

	closureExample()

	deferExample()
}

Verification 2: Small business example

Create a new biz under chapter 4 and create func_biz_test.go exercise. Suppose you are developing a simple order processing system and need to calculate the total price of the goods in the order and apply discounts. You can use functions to handle this business logic. Here's a simple example:

package biz

import (
	"fmt"
	"testing"
)

type Product struct {
	Name  string
	Price float64
}

func calculateTotal(products []Product) float64 {
	total := 0.0
	for _, p := range products {
		total += p.Price
	}
	return total
}

func applyDiscount(amount, discount float64) float64 {
	return amount * (1 - discount)
}

func TestBiz(t *testing.T) {
	products := []Product{
		{Name: "Product A", Price: 10.0},
		{Name: "Product B", Price: 20.0},
		{Name: "Product C", Price: 30.0},
	}

	total := calculateTotal(products)
	fmt.Printf("Total before discount: $%.2f\n", total)

	discountedTotal := applyDiscount(total, 0.1)
	fmt.Printf("Total after 10%% discount: $%.2f\n", discountedTotal)
}

(5) Object-oriented programming

Create chapter 5 in the src directory. The Go language supports Object-Oriented Programming (OOP), although compared with some traditional object-oriented programming languages ​​(such as Java and C++), the implementation of Go may be slightly different. In the Go language, there is no concept of classes, but object-oriented features can be achieved through structures and methods.

1. Structure definition

In the Go language, a structure is a custom data type used to combine fields (member variables) of different types to create a new data type. Create the struct directory and write struct_test.go. The following are examples of the definition, use and verification of the structure:

package _struct

import (
	"fmt"
	"testing"
)

// 定义一个结构体
type Person struct {
	FirstName string
	LastName  string
	Age       int
}

func TestStruct(t *testing.T) {
	// 创建结构体实例并初始化字段
	person1 := Person{
		FirstName: "Alice",
		LastName:  "Smith",
		Age:       25,
	}

	// 访问结构体字段
	fmt.Println("First Name:", person1.FirstName) // Output: First Name: Alice
	fmt.Println("Last Name:", person1.LastName)   // Output: Last Name: Smith
	fmt.Println("Age:", person1.Age)              // Output: Age: 25

	// 修改结构体字段的值
	person1.Age = 26
	fmt.Println("Updated Age:", person1.Age) // Output: Updated Age: 26
}

The definition of a structure can contain multiple fields, and each field can be of a different data type. You can also nest other structures within structures to form more complex data structures. Writing struct_cmpx_test.go example:

package _struct

import (
	"fmt"
	"testing"
)

type Address struct {
	Street  string
	City    string
	ZipCode string
}

type PersonNew struct {
	FirstName string
	LastName  string
	Age       int
	Address   Address
}

func TestCmpxStruct(t *testing.T) {
	person2 := PersonNew{
		FirstName: "Bob",
		LastName:  "Johnson",
		Age:       30,
		Address: Address{
			Street:  "123 Main St",
			City:    "Cityville",
			ZipCode: "12345",
		},
	}

	fmt.Println("Full Name:", person2.FirstName, person2.LastName)
	fmt.Println("Address:", person2.Address.Street, person2.Address.City, person2.Address.ZipCode)
}

2.Instance creation and initialization

In Go language, there are many ways to create and initialize structure instances. Create the creatinit directory. The following are several common instance creation and initialization methods. The specific code is creatinit_test.go

  • Literal initialization : You can use curly braces {}to initialize fields of a structure instance.
  • Partial field initialization: If you only want to initialize some fields of the structure, you can omit other fields.
  • Initialization using field names: Field values ​​can be specified based on field names without sequential initialization.
  • Default value initialization: The fields of a structure can be initialized according to the default value of its type.
  • Using the new function: You can use newthe function to create a pointer to a structure and return its pointer.
  • Field sequence initialization: Field names can be optionally omitted, but at this time the values ​​need to be assigned in the order of the structure fields.
package creatinit

import (
	"fmt"
	"testing"
)

type Person struct {
	FirstName string
	LastName  string
	Age       int
}

/**
 * @author zhangyanfeng
 * @description 字面量初始化
 * @date 2023/8/26  15:09
 **/
func TestCreateObj1(t *testing.T) {
	person1 := Person{
		FirstName: "Alice",
		LastName:  "Smith",
		Age:       25,
	}
	fmt.Println(person1.FirstName, person1.LastName, person1.Age) // Output: Alice Smith 25
}

/**
 * @author zhangyanfeng
 * @description 部分字段初始化
 * @date 2023/8/26  15:10
 **/
func TestCreateObj2(t *testing.T) {
	person2 := Person{
		FirstName: "Bob",
		Age:       30,
	}
	fmt.Println(person2.FirstName, person2.LastName, person2.Age) // Output: Bob  30
}

/**
 * @author zhangyanfeng
 * @description 使用字段名初始化
 * @date 2023/8/26  15:12
 **/
func TestCreateObj3(t *testing.T) {
	person3 := Person{
		LastName:  "Johnson",
		FirstName: "Chris",
		Age:       28,
	}
	fmt.Println(person3.FirstName, person3.LastName, person3.Age) // Output: Chris Johnson 28
}

/**
 * @author zhangyanfeng
 * @description 默认值初始化
 * @date 2023/8/26  15:13
 **/
func TestCreateObj4(t *testing.T) {
	var person4 Person
	fmt.Println(person4.FirstName, person4.LastName, person4.Age) // Output:   0
}

/**
 * @author zhangyanfeng
 * @description 使用 new 函数
 * @date 2023/8/26  15:14
 **/
func TestCreateObj5(t *testing.T) {
	person5 := new(Person)
	person5.FirstName = "David"
	person5.Age = 22
	fmt.Println(person5.FirstName, person5.LastName, person5.Age) // Output: David  22
}

/**
 * @author zhangyanfeng
 * @description 字段顺序初始化
 * @date 2023/8/26  15:24
 **/
func TestCreateObj6(t *testing.T) {
	// 使用字段顺序初始化
	person := Person{"Alice", "Smith", 25}
	fmt.Println(person.FirstName, person.LastName, person.Age) // Output: Alice Smith 25
}

3. Behavior (method) definition

In Go, a method is a function associated with a specific type that can be called on instances of that type. Methods enable type operations to be placed together with the type definition, improving code readability and maintainability.

Create a method directory for coding practice. The following is the definition, use and analysis of Go language methods:

method definition

In Go language, methods are defined by adding receivers to functions. The receiver is an ordinary parameter, but it is placed before the method name to specify which type the method is associated with. Create method_define_test.go

package method

import (
	"fmt"
	"testing"
)

type Circle struct {
	Radius float64
}

// 定义 Circle 类型的方法
func (c Circle) Area() float64 {
	return 3.14159 * c.Radius * c.Radius
}

func TestMethodDef(t *testing.T) {
	c := Circle{Radius: 5}
	area := c.Area()
	fmt.Printf("Circle area: %.2f\n", area) // Output: Circle area: 78.54
}

In the above example, we defined a Circlestruct and then defined a Areamethod named on it. This method can c.Area()be called via , where cis an Circleinstance of type.

method call

The syntax of method calling is 实例.方法名(), that is, calling the method through the instance. Create method_rpc_test.go

package method

import (
	"fmt"
	"testing"
)

type Rectangle struct {
	Width  float64
	Height float64
}

func (r Rectangle) Area() float64 {
	return r.Width * r.Height
}

func TestMethonRpc(t *testing.T) {
	rect := Rectangle{Width: 3, Height: 4}
	area := rect.Area()
	fmt.Printf("Rectangle area: %.2f\n", area) // Output: Rectangle area: 12.00
}

pointer receiver

The Go language supports using pointers as receivers of methods, so that the field values ​​of the receiver instance can be modified. Create method_rec_test.go

package method

import (
	"fmt"
	"testing"
)

type Counter struct {
	Count int
}

func (c *Counter) Increment() {
	c.Count++
}

func TestMethonRec(t *testing.T) {
	counter := Counter{Count: 0}
	counter.Increment()
	fmt.Println("Count:", counter.Count) // Output: Count: 1
}

In the above example, Incrementthe method uses a pointer receiver so that Countthe value of the field is modified after the method is called.

The difference between methods and functions

The main difference between methods and functions is that a method is a function of a specific type, which is more closely related to the type and can access the fields and other methods of the type. Functions are blocks of code that are independent of a specific type. Methods are often used to implement specific types of behavior, while functions can be used for general operations.

By defining methods, you can make the operation of types more natural and consistent, improving the readability and modularity of your code.

It can be explained here that in method_rpc_test.go, we Rectangledefine a Areamethod named for the structure, which can be called by rect.Area(). Methods are directly associated with type Rectangleand have access to Rectanglefields of ( Widthand Height).

In order to compare with the method, we create a method in the corresponding method body as follows

// 定义一个函数来计算矩形的面积
func CalculateArea(r Rectangle) float64 {
    return r.Width * r.Height
}

In this example, we define a CalculateAreafunction called which accepts a Rectangleparameter of type to calculate the area of ​​a rectangle. The function is independent of Rectanglethe type, so it cannot directly access Rectanglethe fields of the .

Summary: The difference between methods and functions is that methods are functions of a specific type, which are more closely related to the type and can access fields and other methods of the type. A function is a block of code that is independent of a specific type and is usually used for common operations. In the above example, the method is associated with the rectangle and can directly access the rectangle's fields; the function is an independent calculation process and is not directly associated with any specific type.

By using methods, we can make our code more natural and consistent, improving its readability and modularity, especially when implementing specific types of behavior.

4. Interface definition and use

In the Go language, an interface is a way of defining a collection of methods. It specifies the signature of a set of methods without involving implementation details. Through interfaces, polymorphism and code decoupling can be achieved, allowing objects of different types to operate in a consistent manner.

Create an interface directory for subsequent exercises. The following is an explanation of the Go language interface:

Define interface

An interface is a collection of methods, typedefined through keywords. An interface defines a set of method signatures but does not contain method implementations. Create interface_test.go for code practice

package interface_test

import (
	"fmt"
	"testing"
)

// 定义一个简单的接口
type Shape interface {
	Area() float64
}

// 定义两个实现 Shape 接口的结构体
type Circle struct {
	Radius float64
}

func (c Circle) Area() float64 {
	return 3.14159 * c.Radius * c.Radius
}

type Rectangle struct {
	Width  float64
	Height float64
}

func (r Rectangle) Area() float64 {
	return r.Width * r.Height
}

func TestInterface(t *testing.T) {
	shapes := []Shape{
		Circle{Radius: 2},
		Rectangle{Width: 3, Height: 4},
	}

	for _, shape := range shapes {
		fmt.Printf("Area of %T: %.2f\n", shape, shape.Area())
	}
}

In the above example, we define an Shapeinterface named, which requires the implementation of a Areamethod for calculating the area of ​​the figure. Then, we defined two structures Circleand Rectangle, and implemented Areathe methods respectively. By using interfaces, we can put different types of graphics objects into the same slice and then call their Areamethods through a loop.

Implementation of interface

Any type is considered to implement the interface as long as it implements all methods defined in the interface. The implementation of the interface is implicit and does not require explicit declaration. As long as the method signature is the same as the method signature in the interface, the type is considered to implement the interface.

Interface polymorphism

Due to the polymorphism of interfaces, we can regard the objects that implement the interface as the interface itself. In the above example, shapesdifferent types of objects are stored in the slice, but they all implement Shapethe interface so the methods can be called in a unified way Area.

By using interfaces, code can be abstracted and decoupled, making the code more flexible and extensible. Interfaces are widely used in the Go language to define common behaviors and constraints.

5. Expansion and reuse

In the Go language, the way to extend and reuse code is different from traditional object-oriented languages ​​​​(such as Java). Go encourages the use of features such as composition, interfaces, and anonymous fields to achieve code extension and reuse, rather than through class inheritance.

Create the extend directory for subsequent exercises. The following is a detailed explanation of extension and reuse in Go language:

Combining and nesting

Composition in the Go language allows you to nest one structure type within another structure type to achieve code reuse. Nested structures can access their members directly through field names. Create composition_test.go

package extend

import (
	"fmt"
	"testing"
)

type Engine struct {
	Model string
}

type Car struct {
	Engine
	Brand string
}

func TestComposition(t *testing.T) {
	car := Car{
		Engine: Engine{Model: "V6"},
		Brand:  "Toyota",
	}

	fmt.Println("Car brand:", car.Brand)
	fmt.Println("Car engine model:", car.Model) // 直接访问嵌套结构体的字段
}

In this example, we use composition to create Cara struct with nested Enginestructs inside it. Through nesting, Cara structure can directly access Enginethe fields of the structure.

Interface implementation

With an interface, you can define a set of methods that different types can then implement. This enables polymorphism and code decoupling, so that objects of different types can be operated through the same interface. Create interface_ext_test.go

package extend

import (
	"fmt"
	"math"
	"testing"
)

// 定义 Shape 接口
type Shape interface {
	Area() float64
	Perimeter() float64
}

// 定义 Circle 结构体
type Circle struct {
	Radius float64
}

// 实现 Circle 结构体的方法,以满足 Shape 接口
func (c Circle) Area() float64 {
	return math.Pi * c.Radius * c.Radius
}

func (c Circle) Perimeter() float64 {
	return 2 * math.Pi * c.Radius
}

// 定义 Rectangle 结构体
type Rectangle struct {
	Width  float64
	Height float64
}

// 实现 Rectangle 结构体的方法,以满足 Shape 接口
func (r Rectangle) Area() float64 {
	return r.Width * r.Height
}

func (r Rectangle) Perimeter() float64 {
	return 2 * (r.Width + r.Height)
}

func TestInterfaceExt(t *testing.T) {
	circle := Circle{Radius: 3}
	rectangle := Rectangle{Width: 4, Height: 5}

	shapes := []Shape{circle, rectangle}

	for _, shape := range shapes {
		fmt.Printf("Shape Type: %T\n", shape)
		fmt.Printf("Area: %.2f\n", shape.Area())
		fmt.Printf("Perimeter: %.2f\n", shape.Perimeter())
		fmt.Println("------------")
	}
}

In the above example, we defined an Shapeinterface called which has two methods Area()and Perimeter(), used to calculate the area and perimeter of a shape respectively. Then, we implemented the two methods of the Circleand Rectanglestructure respectively, so that they satisfy Shapethe interface.

By putting different types of shape instances into a slice, we can call the and methods []Shapein a unified way , achieving code polymorphism and decoupling. In this way, no matter we add new shapes later, as long as they implement the interface methods, they can be seamlessly integrated into the calculator.Area()Perimeter()Shape

Anonymous field and method reuse

By using anonymous fields, one structure can inherit the fields and methods of another structure. Create other_ext_test.go

package extend

import (
	"fmt"
	"testing"
)

type Animal struct {
	Name string
}

func (a Animal) Speak() {
	fmt.Println("Animal speaks")
}

type Dog struct {
	Animal
	Breed string
}

func TestOtherExt(t *testing.T) {
	dog := Dog{
		Animal: Animal{Name: "Buddy"},
		Breed:  "Golden Retriever",
	}

	fmt.Println("Dog name:", dog.Name)
	dog.Speak() // 继承了 Animal 的 Speak 方法
}

In the above example, Dogthe struct nests Animala struct, thereby inheriting Animalthe fields and methods of .

In these ways, you can extend and reuse code in the Go language. Although Go does not emphasize class inheritance as much as traditional object-oriented languages, through features such as composition, interfaces, and anonymous fields, you can still achieve similar effects, making the code more flexible, more readable, and maintaining low coupling.

6. Empty interfaces and assertions

Empty interfaces and assertions are important concepts in the Go language for dealing with undefined types and type conversions.

Create the emptyassert directory for subsequent exercises. The following is a summary of learning about empty interfaces and assertions:

Empty Interface

The empty interface is the most basic interface in the Go language. It does not contain any method declarations. Therefore, the empty interface can be used to represent any type of value. The declaration method of empty interface is interface{}.

The main use of the empty interface is in scenarios where you need to deal with undefined types. By using the empty interface, any type of value can be accepted and stored, similar to dynamic typing in other programming languages. However, it should be noted that using an empty interface may result in reduced type safety because the concrete type cannot be checked at compile time.

Assertion (Type Assertion)

Assertion is a mechanism to recover a concrete type in an empty interface, which allows us to check the actual type of a value in an empty interface at runtime and convert it to the corresponding type. The syntax for assertions is value.(Type)where valueis the interface value and Typeis the concrete type to be asserted.

Create emptyassert_test.go for verification:

package emptyassert

import (
	"fmt"
	"testing"
)

func DoSomething(p interface{}) {
	switch v := p.(type) {
	case int:
		fmt.Println("Integer", v)
	case string:
		fmt.Println("String", v)
	default:
		fmt.Println("Unknow Type")
	}
}

func TestEmptyInterfaceAssertion(t *testing.T) {
	DoSomething(10)
	DoSomething("10")
}

func TestEmptyAssert(t *testing.T) {
	var x interface{} = "hello"
	str, ok := x.(string)
	if ok {
		fmt.Println("String:", str)
	} else {
		fmt.Println("Not a string")
	}
}

The content of each test function is explained one by one below:

  1. func DoSomething(p interface{}) { ... }: Defines a function DoSomethingthat accepts an empty interface parameter p, then performs type assertions based on the actual type of the interface value, and outputs different information based on different types.

  2. func TestEmptyInterfaceAssertion(t *testing.T) { ... }: Test the assertion operation of the empty interface.

    Call DoSomething(10), pass the integer 10to the function, and the function outputs the integer type information based on the type assertion. Call DoSomething("10"), pass the string "10"to the function, and the function outputs the string type information based on the type assertion.
  3. func TestEmptyAssert(t *testing.T) { ... }: Test the type assertion operation of the empty interface.

    Declare an empty interface variable xand assign the string "hello"value to it. Use type assertion x.(string)to determine xwhether it is a string type. If so, assign it to a variable strand output the string value; otherwise, output "Not a string".

These test functions show the assertion operation of the empty interface in the Go language. Through type assertions, the specific type in the empty interface can be determined and the corresponding operations can be performed.

Summary: Empty interfaces and assertions are powerful tools in the Go language for dealing with undefined types and type conversions. Empty interfaces allow any type of value to be stored, while assertions allow us to check and convert the actual type of interface values ​​at runtime. Using these mechanisms allows for more flexible and versatile code when you need to handle different types of values. But when using empty interfaces and assertions, be careful to maintain type safety and perform appropriate error handling.

7.GO interface best practices

In Go language, the best practices of using interfaces can improve the readability, maintainability and flexibility of the code. Here are some best practices for Go interfaces:

  • Small interfaces and large interfaces: Try to design a small interface. An interface should only contain a small number of methods instead of designing a large and comprehensive interface. This avoids unnecessary burden in implementing the interface and makes the interface more general.
  • Design interfaces based on usage scenarios: When designing interfaces, you should consider usage scenarios rather than starting from specific implementations. Think about how interfaces are used in your application, and what methods the interface should provide to satisfy those use cases.
  • Use appropriate naming: Use clear naming for interfaces and methods so that they convey their purpose and functionality. Naming should be readable and expressive so that other developers can easily understand the purpose of the interface.
  • Avoid unnecessary interfaces: Do not create an interface for each type, use interfaces only when there is indeed shared behavior and functionality between multiple types. Don't overuse interfaces to avoid unnecessary complexity.
  • Use interfaces as function parameters and return values: Using interfaces as function parameters and return values ​​can make the function more versatile, allowing different types of parameters to be passed in, and different types of results returned. This improves code reusability and scalability.
  • Comments and documentation: Provide clear documentation and comments for the interface, explaining the purpose of the interface, the functionality of the methods, and the expected behavior. This can help other developers better understand how the interface is used.
  • Use case-driven design: When designing an interface, you can start from the perspective of usage, first consider how the interface is called in actual scenarios, and then design the methods and signatures of the interface.
  • Separate the implementation and definition of the interface: Separating the implementation of the interface from the definition of the interface can make the implementation more flexible and new types can be implemented without modifying the interface definition.
  • Default implementation: In the interface definition, you can provide default implementations for certain methods, thereby reducing the workload when implementing the interface. This is useful for optional methods or the default behavior of certain methods.
  • Use empty interface with caution: Use empty interface ( interface{}) with caution as it reduces type safety. Only use empty interfaces when you really need to handle values ​​of different types, and be careful about type assertions and error handling.

In short, when designing and using interfaces, you should choose the appropriate solution based on actual needs and project characteristics. Following the above best practices can help write Go code that is more maintainable, scalable, and readable.

(6) Write a good error mechanism

Create chapter 6 in the src directory. The error handling mechanism in Go language is implemented by returning error values ​​instead of using exceptions. This error handling mechanism is very clear and controllable, allowing developers to handle various error situations accurately.

1.Basic usage introduction

Create the basic directory and write basic_error_test.go

Error type

In Go, errors are represented as a errortype that implements the interface. errorThe interface has only one method, i.e. Error() stringit returns a string describing the error.

type error interface {
    Error() string
}

Return error value

When a function encounters an error condition, it usually returns an error value. This error value can be a custom type that implements errorthe interface, or it can be a predefined error type in the Go standard library, such as errors.New()an error created by .

error checking

The caller usually needs to explicitly check the error returned by the function to determine whether an error occurred. This can be achieved by using the statement after calling the function if.

The above two directly write the code as follows:

package basic

import (
	"errors"
	"fmt"
	"testing"
)

var LessThanTwoError = errors.New("n should be not less than 2")
var LargerThenHundredError = errors.New("n should be not larger than 100")

func GetFibonacci(n int) ([]int, error) {
	if n < 2 {
		return nil, LessThanTwoError
	}
	if n > 100 {
		return nil, LargerThenHundredError
	}
	fibList := []int{1, 1}

	for i := 2; /*短变量声明 := */ i < n; i++ {
		fibList = append(fibList, fibList[i-2]+fibList[i-1])
	}
	return fibList, nil
}

func TestGetFibonacci(t *testing.T) {
	if v, err := GetFibonacci(1); err != nil {
		if err == LessThanTwoError {
			fmt.Println("It is less.")
		}
		t.Error(err)
	} else {
		t.Log(v)
	}

}

2.Error chain

Create the chain directory and write error_chain_test.go

In some cases, errors can contain additional information to better understand the cause of the error. fmt.Errorf()Errors containing additional information can be created using the function.

Suppose we are building a library for file operations, which contains file reading and writing functions. Sometimes, various errors may occur during file reading or writing, such as file non-existence, permission issues, etc. We would like to be able to provide more contextual information about the error.

package chain

import (
	"errors"
	"fmt"
	"testing"
)

// 自定义文件操作错误类型
type FileError struct {
	Op   string // 操作类型("read" 或 "write")
	Path string // 文件路径
	Err  error  // 原始错误
}

// 实现 error 接口的 Error() 方法
func (e *FileError) Error() string {
	return fmt.Sprintf("%s %s: %v", e.Op, e.Path, e.Err)
}

// 模拟文件读取操作
func ReadFile(path string) ([]byte, error) {
	// 模拟文件不存在的情况
	return nil, &FileError{Op: "read", Path: path, Err: errors.New("file not found")}
}

func TestChain(t *testing.T) {
	filePath := "/path/to/nonexistent/file.txt"
	_, err := ReadFile(filePath)
	if err != nil {
		fmt.Println("Error:", err)
		// 在这里,我们可以检查错误类型,提取上下文信息
		if fileErr, ok := err.(*FileError); ok {
			fmt.Printf("Operation: %s\n", fileErr.Op)
			fmt.Printf("File Path: %s\n", fileErr.Path)
			fmt.Printf("Original Error: %v\n", fileErr.Err)
		}
	}
}

Here is an explanation of the code:

  1. FileErrorStructure: Defines a custom error type FileError, containing the following fields:

    • Op: Operation type, indicating whether it is a read ("read") or write ("write") operation.
    • Path: File path, indicating which file is involved.
    • Err: Original error, containing underlying error information.
  2. Error()Method: FileErrorImplements the method errorof the interface for the structure Error(), which is used to generate a text description of the error.

  3. ReadFile()Function: simulate file reading operation. In this example, the function returns an FileErrorerror of type , simulating the situation where the file does not exist.

  4. TestChain()Test Function: Demonstrates how to use custom error types in error handling.

    • A file path is defined filePathand ReadFile(filePath)the function is called to simulate a file read operation.
    • Check for errors and output an error message if an error occurs.
    • In error handling, type assertions are used to check whether the error is of *FileErrortype, and if so, more contextual information can be extracted, such as operation type, file path, and original error information.

3.Panic and Recover

In the Go language, panicand recoverare mechanisms for handling exception situations, but they should be used with caution and only in specific situations, not as a replacement for normal error handling mechanisms. Here is a detailed explanation of panicand recover, with a specific use case:

panic

Create panica directory and write panic_test.go. panicis a built-in function used to cause a runtime panic. When the program encounters a fatal error that cannot continue execution, you can use panicto interrupt the normal flow of the program. But misuse should be avoided panicas it can cause the program to crash without providing a friendly error message. Typically panicused to indicate an unrecoverable error in a program, such as an out-of-bounds slice index.

package panic

import (
	"fmt"
	"testing"
)

func TestPanic(t *testing.T) {
	arr := []int{1, 2, 3}
	index := 4
	if index >= len(arr) {
		panic("Index out of range")
	}
	element := arr[index]
	fmt.Println("Element:", element)
}

In the above example, if the index indexexceeds arrthe range of the slice, it will be triggered panic, causing the program to crash. In this case, panicit is used to indicate an unrecoverable error in the program.

recover

Create recovera directory and write recover_test.go. recoverAlso a built-in function for recovering from panicruntime panics caused by . It can only deferbe used inside a delay function ( ) and is used to restore the program's control flow, not to handle errors. Typically, after an occurrence panic, recoveryou can catch it in a delay function panic, perform some cleanup, and then the program continues execution.

package recover

import (
	"fmt"
	"testing"
)

func cleanup() {
	if r := recover(); r != nil {
		fmt.Println("Recovered from panic:", r)
	}
}

func TestRecover(t *testing.T) {
	defer cleanup()
	panic("Something went wrong")
	fmt.Println("This line will not be executed")
}

In the above example, panicafter triggering, cleanupthe in the function recoveris captured panic, and an error message is printed. The program will then continue to execute, but it should be noted that the control flow will not return to panicthe place where it was triggered, so fmt.Printlnit will not be executed.

In summary, panicand recovershould be used with caution and only in special cases, such as unrecoverable errors or cleanup operations in deferred functions. In most cases, error return values ​​should be used in preference to handling exceptions, as this approach is safer, more controllable, and provides better error information and error handling. panicYou should only consider using and in specific circumstances, such as when encountering an unrecoverable error recover.

4. Custom error types

Create definea directory and write error_define_test.go.

In Go, you can define your own error types as needed, just meet errorthe requirements of the interface. This allows you to create more descriptive and contextual error types.

In Go, custom error types are a powerful way to create more descriptive and contextual errors to provide better error information. Custom error types must meet errorthe requirements of the interface, that is, implement Error() stringthe method. Here's an example showing how to customize an error type and validate its use case:

package define

import (
	"fmt"
	"testing"
	"time"
)

// 自定义错误类型
type TimeoutError struct {
	Operation string    // 操作名称
	Timeout   time.Time // 超时时间
}

// 实现 error 接口的 Error() 方法
func (e TimeoutError) Error() string {
	return fmt.Sprintf("Timeout error during %s operation. Timeout at %s", e.Operation, e.Timeout.Format("2006-01-02 15:04:05"))
}

// 模拟执行某个操作,可能会超时
func PerformOperation() error {
	// 模拟操作超时
	timeout := time.Now().Add(5 * time.Second)
	if time.Now().After(timeout) {
		return TimeoutError{Operation: "PerformOperation", Timeout: timeout}
	}
	// 模拟操作成功
	return nil
}

func TestDefineError(t *testing.T) {
	err := PerformOperation()
	if err != nil {
		// 检查错误类型并打印错误信息
		if timeoutErr, ok := err.(TimeoutError); ok {
			fmt.Println("Error Type:", timeoutErr.Operation)
			fmt.Println("Timeout At:", timeoutErr.Timeout)
		}
		fmt.Println("Error:", err)
	} else {
		fmt.Println("Operation completed successfully.")
	}
}

Here is an explanation of the code:

  1. TimeoutErrorStructure: Defines a custom error type TimeoutError, containing the following fields:

    • Operation: Operation name, indicating which operation timed out.
    • Timeout: Timeout time, indicating the time point when the operation times out.
  2. Error()Method: TimeoutErrorImplements the method errorof the interface for the structure Error(), which is used to generate a text description of the error.

  3. PerformOperation()Function: simulates performing an operation and may time out. In this example, if the current time exceeds the timeout, an TimeoutErrorerror of type is returned.

  4. TestDefineError()Test Function: Demonstrates how to use custom error types in error handling.

    • Call PerformOperation()the function to simulate the operation and check if an error occurred.
    • If an error occurs, first check whether the error type is TimeoutError, if so, extract the timeout operation and timeout time, and output relevant information.
    • Finally, regardless of whether an error occurred, an error message or a successful completion message is output.

This example shows how to customize error types and how to leverage these custom error types in error handling to provide more contextual information, making error handling more informative and flexible. Here, TimeoutErroradditional information about timeout operations and timeout periods is provided.

Guess you like

Origin blog.csdn.net/xiaofeng10330111/article/details/132390106