当前位置: 首页 > article >正文

一天速转golang!

首先你需要会一门后端语言,以及要有基本的网络编程能力和并发思想

环境

本人在linux环境下下载的go1.23.3版本,使用Vim+go作为IDE

具体做法此处不再赘述,自行查阅其他博客

第一个Golang程序

  • package main ——定义声明包名(可以类比java来理解go)

​ 必须在源文件中非注释的第一行指明这个文件属于哪个包,如:package main。package main表示一个可独立执行的程序,每个 Go 应用程序都包含一个名为 main 的包。

  • import "fmt" ——引入包

告诉 Go 编译器这个程序需要使用 fmt 包(的函数,或其他元素),fmt 包实现了格式化 IO(输入/输出)的函数。(本例中的println函数)

四种变量的声明方式

省去var的方式只用于局部变量!

//声明全局变量 方法一二三都可以 方法四不可以!
func main(){
    //方法一:声明一个变量,用var int的话,不给他初始值,默认为0
    var a int
    fmt.Println("a = ", a) //a = 0
    
    //方法二:声明一个变量,给他一个初始值
    var b int = 100
    fmt.Println("b = ", b) //b = 100

    //方法三:初始化的时候,可以省去数据类型,通过值自动匹配当前变量的数据类型
    var c = 100
    fmt.Println("c = ", c) //c = 100
    fmt.Println("type of c = %T\n", c) //打印c的类型

    //方法四:(最常用)省去var关键字,直接自动匹配
    e := 100
    fmt.Println("e = ", e) //e = 100
    fmt.Println("type of e = %T\n", e) //打印c的类型
}
//声明多个变量
var xx, yy int = 100,200
var kk, ll = 100, "ABC"
var{
    vv int = 100
    jj bool = true
}

const与iota的注意事项

iota是逐行累加,一行再多变量,他们的iota也是一样的

iota的使用非常局限,只能配合const出现

//const来定义枚举变量
const{
    //可以在const()添加关键字iota,每行的iota都会累加1,第一行的iota默认0
    BEIJING = iota
    SHANGHAI
    HAERBING
}
const{
    a, b = iota+1, iota+2 // iota = 0, a = iota+1, b = iota+2, a = 1, b = 2
    c, d                  // iota = 1, c = iota+1, d = iota+2, c = 2, d = 3
    e, f                  // iota = 2, e = iota+1, f = iota+2, e = 3, f = 4
    
    g, h = iota*2, iota*3 // iota = 3, e = iota*2, f = iota*3, e = 6, f = 9
    i, k                  // iota = 4, e = iota*2, f = iota*3, e = 8, f = 12
}
func main(){
    //const是常量,只读属性,不可修改
    const length int = 10
    fmt.Println("SHANGHAI = ", SHANGHAI) // 1
    fmt.Println("HAERBING = ", HAERBING) // 2
}

多返回值的三种写法

//返回一个返回值
func foo1(a string, b int) int {
...
    c := 100
    return c
}
//返回多个返回值,匿名
func foo2(a string, b int) (int, int){
...
    return 666,777
}
//返回多个返回值,有形参名
func foo3(a string, b int) (r1 int, r2 int){
...
    r1 = 1000
    r2 = 2000
    return
}
//返回多个返回值,有形参名,r1, r2 int也行,逆天语法
func foo4(a string, b int) (r1, r2 int){
...
    r1 = 1000
    r2 = 2000
    return
}

import相关

main,import以及init的流程:

import之后不会继续执行,而是解析包

现在自定义两个包,lib1.go和lib2.go,路径如下

//lib1.go
package lib1
import "fmt"
func Lib1Test(){
    fmt.Println("lib1Test()...")
}
func main(){
    fmt.Prinln("lib1.init()...")
}

//lib2.go
package lib2
import "fmt"
func Lib21Test(){
    fmt.Println("lib2Test()...")
}
func main(){
    fmt.Prinln("lib2.init()...")
}

//main.go
package main

