Golang学习笔记

217 阅读4分钟

golang很特别:

  • 没有对象,没有继承多态,没有泛型,没有try/catch
  • 有接口,函数式编程,CSP并发模型(gorouting + channel)

go version:查看版本 go env:查看环境配置

安装好go后,打开go moudle,修改国内镜像配置:

go env -w GO111MODULE=on

go env -w GOPROXY=https://goproxy.cn,direct

安装goimports:

go get -v golang.org/x/tools/cmd/goimports

然后进入vscode,新建一个go文件,vscode会弹出两个窗口,都选择安装即可。

基本语法

变量

golang定义完变量后是有默认初值的,不像C是不确定值,也不像java是null。

package main

import (
	"fmt"
)

// 同时定义多个变量
var (
        aa = 3
        bb = "kkk"
        cc = true
)

func variableDefine() {
	var a, b int;
	var c string;
	var d, e, f = 1, "abc", true // 类型推断
	g := "short" // 缩写(不能用在函数外)
	fmt.Printf("%d %d %q\n", a, b, c) // %q可以打出引号
	fmt.Println(d, e, f, g)
}

func main() {
	variableDefine()
}

内建变量类型

  • bool, string
  • (u)int, (u)int8, (u)int16, (u)int32, (u)int64, uintptr
  • byte, rune
  • float32, float64, complex64, complex128 int 后没数字就是根据操作系统是32位还是64位决定。go中没有 long 类型,需要长一点就用 int64ptr 就是指针,比C的指针要方便好用很多。
    rune 是字符类型,因为 char 只有一字节,很多编码是超出一字节的(如一个汉字两字节),会造成很多坑。因此采用 rune,32位4字节。byte 依然是8位。
    go中也没有 double 类型。
    complex64 的实部和虚部都是 float32complex128 的实部和虚部都是 float64

强制类型转换

golang中只有强制类型转换,没有隐式类型转换。

func main() {
	var a, b = 3, 4
	var c int = int(math.Sqrt(float64(a * a + b * b)))
	fmt.Println(c)
}

上面的代码中,math.Sqrt接收一个float64的参数,返回一个float64的值。因此必须对输入的int类型做强制类型转换,计算完后赋给int类型的c时也需要强制类型转换,如果算出来的不是5,而是4.99等,强制类型转换后值会变成4,要特别注意处理这种情况。

常量

类似C中的常量,是做一个值的替换,但是不用像C中一样全部大写。不类似js中的const

func consts() {
	const filename string = "abc.txt"
	const a, b = 3, 4
	var c int = int(math.Sqrt(a*a + b*b))
	fmt.Println(filename, c)
}

func main() {
	consts()
}

常量可以指定类型,也可以不指定,不指定的时候类型是不确定的,const不指定类型的数字可以作为任意数字类型属于。像上面代码中,a和b不指定类型的话,传入math.Sqrt时就可以不做强制类型转换。

枚举

go语言没有枚举关键字,一般就用一组const来表示。

func main() {
	const(
		cpp = 0
		java = 1
		python = 2
		golang = 3
	)
	fmt.Println(cpp, java, python, golang) // 0 1 2 3
}

go为自增的const设计了一种简便写法:

func main() {
	const(
		cpp = iota
		java
		python
		golang
	)
	fmt.Println(cpp, java, python, golang) // 0 1 2 3
}

// 跳过某些值
func main() {
	const(
		cpp = iota
		_
		python
		golang
		javascript
	)
	fmt.Println(cpp, javascript, python, golang) // 0 4 2 3
}

// iota是一种表达式
func main() {
	const(
		b = 1 << (10 * iota)
		kb
		mb
		gb
		tb
		pb
	)
	fmt.Println(b, kb, mb, gb, tb, pb) // 1 1024 1048576 1073741824 1099511627776 1125899906842624
}

判断、选择、循环

if

判断条件不需要加括号,其他和C、js差不多。

func main() {
	const filename = "abc.txt"
	contents, err := ioutil.ReadFile(filename)
	if err != nil {
		fmt.Println(err)
	} else {
		fmt.Printf("%s\n", contents)
	}
}

// 另一种写法
func main() {
	const filename = "abc.txt"
	if contents, err := ioutil.ReadFile(filename); err != nil {
		fmt.Println(err)
	} else {
		fmt.Printf("%s\n", contents)
	}
}
  • if语句的条件里可以赋值
  • if语句的条件里赋值的变量作用域就在这个if语句里面,如上面第二种写法contents只能在if-else里访问。

switch

switch 会自动break,除非使用 fallthroughpanic 会中断程序执行,输出错误信息。

func eval(a, b int, op string) int {
	var result int
	switch op {
	case "+":
		result = a + b
	case "-":
		result = a - b
	case "*":
		result = a * b
	case "/":
		result = a / b
	default:
		panic("unsupported operator:" + op)
	}
	return result
}

// switch后面可以不跟表达式
func grade(score int) string {
	g := ""
	switch {
	case score < 0 || score > 100:
		panic(fmt.Sprintf("Wrong score: %d", score))
	case score < 60:
		g = "F"
	case score < 80:
		g = "C"
	case score < 90:
		g = "B"
	case score <= 100:
		g = "A"
	}
	return g
}

func main() {
	fmt.Println(eval(2, 3, "+"))
	fmt.Println(grade(59))
	fmt.Println(grade(79))
	fmt.Println(grade(89))
	fmt.Println(grade(100))
	fmt.Println(grade(101))
}

