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

Golang快速上手01/Golang基础

  • 最近有需求,需要使用go,这几天快速过一遍基础语法,这是今天的总结

  • 项目结构

    Package
    Imports
    全局声明
    函数定义
    main函数
    局部变量
    :=短声明
    var声明

1.变量与常量

1.1变量声明
// 标准声明
var 变量名 类型 = 表达式

// 多变量声明
var (
    name string = "Go"
    version = 1.21  // 类型推断
)

// 短变量声明(仅函数内)
func main() {
    a, b := 10, "text"  // 同时声明多个变量
    _ = 20             // 丢弃值
}
1.2常量声明
const PI = 3.1415  // 无类型常量
const (
    STATUS_OK = 200
    MAX_SIZE  = 1024 << 3  // 允许表达式
)
1.3包与导入
package main  // 可执行程序必须的包声明

// 单行导入
import "fmt"

// 多行导入(推荐)
import (
    "time"
    "log"
)

注意:导入的包必须被使用,否则编译报错

1.4函数特性
// 多返回值函数
func getData() (int, string) {
    return 200, "success"
}

// 使用返回值
code, msg := getData()
_, status := getData()  // 忽略第一个返回值

2.基本数据类型

类型描述范围示例
int默认整数类型,系统相关通常 32 或 64 位
int88 位有符号整数-128 至 127
int1616 位有符号整数-32768 至 32767
int3232 位有符号整数-2^31 至 2^31-1
int6464 位有符号整数-2^63 至 2^63-1
uint无符号整数,系统相关0 至 2^32-1 或 2^64-1
uint88 位无符号整数0 至 255
uint1616 位无符号整数0 至 65535
uint3232 位无符号整数0 至 2^32-1
uint6464 位无符号整数0 至 2^64-1
byteuint8 的别名, ASCII码0 至 255
runeint32 的别名,用于 Unicode 码点-2^31 至 2^31-1
  • 复数类型

    1. complex128:可以使用 a+bi 表示复数,如 0.5+1.5i

      var c complex128 = 0.5 + 1.5i

      • 如果需要 complex64 类型,可以将 complex128 赋值给 complex64 变量,但可能会损失精度。
    2. 可以使用内置函数 func complex(r, i float64) complex128 构造复数类型。

      var c complex128 = complex(0.5, 1.5)

      comple(r, i float64)表明i和r都是float64

  • 指针类型(uintptr/unsafe.Pointer)

    任何类型不能强制转换为uintptr,uintptr可以与unsafe.Pointer,unsafe.Pointer可以转为任何数据类型(不安全)

    a := "Hello World!"  // string: "Hello World!"
    b := []byte(a)  // []byte: [72 101 108 108 111 32 87 111 114 108 100 33]
    c := **(**int)(unsafe.Pointer(&a))  // int: 8022916924116329800
    
  • 字符类型byte/rune

    使用单引号,默认为rune类型,但如果再byte范围呢,也就是ASCII码范围内,可以相互转换两种类型,表达与Clang不同

    特性byte (uint8)rune (int32)
    底层类型无符号8位整型有符号32位整型
    取值范围0 ~ 255-2^31 ~ 2^31-1
    默认值00
    内存占用1字节4字节
    主要用途ASCII字符处理Unicode字符处理
    // 特殊表示法示例
    a := '中'           // Unicode字符直接表示
    b := '\u4e2d'       // Unicode码点表示(中文"中")
    c := '\377'         // 八进制表示最大范围
    d := '\x7F'         // 十六进制表示ASCII字符
    e := '\U0001F604'   // 扩展Unicode表示(笑脸表情)
    

3.内置数据类型

特性值类型引用语义类型指针类型
内存分配位置栈/数据段栈存储指针,堆存储数据
赋值操作深度拷贝浅拷贝(共享底层数据)地址拷贝
空值类型零值nilnil
内存管理自动回收GC管理GC管理
典型类型int, float, structslice, map, chan*int, *struct
函数参数传递值副本传递指针结构副本传递地址副本传递
3.1 指针
  • code

    package main
    
    var a int = 10
    var ptr *int = &a
    
    func main() {
        ptr2 := &a
        println(*ptr2)
    
        // 不能使用:=初始化为空指针,因为编译器无法推断类型
        var ptr1 *int = nil
        println(ptr1)
        // 结构体内方位属性中指针类型指向的指用. 
    }
    
    • 注意go中没有->
