Golang 快速上手基础部分---语法部分 1

105 阅读5分钟

简介

Go语言(也称为Golang)是google在2009年推出的一种编译型编程语言。 相对于大多数语言,golang具有编写并发或网络交互简单、丰富的数据类型、编译快等特点,比较适合于高性能、高并发场景。语言特点如下:

  1. 高性能、高并发
  2. 语法简单、学习曲线平缓
  3. 丰富的标准库
  4. 完善的工具链
  5. 静态链接
  6. 快速编译
  7. 跨平台
  8. 自带 GC(垃圾回收)

入门

开发环境

开发环境搭建

直接通过 官方文档 指示下载即可,对于 mac 用户,下载号 homeBrew 后,执行下列命令完成安装:

brew install go

IDE 推荐

  1. vscode(安装 go 插件)
  2. Golang

基础语法

变量声明

  • 使用关键字 var 进行变量声明
var a int
var b string
var c float64
var d []int
var e [2]int
var f chan int
var g map[int]int
var h bool
  • 也可以在声明的同时进行初始化
// 使用 golang 的自动类型判断
var a int = 1
var a = 1
var b = "abc"
var c = make([]int, 2)
  • 短变量声明(官方推荐的声明方式)
// 使用短变量声明进行声明以及初始化
a := 1
b := "string"
c := false
d := []int{}
f := map[int]int{}

但其不能用于全局变量声明

package main
// 编译错误
a := 3
// 正确全局变量声明
var a = 3

// 常量声明
const (
    b = "abc"
    c = "const Variable"
)

func main() {
    fmt.Println(a, b, c)
}

在 golang 中,在函数中声明的变量一定要使用,否则就会报错; 如果暂时用不到,可以使用 '_' 接收以下变量进行过渡,全局变量和常量在声明之后可以不使用,但是会给出警告。

package main

import "fmt"

const (
	a = "abc"
)

var b = "abd"

func main() {
	var c int
	_ = c
	fmt.Println()
}
  • 结构体声明以及初始化
package main

type Student struct {
    Name string
    Age int
}

var students []Student

func main() {
    // method1
    s1 := Student{
        Name: "name1",
        Age: 18,
    }
    
    // method2
    s2 := Student{"name2", 21}
    
    // method3
    students = []Student{
        Student{"name1", 12},
        Student{"name2", 13},
        Student{"name3", 14},
    }
    
    // method4
    students = []Student{
        {"name1", 12},
        {"name2", 13},
        {"name3", 14},
    }

    
    fmt.Println(s1)
    fmt.Println(s2)
    fmt.Println(students)
}

标准库

strings 库

strings 包中比较常用的方法有:

代码示例参见 strings library exmaple

  • strings.HasPrefix(s, prefix string) bool 用来判断字符串 s 的前缀是否以 prefix 开头;

  • strings.HasSuffix(s, suffix string) bool 用来判断字符串 s 是否以 suffix 结尾;

  • strings.Index(s, substr string) int 用来找到子串 substr 在字符串中首次匹配的索引;

  • strings.Join(elems []string, sep string) string 将字符串切片以分隔符 sep 连接成字符串;

  • strings.Repeat(s string, count int) string 新的字符串将包含 count 个 s;

  • strings.Replace(s, old, new string, n int) string 将字符串 s 中的子串 old 替换成 new,其中 n 指定了替换几次,n 小于 0 时表示全部替换掉;

  • strings.Split(s, sep string) []string 将字符串 s 以分隔符 seq 进行切分,并以字符串数组的形式返回;

  • strings.ToLower(s string) string | strings.ToUpper(s string) string 将字符串 s 转换成全小写 | 全大写;

  • strings.Trim(s, cutset string) string 如果字符串的前后缀是 cueset,那么前后缀后返回新的字符串;

  • strings.TrimPrefix(s, prefix string) string | strings.TrimSuffix(s, suffix string) string 只删除匹配的前缀或者后缀;

  • 字符串拼接

    • 声明一个 strings.Builder 类型
    • func (b *Builder) Write(p []byte) (int, error) 调用它的 Write 方法写入一个指定的字节切片,返回成功写入的字节数目和错误信息;
    • 也可以调用 func(b *Builder) WriteString(s string) (int, error) 直接写入字符串形式的数据(底层还是调用的 Write 函数);
    // Builder 结构体
    // 第一个 addr 字段是为了解决一个 hack 攻击,这一攻击可以绕过 go 的逃逸分析机制,从而导致 Builder 逃逸到堆上;
    type Builder struct {
    addr *Builder // of receiver, to detect copies by value
    buf  []byte   // 作为缓存区使用的只追加数据的字节切片
    }
    
    // 简单实用,用于字符串拼接
    var builder strings.Builder
    str1, str2 := "abc", "def"
    builder.Write([]byte(str1))
    builder.WriteString(str2)
    fmt.Println(builder.String())
    
    // Output: abcdef
    
    • 类似的字符串拼接方式还有 strings.Join、'+' 、bytes.Builder、fmt.Sprintf;
    • 性能比较:strings.Join ≈ strings.Builder > '+' > fmt.Sprintf;
    • strings.Join 底层是基于 strings.Builder 实现的,而且可以自定义字符串之间的分隔符,在 Join 方法中调用了 b.Grow(n) 进行容量的预分配,n 的长度就是我们要拼接的字符串长度,所以可以避免重新去申请一个更大底层字节数组,减少了内存分配,因此比较高效。源码核心部分如下:
    func Join(elems []string, sep string) string {
           ...
           n := len(sep) * (len(elems) - 1) // 计算所需分隔符数量
           // + 将字符串数组的总长度加到 n 上
            for i := 0; i < len(elems); i++ {
                    n += len(elems[i])
            }
           // use strings.Builder
           var b Builder
           // 提前为缓冲区分配所需空间,避免内存再分配
           b.Grow(n)
           // 往缓冲区中依次写入字符串和分隔符
           ...
           b.WriteString(seq)
           b.WriteString(element[i]) // i 表示遍历指针
           return b.String()
    }
    
    
  • 字符串类型的数据读取器 Reader(实现了 io.Reader 接口)

    • 调用 func NewReader(s string) *Reader 返回一个从 s 中读取数据的读取器,类似于 bytes.NewBufferString 但是更加高效并且只读。
    • 调用 func (*Reader) WriteTo(w io.Writer) (n int64, error) 将读取到的数据写入 w(实现了 io.Writer 接口),返回成功写入的字节数以及相应的错误信息;
    • 可以使用字节缓存区接收读取器的数据;
    b := []byte{}
    // 使用字节切片 b 作为缓存区的底层数组
    buf := bytes.NewBuffer(&b)
    // reader 读取数据后就可以作为数据源
    reader := strings.NewReader("abcdefg")
    // 将读取器中的内容(也是一种缓存区)写入字节缓存区中
    reader.WriteTo(buf)
    
    fmt.Println(buf.String())
    
    // 以高效只读的方式读取字符串内容
    reader1 := strings.NewReader("this is test string")
    data := make([]byte, 20)
    // 将 reader1 缓存区中的数据写入到 data 中,或者说将数据读取到 data 中
    reader1.Read(data)
    fmt.Println(string(data))
    // Output : this is test string
    
    • WriteTo 底层代码中关键的两行代码为:
    type Reader struct {
    s        string
    i        int64 // current reading index
    prevRune int   // index of previous rune; or < 0
    }
    // 通过切割字符串读取缓冲区中未读取的字符串(Reader 结构体中记录了已经读取的数据的起始位置)
    s := r.s[r.i:] 
     // 调用 io 方法将从读取器字节缓冲区中读取到字符串流写入到指定的 io.Writer 中,即我们指定的字节缓冲区 buf;
    m, err := io.WriteString(w, s)