Go 语言初探:从基础到实战

news2024/12/28 4:31:04

1.Go概述

程序是一段计算机指令的有序组合。程序=算法+数据结构。任何程序都可以将模块通过三种基本的控制结构(顺序、分支、循环)进行组合来实现。

Go(也称为Golang)是一种由Google开发的开源编程语言。设计目标是使编程更简单、高效和可靠。Go旨在提供高性能、简洁且易于理解的语法。它结合了传统编译型语言的速度和性能,以及动态类型语言的易用性和便捷性。
Go语言特性:

1.静态编译

2.少即是多,语法简洁简单,可读性强

3.原生支持并发编程

4.Duck模型的非侵入式接口

5.强调组合,组合优于继承

6.支持多种操作系统和体系结构的交叉编译,这里的 targetOS 是目标操作系统(如 windows、linux、darwin 等),targetArchitecture 是目标体系结构(如 amd64、arm、386 等)。

GOOS=targetOS GOARCH=targetArchitecture go build
GOOS=windows GOARCH=amd64 go build

7.大量使用接口和内置函数来提高代码的复用度

8.支持和C 语言相互调用的机制(CGO),Go 中使用 C 包来调用 C 函数,以及使用特殊的类型来处理在 Go 和 C 之间传递的数据。以下是一个简单的示例,展示了如何在 Go 中调用 C 函数:

#include <stdio.h>

void helloFromC() {
    printf("Hello from C!\n");
}
package main

/*
#cgo CFLAGS: -g -Wall
void helloFromC();
*/
import "C"

func main() {
    C.helloFromC()
}

9.精确依赖,并通过增量编译、并行编译以及缓存编译结果来缩短编译时间

Go语言基本命令:

2.Go基本语法

Go语言中共有25个关键字,是静态强类型语言。

强类型:编译器会确认每个变量应有的类型,错误使用将引发错误;

静态:仅支持编译时自动推断类型

在这里插入图片描述

Go语言中的条件语句,if判断后面不需要(),同时if可带一个初始化子语句用;跟条件分开。同时Go也不支持三元运算符

/*
if SimpleStmt;Expression {
	statement
	......
}
*/
if i:=10;i>8 {
    //条件语句
}

switch语句说明:

注意:math.Floor(num)函数用于返回小于num的最小整数

同时,switch是惰性求值,只有在需要求值时才去计算表达式,从而降低消耗,提升性能。
在这里插入图片描述

for循环:

1.GO 的循环语句只有for ,没有while/do while
2.for 语句后面同样不用加( )
3.for语句的三个部分,省略任何一个时,分号不能省略
4.只留条件判断时,可以不用分号 (相当于while语句)
5.全部省略,变为无限循环

//while:
for experssion {
    
}
//无限循环
for {
    if state {
        break
    }
}

goto 可以跟标签更紧密合作,可以代替break 跳出多重循环

手动实现排序

手写实现的冒泡排序,Go代码如下所示:

这里需要注意,切片作为参数传递是引用传递!

func bubbleSort(nums []int){
    n:=len(nums)
    // 这里i是定义排序好的数量
    for i:=0;i<n-1;i++ {
        // 每次排序都是从第一个元素开始冒泡
        for j:=0;j<n-1-i;j++ {
            if nums[j]>nums[j+1] {
                nums[j+1],nums[j]=nums[j],nums[j+1]
            }
        }
    }
}

手写实现插入排序,Go代码如下所示:

func insertSort(nums []int){
    n:=len(nums)
    // 从无序组第二个元素开始依次插入有序组中
    for i:=1;i<n;i++{
        key:=nums[i]
        j:=i-1
        for j>=0 && key<nums[j]{
            nums[j+1]=nums[j]
            j--
        }
        nums[j+1]=key
    }
}

手动实现快速排序,具体Go代码如下

func quickSort(nums []int){
    n:=len(nums)
    if n<2 {
        return 
    }
    // 定义基准线
    pivot:=nums[0]
    low,high:=0,n-1
    for low<=high {
        if nums[low]<=pivot{
            low++
        }else{
            nums[low],nums[high]=nums[high],nums[low]
            high--
        }
    }
    // 交换基准元素位置
    nums[0],nums[high]=nums[high],nums[0]
    // 递归排序左右子数组
    quickSort(nums[:high])
    quickSort(nums[high+1:])
}

3.基础数据类型及运算

Go语言中基础数据类型有:整型、浮点型、复数、布尔型、字符型、字符串型以及错误类型

可以用 reflect.TypeOf 函数来查看类型名称

1.基础类型

1.整型

整型按照是否有符号可以分为:有符号位和无符号位

整型按照位数可以分为:int int8 int 16 int32 int32 int64

这里需要注意:不同的整型之间是不能直接比较,不能直接运算

2.浮点数

浮点数主要包括float32和float64

标准库math包中的函数都使用float64
在这里插入图片描述

3.复数

复数由两个浮点数表示,一个实部、一个虚部

有两种复数类型,即complex64(两个float32组成)和complex128(两个float64组成)

有三个内置复数处理函数

complex(float,float) 创建复数

real() 获取实部

image() 获取虚部

package main

import (
	"fmt"
)

func main() {
	// 创建复数
	var comp1 complex64 = complex(2, 3)     // 实部为2,虚部为3
	comp2 := complex(4.5, 7.1)             // 使用默认类型complex128

	// 输出复数
	fmt.Println("Complex 1:", comp1)
	fmt.Println("Complex 2:", comp2)

	// 访问实部和虚部
	fmt.Println("Real part of Complex 1:", real(comp1))     // 输出实部
	fmt.Println("Imaginary part of Complex 1:", imag(comp1)) // 输出虚部
}
4.布尔类型

布尔值主要包括true和false,类型长度为1byte

布尔类型无法被其他类型赋值,也不支持类型转换

这里布尔类型不支持用0和1表示真假

if 和for 语句的条件部分必须是布尔类型的值或表达式

2.运算符

运算符主要包括算术运算符、关系运算符、逻辑运算符、赋值运算符以及位运算符

1.算术运算符

算术运算符主要包括加减乘除、取模、自增、自减

**注意:**Go语言自增只支持变量++,不支持++变量,自减也是

2.关系运算符

关系运算符主要包括

==
!=
>
<
>=
<=

注意:由于布尔类型不支持转换整型,因此不等式连写,例如x<y<z这种语法是错误的!

3.逻辑运算符

逻辑运算符主要包括取反!、且&&、或||

4.赋值运算符

在这里插入图片描述

5.位运算符

位运算符包括

<< 左移 相当于乘以2
>> 右移 相当于除以2
&  位与
|  位或
^ 异或

位运算只对整数其作用,是底层运算,效率较高!
在这里插入图片描述

4.集合数据类型

Go语言中主要有三种类型的集合,分别是数组Array、切片Slice以及Map