3.2结构体
  • code

    package main
    
    import (
    	"fmt"
    )
    
    type Book struct {
    	name string
    	author string
    	subject *string
    }
    var a Book // 初始化默认结构体
    var s1 string = "Go 语言"
    var subject1 *string = &s1
    
    var b = Book{name: "Go", author: "www.runoob.com", subject: subject1} // 初始化结构体
    var c = Book{author: "www.runoob.com", name: "Go"} // 局部初始化结构体
    var e *Book = new(Book) // 使用指针
    
    type Book1 struct {
    	string
        int
    }
    
    var a1 Book1 = Book1{"Golang 入门经典", 10}
    
    var a2 = struct{
        name string
        author string
    }{"Golang 入门经典", "Unknown"}
    
    type Cat struct {
    	name string
    	age int
    }
    
    type Dog struct {
    	name string
    	age int
    }
    
    func main() {
    	d := Book{author: "www.runoob.com", name: "Go"} // :=
    	println(a.name)
    	println(*b.subject)
    	println(c.name)
    	println(d.name)
    	println(e.name)
    	fmt.Println("Hello, playground")
        
    	fmt.Println(a1)
    	fmt.Println(a2)
    	println(a1.string)
        
    	var car1 Cat
    	var car2 Dog
    	fmt.Println(car1)
    	fmt.Println(car2)
    	// 如果两个结构体具有完全相同的定义,它们可以进行显式类型转换。
    	car1 = Cat(car2)
    	fmt.Println(car1)
    
    }
    
    • 结构体初始化方式

      Go 提供多种初始化结构体的办法,代码片段中展示了多种示例:

      1. 零值初始化:

        • 语法:var a Book
        • 效果:所有字段被初始化为零值。根据字段类型,字符串为 “”,整数为 0,指针为 nil。
        • 示例:var a Book 会让 a.name = “”,a.author = “”,a.subject = nil。
      2. 字段赋值初始化:

        • 语法:var b = Book{name: “Go”, author: “www.runoob.com”, subject: subject1}
        • 效果:指定字段名和值,未指定的字段为零值。代码中 subject1 是一个字符串指针,b.subject 被设置为该指针。
        • 注意:字段顺序无关,Go 会根据字段名匹配。
      3. 部分初始化:

        • 语法:var c = Book{author: “www.runoob.com”, name: “Go”}
        • 效果:只指定部分字段,未指定的字段(如 subject)为零值,即 nil。
      4. 使用 new 函数:

        • 语法:var e *Book = new(Book)
        • 效果:new(Book) 分配内存,返回指向 Book 的指针,所有字段初始化为零值。访问字段用 e.name,Go 自动解引用。
        • 与直接声明的区别:new 返回指针,适合需要传递指针的场景。
      5. 匿名结构体初始化:

        • 语法:var a2 = struct{name string; author string}{“Golang 入门经典”, “Unknown”}
        • 效果:定义并初始化一个匿名结构体,字段顺序初始化,适合临时使用的小型结构体。
    • 匿名字段与访问

      type Book1 struct {
          string
          int
      }
      
      var a1 Book1 = Book1{"Golang 入门经典", 10}
      
      • 匿名字段:字段没有显式名称,直接使用类型名作为字段名。在 Book1 中,字段名为 string 和 int,可以直接访问 a1.string 和 a1.int。

      • 限制:每个属性的类型必须唯一,否则会冲突。例如,不能有多个 string 字段。

    • 类型转换与字段一致性

      type Cat struct {
          name string
          age int
      }
      
      type Dog struct {
          name string
          age int
      }
      
      var car1 Cat
      var car2 Dog
      car1 = Cat(car2) // 这行无法编译
      
      • 类型转换规则:在 Go 中,即使两个结构体的字段完全相同(如 Cat 和 Dog 都有 name string 和 age int),如果它们是独立定义的类型,则被视为不同类型,不能直接转换。

        • 需要手动赋值或者建构造函数

          car1.name = car2.name
          car1.age = car1.age
          
          car1 = Cat{name: car2.name, age: car2.age}
          
