Go 基本语法小结 (1) 😘 | 青训营

148 阅读14分钟

根据自己的理解整理的笔记

请不要转载

Golang 基础语法

主要内容

  • 项目结构
  • 变量
  • 指针和引用
  • 定义函数
  • 定义结构/接口
  • 闭包
  • 关键字和控制语句
  • 标准库

项目结构

基本概念

  1. 命名空间:package提供了一个命名空间,用于避免不同package中的标识符冲突。通过使用不同的package,可以将代码组织成独立的模块,并避免命名冲突。

  2. 可见性:Go语言中的标识符(变量、函数、结构体等)的可见性受限于其所属的package。标识符以小写字母开头的,只能在所属的package内部访问,而以大写字母开头的标识符可以被其他package引用和访问。

  3. 导入:通过import语句,可以在一个package中引入其他package,以便使用其提供的功能。导入的package可以通过其包名来访问其中的标识符, 注意 import 的包不能为循环依赖

  4. 可执行程序:一个特殊的packagemain,它定义了一个可执行程序的入口点。一个Go程序必须包含一个main包,并且在该包中的main函数将作为程序的入口点。 通过使用package,我们可以将代码组织成模块化的单元,并通过导入和可见性规则来管理代码的访问和复用。这有助于提高代码的可维护性、可读性和可测试性。

  5. go.mod 文件:go.mod文件是Go语言模块的管理文件,用于定义和管理项目的依赖关系和版本信息。它是Go语言在版本1.11及以上引入的模块化开发的一部分。其作用包括:

    1. 依赖管理:go.mod文件记录了项目所依赖的外部模块和版本信息。它列出了项目所需的依赖项,并指定了允许使用的版本范围。当构建项目时,Go工具会根据go.mod文件自动下载和管理所需的依赖项。

    2. 版本控制:go.mod文件指定了每个依赖项所允许的版本范围。这使得可以精确控制项目所使用的依赖项的版本,以确保项目的稳定性和一致性。

    3. 模块标识:go.mod文件定义了项目的模块标识,即模块的名称和版本。模块标识在构建和发布模块时起到唯一标识的作用。

    4. 自动化构建:go.mod文件使得构建和发布项目变得更加简单和自动化。通过运行go buildgo test等命令,Go工具会自动处理依赖项的下载和版本管理,以及构建和测试项目。

项目结构示例

Go语言的项目结构通常遵循一定的约定,尽管没有强制要求。常见的Go项目结构包括将源代码文件放置在一个目录中,以及使用go.mod文件来管理依赖关系。典型的Go项目结构如下:

   myproject/
   ├── cmd/
   │   └── main.go
   ├── pkg/
   │   └── mypackage/
   │       └── mypackage.go
   ├── internal/
   ├── api/
   ├── web/
   ├── go.mod
   └── README.md
  • cmd/目录通常包含可执行程序的入口点,每个子目录对应一个可执行程序。
  • pkg/目录包含可导入的包,用于封装和复用代码。
  • internal/目录包含项目内部使用的私有包,不可被其他项目导入。
  • api/web/目录可能包含与API和Web相关的代码。
  • go.mod是Go模块文件,用于管理项目的依赖关系。

变量:

声明

  • 在Go语言中,可以使用var关键字来声明变量。变量的声明可以包含类型和初始值,也可以使用 (:=) 省略类型而由编译器自动推断。
   var name string = "Alice"
   age := 30
   const length = 16 // 常量
  • Go 中如果定义一个没有使用到的变量或依赖,编译器会报错。可以使用 _ 表示占位

  • 变量可以定义在函数外部作为全局变量,在包内均可使用

  • any 表示任意类型,等同于 interface{}