import(
    "GolangStudy/5-init/lib1"
    "GolangStudy/5-init/lib2"//必须写路径,不然找不到
)
func main(){
    lib1.Lib1Test() //go声明必须使用,这里每一行都不能少
    lib2.Lib2Test()
}

此外,import还有匿名导包和给包取别名的方式:

匿名导包用在,咱只需要这个包的某一个函数,但不想import这个包

_"GolangStudy/5-init/lib1"

加个下划线就行


给包起别名,比如包太长了,不愿意总写

mylib2 "GolangStudy/5-init/lib2"

前面加个mylib2即可!接下来调用这个包的函数就是->mylib2.Lib2Test()

或者:

. "GolangStudy/5-init/lib2"

用.去省略,接下来调用这个包的函数就是->Lib2Test()

.不要轻易使用!

defer的执行顺序

defer是在结束的时候执行,并且defer是栈的形式,第一个defer先入栈,后一个再入栈,那么后一个就是先出栈的

func main(){
    //写入defer关键字
    defer fmt.Println("main end1")
    defer fmt.Println("main end2")
    fmt.Println("main::hello go 1")
    fmt.Println("main::hello go 2")
}
/*
main::hello go 1
main::hello go 2
main end2
main end1
*/

defer和return是谁先谁后?结果是return快于defer,return会先被调用,defer是程序所有函数生命周期结束才被调用的!

切片slice相关

slice是动态数组的类型

定义遍历和打印:

//变长数组
func printArray(myArray []int){
    for _, value := range myArray{
        fmt.Println("value = ", value)
    }
}
func main(){
    myArray := []int{1,2,3,4}
    fmt.Print("myArray type is %T\n", myArray)
}

slice的4种声明:

func main(){
    //声明slice1是一个切片,并且初始化,默认值是1,2,3,长度是3
    slice1 := []int{1,2,3}

    //声明slice1是一个切片,但是并没有给slice1分配空间,也就是完全没有容量
    var slice1 []int
    slice1 = make([]int, 2)//先给slice1开辟空间

    //声明slice是一个切片,同时给slice1分配空间,3个空间,初始是0
    var slice1 []int = make([]int, 3)

    //通过:=推导出slice1是一个切片
    slice1 := make([]int, 3)
    
    //判断slice是否为0
    if slice == nil{
        fmt.Println("slice1是一个空切片")
    }
    else{
        fmt.Println("slice1是有空间的")
    }
}

切片容量的追加:

func main(){
    var numbers = make([]int, 3, 5)
    fmt.Println("len = %d, cap = %d, slice = %v\n", len(numbers), cap(numbers))//cap求容量,打印结果:len = 3,cap = 5,slice = [0 0 0]

通过append追加值

//向numbers切片追加一个元素1,, numbers len = 4, [0 0 0 1], cap = 5
numbers = append(numbers, 1)

cap满了之后再append会怎样?底层会再次开辟一个cap空间

切片截取:

numbers := []int{0,1,2,3,4,5,6,7,8}
//切片截取
fmt.Println("numbers[1:4] ==", numbers[1:4]) // 打印子切片从索引1(包含)到索引4(不包含)
fmt.Println("numbers[:3] ==", numbers[:3]) // 默认下限为0
fmt.Println("numbers[4:] ==", numbers[4:]) //默认上限为len(s)

map相关

map的3种声明方式

//声明方式一
var myMap1 map[string]string
//使用map前,给他开辟空间
make(map[string]string, 10)
myMap2["one"] = "java"
myMap2["two"] = "c++"
myMap2["three"] = "python"

//声明方式二
myMap2 := make(map[int]string)
myMap2[1] = "java"
myMap2[2] = "c++"
myMap2[3] = "python"

//声明方式三
myMap3 := map[string]string{
    "one": "php",
    "two": "c++",
    "three": "python",
}

map的使用:

cityMap := make(map[string]string)
//添加
cityMap["China"] = "Beijing"
cityMap["Japan"] = "Tokyo"
cityMap["USA"] = "newYork"

//遍历
for key, value := range cityMap{
    fmt.Println("key = ", key)
    fmt.Println("value = ", value)
}

//删除
delete(cityMap, "China")

//修改
cityMap["USA"] = "DC"

golang的面向对象

封装

type Hero struct{
    Name  string
    Ad    int
    Level int
}
func (this *Hero) show(){
    fmt.Println("Name = ", this.Name)
    fmt.Println("Ad = ", this.Ad)
    fmt.Println("Level = ", this.level)
}
func (this *Hero) GetName() string{
    return this.Name
}
func (this *Hero) SetName(newName string){
    //this只是调用该方法的对象的一个副本
    this.Name = newName
}

继承

//父类
type Human struct{
    name string
    sex  string
}
func (this *Human) Eat(){
    fmt.Println("Human.Eat()...")
}
func (this *Human) Walk(){
    fmt.Println("Human.Walk()...")
}

//子类
type SuperMan struct{
    Human //SuperMan类继承了Human类的方法
    level int
}
//重新定义父类的方法Eat
func (this *SuperMan) Eat(){
    fmt.Println("SuperMan.Eat()...")
}
//子类新方法
func (this *SuperMan) Fly(){
    fmt.Println("SuperMan.Fly()...")
}
func (this *SuperMan) Print(){
    fmt.Println("name = ", this.name)
    fmt.Println("sex = ", this.sex)
    fmt.Println("level = ", this.level)
}
//定义一个子类对象
//s := SperMan{Human{"li4", "female"}, 88}
var s SuperMan
s.name = "li4"
s.sex = "male"
s.level = 88

多态

  • 多态(Polymorphism)是面向对象编程(OOP)中的一个重要概念。它指的是同一个操作作用于不同的对象,可以有不同的解释,产生不同的执行结果。简单来说,就是用同一种方式去调用不同类中的同名方法,这些方法在不同类中有不同的实现,从而展现出不同的行为。
  • 实现多态的条件
    • 要有继承关系。例如,有一个基类(父类)和一个或多个派生类(子类),子类继承父类的属性和方法。
    • 要有方法重写。子类重写(override)父类中的方法,即在子类中重新定义了与父类中同名、同参数列表(参数的类型、个数和顺序相同)的方法,并且方法体的内容不同。
package main

import "fmt"

// 定义一个接口,它有一个方法Sound
type Animal interface {
    Sound() string
}

// 定义狗结构体
type Dog struct{}

// 狗结构体实现Animal接口的Sound方法
func (d Dog) Sound() string {
    return "汪汪汪"
}

// 定义猫结构体
type Cat struct{}

// 猫结构体实现Animal接口的Sound方法
func (c Cat) Sound() string {
    return "喵喵喵"
}

// 定义一个函数,它接受实现了Animal接口的对象,并调用其Sound方法
func MakeSound(a Animal) {
    fmt.Println(a.Sound())
}

func main() {
    dog := Dog{}
    cat := Cat{}

    MakeSound(dog)
    MakeSound(cat)
}

interface{}是空接口,int、string、float32、float64、struct...都实现了interface{}。可以用interface{}类型,引用任意数据类型

func myFunc(arg interface{}){
    fmt.Println("myFunc is called...")
    fmt.Println(arg)
}
type Book struct{
    auth string
}
func main(){
    book := Book{"Golang"}
    myFunc(book)
    myFunc(3.14)
    myFunc("abc")
}
/*
打印输出:
Golang
3.14
abc
*/

那么interface{}是如何区分 此时引用的底层数据是什么类型?

->给interface{}提供"类型断言"机制

func myFunc(arg interface{}){
    fmt.Println("myFunc is called...")
    fmt.Println(arg)
    
    //给 interface{} 提供"类型断言"
    value, ok := arg.(string)
    if !ok {
        fmt.Println("arg is not string type")
    }else{
        fmt.Println("arg is string type, value = ", value)
    }
}

在代码 value, ok := arg.(string) 中,这是一个类型断言表达式,它尝试将接口类型 arg 断言为 string 类型,并且会返回两个值:

  • value

    • 如果接口 arg 确实是 string 类型,那么 value 就会被赋值为接口所包含的 string 值。例如,如果 arg 是通过传入一个字符串 "hello" 得到的接口值,那么当类型断言成功时,value 就会被赋值为 "hello"
    • 如果类型断言失败,value 的值是对应类型的零值。对于 string 类型来说,零值就是 ""(空字符串)。
  • ok

    • ok 是一个布尔值,它用于指示类型断言是否成功。
    • 如果 arg 确实是 string 类型,那么 ok 会被赋值为 true,表示类型断言成功,此时你可以放心地使用 value 变量,因为它已经正确获取到了接口中所包含的 string 值。
    • 如果 arg 不是 string 类型,那么 ok 会被赋值为 false,表示类型断言失败,此时 value 虽然也有值(对应类型的零值),但它并不是我们所期望的接口中原本的正确值,所以需要通过检查 ok 的值来判断是否可以正确使用 value 变量。

 反射

变量结构如下:

OpenFile返回了一个指针类型和一个error

//tty: pair<type:*os.File, value:"/dev/tty">
tty, err := os.OpenFile("/dev/tty", os.O_RDWR, 0)

reflect包:

func ValueOf(i interface{}) Value {...}
// ValueOf用来获取输入参数接口中的数据的值,如果接口为空则返回0
func TypeOf(i interface{}) Type {...}
//TypeOf用来动态获取输入参数接口中的值的类型,如果接口为空则返回nil

具体来说

package main
import (
    "fmt"
    "reflect"
)
func reflectNum(arg interface{}){
    fmt.Println("type: ", reflect.TypeOf(arg)) //float64
    fmt.Println("value: ", reflect.ValueOf(arg)) //1.234
}
func main(){
    var num float64 = 1.234
    reflectNum(num)
}

结构体标签

package main
type resume struct{
    Name string    'info:"name" doc:"mine"'
    Sex  string    'info:"sex"'
}
func findTag(str interface{}){
    t := reflect.TypeOf(str).Elem()
    for i := 0; i < t.NumField(); i ++{
        taginfo := t.Field(i).Tag.Get("info")
        tagdoc := t.Field(i).Tag.Get("doc")
        fmt.Println("info:", taginfo, "doc:", tagdoc)
    }
}

结构体的标签引用在json编解码或者orm映射关系

goroutine

  • Goroutine 是 Go 语言特有的轻量级线程,它可以让一个函数在后台并发地执行。与传统的操作系统线程相比,Goroutine 非常轻量,一个 Go 程序可以轻松地创建成千上万个 Goroutine。
  • 例如,在一个 Web 服务器中,可以为每个传入的请求创建一个 Goroutine 来处理,这样就能够高效地处理多个请求,而不会因为创建大量线程导致系统资源耗尽。
  • 创建 Goroutine 非常简单,只需要在函数调用前加上go关键字。
go func() {
    defer fmt.Println("A.defer")
    //匿名函数
    func() {
        defer fmt.Println("B.defer")
        //退出当前goroutine
        runtime.Goexit()
        fmt.Println("8")
    }()
    for{
        time.Sleep(1*time.Second)
    }
}
/*输出
B
B.defer
A
A.defer
*/

channel

  1. 概念

    • 在 Go 语言中,channel是一种用于在不同的goroutine之间进行通信和同步的机制。它可以被看作是一个管道,数据可以通过这个管道在goroutine之间传递。
  2. 基本操作

    • 创建 channel:可以使用make函数来创建channelchannel有两种类型:无缓冲的channel和有缓冲的channel
      • 无缓冲的 channel
        • 示例:ch := make(chan int)。这创建了一个可以传输int类型数据的无缓冲channel。无缓冲channel在发送和接收数据时必须有对应的接收者和发送者同时准备好,否则会导致goroutine阻塞。
      • 有缓冲的 channel
        • 示例:ch := make(chan int, 3)这创建了一个可以传输int类型数据的有缓冲channel,缓冲大小为 3。有缓冲channel允许在没有接收者的情况下,发送者可以发送一定数量(缓冲大小)的数据,这些数据会存储在channel的缓冲区中,直到被接收。
    • 发送和接收数据
      • 发送数据:使用ch <- value的语法,其中chchannelvalue是要发送的数据。例如,ch := make(chan int); go func() { ch <- 5 }(),这里在一个goroutine中向channel发送了数字 5。
      • 接收数据:使用value := <-ch的语法来接收channel中的数据。例如,ch := make(chan int); go func() { value := <-ch; fmt.Println(value) }(),这个goroutine会从channel中接收数据并打印出来。
  3. 同步作用

    • channel除了用于数据传输,还具有同步goroutine的作用。
    • 例如,当一个goroutine向无缓冲channel发送数据时,如果没有其他goroutine来接收这个数据,发送数据的goroutine会阻塞,直到有接收者出现。同样,当一个goroutine试图从一个空的channel接收数据时,它也会阻塞,直到有数据被发送到这个channel
    • 这种阻塞机制可以确保goroutine之间的协调和同步。比如在一个并发计算任务中,一个goroutine完成部分计算后,通过channel发送结果给另一个goroutine,第二个goroutine在收到数据前会一直等待,这样就保证了数据的正确传递和任务的有序进行。
  1. 关闭 channel

    • 可以使用close(ch)来关闭channel。关闭channel后,再试图向其发送数据会引发panic,但仍然可以从已经关闭的channel中接收数据。
    • 接收已关闭channel的数据时,会正常接收缓冲区中剩余的数据,当缓冲区为空时,会得到对应类型的零值,并且可以通过一个额外的返回值来判断channel是否已经关闭。例如,value, ok := <-ch,如果okfalse,则表示channel已经关闭。
    • 关闭channel通常用于表示数据发送已经完成,让接收者知道不会再有新的数据发送过来了。例如,在一个生产者 - 消费者模型中,当生产者完成所有数据的生产后,可以关闭channel,消费者在接收完剩余数据后,通过检查channel是否关闭来结束接收操作。
func main() {
    c := make(chan int)
    go func(){
        defer fmt.Println("goroutine结束")
        fmt.Println("goroutine 正在运行...")
        c <- 666 //将666发送给c
    }()
    num := <-c //从c中接受数据,并赋值给num
    fmt.Println("num = ", num)
    fmt.Println("main goroutine 结束...")
}
/* 输出
goroutine 正在运行...
goroutine结束
num = 666
main goroutine 结束...
*/

 


http://www.kler.cn/a/404098.html

相关文章:

  • 网站推广实战案例:杭州翔胜科技有限公司如何为中小企业打开市场大门
  • 解决upload上传之后,再上传没有效果
  • 【软考】系统架构设计师-计算机系统基础(4):计算机网络
  • .net6.0(.net Core)读取 appsettings.json 配置文件
  • 嵌入式linux中QT信号与槽基本操作与实现
  • pytest结合allure做接口自动化
  • docker 相关组成
  • 网络中的TCP协议详解
  • vxe-table 打印出货单、自定义打印单据
  • 【2024APMCM亚太杯B题】空调形状优化 模型+代码+论文
  • 什么是 C++ 中的移动语义?它的作用是什么?
  • Apache Maven 标准文件目录布局
  • STM32的中断(什么是外部中断和其他中断以及中断号是什么)
  • Elasticsearch面试内容整理-性能优化
  • mac2024 安装node和vue
  • pymysql模块
  • 【定长滑动窗口】【刷题笔记】
  • MySQL深度剖析-全局锁、表锁、行锁
  • JSON.toJSONString(awards) 全是空 [{}{}{}{}{}]
  • .NET高效下载word文件
  • 23 Jumping Back and Forth
  • debian 如何进入root
  • JS推荐实践
  • AI社媒引流工具:解锁智能化营销的新未来
  • Java语言编程,通过阿里云mongo数据库监控实现数据库的连接池优化
  • 排序【数据结构】【算法】