1.数组

数组是同类元素的集合,数组变量声明后,其元素类型、数组长度均不可变

数组声明:

// 只声明未赋值H
var arr1 [5]int
// 直接赋值
arr2:=[3]int{1,2,3}
// 数组长度由初始化数量确定
arr3:=[...]int{1,2,3} //...不可省略
// 对含有下标的元素赋初值 其余元素保持零值
arr4:=[4]{0:99,3:100}

数组复制:

数组变量之间进行复制会拷贝整个数组(值拷贝)

	a := [...]string{"USA", "China", "India", "Germany"}
    b := a 
    b[0] = "Singapore"
    fmt.Println("a is ", a)
    fmt.Println("b is ", b)   
    //a is [USA China India Germany]  
    //b is [Singapore China India Germany]

数组传参:

与数组复制类型,只是实参拷贝一份给形参,函数调用结束则销毁,二者相互独立,传递大数组时效率较低!

func changeLocal(num [5]int) {
    num[0] = 55
    fmt.Println("inside function ", num)
}

func main() {
    num := [...]int{5, 6, 7, 8, 8}
    fmt.Println("before passing to function ", num)
    changeLocal(num) //num is passed by value
    fmt.Println("after passing to function ", num)
}
//before passing to function  [5 6 7 8 8]  
//inside function  [55 6 7 8 8]  
//after passing to function  [5 6 7 8 8] 

数组遍历:

数组遍历可以采用for循环遍历或range遍历

// for循环
 	a := [...]float64{67.7, 89.8, 21, 78}
    for i := 0; i < len(a); i++ { 
        fmt.Printf("%d th element of a is %.2f\n", i, a[i])
    }
// range遍历
	a := [...]float64{67.7, 89.8, 21, 78}
    for i, v := range a { // 第一个参数为序号,第二个为变量
        fmt.Printf("%d the element of a is %.2f\n", i, v)
    }
    //0 the element of a is 67.70  
    //1 the element of a is 89.80  
    //2 the element of a is 21.00  
    //3 the element of a is 78.00

此外,range遍历的两个参数,如果只用一个会报错,可以用_占位符来表示只用一个参数

多维数组:

	a := [3][2]string{
        {"lion", "tiger"},
        {"cat", "dog"},
        {"pigeon", "peacock"}, //此处,不可忽略,否则报错
    }
    for _, v1 := range a {
        for _, v2 := range v1 {
            fmt.Printf("%s ", v2)
        }
        fmt.Printf("\n")
    }

2.切片

由于数组的定长性和值拷贝限制其使用,因此提供切片使用,即提供长度可变的数组引用

切片声明时不能给定底层数组大小,否则变成了数组声明,同时可以使用内置函数make来声明和初始化!

切片是引用类型,不支持==运算

创建切片:

这里需要注意,如果创建切片时指定底层数组,一旦切片改变,底层数组元素也会发生改变,因为切片是对原数组的引用。因此,多个切片可以共享同一个底层数组。

// 指定底层数组创建
 	a := [5]int{76, 77, 78, 79, 80}//底层数组
    s1 := a[0:4] // from a[0] to a[3]
    s2 := a[:4]  // from a[0] to a[3]
    s3 := a[2:5] // from a[2] to a[4]
    s4 := a[2:]  // from a[2] to a[4]
    fmt.Printf("%v\n%v\n%v\n%v", s1, s2, s3, s4)
    //[76 77 78 79]
    //[76 77 78 79]
    //[78 79 80]
    //[78 79 80]
// 同时创建数组和切片
//指定数组大小,只创建数组    
c := [3]int{6, 7, 8}
//不指定数组大小,返回切片引用,底层数组匿名 
d := []int{6, 7, 8}  
//用...推断数组大小,只创建数组 
e := [...]int{6, 7, 8} 

内置函数 len() 返回切片当前长度
内置函数cap()返回切片底层数组容量

切片动态增加:

内置函数 append() 动态扩展切片,在底层数组容量范围内,会直接覆盖底层数组元素

package main

import "fmt"

func main() {
    arr := [7]int{9, 8, 7, 6, 5, 4, 3}
    sli := arr[1:3]
    sli = append(sli, 20) // 增加一个20,切片容量扩展一倍
    fmt.Printf("%v\n", arr) //[9 8 7 20 5 4 3]
    fmt.Printf("%v\n", sli) //[8 7 20]
}

切片动态增加时,当超过底层数组容量大小时,会重新创建底层数组,并转移数据
切片增长在元素小于1000时,成倍增长,超过1000,增长速率大概为1.25

	cars := []string{"Ferrari", "Honda", "Ford"}
    fmt.Println("cars:", cars, "length", len(cars), "capacity", cap(cars))  
    //cars: [Ferrari Honda Ford] length 3 capacity 3
    fmt.Printf("%x\n", &cars[0])
    //c000080330
    cars = append(cars, "Toyota")
    fmt.Println("cars:", cars, "length", len(cars), "capacity", cap(cars))
    //cars: [Ferrari Honda Ford Toyota] length 4  capacity 6 //why 6
    fmt.Printf("%x\n", &cars[0])    
    //c0000a4000   //Why

切片合并:

内置函数 append() 还支持切片的合并,用…运算符把对应切片所有元素都取出

	veggies := []string{"potatoes", "tomatoes", "brinjal"}
    fruits := []string{"oranges", "apples"}
    food := append(veggies, fruits...) //... 不可忽略
    fmt.Println("food:", food)
    //food: [potatoes tomatoes brinjal oranges apples]

切片传参:

在函数传参时,复制的是结构体拷贝,实现引用传递

func subtactOne(numbers []int) {
    for i := range numbers {
        numbers[i] -= 2
    }
}
func main() {
    nos := []int{8, 7, 6}
    fmt.Println("slice before function call", nos)
    //slice before function call [8 7 6]
    subtactOne(nos)
    fmt.Println("slice after function call", nos)
    //slice after function call [6 5 4]
}

多维切片:

多维切片比多维数组灵活,每行元素个数不必相同

 	pls := [][]string{
        {"C", "C++", "C#"},
        {"JavaScript"},
        {"Go", "Rust"},
    }
    for _, v1 := range pls {
        for _, v2 := range v1 {
            fmt.Printf("%s ", v2)
        }
        fmt.Printf("\n")
    }
    //C C++ C# 
    //JavaScript 
    //Go Rust 

3.Map

Map用于存储一系列无序的键值对,是引用类型,不支持==运算(nil除外)

Map不是线程安全的,不支持并发写

1.初始化

Map零值不可用,只声明不初始化为nil值,不分配底层存储空间,不能添加元素
用字面量或make函数进行初始化后可以添加元素

	var m1 map[string]int
    fmt.Println(m1 == nil)
    //true
    //m1["a"] = 1 //error

    m2 := map[string]int{}
    fmt.Println(m2 == nil)
    //false
    m2["a"] = 1 //ok

    m3 := make(map[string]int)
    fmt.Println(m3 == nil)
    //false
    m3["a"] = 1 //ok