3.2.1嵌套与嵌入(类似继承)
  1. 嵌套结构体

    • 定义:一个结构体包含另一个结构体作为字段。

    • 示例:

      type Address struct {
          street string
          city string
      }
      
      type Person struct {
          name string
          address Address
      }
      
    • 访问:使用点号访问嵌套字段,例如 person.address.street。

    • 用途:适合表示层次化数据,如地址信息嵌套在人员信息中。

  2. 嵌入结构体(类似继承)

    • 定义:通过嵌入实现类似继承的效果,一个结构体包含另一个结构体的匿名字段。

    • 示例:

      type Animal struct {
          name string
          age int
      }
      
      type Cat struct {
          Animal
          color string
      }
      
    • 访问:嵌入的字段可以直接访问,例如 cat.name 和 cat.age,无需通过 Animal 前缀。

    • 方法继承:如果 Animal 有方法,Cat 也可以直接调用这些方法,类似于继承。

    • 扩展:Cat 可以添加新字段(如 color)或覆盖方法(通过同名方法)。

3.3数组
  • code

    package main
    
    import "fmt"
    
    // 定义数组 <定义变量关键字> <变量名> [数组长度] <数组元素类型>
    var a [3]int
    var b [3]int = [3]int{1, 2, 3}
    var c = [...]int{1, 2, 3}  // [...]int是数组,不是切片
    
    
    func main() {
    	fmt.Println(a)
    	fmt.Println(b)
    	d := [3]int{1, 2, 3}
    	fmt.Println(d)
    
    	// 数组名不会转换为指向数组首元素的指针,类似于std::array,所以会传递数组的时候会产生很开销,建议使用指针
    	var a [3]int = [3]int{1, 2, 3}
    	var p *[3]int = &a
    
    }
    
    • 和结构体一样,如果直接初始化,就不要写类型声明
    • 和cpp数组名的意义不同
3.4切片
3.4.1切片的定义
  • code

    package main
    
    import "fmt"
    
    // 定义切片
    var slice []int  // 注意:[3]int / [...]int 都是数组不是切片
    var a = []int{1, 2, 3, 4, 5}
    
    func main() {
    	a := []int{1, 2, 3, 4, 5}
    	fmt.Println(a)
    	var b = make([]int, 5) // slice,map,channel,第一个参数为类型,第二个参数为长度,第三个参数为容量
    	fmt.Println(b)
    
    }
    
    • 和数组类似,只是数组里不要写数字
3.4.2切片介绍
  • slice是对数据的引用,而非拷贝

  • 切片是一种数据类型也是一种操作

    func main() {
    	// 切片操作
    	var a = []int{1, 2, 3, 4, 5}
    	fmt.Println(a[1:3]) // [2 3]
    	fmt.Println(a[:3])  // [1 2 3]
    	fmt.Println(a[1:])  // [2 3 4 5]
    	fmt.Println(a[:])   // [1 2 3 4 5]
    }
    
  • appendcopy

    • 都是对数据的拷贝,也叫深拷贝

      var c = []int{1, 2}
      fmt.Println(c) // [1 2]
      var d = []int{3, 4, 5, 6}
      fmt.Println(d) // [3 4 5 6]
      var e []int = append(c, 3, 4, 5, 6)
      fmt.Println(e) // [1 2 3 4 5 6]
      var f []int = append(c, d...) // ...表示将切片d打散,解包
      fmt.Println(f) // [1 2 3 4 5 6]
      var i int = copy(c, d) // 将切片d复制到切片c中,返回复制的元素个数,以长度短的为准
      fmt.Println(i) // 2
      
  • 原地删除操作

    a = a[start:]  // 删除前 start 个元素
    
    a = a[:end]  // 保留前 end 个元素
    
    a = a[start:end]  // 截取索引 [start, end) 元素
    
    a = append(a[:start], a[end:]...)  // 删除索引 [start, end) 元素
    
  • 原地插入

    a = append(a, b...) // 原地末尾追加
    a = append(b, a...) // 原地首部插入
    a = append(append(a[:st], b...), a[st:]...) // 原地中间插入
    a := []int{1, 2, 3, 4, 5}
    b := []int{10, 20, 30}
    st := 2 // 要插入的位置是索引 2 位置
    // a = [1, 2, 10, 20, 30, 3, 4, 5]
    