输出:

5
F
C
B
A
panic: Wrong score: 101

goroutine 1 [running]:
main.grade(0x65, 0xc00000e018, 0xc000068f28)
        /Users/pengwei/Desktop/golearn/hello.go:26 +0x108
main.main()
        /Users/pengwei/Desktop/golearn/hello.go:45 +0x2e8
exit status 2

函数定义

// 单个返回值
func func_name(a, b int) int { //同类型,可以省略  a, b int
    return a + b
}

// 多个返回值
func SumAndProduct(A, B int) (int, int) {
    return A+B, A*B
}

命名返回值:可以给一个函数的返回值指定名字。如果指定了一个返回值的名字,则可以视为在该函数的第一行中定义了该名字的变量。

func rectProps(length, width float64) (area, perimeter float64) {
    area = length * width
    perimeter = (length + width) * 2
    return // no explicit return value
}

func main() {
    area, perimeter := rectProps(10.8, 5.6)
    fmt.Printf("Area %f Perimeter %f", area, perimeter)
}

多返回值常用于返回错误:

func eval(a, b int, op string) (int, error) {
    switch op {
    case "+":
        return a + b, nil;
    case "-":
        return a - b, nil;
    default:
        return 0, fmt.Errorf("unsupported operation: " + op)
    }
}

func main() {
    fmt.Println(eval(3, 4, "x"))
}

函数还可以作为参数传入另一个函数:

func apply(op func(int, int) int, a, b int) int {
    p := reflect.ValueOf(op).Pointer()
    opName := runtime.FuncForPC(p).Name()
    fmt.Printf("Calling function %s with args " + "(%d, %d)\n", opName, a, b)
    return op(a, b)
}

func main() {
    fmt.Println(apply(func(a int, b int) int {
        return int (math.Pow(float64(a), float64(b)))
    }, 3, 4))
}

不定参数:

func sum(numbers ...int) int {
    s := 0
    for i := range numbers {
        s += numbers[i]
    }
    return s
}

func main() {
    fmt.Println(sum(1, 2, 3, 4, 5))
}

指针、数组、容器

指针

func main() {
    fmt.Println(sum(1, 2, 3, 4, 5))
    var a int = 2
    var pa *int = &a
    *pa = 3
    fmt.Println(a)
}

golang中的指针比C中的指针简单,体现在golang中的指针不能进行运算。

值传递和引用传递

C中参数的传递分为值传递和引用传递:

void pass_by_val(int a) {
    a++;
}

void pass_by_ref(int& a) {
    a++;
}

int main() {
    int a = 3;
    
    pass_by_val(a);
    printf("After pass_by_val: %d\n", a); // 3
    
    pass_by_ref(a);
    printf("After pass_by_ref: %d\n", a); // 4

golang中只有值传递。可以使用指针实现引用传递。

func swap(a, b *int) {
	*b, *a = *a, *b
}

func main() {
	a, b := 3, 4
	swap(&a, &b)
	fmt.Println(a, b) // 4, 3
}

更简单的方式实现交换:

func swap(a, b int) (int, int) {
    return b, a
}

func main() {
    a, b := 3, 4
    a, b = swap(a, b)
    fmt.Println(a, b) // 4, 3
}

数组

func main2() {
    var arr1 [5]int
    arr2 := [3]int{1, 3, 5}
    arr3 := [...]int{2, 4, 6, 8, 10}
    var grid [4][5]bool

    fmt.Println(arr1, arr2, arr3)
    fmt.Println(grid)
    
    // for i := 0; i < len(arr3); i++ {
    // 	fmt.Println(arr3[i])
    // }

    // 遍历数组用range更方便
    for i, v := range arr3 {
        fmt.Println(i, v)
    }
}

golang中的数组是值类型,而不是引用类型。

  • [3]int[5]int 是不同类型
  • 调用 func f(arr [3]int) 会拷贝数组
func printArray(arr [5]int) {
	arr[0] = 100
	for i, v := range arr {
		fmt.Println(i, v)
	}
}

func main() {
	var arr1 [5]int
	// arr2 := [3]int{1, 3, 5} // 无法传入printArray,因为[3]int和[5]int不是同一类型
	arr3 := [...]int{2, 4, 6, 8, 10}

	printArray(arr1)
	printArray(arr3)

	fmt.Println(arr1)
	fmt.Println(arr3)
}

// 结果
0 100
1 0
2 0
3 0
4 0
0 100
1 4
2 6
3 8
4 10
[0 0 0 0 0]
[2 4 6 8 10]

如果传的是数组的指针就可以修改数组了:

func printArray(arr *[5]int) {
	arr[0] = 100
	for i, v := range arr {
		fmt.Println(i, v)
	}
}

func main() {
	var arr1 [5]int
	// arr2 := [3]int{1, 3, 5}
	arr3 := [...]int{2, 4, 6, 8, 10}

	printArray(&arr1)
	printArray(&arr3)

	fmt.Println(arr1)
	fmt.Println(arr3)
}

// 结果
0 100
1 0
2 0
3 0
4 0
0 100
1 4
2 6
3 8
4 10
[100 0 0 0 0]
[100 4 6 8 10]

这个感觉操作数组很麻烦,但其实golang中很少直接操作数组,一般都是操作数组的切片。

面向接口

结构体

duck typing的概念

组合的思想

函数式编程

闭包的概念

例题

工程化

资源管理,错误处理

测试和文档

性能调优

并发编程

goroutine和channel

理解调度器

例题