2.赋值

可在Map初始化后进行元素赋值,也可在Map初始化时直接元素赋值

	personSalary := make(map[string]int)
    personSalary["steve"] = 12000
    personSalary["jamie"] = 15000
    personSalary["mike"] = 9000
    //初始化时,直接赋值
    personSalary := map[string]int{
        "steve": 12000,
        "jamie": 15000,
    }

3.元素查找

Map元素通过下标访问其实可以返回两个值(底层实际为函数,Comma-ok 法)
1.对应的value
2.对应的key是否存在的布尔值

	personSalary := map[string]int{
        "steve": 12000,
        "jamie": 15000,
    }
    value, ok := personSalary["joe"]
    if  ok  == true {
        fmt.Println("Salary of joe is", value)
    } else {
        fmt.Println("joe not found")
    }
4.Map元素遍历

Map元素可以使用range 遍历,但不保证顺序

	personSalary := map[string]int{
        "steve": 12000,
        "jamie": 15000,
    }
    personSalary["mike"] = 9000
    for key, value := range personSalary {
        fmt.Printf("personSalary[%s] = %d\n", key, value)
    }
    //personSalary[mike] = 9000
    //personSalary[steve] = 12000
    // personSalary[jamie] = 15000
5.Map元素删除

使用内置函数delete()删除Map元素
1.key存在,对应元素被删除
2.key不存在,什么都不发生

	personSalary := map[string]int{
        "steve": 12000,
        "jamie": 15000,
    }
    personSalary["mike"] = 9000
    fmt.Println("map before deletion", personSalary)
    delete(personSalary, "steve")
    fmt.Println("map after deletion", personSalary)
    //map before deletion map[jamie:15000 mike:9000 steve:12000]
    //map after deletion map[jamie:15000 mike:9000]

5.Go函数

函数效率高则程序效率高,建议多用标准库函数

1.函数定义

//语法格式
func funcName(paramList)(resultList) {
	coding ……
}
//paramList = input1 type1,input2 type2 ……
//resultList = output1 type1,output2 type2 ……
//多个相邻相同类型参数可以使用简写
func add(a, b int) int {
    return a + b
}

Go函数中可以有多个返回值,同时返回值可以有变量名,并在函数体内可见。

**注意:**不支持函数重载,因为重载只是偶尔有用,但在实践中会引起无解和导致脆弱性

2.参数(不定参数)

不定参数,形参数目可变、不确定
不定参数声明语法格式: param … type

不定参数的形参在函数内是切片

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

上述不定参数是不定数量,但类型是相同的,如果要实现不定数量,同时不定类型,则需要通过接口类型interface{}作为参数实现

func printAll(vals ...interface{}) {
    for _, val := range vals {
        fmt.Println(val)
    }
}

3.匿名函数

匿名函数相当于函数字面量,可以使用函数的地方就可以使用匿名函数

//匿名函数直接调用
func(a,b int )int{
    return a-b
}(5,4)
//匿名函数赋值给函数变量
var sum = func(a,b int )int{
    return a+b
}
//函数作为返回值
func getFun(op string) func(a,b int )int {
    return func(a,b int )int{
        return a+b
    }
}

4.闭包

闭包=函数+引用环境,常见于在函数内部定义匿名函数,并且该匿名函数访问定义它的外部函数的作用域

package main
import "fmt"
func main() {
    // 外部函数外的变量
    outsideVar := 10
    // 内部函数,形成闭包
    closureFunc := func() {
        fmt.Println(outsideVar) // 闭包函数内部访问外部变量
    }
    closureFunc() // 调用闭包函数
}

函数柯里化:

函数柯里化就是把接收多个参数的函数变换成接收单一参数的函数

函数柯里化是一种将多参数函数转换为一系列单参数函数的过程。这种转换的结果是,原始函数可以通过一系列较少参数的函数来调用。

5.延迟调用(defer)

Go函数支持defer进行延迟调用

defer类似OOP语言异常处理中的final子句,常用来保证系统的资源的回收和释放。

	defer Println("last")
    Println("main body")
    Println("first")
    //main body
    //first
    //last