3.5字符串
3.5.1字符串初始化
var a string = "Hello World!" // 显式类型
var b = "Golang 入门经典" // 类型推断
c := "abcABC123" // 短声明
  • 与数组、切片、结构体类似,如果确定类型,可以直接类型推断,就是第二中初始化方法
  • 字符串可以认为是一个静态切片类型,可以进行相应的切片操作。不可使用 appendcopy 函数。还需注意多字节字符(如中文)可能导致切片不完整
3.5.2字符串的属性与操作
  • 字符串不可变,意味着创建后无法直接修改内容。内部使用字节数组存储,结构为 StringHeader(包含数据指针和长度),类似 Java 的字符串。

    type StringHeader struct {
        Data uintptr
        Len int
    }
    
  • 字符串拼接

    有三种拼接方式:

    • 使用 + 运算符:c := a + b,简单但效率低,适合少量拼接。

    • 使用 strings.Joinc = strings.Join([]string{a, b}, ""),适合多个字符串拼接。

    • 使用 strings.Builder:推荐高效方式,示例:

      package main
      
      import (
      	"fmt"
      	"strings"
      )
      
      func main() {
      	var a string = "a"
      	var b string = "b"
      	var builder strings.Builder
      	builder.WriteString(a)
      	builder.WriteString(b)
      	fmt.Println(builder.String())
      }
      
  • 字符串编码:把字符串转换为数组

    c := []byte(a)
    fmt.Println(c) // [97]
    d := len(c)
    fmt.Println(d) // 1
    e := []rune(a)
    fmt.Println(e) // [97]
    
3.5.3其它功能
  • 字符串操作与转换功能

    1. 检查功能

    以下是 strings 包中用于检查字符串的函数:

    FunctionDescription
    strings.Contains(s, substr)返回 true 如果 s 包含 substr
    strings.HasPrefix(s, prefix)返回 true 如果 s 以 prefix 开头
    strings.HasSuffix(s, prefix)返回 true 如果 s 以 prefix 结尾
    strings.Index(s, substr)substr 在 s 中第一次出现的索引
    strings.LastIndex(s, substr)substr 在 s 中最后次出现的索引
    a := "hello, world"
    b := "hello"
    fmt.Println(strings.Contains(a, b)) // true
    fmt.Println(strings.Contains(a, "world")) // true
    fmt.Println(strings.HasPrefix(a, b)) // true
    fmt.Println(strings.HasSuffix(a, b)) // false
    fmt.Println(strings.Index(a, b)) // 0
    fmt.Println(strings.LastIndex(a, b)) // 0
    
    1. 转换功能

    以下是 strings 包中用于转换字符串大小写的函数:

    FunctionDescription
    strings.ToLower(s)返回 s 的小写版本
    strings.ToUpper(s)返回 s 的大写版本
    a := "hello, world"
    b := "hello"
    fmt.Println(strings.ToUpper(a)) // HELLO, WORLD
    fmt.Println(strings.ToLower(b)) // hello
    
    1. 分割与连接功能

    以下是 strings 包中用于分割和连接字符串的函数:

    FunctionDescription
    strings.Split(s, sep)根据 sep 将 s 分割为字符串切片
    strings.Join(elem, sep)使用 sep 连接 elem 中的元素
    a := "hello, world"
    b := "hello"
    // split
    fmt.Println(strings.Split(a, ",")) // [hello  world]
    // join
    fmt.Println(strings.Join([]string{"hello", "world"}, ",")) // hello,world
    
    1. 替换功能

    以下是 strings 包中用于替换字符串的函数:

    FunctionDescription
    strings.Replace(s, old, new, n)在 s 中替换前 n 次 old 为 new
    strings.ReplaceAll(s, old, new)在 s 中替换所有 old 为 new
    a := "hello, world"
    b := "hello"
    // replace
    fmt.Println(strings.Replace(a, "world", "golang", 1)) // hello, golang
    // replace all
    fmt.Println(strings.ReplaceAll(b, "l", "x")) // hexxo
    
    1. 修剪功能

    以下是 strings 包中用于修剪字符串的函数:

    FunctionDescription
    strings.TrimSpace(s)去除 s 前后空白字符
    strings.Trim(s, chars)去除 s 前后 chars 中的字符
    a := "hello, world"
    b := "hello"
    // trim
    fmt.Println(strings.Trim("  hello, world  ", " ")) // hello, world
    // trim left
    fmt.Println(strings.TrimLeft("  hello, world  ", " ")) // hello, world
    // trim right
    fmt.Println(strings.TrimRight("  hello, world  ", " ")) //   hello, world
    // TrimSpace
    fmt.Println(strings.TrimSpace("  hello, world  ")) // hello, world
    // TrimPrefix
    fmt.Println(strings.TrimPrefix("hello, world", "hello")) // , world
    
  • 字符串转换功能

    以下是用于字符串转换的函数:

    1. 转为字符串
    FunctionDescription
    fmt.Sprint(a …any)将任意类型转换为字符串
    t := 1
    // Sprint
    fmt.Sprint(t)
    
    1. 从字符串转换
    FunctionDescription
    strconv.ParseBool(s)解析 s 为布尔值,返回 (bool, error)
    strconv.ParseComplex(s, bitSize)解析 s 为复数,返回 (complex128, error)
    strconv.ParseFloat(s, bitSize)解析 s 为浮点数,返回 (float64, error)
    strconv.ParseInt(s, base, bitSize)解析 s 为整数,返回 (int64, error)
    strconv.ParseUint(s, base, bitSize)解析 s 为无符号整数,返回 (uint64, error)
    a := "hello, world"
    b := "hello"
    // ParseBool
    fmt.Println(strconv.ParseBool("true")) // true <nil>
    // ParseInt
    fmt.Println(strconv.ParseInt("17", 10, 64)) // 17 <nil>
    // ParseFloat
    fmt.Println(strconv.ParseFloat("3.14", 64)) // 3.14 <nil>
    // ParseUint
    fmt.Println(strconv.ParseUint("17", 10, 64)) // 17 <nil>
    // ParseComplex
    fmt.Println(strconv.ParseComplex("1+2i", 64)) // (1+2i) <nil>
    
