本文最后更新于 2025年1月22日 凌晨
部分内容和 C 语言的是一样的,所以就只记了不一样的
数据类型 字符类型 1 2 3 byte 等价 uint8 可以表达 ASCII 字符rune 等价 int32 可以表达 Unicode 字符string 字符串即字节序列,可以转化为 []byte 类型即字节切片
派生类型 1 2 3 4 5 6 7 8 数组 [1 ]int 长度为 1 的整型数组 切片 []float64 64 位浮点数切片 映射表 map [string ]int 键为字符串,值为整型的映射表 结构体 type Gopher struct {} Gopher 结构体 指针 *int 函数 type f func () 无参无返回值的函数 接口 type Gopher interface {} Gopher 接口 通道 chan int 整型通道
零值 零值又称为 zero value
,并不是指数字零 ,而是说一个类型的空值或默认值
1 2 3 4 5 6 数字类型 0 布尔类型 false 字符串类型 "" 数组 固定长度的对应类型的零值集合 结构体 内部字段都是零值的结构体 切片、映射表、函数、接口、指针、通道 nil
nil nil
类似于 C 语言中的 null
,但有区别。nil
只是一些引用类型的零值,并且不属于任何类型,仅仅是一个变量。
常量 初始化 常量的声明需要 const
关键字,并且在初始化时必须赋值,不然无法通过编译
1 2 3 4 const name string = "butter" const msg = "hello butt3rf1y" const num = 114514 const numExpression = (1 +2 +3 ) / 2 % 100 + num
批量声明常量可以用 ()
,可以存在多个
1 2 3 4 5 6 7 8 9 const ( Count = 1 Name = "butter" )const ( Size = 14 Len = 51 )
在同一个常量组中,如果前一个常量赋值且后面没有赋值,那么默认后面的和前面赋值常量值相等,也就是说其值默认就是前面常量的值
1 2 3 4 5 6 7 const ( A = 1 B C D E )
iota iota
是一个内置常量标识符,表示常量声明中无类型整数序数
列子
1 2 3 4 5 6 7 const ( Num = iota Num1 Num2 Num3 Num4 )
也能这样写
1 2 3 4 5 6 7 const ( Num = iota *2 Num1 Num2 Num3 Num4 )
还可以
1 2 3 4 5 6 7 const ( Num = iota << 2 *3 + 1 Num1 Num2 Num3 = iota Num4 )
iota
的值本质上就是 iota
所在行相对于当前 const
分组的第一行的差值。而不同的 const
分组则相互不会影响。
1 2 3 4 5 6 7 8 9 const ( Num = iota <<2 *3 + 1 Num2 = iota <<2 *3 + 1 _ Num3 Num4 = iota _ Num5 )
总结:iota
在 const
块中从0开始,每次定义一个新常量时递增1,直到用新的 const
重置。如果常量定义中没有显式赋值,则会继承前一个常量的表达式。并且iota
的值在显式使用时才会更新。
枚举 在 go 中,通过自定义类型 + const + iota 来实现枚举
1 2 3 4 5 6 7 8 type Season uint8 const ( Spring Season = iota Summer Autumn Winter )
枚举出来的结果实际上就是数字,go 也不支持直接将其转换为字符串,但我们可以通过给自定义类型添加方法来返回其字符串表现形式,实现 Stringer
接口就行
1 2 3 4 5 6 7 8 9 10 11 12 13 func (s Season) String() string { switch s { case Spring: return "spring" case Summer: return "summer" case Autumn: return "autumn" case Winter: return "winter" } return "" }
变量 声明 go 中的声明是后置的,变量的声明会用到 var
关键字,格式为 var 变量名 类型名
1 2 3 var happy string var intNUm int var char byte
如果要多个同样类型的变量时,可以写在一起
声明多个不同类型的变量时,可以使用 ()
,可以存在多个 ()
1 2 3 4 5 6 7 8 9 10 var ( name string age int address string )var ( school string class int )
注:变量如果只是声明而不赋值,那么变量存储的值就是对应类型的零值。
赋值 跟 C 语言规则差不多,但还可以分别赋值,比如说
1 2 3 var name string var age int name, age = "happy" , 1
但是每次都要指定类型,还可以短变量初始化,省略 var
关键字和后置类型,编译器会自己判断
虽然可以不用指定类型,但是在后续赋值时,类型必须保持一致,不然不能通过编译。还得注意的是,短变量初始化不能用 nil
,因为 nil
不属于任何类型,编译器无法推断。
短变量还能批量初始化
短变量声明虽然无法对一个已存在的变量使用,但是在赋值旧变量的同时声明一个新的变量时可以使用
在 go 中,所有在函数中的变量都必须要被使用,如果只是声明了变量,没有使用它就会报错,但仅适用于函数内的变量,对于函数外的包级变量无用。
匿名 用 _
可以表示不需要某一个变量
1 Open(name string ) (*File, error )
比如 os.Open
函数有两个返回值,我们只想要第一个,不想要第二个,可以按照下面这样写
1 file, _ := os.Open("readme.txt" )
未使用的变量是无法通过编译的,当你不需要某一个变量时,就可以使用下划线 _
代替。
交换 在 go 中,如果想要交换两个变量的值,不需要使用指针,可以使用赋值运算符直接进行交换,语法上看起来非常直观,例子如下
1 2 num1, num2 := 114 , 514 num1, num2 = num2, num1
三个变量也是如此
1 2 num1, num2, num3 := 114 , 514 , 22 num1, num2, num3 = num3, num2, num1
go 里面有一个特别有意思的规则,进行多个变量赋值运算时,它的顺序是先计算值再赋值,不是从左到右计算,这就和其他语言大相径庭了。比如说下面的例子
1 2 a, b, c := 0 , 1 , 1 a, b, c = b, c, a+b
上面代码答案是
它会将 a, b, c 三个数的值分别计算好再赋给它们,就等同于下面这段代码
当涉及到函数调用时,这个效果就更明显,有一个函数sum
可以计算两个数字的返回值
1 2 3 func sum (a, b int ) int { return a + b }
通过函数来进行两数相加
1 2 a, b, c := 0 , 1 , 1 a, b, c = b, c, sum(a, b)
结果没有变化,因为在计算sum
函数返回值时,它传入的参数依旧是 0 和 1
所以代码应该写成这样
比较 变量之间的比较时它们的类型必须相同,go 语言中不存在隐式类型转换,像下面这样的代码编译是无法通过的
1 2 3 4 5 func main () { var a uint64 var b int64 fmt.Println(a == b) }
所以必须使用强制类型转换
1 2 3 4 5 func main () { var a uint64 var b int64 fmt.Println(int64 (a) == b) }
可以使用 min
函数比较最小值
1 minVal := min(1 , 2 , -1 , 1.2 )
使用 max
函数比较最大值
1 maxVal := max(100 , 22 , -1 , 1.12 )
它们的参数支持所有的可比较类型,go 中的可比较类型有
布尔
数字
字符串
指针
通道 (仅支持判断是否相等)
元素是可比较类型的数组(切片不可比较)(仅支持判断是否相等)(仅支持相同长度的数组间的比较,因为数组长度也是类型的一部分,而不同类型不可比较)
字段类型都是可比较类型的结构体(仅支持判断是否相等)
除此之外,还可以通过导入标准库 cmp
来判断,不过仅支持有序类型的参数,在 go 中内置的有序类型只有数字和字符串。
1 2 3 4 5 6 import "cmp" func main () { cmp.Compare(1 , 2 ) cmp.Less(1 , 2 ) }
代码块 函数内部可以通过花括号建立一个代码块,彼此之间的变量作用域是相互独立的,不受干扰,无法访问,但是会受到父块中的影响。
输入输出 文件描述符 1 2 3 4 5 var ( Stdin = NewFile(uintptr (syscall.Stdin), "/dev/stdin" ) Stdout = NewFile(uintptr (syscall.Stdout), "/dev/stdout" ) Stderr = NewFile(uintptr (syscall.Stderr), "/dev/stderr" ) )
在 os
包下有三个外暴露的文件描述符,其类型都是 *os.File
,分别是:
1 2 3 os.Stdin 标准输入 os.Stdout 标准输出 os.Stderr 标准错误
输入 read 像读文件一样(麻烦)
1 2 3 4 5 func main () { var buf [1024 ]byte n, _ := os.Stdin.Read(buf[:]) os.Stdout.Write(buf[:n]) }
fmt fmt
包提供了几个函数
1 2 3 4 5 6 7 8 func Scan (a ...any) (n int , err error )func Scanln (a ...any) (n int , err error )func Scanf (format string , a ...any) (n int , err error )
读取两个数字
1 2 3 4 5 func main () { var a, b int fmt.Scanln(&a, &b) fmt.Printf("%d + %d = %d\n" , a, b, a+b) }
读取固定长度的数组
1 2 3 4 5 6 7 8 9 10 11 func main () { n := 10 s := make ([]int , n) for i := range n { fmt.Scan(&s[i]) } fmt.Println(s) } >> 1 2 3 4 5 6 7 8 9 10 >> [1 2 3 4 5 6 7 8 9 10 ]
bufio 有大量输入要读取的时建议使用 bufio.Reader
1 2 3 4 5 6 func main () { reader := bufio.NewReader(os.Stdin) var a, b int fmt.Fscanln(reader, &a, &b) fmt.Printf("%d + %d = %d\n" , a, b, a+b) }
scanner 与 bufio.Reader
类似,不过它按行读取
1 2 3 4 5 6 7 8 9 10 func main () { scanner := bufio.NewScanner(os.Stdin) for scanner.Scan() { line := scanner.Text() if line == "exit" { break } fmt.Println("scan" , line) } }
结果
1 2 3 4 5 6 7 1 scan 1 2 scan 2 3 scan 3 exit
输出 stdout 可以直接将字符串写入到标准输出中
1 2 3 4 5 6 7 package mainimport "os" func main () { os.Stdout.WriteString("hello butt3rf1y!" ) }
print/println go 中两个内置的函数 print
,println
,他们会将参数输出到标准错误中,仅做调试用,一般不推荐使用
fmt fmt
包提供了 fmt.Println
函数,该函数默认会将参数输出到标准输出中。它的参数支持任意类型 ,如果类型实现了 String
接口也会调用 String
方法来获取其字符串表现形式,所以它输出的内容可读性比较高,适用于大部分情况,不过由于内部用到了反射,在性能敏感的场景不建议大量使用。
bufio bufio
提供了可缓冲的输出方法,它会先将数据写入到内存中,积累到了一定阈值再输出到指定的 Writer
中,默认缓冲区大小是 4KB
,在文件 IO,网络 IO 的时候使用比较好。
1 2 3 4 5 func main () { writer := bufio.NewWriter(os.Stdout) defer writer.Flush() writer.WriteString("hello butt3rf1y!" ) }
格式化 go 中的格式化输出功能基本上由 fmt.Printf
函数提供,跟 C 语言差不多。在 %
与格式化动词之间加上一个空格能达到分隔符的效果。