变量类型

  1. 基本类型(Primitive Types):

    • 整数类型:intint8int16int32int64uintuint8uint16uint32uint64uintptr
    • 浮点数类型:float32float64
    • 复数类型:complex64complex128
    • 布尔类型:bool
    • 字符串类型:string
    • 字符类型:byteuint8的别名)和runeint32的别名)。
  2. 复合类型(Composite Types):

    • 数组类型:[n]T,表示具有固定长度的元素序列。
    • 切片类型:[]T,表示可变长度的元素序列。
    • 映射类型:map[K]V,表示键值对的集合。
    • 结构体类型:struct,表示字段的集合。
    • 接口类型:interface{},表示方法的集合。
    • 函数类型:func,表示函数(闭包)类型。
  3. 指针类型(Pointer Types):使用*T表示,指向类型T的指针。

  4. 通道类型(Channel Types):用于在 Goroutine 之间进行通信和同步。

这些是Go语言中常见的变量类型。通过使用这些类型,可以声明和操作各种不同类型的变量。

指针和引用

  • Go语言支持指针类型,可以使用&操作符获取变量的地址,使用*操作符获取指针指向的值(解引用。例如:
var x int = 10
var ptr *int = &x
fmt.Println(*ptr) // 输出:10
  • nil 表示空指针

定义函数:

  • 在Go语言中,可以使用func关键字来定义函数。函数可以有参数和返回值。例如:
func add(a, b int) int {
	return a + b
}
  • 定义递归函数
func getHeight(t *Tree) int {
	if t == nil {
		return 0
	} else {
		return max(getHeight(t.Left), getHeight(t.Right)) + 1
	}
}
  • 定义多返回值函数
func multiResult(a, b int) (c, d int) {
	return a, b
}
  • Golang 不支持方法重载

  • 在Go语言中,只有以大写字母开头的标识符(包括方法,字段)才可以从包外部访问,也被称为导出标识符。

定义结构/接口:

结构

  • 在Go语言中,可以使用type关键字来定义自定义的结构体和接口。结构体用于定义自定义的数据类型。例如:
//定义新结构
type Tree struct {
	Val   int
	Left  *Tree
	Right *Tree
}

// 取别名
type Array []*int
  • Go语言支持面向对象编程(OOP),但与传统的面向对象语言不同,Go语言没有类的概念。它通过结构体和方法来实现面向对象的特性。可以将方法与结构体关联,从而实现方法的调用。 即类似成员方法,可以使用 obj.method(..) 调用函数
type List struct {
	Value int
	next  *List
}

func (l *List) InsertOne(node *List) {
	node.next = l.next
	l.next = node
}

func (l *List) HasNext() bool {
	return l.next == nil
}

func (l *List) PrintList() {
	dummy := l
	for dummy.HasNext() {
		fmt.Printf("%d ", dummy.Value)
	}
}
  • 标签:

Go语言中没有直接的注解(Annotation)机制,但可以使用标签(Tag)来为结构体的字段添加元数据。标签是以反引号包围的字符串,可以在运行时通过反射来读取。例如:

type Person struct {
       Name string `json:"name"`
       Age  int    `json:"age"`
}

接口

  • 接口用于定义方法集合
type Animal interface {
	Sound() string
}
  • 在Go语言中,接口的实现是隐式的,即不需要在类型声明中显式声明它实现了某个接口。只要类型的方法集包含了接口中定义的所有方法,它就被认为是实现了该接口。
  • 在实现了某接口的所有方法后,便可以通过接口接收此结构体的实例(多态)。或者通过类型转换将接口类型转为结构体实例

闭包:

  • 在Go语言中,闭包是指一个函数捕获并引用了其外部作用域中的变量。闭包可以在函数内部定义,并可以访问外部函数的变量。例如:
   func increment() func() int {
       count := 0
       return func() int {
           count++
           return count
       }
   }

在上述示例中,我们定义了一个名为increment的函数,它返回一个闭包函数。闭包函数捕获了外部函数中的count变量,并在每次调用时递增并返回它。

  • 使用闭包
func closure(a []int) {
    // 将整数转为字符串
	var function = func(a []int) []string {
		var res []string
		for i := 0; i < len(a); i++ {
			res = append(res, strconv.Itoa(a[i]))
		}
		return res
	}
	// 使用闭包
	var tmp = function(a)
	for index, value := range tmp {
		fmt.Printf("%s in index %d \n", value, index)
	}
}

关键字

所有关键字

break default func interface select case defer go map struct chan else goto package switch const fallthrough if range type continue for import return var

包括代码结构,控制语句,goroutine, 数据类型的基本功能

循环

在 Go 语言中,有多种形式的循环语句可供使用。以下是 Go 语言中常用的循环语句形式:

  1. for 循环:

    for initialization; condition; post {
        // 循环体
    }
    
  2. for 循环的简化形式:

    for condition {
        // 循环体
    }
    
  3. for 循环的无限循环形式:

    for {
        // 循环体
    }
    
  4. range 循环:

    for index, value := range collection {
        // 循环体
    }
    
  5. range 循环的简化形式(忽略索引或值):

    for _, value := range collection {
        // 循环体
    }
    
  6. goto:跳转 (不能跳转到其他函数或方法中的标签, 只能在同一个代码块内跳转到标签。)

    func main() {
        i := 0
    
    loop:
        for i < 5 {
            fmt.Println(i)
            i++
            if i == 3 {
                goto loop
            }
        }
    }
    
  • for 循环是最常见的循环形式,用于在给定条件下重复执行一段代码。range 循环用于遍历集合(如数组、切片、映射、通道等)中的元素。

  • 可以使用 breakcontinue 语句来控制循环的执行流程。break 用于终止循环,continue 用于跳过当前循环迭代并进入下一次迭代。

分支语句

在 Go 语言中,有多种形式的分支语句可供使用。以下是 Go 语言中常用的分支语句形式:

  1. if 语句:

    if condition {
        // 条件为真时执行的代码块
    } else {
        // 条件为假时执行的代码块
    }
    
  2. if 语句的简化形式(只有条件部分):

    if condition {
        // 条件为真时执行的代码块
    }
    
  3. if 语句的初始化部分:

    if initialization; condition {
        // 条件为真时执行的代码块
    }
    
  4. switch 语句:

    switch expression {
    case value1:
        // expression 等于 value1 时执行的代码块
    case value2:
        // expression 等于 value2 时执行的代码块
    default:
        // expression 不等于任何 case 时执行的代码块
    }
    
  5. switch 语句的初始化部分:

    switch initialization; expression {
    case value1:
        // expression 等于 value1 时执行的代码块
    case value2:
        // expression 等于 value2 时执行的代码块
    default:
        // expression 不等于任何 case 时执行的代码块
    }
    
  6. switch 语句的简化形式(只有表达式部分):

    switch expression {
    case value1, value2:
        // expression 等于 value1 或 value2 时执行的代码块
    default:
        // expression 不等于任何 case 时执行的代码块
    }
    
  7. fallthrough:case 穿透

        switch i {
        case 1:
            fmt.Println("One")
            fallthrough
        case 2:
            fmt.Println("Two")
            fallthrough
        case 3:
            fmt.Println("Three")
        }
    

标准库

预定义软件包 builtin

builtin软件包中的函数和类型是Go语言的内置功能,无需导入即可直接使用。它们提供了一些基本的操作和功能,用于支持Go语言的核心特性和语法。

以下是对Go语言builtin包中几个常用函数的使用示例:

  1. 类型转换函数:

    num := 10
    str := string(num)
    
  2. 内存分配函数:

    ptr := new(int)
    *ptr = 42
    slice := make([]int, 0, 10)
    
  3. 类型信息函数:

    // 获取容量和长度,追加元素
    str := "Hello, Go!"
    length := len(str)
    
    nums := []int{1, 2, 3, 4, 5}
    capacity := cap(nums)
    
    nums = append(nums, 6)
    
  4. 类型断言函数:

    // 定义了一个空接口类型的变量 val,并将其赋值为字符串 "Hello, Go!"。空接口类型可以表示任意类型的值。
    var val interface{} = "Hello, Go!" 
    // 类型断言, 它尝试将 val 的值断言为 string 类型,并将结果赋值给变量 str。如果断言成功,str 将包含断言后的字符串值,同时 ok 的值将为 true。如果断言失败,str 将为零值(空字符串),同时 ok 的值将为 false。
    str, ok := val.(string)
    if ok {
        fmt.Println(str)
    }
    

其他常用标准库包

  1. fmt 包:用于格式化输入和输出,包括打印、格式化字符串、读取输入等。

    • fmt.Println(): 打印输出到标准输出。
    • fmt.Printf(): 格式化输出到标准输出。
    • fmt.Sprintf(): 格式化字符串并返回结果。
  2. strings 包:用于处理字符串的函数。

    • strings.Contains(): 判断字符串是否包含子串。
    • strings.Split(): 将字符串分割为子串切片。
    • strings.Join(): 将字符串切片连接为一个字符串。
  3. strconv 包:用于字符串和基本数据类型之间的转换。

    • strconv.Atoi(): 将字符串转换为整数。
    • strconv.ParseFloat(): 将字符串转换为浮点数。
    • strconv.Itoa(): 将整数转换为字符串。
  4. os 包:提供与操作系统交互的功能。

    • os.Args: 获取命令行参数。
    • os.Exit(): 终止程序并退出。
    • os.Open(): 打开文件。
  5. time 包:用于处理时间和日期。

    • time.Now(): 获取当前时间。
    • time.Parse(): 解析字符串为时间。
    • time.Sleep(): 暂停一段时间
  6. math 包:提供了数学运算相关的函数和常量。

    • math.Abs(): 返回一个数的绝对值。
    • math.Sin(): 返回一个角度的正弦值。
    • math.Sqrt(): 返回一个数的平方根。
  7. sort 包:提供了对切片和用户自定义数据类型进行排序的函数。

    • sort.Ints(): 对整数切片进行升序排序。
    • sort.Strings(): 对字符串切片进行升序排序。
    • sort.Slice(): 对切片进行自定义排序。

Golang 基本IO操作

主要内容

  • 标准 IO
  • 文件 IO

标准 IO

builtin 中的 IO

builtin 中定义了两个打印函数,可用于控制台打印字符串和数字.

func TestBuiltinIO() {
	// 获取环境变量
	println(os.Getenv("paas_url"))
	// 单个字符按 ascii 码打印
	print(1, 2, 3, 4, 5, '6', '7', "8", "\n")
	println("Done", "builtin IO")
}

fmt 包中的 IO

fmt 包中实现了完善的IO函数,默认使用标准IO文件对象(os.Stdin,os.Stdout)进行输入输出

func TestStandardIO() {
	fmt.Print("Enter a string: ")
	var name string
	var num int
	var ch int
	var f float64
	fmt.Scanln(&name)
	fmt.Print("Enter with %d %c %f: ")
	_, err := fmt.Fscanf(os.Stdin, "%d %c %f\n", &num, &ch, &f)
	if err != nil {
		fmt.Println("error", err)
		return
	}
	cnt, _ := fmt.Fprintf(os.Stdout, "%s,%.2f\n", name, f)
	fmt.Printf("parameter count: %d\n", cnt)
	fmt.Println("Done", "standard IO")
}

重定向

可以通过赋值标准IO文件进行重定向

func TestRedirectStandardIO() {
	// 获取工作目录output
	dir, _ := os.Getwd()

	// 将文件设置为标准输入
	file1, _ := os.Open(dir + "/files/standardIO/input.txt")
	os.Stdin = file1

	// 从标准输入读取内容
	input, _ := bufio.NewReader(os.Stdin).ReadString('\n')
	fmt.Println(input)

	// 将文件设置为标准输出
	file2, _ := os.Open(dir + "/files/standardIO/output.txt")
	os.Stdout = file2
	_, err := fmt.Println(input + " output")
	if err != nil {
		println(err.Error())
	}
}

异常处理

  • func Println(a ...any) (n int, err error), func Scanln(a ...any) (n int, err error)

输入输出函数都有返回值 (n int, err error) 用于返回解析的参数个数和处理IO异常

_, err := fmt.Fscanf(os.Stdin, "%d %c %f\n", &num, &ch, &f)
if err != nil {
    fmt.Println("error", err)
    return
}
cnt, _ := fmt.Fprintf(os.Stdout, "%s,%.2f\n", name, f)
fmt.Printf("parameter count: %d\n", cnt)

文件操作

defer 关键字

defer 是Go语言中的一个关键字,用于延迟(defer)函数或方法的执行。通过使用 defer 关键字,可以确保在函数执行完毕前,某个函数或方法会被调用。常用于资源释放、文件关闭、解锁互斥锁等操作,以确保在函数返回之前进行必要的清理工作。以下是 defer 关键字的一些特点和用法:

  • 延迟执行:使用 defer 关键字可以将函数或方法的执行推迟到当前函数返回之前。无论函数是正常返回还是发生异常,被延迟的函数都会被执行。

  • 堆栈机制:被延迟执行的函数或方法会被添加到一个堆栈中。当函数返回时,延迟执行的函数会按照后进先出(LIFO)的顺序执行。

  • 参数求值:被延迟执行的函数的参数会在 defer 语句出现时进行求值,而不是在实际执行时求值。这意味着函数参数的值会被保存并在延迟执行时使用。

打开文件

func readFileByChar(filename string) {
	file, err := os.Open(filename)

	if err != nil {
		fmt.Println("Error opening file:", err)
		return
	}

	defer func(file *os.File) {
		err := file.Close()
		if err != nil {
			_ = fmt.Errorf(err.Error())
		}
	}(file)

	scanner := bufio.NewScanner(file)
	for scanner.Scan() {
		line := scanner.Text()
		fmt.Println(line)
	}
	if err := scanner.Err(); err != nil {
		fmt.Println("Error reading file:", err)
	}
}

异常处理

file, err := os.Create(getPath(filename))

if err != nil {
    fmt.Println("Error opening file:", err)
    return
}

defer func(file *os.File) {
    err := file.Close()
    if err != nil {
        _ = fmt.Errorf(err.Error())
    }
}(file)

读写字符流

  • 按行读写文本
func ReadFileByChar(filename string) {
	file, err := os.Open(getPath(filename))

	// 异常处理
	if err != nil {
		fmt.Println("Error opening file:", err)
		return
	}

	// 关闭文件
	defer func(file *os.File) {
		err := file.Close()
		if err != nil {
			_ = fmt.Errorf(err.Error())
		}
	}(file)

	// 读取文件
	scanner := bufio.NewScanner(file)
	for scanner.Scan() {
		line := scanner.Text()
		fmt.Println(line)
	}
	if err := scanner.Err(); err != nil {
		fmt.Println("Error reading file:", err)
	}
}


func WriteFileByChar(filename string, text string) {
	file, err := os.Create(getPath(filename))

	if err != nil {
		fmt.Println("Error opening file:", err)
		return
	}

	defer func(file *os.File) {
		err := file.Close()
		if err != nil {
			_ = fmt.Errorf(err.Error())
		}
	}(file)

	// 写文件
	cnt, err := fmt.Fprintln(file, text)
	fmt.Printf("%d bytes are written\n", cnt)
	if err != nil {
		println(err.Error())
	}
}

访问权限

  • 可以使用 file.chmod 查看和修改文件的权限。

  • 可以使用os.Stat()函数来获取文件信息,然后通过Mode()方法获取文件的权限信息。