3.6channel
  • channel用于多线程线程管道通信与同步

    package main
    
    import (
    	"fmt"
    )
    
    func main() { 
    	// a := make(chan <type>, [<size>])
    	a := make(chan int, 2)  // 不允许直接转换的定义方法
    	fmt.Println(a)
    	a <- 1
    	a <- 2
    }
    
    • 直接使用 var a chan int 是错误的,其没有构造 chan 结构。因为这是一个引用数据类型
    • 发送数据:channel <- data,如 a <- 1。
    • 接收数据:data := <- channel,如 x := <- a。
  • 无缓冲通道的阻塞

    无缓冲通道没有存储空间,发送操作会阻塞直到有一个接收操作准备好接收数据;同样,接收操作会阻塞直到有一个发送操作准备好提供数据。这意味着发送方和接收方必须同时准备好,数据才能传递。

    b := make(chan int)  //有缓冲区大小为2的 int 通道
    select {
    case x := <- b:
        fmt.Println(x)
    default:
        // 无数据时执行
    }
    
    • 这允许程序检查数据而不等待
  • 有缓冲通道的阻塞

    有缓冲通道有一个固定大小的缓冲区,发送操作只有在缓冲区满时才会阻塞;接收操作只有在缓冲区空时才会阻塞。缓冲区允许发送和接收操作在不同时间发生,只要缓冲区有空间或数据即可。

  • 意外细节

    你可能不知道的是,即使有缓冲通道,如果接收操作先于发送操作执行且缓冲区空,接收操作也会阻塞,直到发送操作填入数据。这与无缓冲通道的同步性质类似,但缓冲区提供了更多灵活性。

3.7map
  1. 初始化map

    // a := make(map[<keyType>]<valueType>)
    // b := map[<keyType>]<valueType>{<key>: <value>...}
    a := make(map[string]int)
    b := map[string]int{"a": 1, "b": 2, "c": 3}
    fmt.Println(a)
    fmt.Println(b)
    
    • 和channel类似
  2. crud

    // crud
    // 改
    b["d"] = 4
    fmt.Println(b) // map[a:1 b:2 c:3 d:4]
    // 删
    delete(b, "d")
    fmt.Println(b) // map[a:1 b:2 c:3]
    // 查看是否存在这个key,存在返回value,否则返回0
    x := b["a"]
    fmt.Println(x) // 1
    
    // 遍历
    for k, v := range b {
        fmt.Println(k, v)
    }
    
    for k := range b {
        fmt.Println(k)
    }
    
    for _, v := range b {
        fmt.Println(v)
    }
    