使用defer函数时,会把当时的实参值传递给形参,即使后序实参发生变化也不影响函数结果!

	a := 5
    defer fmt.Println(defer 注册函数时的a值", a)
    a = 10
    fmt.Println(“普通函数的a值", a)
    //普通函数的a值 10
    //defer 注册函数时的a值 5

此外,使用多个defer时,这些defer调用以先进后出(FILO)顺序在函数返回前被执行!

    name := "Naveen"
    fmt.Printf("Original String: %s\n", string(name))
    fmt.Printf("Reversed String: ")
    for _, v := range []rune(name) {
        defer fmt.Printf("%c", v)
    }
    //Original String: Naveen
    //Reversed String: neevaN

6.递归函数

形式上:一个正在执行的函数调用了自身(直接递归).

递归不能无限制调用,因为栈空间有限
递归中必须有完成终极任务的语句
递归调用参数逐渐逼近结束条件
递归的目的是简化设计使程序易读,但通常效率较低

6.结构体和方法

1.结构体

1.结构体定义

结构体把有内在联系的不同类型的数据统一成一个整体,使它们相互关联

结构体是变量的集合,从外部看是一个实体

type Employee struct{
    firstName string
    lastName  string
    age       int
    salary    int
}
2.带标签的结构体

结构体中的字段除了名字和类型外,还可以有一个可选的标签(tag)

标签是一个附属于字段的字符串,用于描述字段信息
标签还可以按key1:“value1” key2:“value2”键值对进行修饰,来提供编码、解码、ORM等转化辅助

可以使用反射,获取结构体标签中的每一个键值对

package main

import (
	"fmt"
	"reflect"
)

type Person struct {
	Name string `json:"name" validate:"required"`
	Age  int    `json:"age" validate:"min=18"`
}

func main() {
	p := Person{Name: "Alice", Age: 25}
	// 获取结构体字段的标签信息
	t := reflect.TypeOf(p)
	for i := 0; i < t.NumField(); i++ {
		field := t.Field(i)
		fmt.Printf("Field: %s, Tag: %s\n", field.Name, field.Tag)
	}
}
3.结构变量初始化

1.可以使用字段名初始化,这样不需要按顺序,未指定的字段为零值

    emp1 := Employee{
        firstName: "Sam",
        age:       25,
        salary:    500,
        lastName:  “Anderson”, //逗号不能忽略
    }

2.用字面量初始化,按字段类型声明顺序并全部设置,顺序不对或遗漏字段报错

emp2 := Employee{"Thomas", "Paul", 29, 800}
4.访问修改字段值

1.采用 结构变量.字段

emp := Employee{"Thomas", "Paul", 29, 800}
fmt.Println(emp.age)

2.采用(*结构变量指针).字段

emp := &Employee{"Sam", "Anderson", 55, 6000}
fmt.Println("First Name:", (*emp).firstName)

3.采用 结构变量指针.字段,不支持->

emp := &Employee{"Sam", "Anderson", 55, 6000}
fmt.Println("First Name:", emp.firstName)
4.匿名字段

结构体字段也可以省略字段名,字段名默认为对应数据类型名称(数据类型不能重复)

	type Person struct {
        string
        int
    }
    p := Person{"Naveen", 50}
    p.int =60

2.方法

方法是对具体类型行为的封装,本质上是绑定到该类型的函数。

OO语言的方法通常有个隐藏的this或self指针来指向对象,Go把这个隐藏指针暴露出来,称为接受者。

func (t Type) funcName(paramList)(resultList)
func (t *Type) funcName(paramList)(resultList)
1.方法实例
type Employee struct {
    name     string
    salary   int
    currency string
}
//定义方法
func (e Employee) displaySalary() {
    fmt.Printf("Salary of %s is %s%d", e.name, e.currency, e.salary)
}
func main() {
    emp1 := Employee{
        name:     "Sam Adolf",
        salary:   5000,
        currency: "$",
    }
    emp1.displaySalary() 
}

其实方法可以使用等价的函数实现,具体如下所示:

type Employee struct {  
    name     string
    salary   int
    currency string
}
func displaySalary(e Employee) {  
    fmt.Printf("Salary of %s is %s%d", e.name, e.currency, e.salary)
}
func main() {  
    emp1 := Employee{
        name:     "Sam Adolf",
        salary:   5000,
        currency: "$",
    }
    displaySalary(emp1)
}

既然函数可以做,那为什么还需要方法呢?

在 Go 语言中,方法和等价的函数都能完成类似的工作。虽然它们可以完成相同的任务,但方法和函数之间存在一些差异和适用场景,其中方法更适合于特定类型的操作和面向对象的编程

GO 的函数不能重载,导致不同类型不能用同名函数,而不同类型的方法可以同名
GO 不支持class ,使用结构代替类,结构字段用来封装对象属性,方法用来封装对象的行为

此外,方法并非结构体专有,所有自定义类型都可以定义方法

type myInt int //自定义类型

func (a *myInt) add(b myInt) myInt {
    return *a + b
}

num1 := myInt(5)
num2 := myInt(10)
sum := num1.add(num2)

7.接口

Go语言中的接口是Duck模型的非侵入式接口,与传统的接口不同,非侵入式接口其具体类型实现接口不需要显式声明,只要其方法集是接口的超集,编译时会进行对应校验!

GO 接口只有方法签名,没有数据字段,没有函数体代码

类型的方法集是多个接口的超集,则实现多个接口

1.接口类定义

// 命令接口类型
type interfaceName interface{//接口类型命名通常以er为后缀
    methodName(paramList)(resultList)
    otherInterfaceName
}
// 匿名接口类型
interface{
    methodName(paramList)(resultList)
    otherInterfaceName
}

而如果匿名接口中方法集为空,即是interface{}是一种空接口,所有的类型都实现了空接口,都可以赋值或传递给空接口。

2.接口初始化

只声明未赋值的接口变量为nil
接口变量初始化需要把接口绑定到具体类型实例
未初始化的接口变量不能调用其方法
方法的接收者才能给接口变量赋值
接口变量的值包括底层类型的值和具体类型

package main

import (
	"fmt"
)

// 接口定义
type Speaker interface {
	Speak() string
}

// 实现接口的结构体
type Dog struct{}

// Dog 结构体实现 Speak 方法
func (d Dog) Speak() string {
	return "Woof!"
}

// 创建接口的实例
func NewSpeaker() Speaker {
	return Dog{} // 返回一个 Dog 类型,它满足了 Speaker 接口
}

func main() {
	// 初始化接口并调用方法
	speaker := NewSpeaker()
	fmt.Println(speaker.Speak())
}

此外,一个接口可以包含一个或者多个接口,即嵌套接口

type ReadWrite interface {
    Read(b Buffer) bool
    Write(b Buffer) bool
}
type File interface{
    ReadWrite
    close() bool
}

3.接口类型断言

接口类型断言用来判断实现某个接口的变量是否为某个类型

若是,则返回该类型的值和true

不是,则返回该类型的零值和false

// interfaceName.(typeName)
    var a interface{} = 56
    v, ok := a.(int)
    fmt.Println(v, ok)
    //56 true
    var b interface{} = true
    v, ok = b.(int)
    fmt.Println(v, ok)
    //0 false

4.接口类型查询

接口类型查询是使用swtich语句确定接口变量底层类型

.(type)只能用于switch表达式是因为变量底层类型判断只能用接口类型断言,go 只能判断变量内存格式是否匹配某种类型,并按某种类型来解析值

func findType(i interface{}) {
    switch i.(type) {//.(type)只能用于switch表达式
    case string:
        fmt.Printf("string and value is %s\n", i.(string))
    case int:
        fmt.Printf("int and value is %d\n", i.(int))
    default:
        fmt.Printf("Unknown type\n")
    }
}
findType(77)
findType(89.98)
// int and value is 77 
// Unknown typ

5.Stringer接口

Stringer 接口是 Go 语言中的一个接口,它只包含一个方法:String()用于返回该类型的字符串表示形式。这个接口通常被用来自定义类型的字符串输出格式。

在 Go 语言中,如果某个类型实现了 Stringer 接口,那么你可以使用 fmt 包中的打印方法(如 PrintlnSprintf)来自定义该类型的输出方式。

以下是 Stringer 接口的定义:

type Stringer interface {
    String() string
}

接口的 String() 方法返回一个字符串。实现了 Stringer 接口的类型可以定义自己的 String() 方法,以便自定义该类型的字符串输出

以下是一个简单的示例,演示了如何使用 Stringer 接口:

package main
import (
    "fmt"
)
type Person struct {
    Name string
    Age  int
}
// 实现 Stringer 接口
func (p Person) String() string {
    return fmt.Sprintf("%s is %d years old", p.Name, p.Age)
}
func main() {
    person := Person{"Alice", 30}
    fmt.Println(person) // 输出: Alice is 30 years old
}

在这个例子中,Person 类型实现了 Stringer 接口,重写了 String() 方法,这样当我们使用 fmt.Println 打印 Person 类型的变量时,会调用 String() 方法,并输出该类型的自定义字符串格式。

总结,实现了Stringer接口的类型变量,使用fmt.Println方法打印该对象时,可以按照指定一定的格式输出,与Java中重写toString方法类似。

6.Sorter接口

标准库的sort包,定义排序要实现三个方法:

//Len()   反映元素个数的方法
//Less(i, j)  比较第 i 和 j 个元素
//Swap(i, j) 交换第 i 和 j 个元素
// 具体Sorter接口定义如下
type Sorter interface {
    Len() int
    Less(i, j int) bool
    Swap(i, j int)
}

请基于Sorter接口实现冒泡排序:

package main

import (
	"fmt"
)

type Sorter interface {
	Len() int
	Less(i, j int) bool
	Swap(i, j int)
}

type IntArray []int

func (arr IntArray) Len() int {
	return len(arr)
}

func (arr IntArray) Less(i, j int) bool {
	return arr[i] < arr[j]
}

func (arr IntArray) Swap(i, j int) {
	arr[i], arr[j] = arr[j], arr[i]
}

func BubbleSort(data Sorter) {
	n := data.Len()
	for i := 0; i < n-1; i++ {
		for j := 0; j < n-i-1; j++ {
			if data.Less(j+1, j) {
				data.Swap(j, j+1)
			}
		}
	}
}

func main() {
	array := IntArray{64, 34, 25, 12, 22, 11, 90}
	fmt.Println("Unsorted array:", array)

	BubbleSort(array)
	fmt.Println("Sorted array:", array)
}

7.接口特性

接口特性简称动静结合

接口静态特性:

支持在编译阶段的类型检查:当一个接口类型变量被赋值时,编译器会检查右值的类型是否实现了该接口方法集合中的所有方法。

接口动态特性:

即:使用空接口变量可以使用不同类型的变量赋值

在运行时存储在接口类型变量中的值的真实类型。比如:var i interface{} = 13中接口变量i的动态类型为int。
在运行时可以被赋值为不同的动态类型变量,从而支持运行时多态。

8.反射

变量的最基本信息是类型和值,反射可以在程序运行时检查变量的类型和值

通过反射可以获取结构体变量的各字段信息,甚至包括结构字段的tag信息

package main
import (
	"fmt"
	"reflect"
)
type Person struct {
	Id   int //首字母大写表示公开字段
	Name string
	Sex  string
}
func (this Person) Call() {
	fmt.Println("我正在打电话")
}
func getTypeAndValue(object interface{}) {
	//动态获取对象object的类型信息
	objectType := reflect.TypeOf(object)
	objectValue := reflect.ValueOf(object)
	fmt.Println("type =", objectType.Name())
	fmt.Println("type =", objectType, "value =", objectValue)
	// objectType.NumField() 获取字段的总数
	for i := 0; i < objectType.NumField(); i++ {
		field := objectType.Field(i)
		value := objectValue.Field(i)
		fmt.Printf("type %d = %v\n", i, field.Type)
		fmt.Printf("name %d = %v\n", i, field.Name)
		fmt.Printf("value %d = %v\n", i, value.Interface())
	}
	for i := 0; i < objectValue.NumMethod(); i++ {
		method := objectValue.Method(i)
		method.Call(nil)
	}
}
func main() {
	person := Person{1, "nancy", "mail"}
	getTypeAndValue(person)
}

8.错误处理

Go语言里没有异常机制,只有错误处理,错误通过函数的多返回值来处理

Go语言的错误主要有:编译时错误、运行时错误以及逻辑错误

Go语言的错误处理方式

1.可处理,通过函数返回错误进行处理

2.不可处理,通过panic抛出错误,退出程序

1.error接口

通过error 接口 实现错误处理的标准模式,打印错误时自动调用Error()函数

type error interface{
    Error() string
}

可能出错的函数最后一个返回值为错误类型,检查该返回值是否为nil,是则处理错误,否则正常调用

 	f, err := os.Open("/test.txt")
    if err != nil {
        fmt.Println(err)
        return
    }
    fmt.Println(f.Name(), "opened successfully")

2.if快乐路径原则

“快乐路径”原则是编程中的一种设计理念,其指导思想是使函数的主要路径尽可能保持“快乐”,也就是函数的主要工作或逻辑能够尽快完成,而不被意外情况干扰。

一个例子就是通过合理的错误检查和返回,将错误处理逻辑放在函数的开头,而将主要逻辑和处理放在函数的主要部分。这样可以尽早退出函数并返回错误,但同时保持主要逻辑在函数主体内部,让主逻辑尽可能快乐。

func PerformTask(param int) (result int, err error) {
    // 错误检查放在前面
    if param < 0 {
        return 0, errors.New("param cannot be negative")
    }

    // 主逻辑放在主体内部
    // 这里是函数的主要逻辑,称为快乐路径
    result = param * 2
    return result, nil
}

3.自定义错误

error是Go语言内置的接口类型,只有一个方法Error(),用于返回错误信息的字符串表示。

标准库中errors包提供了创建简单错误信息的函数。

package main

import (
	"errors"
	"fmt"
)

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

func main() {
	result, err := divide(6, 2)
	if err != nil {
		fmt.Println("Error:", err)
	} else {
		fmt.Println("Result:", result)
	}

	result, err = divide(3, 0)
	if err != nil {
		fmt.Println("Error:", err)
	} else {
		fmt.Println("Result:", result)
	}
}

此外,还可以使用fmt包的Errorf 函数创建自定义错误!

package main

import (
	"fmt"
)
func someFunction() error {
	return fmt.Errorf("This is a more detailed error: %s", "specific error message")
}

func main() {
	err := someFunction()
	if err != nil {
		fmt.Println("Error:", err)
	}
}

当多处错误处理存在代码重复时,可以使用goto集中处理错误!

err := firstCheckError()
if err != nil {
    goto onExit
}
err = secondCheckError()
if err != nil {
    goto onExit
}
// 正常处理代码
onExit:
fmt.Println(err)
exitProcess()

4.panic

panic 是 Go 语言中的内建函数之一,用于在发生不可恢复的错误时引发程序中止。panic 会停止当前函数的执行,并向调用者传播一个引发恐慌的信号,随后程序将被终止。

通常情况下,panic 用于处理严重错误,如数组越界、空指针引用等。当它被调用时,程序将停止执行当前函数,开始执行延迟(defer)函数,然后程序会崩溃,并显示 panic 产生的错误信息。 在遇到 panic 时,程序的正常流程会被打破,不会再继续执行当前任务。

在开发过程中,尽量避免使用 panic,而应该在可以预测和处理的情况下使用错误返回(error returns)或其他适当的处理方式,因为 panic 不可恢复,容易引起程序不稳定。

package main
import "fmt"
func someFunc() {
    // 模拟一个无法处理的错误
    err := someErrorOccurred()
    if err != nil {
        panic("An unexpected error occurred: " + err.Error())
    }
}
func main() {
    fmt.Println("Starting the program.")
    someFunc()
    fmt.Println("End of the program.")
}

在这里插入图片描述

5.recover

在 Go 语言中,recover 函数用于恢复程序的执行,从恐慌状态(panic)中恢复。recover 只有在延迟函数(defer)的内部调用时才会生效。

通常情况下,recoverdefer 配合使用,以便在程序进入恐慌状态时恢复程序执行。

下面是一个使用 recover 来捕获并处理恐慌状态的示例:

package main

import (
	"fmt"
)

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

func someFunc() {
	defer recoverDemo()

	// 模拟一个恐慌状态
	panic("Something went wrong!")
}

func main() {
	fmt.Println("Starting the program.")
	someFunc()
	fmt.Println("End of the program.")
}

在这个示例中,recoverDemo 函数作为一个延迟函数(defer)在 someFunc 中执行。当 someFunc 函数引发了恐慌状态,recoverDemo 中的 recover 函数捕获到这个恐慌,然后打印出了错误信息。

请注意,recover 函数只在延迟函数中有效。在非延迟函数中调用 recover 是无效的,且只有在恐慌状态发生时才能捕获错误信息。

9.并发

1.进程、线程、协程

进程是程序在内存中运行时,操作系统对其进行资源分配和调度的独立单位
线程是进程的一个执行实体,是进程内部进行的一条执行路径,是 CPU 调度和分派的基本单位,它是比进程更小的能独立运行的基本单位
每个进程至少包括一个线程,每个进程的初始线程被称为主线程,主线程终止,进程终止
协程是轻量级的线程,一个线程可以拥有多个协程
进程和线程是操作系统级的,协程是编译器级的。协程不被操作系统内核管理,而完全由程序控制,因此没有线程切换的开销。和多线程比,数量越多,协程的性能优势就越明显。协程的最大优势在于其轻量级,可以轻松创建上万个而不会导致系统资源衰竭

2.goroutine

在Go语言中,Goroutine是并发执行的基本单元。它们是Go运行时环境中的轻量级线程,由Go调度器分配到逻辑处理器上执行。Goroutine的运行并不依赖于物理处理器或操作系统线程。每个逻辑处理器(P)负责运行Goroutine,多个P可以运行在一个物理处理器(CPU)上。

3.协程通信

不要通过共享内存来通信,而是通过通信来共享内存

协程间的通信常见是两种方式:

1.共享数据:很多语言采用共享内存来实现程序数据同步,确保程序以合乎逻辑的方式执行。在程序执行过程中,进程或线程可能对共享数据加锁,以禁止其他进程或线程修改它。总体编程复杂性高

2.消息机制:每个并发单元是独立个体,多个并发单元的数据不共享,通过消息通信来同步数据。

4.channel通道

通道是一种特殊的类型,同时只能有一个 goroutine 访问通道进行发送和获取数据。

通道写入和读取使用 <- 运算符
写入 :通道<-变量
读取: 变量<-通道

5.缓冲通道

通道包括无缓冲通道和有缓冲通道
无缓冲通道 make(chan datatype)
有缓冲通道 make(chan datatype,capacity)

无缓冲通道只能存储一条消息,有缓冲通道可以根据make函数的capacity参数存储n条消息,按FIFO读出

func receiver(c chan string) {
    for msg := range c {
        fmt.Println(msg)
    }
}
func main() {
    messages := make(chan string, 2)
    messages <- "hello"
    messages <- "world"
    go receiver(messages)   
    time.Sleep(time.Second * 1)
}//hello world	

此外,还能使用内置函数返回缓冲通道状态

​ len()获取通道当前缓存数
​ cap()获取通道缓存容量

    ch := make(chan string, 3)
    ch <- "naveen"
    ch <- "paul"
    fmt.Println("capacity is", cap(ch))
    fmt.Println("length is", len(ch))
    fmt.Println("read value", <-ch)
    fmt.Println("new length is", len(ch))
    //capacity is 3
    //length is 2
    //read value naveen
    //new length is 1

无缓冲通道,写入等待读取,读取等待写入,在双方准备好之前是阻塞的

有缓冲通道,通道已满时的写入会等待,通道已空的读取会等待

6.关闭通道

关闭通道使用内置函数close(),实际上是关闭写入,即发送者告诉接收者不会再有数据发往通道

接收者能够在通道接收数据的同时,获取通道是否已关闭的参数 。

func producer(chnl chan int) {  
    for i := 0; i < 10; i++ {
        chnl <- i
    }
    close(chnl)
}
func main() {  
    ch := make(chan int)
    go producer(ch)
    for {
        v, ok := <-ch
        if ok == false {
            break
        }
        fmt.Println("Received ", v, ok)
    }
}

此外,for range能够自动判断通道是否关闭,具体代码如下所示:

func producer(chnl chan int) {  
    for i := 0; i < 10; i++ {
        chnl <- i
    }
    close(chnl)
}
func main() {  
    ch := make(chan int)
    go producer(ch)
    for v := range ch {
        fmt.Println("Received ",v)
    }
}

7.WaitGroup

sync.WaitGroup 用于等待一组 Go 协程执行完成后再执行主程序的方法。它提供了一个简单的机制,以便主程序知道其他所有协程何时执行完成。

在使用 sync.WaitGroup 时,主要有三个函数:

  • Add(int):增加要等待的协程数量。
  • Done():标志已完成的协程。
  • Wait():等待所有的协程都完成。

通常,Add 函数用于计数要等待的协程数量,然后在协程的函数中使用 Done 标志已经执行完毕,最后使用 Wait 阻塞主程序,直到所有协程都执行完毕。

image-20231107161709866

以下是一个示例,演示了 sync.WaitGroup 的用法:

package main

import (
	"fmt"
	"sync"
	"time"
)

func worker(id int, wg *sync.WaitGroup) {
	defer wg.Done() // 标志协程完成
	fmt.Printf("Worker %d starting\n", id)
	time.Sleep(time.Second) // 模拟工作
	fmt.Printf("Worker %d done\n", id)
}

func main() {
	var wg sync.WaitGroup

	for i := 1; i <= 5; i++ {
		wg.Add(1) // 增加等待的协程数量
		go worker(i, &wg)
	}

	wg.Wait() // 等待协程执行完成
	fmt.Println("All workers have finished")
}

在这个示例中,我们启动了五个协程,每个协程模拟一些工作(通过 time.Sleep 模拟)。Add 用于增加要等待的协程数量,Done 标志协程已执行完成,而 Wait 阻塞了主程序直到所有的协程都执行完毕。

8.猜数字例题

这里分享一道使用协程的例题,具体例题如下:感兴趣的小伙伴可以尝试一下
在这里插入图片描述

9.计时器Timer

协程间的通信需设置超时等辅助机制

一次性计时器:定时器只计时一次,结束便停止

package main

import (
	"fmt"
	"time"
)

func main() {
	timer1 := time.NewTimer(2 * time.Second)

	<-timer1.C // 阻塞等待定时器信号
	fmt.Println("Timer 1 expired")

	timer2 := time.NewTimer(1 * time.Second)
	go func() {
		<-timer2.C
		fmt.Println("Timer 2 expired")
	}()

	stop2 := timer2.Stop() // 停止定时器2
	if stop2 {
		fmt.Println("Timer 2 stopped")
	}
}

在这里插入图片描述

10.定时器Ticker

周期性定时器:定时器周期性进行计时,除非主动停止,否则将永久运行

在Go语言中,time.Ticker 是用于重复间隔性触发操作的工具。与 time.Timer 不同,time.Ticker 会在一定的时间间隔内重复向通道发送时间事件。

主要方法如下所示:

func NewTicker(d Duration) *Ticker 指定一个时间创建一个Ticker , Ticker一经创建便开始计时,不需要额外的启动命令
func (t *Ticker) Stop() 停止计时,但管道不会被关闭

示例代码如下所示:

package main

import (
	"fmt"
	"time"
)

func main() {
	ticker := time.NewTicker(1 * time.Second)
	defer ticker.Stop()

	for {
		select {
		case <-ticker.C:
			fmt.Println("Ticker ticked")
		}
	}
}

11.select

多路复用是在一个信道上传输多路信号或数据流,比如网线

select 借用网络多路复用的概念,用于监听多个通道,同时响应多个通道

多个通道都没有可写或可读的状态,select 会阻塞
有一个通道是可写或可读的, select 会执行该通道语句
有多个通道是可写或可读的, select 会随机选择其中一个执行

select 语句是 Go 语言用于处理通道操作的关键工具。它可以同时监听多个通道操作,一旦某个通道可操作(有消息可以接收或发送),就执行相应的 case 语句。select 语句有点类似于 switch 语句,但是专门用于通道的操作。

下面是一个示例,演示了 select 语句的用法:

package main

import (
	"fmt"
	"time"
)

func main() {
	ch1 := make(chan string)
	ch2 := make(chan string)

	go func() {
		time.Sleep(2 * time.Second)
		ch1 <- "one"
	}()

	go func() {
		time.Sleep(1 * time.Second)
		ch2 <- "two"
	}()

	for i := 0; i < 2; i++ {
		select {
		case msg1 := <-ch1:
			fmt.Println("Received", msg1)
		case msg2 := <-ch2:
			fmt.Println("Received", msg2)
		}
	}
}

在这个示例中,通过两个协程向两个不同的通道 ch1ch2 发送消息。select 语句会监听这两个通道的状态,一旦有数据可接收,就执行相应的 case 语句,最终输出接收到的消息。

12.Mutex

多个线程同时竞争使用某个变量可能会导致结果失控

mutex,互斥锁,用来保证某个变量在任一时刻,只能有一个线程访问;mutex 用Lock()和Unlock()来创建资源的临界区,这一区间内的代码是线程安全的,任何一个时间点都只能有一个goroutine执行这段区间的代码

mutex 也可以用通道来代替,通道底层基于mutex,即mutex性能更高,通常不涉及线程交互数据的用mutex,其他性能要求不敏感用通道

package main

import (
	"fmt"
	"sync"
)

var count = 0
var mutex sync.Mutex

func increment() {
	mutex.Lock()         // 通过 Lock() 方法锁住共享资源
	count++
	mutex.Unlock()       // 通过 Unlock() 方法解锁共享资源
}

func main() {
	var wg sync.WaitGroup

	for i := 0; i < 1000; i++ {
		wg.Add(1)
		go func() {
			defer wg.Done()
			increment()
		}()
	}

	wg.Wait()

	fmt.Println("Count:", count)
}

13.RWMutex

Mutex 在大量并发时,同一时刻只有一个协程持有锁,其他阻塞等待,性能下降
RWMutex在Mutex的基础之上增加了读、写的信号量,并使用了类似引用计数的读锁数量,可使多个协程持有读锁,适合应用在具有一定并发量且读多写少的场合。

注意:

RWMutex中可以申请多个读锁,有读锁时申请写锁将会被阻塞

只要有写锁,后序申请读锁和写锁都会被阻塞

主要方法如下:

func (rw *RWMutex) Lock() //申请写锁
func (rw *RWMutex) Unlock() //释放写锁
func (rw *RWMutex) RLock() //申请读锁
func (rw *RWMutex) RUnlock()//释放读锁
package main
import (
	"fmt"
	"sync"
)

var sharedData int
var rwMutex sync.RWMutex

func readData() {
	rwMutex.RLock() // 读取共享资源时使用 RLock() 方法
	defer rwMutex.RUnlock()
	fmt.Println("Read Data:", sharedData)
}

func writeData(value int) {
	rwMutex.Lock() // 写入共享资源时使用 Lock() 方法
	defer rwMutex.Unlock()
	sharedData = value
	fmt.Println("Write Data:", value)
}

func main() {
	// 读取数据
	for i := 0; i < 5; i++ {
		go readData()
	}

	// 写入数据
	for i := 0; i < 5; i++ {
		go writeData(i)
	}

	// 等待所有协程执行完毕
	fmt.Scanln()
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1184952.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

配置802.1x本地认证,以识别用户身份的示例

组网图形 图1 802.1x本地认证组网图 规格组网需求操作步骤配置注意事项 规格 适用于所有版本、所有形态的AR路由器。 组网需求 PC1(10.10.10.2/30)直接连接到RouterA的Eth2/0/1端口&#xff0c;RouterA的VLANIF10接口IP地址10.10.10.1/30&#xff08;为PC1上的网关IP地址&a…

基于CLIP的图像分类、语义分割和目标检测

OpenAI CLIP模型是一个创造性的突破&#xff1b; 它以与文本相同的方式处理图像。 令人惊讶的是&#xff0c;如果进行大规模训练&#xff0c;效果非常好。 在线工具推荐&#xff1a; Three.js AI纹理开发包 - YOLO合成数据生成器 - GLTF/GLB在线编辑 - 3D模型格式在线转换 - 3D…

一维码和二维码图像优化——提高读码率

1.算子 1.1 decompose3 &#xff08;彩色图像分割算子&#xff09; 算子&#xff1a;decompose3 ——将三通道图像转换为三个图像函数原型&#xff1a;decompose3(MultiChannelImage : Image1, Image2, Image3 : : ) 功能&#xff1a;将3通道图像转换为具有相同定义域&#…

智慧安防:监控防盗两不误的安防视频监控系统是什么样的?

随着社会的不断发展&#xff0c;安全问题越来越受到人们的关注&#xff0c;特别是对于居住在城市里的人们来说&#xff0c;盗窃问题是影响他们生活质量的重要因素之一。因此&#xff0c;根据市场需求&#xff0c;以监控防盗两不误的智慧监控系统得到了广泛的推广和应用。 一般…

不充不行(同时跑三辆车)

欢迎来到程序小院 不充不行 玩法&#xff1a;点击鼠标左键长按充电桩&#xff0c;别让车落回底线&#xff0c;三辆车同时在跑&#xff0c;要控制三个充电桩的电量&#xff0c;电量为0即为游戏结束&#xff0c;看看你能坚持多少秒哦^^。开始游戏https://www.ormcc.com/play/gam…

迅镭激光与江苏中红外激光研究院达成战略合作意向

11月6日&#xff0c;江苏中红外激光研究院院长沈德元、江苏师范大学物电学院系主任韩彩芹、江苏中红外激光研究院技术副总王飞等领导莅临迅镭激光调研指导并进行合作会谈&#xff0c;迅镭激光董事长颜章健热情接待。双方就成果转化、产业合作、专业人才培养等方面进行深入洽谈&…

Centos7下安装-使用K3S

本文主要内容&#xff1a; 1.安装k3s 2.在idea中&#xff0c;编辑yml文件创建pod 3.在k3d中pod基本操作 4.在k3d中Labeles标签基本操作 5.在k3d中容器的基本操作 &#xff08;k3s的操作与k8s操作不同处在于&#xff0c;k3s每次执行命令&#xff0c;前面需要加上k3s&#xff09;…

猫罐头什么牌子好?2023营养又美味的猫主食罐头推荐!

亲爱的猫咪主人&#xff0c;你是否为你家小猫咪的挑食问题感到困扰&#xff1f;作为一位在宠物店工作了七年&#xff0c;负责喂养三十多只猫咪的店长&#xff0c;我对许多品牌的猫罐头都非常熟悉了。对于猫罐头哪个牌子好这个问题&#xff0c;我想借此机会分享一些见解。 在本…

linux基础:3.linux基础环境开发工具和配置。

linux基础环境开发工具和配置 一.学习yum工具进行软件安装&#xff1a;1.什么是yum&#xff1a;2.查看软件包&#xff1a;3.安装和删除&#xff1a;4.yum生态&#xff1a; 二.vim的使用&#xff1a;一.快速介绍一下vim二.vim正常模式&#xff1a;2-1&#xff1a;命令模式1.光标…

HarmonyOS应用开发-ArkTS基础知识

作者&#xff1a;杨亮Jerry 作为多年的大前端程序开发工作者&#xff0c;就目前的形式&#xff0c;个人浅见&#xff0c;在未来3-5年&#xff0c;移动端依旧是Android系统和iOS系统的天下。不过基于鸿蒙系统的应用开发还是值得我们去花点时间去了解下的&#xff0c;阅读并实践官…

一杯子三变:揭秘vue单页应用(spa)与内容动态加载的奥秘

&#x1f3ac; 江城开朗的豌豆&#xff1a;个人主页 &#x1f525; 个人专栏 :《 VUE 》 《 javaScript 》 &#x1f4dd; 个人网站 :《 江城开朗的豌豆&#x1fadb; 》 ⛺️ 生活的理想&#xff0c;就是为了理想的生活 ! 目录 ⭐ 专栏简介 &#x1f4d8; 文章引言 一、什…

找不到x3daudio1_7.dll怎么办?五种解决方法帮你解决x3daudio1_7.dll问题

在计算机使用过程中&#xff0c;我们经常会遇到一些错误提示&#xff0c;其中之一就是“X3DAudio1_7.dll丢失”。这个错误通常会导致音频播放异常或无法正常工作。为了解决这个问题&#xff0c;本文将介绍5种修复X3DAudio1_7.dll丢失的方法&#xff0c;帮助大家快速恢复X3DAudi…

Redis的三种特殊数据类型

文章目录 一、Redis geospatial 地理位置二、Redis Hyperloglog 基数统计的算法三、Redis Bitmaps 位存储&#xff08;0、1&#xff09;总结 一、Redis geospatial 地理位置 1.geoadd&#xff1a;将指定的地理空间位置&#xff08;纬度、经度、名称&#xff09;添加到指定的ke…

vue中 process.env 对象为空对象问题

问题&#xff1a;今天在处理vue项目环境问题的时候&#xff0c;发现直接打印 process 对象和打印 process.env 时 env 对象输出结果是不一样的&#xff0c;如下图所示&#xff1a; 在网上搜索了一番后发现还是有挺多朋友对此感到疑惑的&#xff0c;询问了同事&#xff0c;同…

VINS-Mono-后端优化 (一:预积分残差计算-IMU预积分约束)

这里先回顾一下预积分是怎么来的 VINS-Mono-IMU预积分 &#xff08;三&#xff1a;为什么要预积分预积分推导&#xff09; 这里贴出预积分的公式 具体含义解释看对对应的文章 整个误差函数如下 预积分 α \alpha α β \beta β γ \gamma γ 是用 IMU 预积分获得的增量&a…

Xtrabackup将本地数据迁移上云

本机和云端服务器安装xtrabackup #为了防止每次yum操作都会自动更新&#xff0c;卸载这个软件 yum -y remove mysql57-community-release-el7-10.noarch#下载XtraBackup yum install -y https://repo.percona.com/yum/percona-release-latest.noarch.rpm#激活该yum仓库 percon…

vue使用Echarts5实现词云图

先上官网 词云图有些特殊&#xff0c;它属于Echarts 的扩展&#xff0c;需要额外安装Echarts-wordcloud包。 Echarts 官网 Echarts-wordcloud 词云图官网 先安装 npm install echarts-wordcloud npm install echarts echarts-wordcloud再引入 echarts选一个引入就行&#xff…

Unity 利用UGUI制作圆形进度条

在Unity中使用Image和Text组件就可以制作简单的进度条。 1、首先准备好一张环状的PNG图&#xff0c;如下图。 2、把该图导入Unity中并转换成精灵。 3、在场景中创建Image和Text组件&#xff0c;并把上图中的精灵拖到Image的Source Image中&#xff0c;其中Image组件中的Image …

出口美国操作要点汇总│走美国海运拼箱的注意事项│箱讯科技

01服务标准 美国的货物需要细致的服务&#xff0c;货物到港后的服务也是非常重要的。如果在货物到港15天内&#xff0c;如果没有报关行进行(PROCEED)&#xff0c;货物就会进入了G.O.仓库&#xff0c;G.O.仓库的收费标准是非常高的。 02代理资格审核 美国航线除了各家船公司&a…

ORA-00257: Archiver error. Connect AS SYSDBA only until resolved错误解决

错误的原因&#xff1a;是因为服务器分配空间不足&#xff0c;数据库归档日志满导致系统数据库登陆失败。 解决办法&#xff1a;1.删除以前的日志 2.增大归档日志的容量 3.关闭归档模式 一、删除以前的容量 1.登录账号后&#xff0c;查看ORACLE_BASE目录 【oraclelocalhost~】$…