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
-
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.
-
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.
-
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:
-
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.
-
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.
-
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.
-
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.
-
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.
-
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:
-
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
-
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
. -
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
-
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:
-
package main
: Declare that this file belongs to the package named "main", which is the entry package name of a Go program. -
import ("fmt" "os")
: Two standard library packages are introduced, "fmt" for formatting output, and "os" for interacting with the operating system. -
func main() { ... }
: This is the entry function of the program, it will be called first when the program is running. -
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.Args
is a string slice that contains all command line parameters. The first parameter is the name of the program. -
fmt.Println("Hello World", os.Args[1])
: If any parameters are passed to the program, this line of code will be executed. It usesfmt.Println
the function to print a messageos.Args[1]
consisting of the string "Hello World" andos.Args[1]
representing the first argument passed to the program.
To sum up, this code covers the following knowledge points:
-
Package import and use of the standard library: Import the "fmt" and "os" packages through
import
the keyword, and then use the functions and types provided by these packages in your code. -
Obtaining command line parameters: Use
os.Args
to obtain command line parameters. -
Conditional statements: Use
if
conditional statements to determine whether any command line parameters are passed to the program. -
String operations: Use string concatenation operations to concatenate "Hello World" with command line parameters.
-
Formatted output: Use
fmt.Println
the 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:
- Variable declaration: Use
var
keywords to declare a variable, for example:var x int
. - 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
. - Variable assignment: Use assignment operators
=
to assign values to variables, for example:x = 10
. - Multi-variable declaration: Multiple variables can be declared at the same time, for example:
var a, b, c int
. - Variable initialization: Variables can be initialized when declared, for example:
var name string = "John"
. - 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. - 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:
-
package variables
: declares a package named "variables", which is a package name used for testing. -
import "testing"
: Imported the Go language testing framework "testing" package for writing and running test functions. -
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.a
Inside the test function, two integer variables and are declaredb
and initialized to 1, which are the first two numbers of the Fibonacci sequence.- Use
t.Log(a)
print variablea
value to test log. - Use a loop to generate the first 5 numbers of the Fibonacci sequence, each iteration prints the
b
value of to the test log and updates the values ofa
andb
to generate the next number.
-
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
a
and are declaredb
and 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, a
this line of code to implement the exchange ofa
andb
, 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.
- Inside the test function, two integer variables
2.Constant
Prerequisite: Create a constant in the chapter2 directory. The learning summary is as follows:
- Constant declaration: Use
const
keywords to declare a constant, for example:const pi = 3.14159
. - Constant assignment: The value of a constant must be assigned when it is declared. Once assigned, it cannot be modified.
- Enumeration constants: An enumeration can be simulated using a set of constants, for example:
const ( Monday = 1 Tuesday = 2 // ... )
- Type specification: The type of the constant can also be specified, for example:
const speed int = 300000
. - Constant expressions: Constants can be evaluated using expressions, for example:
const secondsInHour = 60 * 60
. - Untyped constants: Constants can be untyped, with the type automatically inferred based on context. For example,
const x = 5
an 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:
-
package constant
: declares a package named "constant", which is a package name used for testing. -
import "testing"
: Imported the Go language testing framework "testing" package for writing and running test functions. -
const (...)
: Two constant blocks are defined.-
In the first constant block, a constant generator is used
iota
to define a series of constants starting from 1 and increasing. In this example,Monday
is assigned a value of 1,Tuesday
is assigned a value of 2, andWednesday
is assigned a value of 3.iota
It 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
iota
to define a series of bitwise left-shifted constants. In this example,Readable
is assigned the value 1,Writable
is assigned the value 2 (10 in binary), andExecutable
is 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.
-
-
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 constantsMonday
andTuesday
to the test log.
- Use to
-
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
a
and initialized to 1, which is 0001 in binary. - Use bitwise operations and bitwise AND operations to check
a
whether a variable has theReadable
,Writable
andExecutable
properties. For example,a&Readable == Readable
the expression checksa
whether the binary representation of containsReadable
the flag bit. - Use to
t.Log()
print the results of the three expressions to the test log.
- Inside the test function, an integer variable is declared
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:
-
Integer Types: Go language provides integer types of different sizes, such as
int
,int8
,int16
,int32
andint64
. Unsigned integer types includeuint
,uint8
,uint16
,uint32
anduint64
. The size of the integer type depends on the computer's architecture, such as 32-bit or 64-bit. -
Floating-Point Types: Go language provides
float32
twofloat64
floating-point types, corresponding to single-precision and double-precision floating-point numbers respectively. -
Complex Types: Go language provides
complex64
twocomplex128
complex types, corresponding to complex numbers composed of two floating point numbers. -
Boolean Type: The Boolean type is used to represent true (
true
) and false (false
) values and is used for conditional judgment and logical operations. -
String Type: String type represents a sequence of characters. Strings are immutable and can be defined using double quotes
"
or backticks .`
-
Character type (Rune Type): Character type
rune
is used to represent Unicode characters, which is an alias of int32. Single quotes are usually used'
to represent characters, such as'A'
. -
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.
-
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.
-
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.
-
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.
-
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.
-
Function Types: Function types represent the signature of a function, including parameter and return value types. Functions can be passed as parameters and returned.
-
Channel Types: Channels are a mechanism for communication and synchronization between coroutines. Channels have send and receive operations.
-
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:
-
type Person struct { ... }
: Defines a structure typePerson
to represent a person's information, includingFirstName
,LastName
andAge
fields. -
type Shape interface { ... }
: Defines an interface typeShape
that requires the implementation of a methodArea()
that returns afloat64
type. -
type Circle struct { ... }
: Defines a structure typeCircle
representing the radius of a circle.func (c Circle) Area() float64 { ... }
:Circle
Implements the methodShape
of the interface for the typeArea()
, which is used to calculate the area of a circle. -
func add(a, b int) int { ... }
: Defines a functionadd
that performs integer addition operations. -
func subtract(a, b int) int { ... }
: Defines a functionsubtract
that performs integer subtraction. -
type Operation func(int, int) int
: Defines a function typeOperation
that accepts two integer parameters and returns an integer result. -
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,
Circle
assigning types toShape
type variables, and calling interface methods. - Demonstrates the definition and use of function types, assigns different functions to
Operation
type 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:
-
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
int
tofloat64
is safe, but a conversionfloat64
from toint
may result in the decimal part being truncated. -
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)
. -
Conversion between incompatible types: The compiler does not automatically convert incompatible types. For example, you cannot directly convert a
string
type toint
a type. -
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:
-
const (...)
: Defines three constantsReadable
, sum,Writable
andExecutable
uses displacement operations to generate different values. -
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.
-
func TestCompareArray(t *testing.T) { ... }
: Defines a test function "TestCompareArray" for testing array comparison.a
Two integer arrays and are declaredb
, as well as another arrayd
where the contents of arraya
and arrayd
are identical.- Use the comparison operator
==
to check whether arraysa
andb
are equal, and whether arraysa
andd
are equal.
-
func TestBitClear(t *testing.T) { ... }
: Defines a test function "TestBitClear" for testing bit clearing operations.- Declare an integer variable
a
and initialize it to7
, i.e. the binary representation0111
. - Use the bit clear operation
&^
to clear the and bitsa
in .Readable
Executable
- Use the bitwise AND operation
&
to checka
whether has theReadable
,Writable
andExecutable
properties.
- Declare an integer variable
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:
- 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.
- 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:
if
statement
if
Statements 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 {
// 代码块
}
switch
statement
switch
Statements are used to execute different branches of code based on different values of an expression. Unlike other languages, Go switch
can automatically match the first branch that satisfies the condition without using break
a 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:
-
According to different circumstances of age, it is judged whether it is a minor, an adult or an elderly person throughfunc TestConditionIf(t *testing.T) { ... }
: Testif
the use of statements.if
the,else if
and branches.else
-
func TestConditionSwitch(t *testing.T) { ... }
: Testswitch
the use of statements. According todayOfWeek
the value of , useswitch
the statement to output the corresponding day of the week. -
func TestSwitchMultiCase(t *testing.T) { ... }
: Test the case whereswitch
the statement has multiplecase
values. Useswitch
the statement to determine the parity of each number and output the corresponding information. -
func TestSwitchCaseCondition(t *testing.T) { ... }
: Testswitch
the conditional expression in the statement. Useswitch
the 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 case
processing of multiple values, as well as switch
the use of conditional expressions in statements.
6. Loop Statements
Prerequisite: Create a loop in the chapter2 directory. The learning summary is as follows:
for
cycle
for
Loops 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 post
perform increment or decrement operations in the statement.
for
Simplified form of loop
Go language loops can also be simplified to only the loop condition part, similar to loops for
in other languages :while
for condition {
// 代码块
}
range
cycle
range
Loops 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:
-
func TestLoopFor(t *testing.T) { ... }
: Test basicfor
loops. Usingfor
a loop, iterate from 1 to 5 to output the number of loop iterations. -
func TestLoopForBasic(t *testing.T) { ... }
: Test a loop without initialization statementsfor
. Usingfor
a 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. -
func TestLoopForRange(t *testing.T) { ... }
:Test usingfor range
iterative slicing. Define an integer slicenumbers
, usefor range
a loop to iterate over each element in the slice, and output the index and value of the element. -
func TestLoopForUnLimit(t *testing.T) { ... }
: Test infinite loops andbreak
statements. Use an infinite loop andbreak
statement to determine whether to terminate the loop inside the loop body, andi
exit the loop when it is greater than 5.
These test functions demonstrate for
the 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:
-
func TestJumpBreak(t *testing.T) { ... }
: Testbreak
the use of statements. Usefor
a loop to iterate from 1 to 5, buti
when the iteration variable equals 3, usebreak
the statement to terminate the loop. -
func TestJumpContinue(t *testing.T) { ... }
: Testcontinue
the use of statements. Usefor
the loop to iterate from 1 to 5, but when the iteration variablei
equals 3, usecontinue
the statement to skip this iteration and continue to the next iteration. -
func TestJumpGoto(t *testing.T) { ... }
: Testgoto
the use of statements. An infinite loop is implemented usinggoto
the statement, which uses labelsstart
andgoto start
to jump to the starting position of the loop inside the loop body. The termination condition of the loop is wheni
is 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:
-
func TestArrayInit(t *testing.T) { ... }
: Test the initialization of the array.- Use different ways to initialize arrays
arr
,arr1
andarr3
. - Modify
arr1
the second element of5
. - Use to
t.Log()
output the element values and contents of different arrays.
- Use different ways to initialize arrays
-
func TestArrayTravel(t *testing.T) { ... }
: Test array traversal.- Use
for
to loop through the arrayarr3
and output the value of each element separately. - Use
for range
to loop through the arrayarr3
and also output the value of each element.
- Use
-
func TestArraySection(t *testing.T) { ... }
: Test the use of array slicing.- Creates an array slice
arr3_sec
based on the entire arrayarr3
. - Use
t.Log()
output array slicearr3_sec
contents.
- Creates an array slice
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
make
the function to create3
a slice with an initial capacity ofslice
and output the initial slice content. - Use a function to add elements
append
to a slice .slice
- Use
copy
the function to copy a sliceslice
to a new slicecopySlice
. - Use Slice
numbers
to make slice cuts and create sub-slicessubSlice
. subSlice
The first element of modification is100
, output the modified slice and the original slice, as well as the copied slice.- Use
for range
a loop to loop through the sliceslice
, 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:
-
func TestBasic(t *testing.T) { ... }
: Basic operations for testing mapping.- Declare and initialize the map
ages
to 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
delete
the function to delete key-value pairs. - Use
age, exists
to get the value and check if the key exists. - Use
for range
a loop to traverse the map and output the information for each key-value pair.
- Declare and initialize the map
-
type Student struct { ... }
: Defines aStudent
structure named to store student information. -
func TestComplex(t *testing.T) { ... }
: Test mapping operations containing complex values.- Declare and initialize two mappings,
studentScores
used to store student scores andstudentInfo
student information. - Add student information and scores to the mapping.
- Use
studentInfo["Alice"]
to get student information and usestudentScores["Alice"]
to get student scores. - Use
for range
a loop to loop through the map and output each student's information and score.
- Declare and initialize two mappings,
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.Join
the 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 range
a 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.Compare
functions 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:
-
func TestString(t *testing.T) { ... }
: Basic operations for testing strings.- Declare a string variable
s
that 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
rune
type slice, output the slice length and Unicode and UTF-8 encoding of Chinese characters.
- Declare a string variable
-
Declare a string containing Chinese charactersfunc TestStringToRune(t *testing.T) { ... }
: Test string torune
conversion.s
,range
convert the string torune
type through traversal and output it. -
Declare a string containing comma delimitersfunc TestStringFn(t *testing.T) { ... }
: Test string-related functions.s
, usestrings.Split
the function to split the string and output each part. Usestrings.Join
the function to merge the split parts into a new string and output it. -
func TestConv(t *testing.T) { ... }
: Test conversion of strings to other types.- Use
strconv.Itoa
to convert an integer to a string. - Concatenate strings and integers and output the result.
- Use
strconv.Atoi
to convert strings to integers, perform addition, and handle error conditions.
- Use
These test functions demonstrate various operations on strings in the Go language, including string length, UTF-8 encoding, rune
type 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 func
begins 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 return
the 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
defer
Statements 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
new
the 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 Circle
struct and then defined a Area
method named on it. This method can c.Area()
be called via , where c
is an Circle
instance 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, Increment
the method uses a pointer receiver so that Count
the 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 Rectangle
define a Area
method named for the structure, which can be called by rect.Area()
. Methods are directly associated with type Rectangle
and have access to Rectangle
fields of ( Width
and 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 CalculateArea
function called which accepts a Rectangle
parameter of type to calculate the area of a rectangle. The function is independent of Rectangle
the type, so it cannot directly access Rectangle
the 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, type
defined 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 Shape
interface named, which requires the implementation of a Area
method for calculating the area of the figure. Then, we defined two structures Circle
and Rectangle
, and implemented Area
the methods respectively. By using interfaces, we can put different types of graphics objects into the same slice and then call their Area
methods 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, shapes
different types of objects are stored in the slice, but they all implement Shape
the 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 Car
a struct with nested Engine
structs inside it. Through nesting, Car
a structure can directly access Engine
the 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 Shape
interface 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 Circle
and Rectangle
structure respectively, so that they satisfy Shape
the interface.
By putting different types of shape instances into a slice, we can call the and methods []Shape
in 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, Dog
the struct nests Animal
a struct, thereby inheriting Animal
the 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 value
is the interface value and Type
is 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:
-
func DoSomething(p interface{}) { ... }
: Defines a functionDoSomething
that accepts an empty interface parameterp
, then performs type assertions based on the actual type of the interface value, and outputs different information based on different types. -
Callfunc TestEmptyInterfaceAssertion(t *testing.T) { ... }
: Test the assertion operation of the empty interface.DoSomething(10)
, pass the integer10
to the function, and the function outputs the integer type information based on the type assertion. CallDoSomething("10")
, pass the string"10"
to the function, and the function outputs the string type information based on the type assertion. -
Declare an empty interface variablefunc TestEmptyAssert(t *testing.T) { ... }
: Test the type assertion operation of the empty interface.x
and assign the string"hello"
value to it. Use type assertionx.(string)
to determinex
whether it is a string type. If so, assign it to a variablestr
and 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 error
type that implements the interface. error
The interface has only one method, i.e. Error() string
it 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 error
the 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:
-
FileError
Structure: Defines a custom error typeFileError
, 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.
-
Error()
Method:FileError
Implements the methoderror
of the interface for the structureError()
, which is used to generate a text description of the error. -
ReadFile()
Function: simulate file reading operation. In this example, the function returns anFileError
error of type , simulating the situation where the file does not exist. -
TestChain()
Test Function: Demonstrates how to use custom error types in error handling.- A file path is defined
filePath
andReadFile(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
*FileError
type, and if so, more contextual information can be extracted, such as operation type, file path, and original error information.
- A file path is defined
3.Panic and Recover
In the Go language, panic
and recover
are 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 panic
and recover
, with a specific use case:
panic
Create panic
a directory and write panic
_test.go. panic
is a built-in function used to cause a runtime panic. When the program encounters a fatal error that cannot continue execution, you can use panic
to interrupt the normal flow of the program. But misuse should be avoided panic
as it can cause the program to crash without providing a friendly error message. Typically panic
used 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 index
exceeds arr
the range of the slice, it will be triggered panic
, causing the program to crash. In this case, panic
it is used to indicate an unrecoverable error in the program.
recover
Create recover
a directory and write recover
_test.go. recover
Also a built-in function for recovering from panic
runtime panics caused by . It can only defer
be used inside a delay function ( ) and is used to restore the program's control flow, not to handle errors. Typically, after an occurrence panic
, recover
you 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, panic
after triggering, cleanup
the in the function recover
is 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 panic
the place where it was triggered, so fmt.Println
it will not be executed.
In summary, panic
and recover
should 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. panic
You should only consider using and in specific circumstances, such as when encountering an unrecoverable error recover
.
4. Custom error types
Create define
a directory and write error_define
_test.go.
In Go, you can define your own error types as needed, just meet error
the 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 error
the requirements of the interface, that is, implement Error() string
the 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:
-
TimeoutError
Structure: Defines a custom error typeTimeoutError
, containing the following fields:Operation
: Operation name, indicating which operation timed out.Timeout
: Timeout time, indicating the time point when the operation times out.
-
Error()
Method:TimeoutError
Implements the methoderror
of the interface for the structureError()
, which is used to generate a text description of the error. -
PerformOperation()
Function: simulates performing an operation and may time out. In this example, if the current time exceeds the timeout, anTimeoutError
error of type is returned. -
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.
- Call
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, TimeoutError
additional information about timeout operations and timeout periods is provided.