3.8函数
3.8.1函数的分类
  1. 具名函数
func <functionName>([parameterList...]) [returnTypes...] {
	<block>
}

func add(x int, y int) (int a) {
    a := x + y
    return 
}
  1. 匿名函数

    <functionName>([parameterList...]) [returnTypes...] {
        <block>
    }
    
    a := add(x int, y int) (int a) {
        a := x + y
        return 
    }
    
3.8.2函数传参
  1. Golang中没有引用类型,所以避免拷贝请使用指针

  2. Golang中args...是解包,...args是封包

    package main
    
    import (
    	"fmt"
    )
    
    func add(a ...int) int {
    	s := 0
    	for _, v := range a {
    		s += v
    	}
    	return s
    }
    
    func main() {
    	fmt.Println(add(1, 2, 3, 4, 5)) // 15
    	fmt.Println(add([]int{1, 2, 3}...)) // 6
    }
    
3.8.3函数返回
  • 如果指定函数返回值名,直接使用 return 返回即可。此时函数返回值已定义,无需在函数体内定义。

    func <functionName>([parameterList...]) [returnTypes...] {
    	<block>
    }
    
    func add(x int, y int) (int a) {
        a := x + y
        return 
    }
    

    这里直接return就好,因为指定了返回参数为a

3.9方法
  • golang实现了对象和方法的静态绑定,虽然golang没有类和对象的概念,方法是golang的类的面向对象实现

    • 静态绑定:方法或函数的调用在编译时已确定,通常与方法重载或静态方法相关。
    • 动态绑定:方法的调用在运行时决定,通常与方法重写(Override)相关。
  • 方法的对象必须是本包结构体。例如 func (builder strings.Builder) <functionName>([parameterList...]) [returnTypes...]是错误的,func (x int) <functionName>([parameterList...]) [returnTypes...] 是错误的。

    // 函数实现
    type File struct {
        fd int
        flag uint
    }
    
    func open(file *File, fp string, mode string) {
        // ...
    }
    
    func close(file *File) {
        // ...
    }
    
    func main() {
        file := File{}
        open(&file, "data.txt", "w")
        close(&file)
    }
    
    // 方法实现
    type File struct {
    	fd int
        flag uint
    }
    
    func (file *File) open(fp string, mode string) error {
        // ...
    }
    
    func (file *File) close(fp string, mode string) error {
    	// ...
    }
    
    func main() {
        file := File{}
        file.open("data.txt", "w")
        file.close()
    }
    
    • 由此可见方法与函数绑定了
  • 参考https://blog.csdn.net/TimeLies_Sea/article/details/131581569


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

相关文章:

  • Ollama 简介及安装部署
  • 网络安全高级职称 网络安全高级认证
  • Spring Boot操作MaxComputer(保姆级教程)
  • 深度学习-6.用于计算机视觉的深度学习
  • 使用DeepSeek实现自动化编程:类的自动生成
  • Java文件操作和IO
  • 若依框架实际国际化前后端统一解决方案
  • JavaWeb后端基础(1)
  • 【算法与数据结构】Dijkstra算法求单源最短路径问题
  • SuperMap iClient3D for WebGL 影像数据可视范围控制
  • Android Java创建ViewModel新api
  • 数据结构与算法-图论-最短路-floyd扩展
  • Python Cookbook-2.15 用类文件对象适配真实文件对象
  • IO流(师从韩顺平)
  • Spark技术系列(二):深入理解RDD编程模型——从原理到生产实践
  • Vue 3 响应式系统深度探索:构建购物车应用 - 精通 `watch` 和 `computed` 的响应式数据
  • Nginx系列05(负载均衡、动静分离)
  • 学习笔记05——HashMap实现原理及源码解析(JDK8)
  • 小程序中头像昵称填写
  • StableDiffusion本地部署 2