go基础语法 | 青训营

82 阅读7分钟

第一个go程序

package main  
  
import "fmt"  
  
func main() {  
fmt.Println("Hello, World!")  
}  
  1. 入口是main函数,{ 括号必须和函数名同一行
  2. 每行结束不需要分号,需要对齐,保存时编译器自动对齐和删除分号
  3. fmt猜测是一个类,或者头文件,因为go是静态编译,fmt相关的都直接搬到该文件中
  4. go以package(包)为管理单位,必须有main包

不懂的地方

fmt是什么
package是什么

命令行运行和参数

go build xxx.go //生成.exe文件  
go run xxx.go //直接运行,编译器也是直接运行  
import (  
"fmt"  
"os"  
)  
func main() {  
list := os.Args  
fmt.Println(len(list))  
for index, data := range list {  
fmt.Printf("%d: %s\n", index, data)  
}  
}  
  1. 和c的命令行参数类似,第一个是程序名字

变量声明

func main() {  
var a, b int  
fmt.Println("a =", a)  
b = 10  
fmt.Println("b =", b)  
}  
  1. 变量声明后必须使用,默认值为0;同理import "fmt",fmt也必须使用
  2. var 变量 数据类型
  3. 这里打印发现"a ="后面自动加了空格

不懂的地方

默认值是0怎么实现的?

变量初始化和auto

func main() {  
var a int = 10  
fmt.Println("a =", a)  
b := 20  
fmt.Printf("b type is %T", b)  
}  
  1. a初始化
  2. b用%T打印类型,:=是自动推导类型,类似c++的auto, 具体是先声明后赋值
  3. printf和c++的相似,而println是自动加换行

疑惑

Println具体实现是什么?

多重赋值

func main() {  
a, b := 10, 20  
a, b = b, a  
fmt.Printf("a = %d, b = %d\n", a, b)  
}  
  1. 交换变量和python类似

疑惑

go的变量和python一样都是指针吗

const

func main() {  
const a int = 10  
const b = 20.2  
fmt.Printf("a = %d, b type is %T", a, b)  
}  
  1. 注意const的自动类型推导不用:=,我想原因是:=是先声明后赋值,而const只能初始化,不能赋值

iota

func main() {  
const (  
a = iota  
b, d = iota, iota  
c = iota  
)  
fmt.Printf("a = %d, b = %d, c = %d, d = %d", a, b, c, d)  
}  
  1. a, b, d, c分别是0, 1, 1, 2,即从0开始++,同一行值一样

数据类型

  1. c的char在go是byte, 注意fmt.Println打印byte成int32
  2. bool, float32, float64, string, int
  3. complex128,复数 var a complex128 = 1.2 + 3.5i, 可以通过real()和imag()取实部和虚部

fmt

  1. %v,自动匹配格式,注意byte匹配成int32, %+v:显示详细信息(如打印结构体时可以用到)
  2. %T, 数据类型, 注意打印bool用的小写%t
  3. 输入:scanfscan,后者自动匹配格式,类似cin

类型转换

func main() {  
var a bool = true  
fmt.Printf("%d", a) //%!d(bool=true)  
}  
  1. int和bool不能相互转换
var a byte = 'c'  
b := int(a)  
  1. byte可以显式转换为int,go中不能隐式数据类型转换

类型别名

type myData int  
var a myData = 10  
fmt.Printf("a = %d", a)  
  1. type和c的typedef类似

运算符

  1. 和c++类似,注意和python不同的是,拥有++和--运算符
  2. 没有?运算符

if, switch

func main() {  
if b := 3; b == 3 {  
fmt.Println("haha")  
}  
}  
  1. 一个初始化语句(可以省略); 条件判断
  2. 注意判断没有()
  3. 和C相似,if...else if... else
  4. switch,默认加上了break,可以加fallthrough即不break

循环

  1. for
func main() {  
sum := 0  
for i := 1; i <= 100; i++ {  
sum += i  
}  
fmt.Println(sum)  
}  
  1. range
func main() {  
var a string = "abcdef"  
for index, data := range a { //两个返回值,下标和数据,类似于python的enumerate  
fmt.Println(index, data)  
}  
for i := range a { //第二个默认丢弃,也可以清晰一点:for i, _ := range a  
fmt.Printf("%d\n", i)  
}  
}  
  1. 两个返回值,下标和数据,类似于python的enumerate
  2. 第二个默认丢弃,也可以清晰一点:for i, _ := range a

函数

  1. 支持多个返回值
  2. 返回值可以是函数
  3. 形参列表写成a, b int表示a, b都是int类型,或者a int, b int
func compare(a, b int) (max, min int) {  
if a >= b {  
max, min = a, b  
} else {  
max, min = b, a  
}  
return max, min  
}  
  
func main() {  
op1, op2 := compare(10, 20)  
fmt.Println("max = ", op1, "min = ", op2)  
}  

疑惑

参数返回那个()作用是什么?

不定参数

func test(a int, args ...int) int { //返回类型是int  
fmt.Println("len args = ", len(args)) // 3  
return a  
}  
func main() {  
var a int = test(10, 1, 2, 3)  
fmt.Println("a = ", a)  
}  
  1. 类似python函数的*args, go用 name ...int表示
  2. 可以切片,如使用实参args[:2]...

函数传递

type myFunc func(int, int) (int, int)  
  
func main() {  
var func1 myFunc  
func1 = compare  
op1, op2 := func1(10, 20)  
fmt.Println("max = ", op1, "min = ", op2)  
}  
  1. 类似c++的函数指针

疑惑

内部如何实现, 函数指针的意义和实现方式,c++和go在这里的区别

匿名函数

func main() {  
f1 := func(a, b int) (max, min int) {  
if a >= b {  
max = a  
min = b  
} else {  
max = b  
min = a  
}  
return max, min  
}  
op1, op2 := f1(10, 20)  
fmt.Println("max = ", op1, "min = ", op2)  
  
//2. 后面直接加()也是调用  
op1, op2 = func(a, b int) (op1, op2 int) {  
return a + b, a - b  
}(10, 5)  
fmt.Println("max = ", op1, "min = ", op2)  
  
}  
  1. 类似c++的lambda

闭包

  1. 在函数作用域外部可以对内部的变量操作,而函数内部也可以操作外部的变量,且变量长期在内存中

全是疑惑!!!不知道怎么使用

defer

func main() {  
defer fmt.Println("over")  
fmt.Println("begin")  
}  
  1. 类似于c++的析构函数,类释放前调用
  2. 若有多个defer,同继承类的析构函数,LIFO原则,最下面的最早调用
  3. 函数发生错误也会调用defer

工程管理

pkg, src, bin目录

  1. 这里需要了解!!!
  2. pkg放导入包,src放源程序,bin放.exe文件
  3. go install自动生成bin和pkg目录,需要设置GOBIN环境变量,需要了解!!!

导入包

  1. import "fmt"
  2. import io "fmt"和python中import numpy as np一样
  3. 同理还有import . "fmt",那么fmt.Printf可以直接写为Printf,但这样名字冲突会出现
  4. import _ "fmt",不用该包,只调用他的init函数
  5. init函数:导入包的执行顺序:import "xxx" --> xxx的 func init --> 源文件的main

同级目录的.go文件

  1. package名字必须一样
  2. 可以直接调用另一个.go文件的函数

不同级目录

package main  
  
import "fmt"  
import "hello/src/hello/test" //包名和文件名是一样的  
  
func main() {  
a := test.Add(10, 20)  
fmt.Println("a = ", a)  
}  
  1. package可以不同
  2. 调用另一个.go文件的函数:package.func()
  3. func首字母大写才能被其他文件调用,否则是私有的

疑惑

可以理解为package就是文件的声明吗?

其他数据类型

  • 指针
  • 数组
  • 切片
  • 字典
  • 结构体

指针

  1. 同C, Printf用%p或%v打印&a的地址, Println自动类型
  2. 指针的数据类型为 int而不是 int
  3. 和C不同的是,空为nil, 没有->运算符,不支持指针运算

new

func main() {  
var p *int = new(int)  
*p = 100  
fmt.Println(*p)  
  
q := new(int)  
*q = 200  
fmt.Println(*q)  
}  
  1. 和C++类似

数组

  1. var arr [10]int, 和C类似,只能用常量初始化数组大小
  2. for index, data := range arr常用遍历
  3. 类似C++的列表初始化:arr := [5]int{1, 2, 3},其他默认0;还可以设置位置:arr := [5]int{2: 30, 4:50}
  4. 快速打印:用Println, 效果如:`[0 0 33 0 55]
  5. 二维数组:
arr := [3][4]int{1: {1, 2, 3, 4}}  
fmt.Println(arr)  
}  
  1. 和vector类似,可以用==比较数组中每个元素是否都一样,=赋值
  2. !!!和C不同的是,函数传参时,形参数组是值传递,所以要用arr *[5]int, func(&arr)才能改变数组元素的值

随机数

import (  
"fmt"  
"math/rand"  
"time"  
)  
  
func main() {  
rand.Seed(time.Now().Unix())  
for i := 0; i < 5; i++ {  
k := rand.Intn(100) //左闭右开  
fmt.Println(k)  
}  
}  

疑惑

Seed中Unix()作用是什么
编译器提示现在不使用Seed了,需要改为什么

切片

  1. a := []int{1, 2}, 或 b := [...]int{2, 3}初始化
  2. 也可以用make初始化,a := make([]int, len, cap), 其中cap可以省略
  3. [low: high: max], len = high - low, cap = max - low, 底层是原数组,同一个地址
  4. slice = append(slice, val1, val2)在后面加入元素,自动扩容
  5. 扩容特点和vector类似:
func main() {  
arr := make([]int, 0, 1)  
old_size := cap(arr)  
for i := 0; i < 8; i++ {  
fmt.Println(i, old_size)  
arr = append(arr, 1)  
if old_size < cap(arr) {  
old_size = cap(arr)  
}  
}  
}  
  1. copy(s1, s2), 注意理解复制时的指针即可
  2. !!!函数传递切片是引用传递,和C的数组类似

疑惑

go的数组和切片作为参数传递时为什么不同,这样设计的意义在哪里

map,字典

  1. 无序,键值唯一
  2. 初始化:hash := make(map[int]string, 10), 10是cap, 会自动扩容
  3. hash := map[int]string{1: "tim", 2: "300"}也可以初始化
  4. 遍历以及判断是否存在map中:
func main() {  
//遍历  
hash := map[int]string{1: "tim", 2: "300"}  
for key, value := range hash {  
fmt.Println(key, value)  
}  
//判断是否存在  
val, exist := hash[1]  
fmt.Println(val, exist) //false时,val为空字符串  
}  
  1. 删除:delete(hash, 1),删除key值
  2. 作为函数参数:引用传递

结构体

type student struct {  
name string  
score int  
}  
  
func main() {  
var s1 student = student{"tim", 90}  
fmt.Println(s1)  
s2 := student{name: "amy"}  
fmt.Println(s2)  
}  
  1. 结构体初始化如上代码块
  2. 结构体指针:
s2 := &student{name: "amy"}  
fmt.Println(*s2)  
  1. 结构体指针访问用.和(*p).效果一样,fmt.Println(s2.name)
  2. 函数传递结构体时是值传递,所以需要改变里面内容时要加*和&
  3. 补充:public和privated在go里通过不同package里面的首字母判断,首字母大写是public,反之privated