基础
每个go程序都叫做go模块 在开始写代码之前要初始化go 使用go mod init <地址>推荐使用github的仓库为地址 如果是本地开发 随便写一个就可以了 在go中存在未使用的变量或者引用时 是无法编译通过的 某些函数会返回多个值 如果我们只需要其中的某些 我们可以使用_来忽略 go mod tidy添加需要的依赖,删除不需要的依赖
package main
import (
"fmt"
)
func main() {
fmt.Println("开始连接数据库~")
}
一段简答的go代码 我们从头往下看 package main 每个源文件的顶部都必须声明它属于哪个包 用来管理功能相近的源文件 main是一个特殊的包 带有main函数中必须是main包 import 用于导入需要用到下依赖 func函数关键字
数据类型与流程控制
全局变量需要使用var关键字进行定义 nn.(type)类型断言类型断言用于检查接口类型的值是否可以被转换为某个具体类型。如果nn不是type类型就会panic 空值:nil 整型类型: int(取决于操作系统), int8, int16, int32, int64, uint8, uint16, … 浮点数类型:float32, float64 字节类型:byte (等价于uint8) 字符串类型:string 布尔值类型:boolean,(true 或 false) 变量可以通过var和:=来创建go创建的变量必须使用,不然是不能通过编译的
var i2 int64
var iii int = 45
i2 = int64(48)
reflect.TypeOf(i2).Kind()//可以获得变量的类型
len()//获得变量长度
在 Go 语言中,字符串使用 UTF8 编码,UTF8 的好处在于,如果基本是英文,每个字符占 1 byte,和 ASCII 编码是一样的,非常节省空间,如果是中文,一般占3字节。 这样就导致了在用字符串处理中文时会出现问题
var str string = "你好, world!"
fmt.Println(str[0], i2)//这里不是·你·
runeArr := []rune(str)//解决办法是转换为rune数组
//这样无论占多少个字节都用int32来表示,打印的时候再用string转换
fmt.Println(runeArr[0], string(runeArr[0]))
数组和切片
slice1 := make([]float32, 0) // 长度为0的切片
//初始长度是3最大长度是5
slice2 := make([]float32, 3, 5)
a := [5]int{1,2,3,4,5}//固定长度数值,声明时初始化
// 添加元素,切片容量可以根据需要自动扩展
slice2 = append(slice2, 1, 2, 3, 4) // [0, 0, 0, 1, 2, 3, 4]
fmt.Println(len(slice2), cap(slice2)) // 7 12
// 子切片 [start, end)
sub1 := slice2[3:] // [1 2 3 4]
sub2 := slice2[:3] // [0 0 0]
sub3 := slice2[1:4] // [0 0 1]
// 合并切片
combined := append(sub1, sub2...) // [1, 2, 3, 4, 0, 0, 0]
//`sub2...` 是切片解构的写法,将切片解构为 N 个独立的元素
map 类似于 java 的 HashMap,Python的字典(dict),是一种存储键值对(Key-Value)的数据解构。使用方式和其他语言几乎没有区别。
// 仅声明
m1 := make(map[string]int)
// 声明时初始化
m2 := map[string]string{
"Sam": "Male",
"Alice": "Female",
}
// 赋值/修改
m1["Tom"] = 18
指针即某个值的地址,类型定义时使用符号*
,对一个已经存在的变量,使用 &
获取该变量的地址。一般来说,指针通常在函数传递参数,或者给某个类型定义新的方法时使用。
str := "Golang"
var p *string = &str // p 是指向 str 的指针
*p = "Hello"
fmt.Println(str) // Hello 修改了 p,str 的值也发生了改变
a := 11
if a > 15{//条件不需要加括号
}
else if a < 5{
}
if aa := 4; aa < 4{//可以在if中创建局部变量
}
type Gender int8
const (
MALE Gender = 1
FEMALE Gender = 2
)
switch a{
case 1://默认不会执行下一个
case 2:
fallthrough//这样就会执行下一个
case 3:
}
- 在这里,使用了
type
关键字定义了一个新的类型 Gender。 - 使用 const 定义了 MALE 和 FEMALE 2 个常量,Go 语言中没有枚举(enum)的概念,一般可以用常量的方式来模拟枚举。
- 和其他语言不同的地方在于,Go 语言的 switch 不需要 break,匹配到某个 case,执行完该 case 定义的行为后,默认不会继续往下执行。如果需要继续往下执行,需要使用 fallthrough
特殊的for循环for range
nums := []int{10, 20, 30, 40}
for i, num := range nums {
fmt.Println(i, num)
}
// 0 10
// 1 20
// 2 30
// 3 40
m2 := map[string]string{
"Sam": "Male",
"Alice": "Female",
}
for key, value := range m2 {
fmt.Println(key, value)
}
// Sam Male
// Alice Female
sum := 0
for i := 0; i < 10; i++ {
if sum > 50 {
break
}
sum += i
}
普通的
函数与错误处理
如果我们设置了返回值的名字,我们可以直接在函数内设置返回值,不需要用return返回了,如果每设置也没有return就会返回一个默认的值
一个典型的函数定义如下,使用关键字 func
,参数可以有多个,返回值也支持有多个。
func funcName(param1 Type1, param2 Type2, ...) (return1 Type3, ...) {
// body
}
//go可以返回多个参数
func div(a, b int) (int, int) {
return a / b, a % b
}
//我们可以给返回值设置一个名字,这样就可以不使用return返回
func add(a, b int) (num int) {
num = a + b
return
}
如果函数实现过程中,如果出现不能处理的错误,可以返回给调用者处理。比如我们调用标准库函数
_, err := os.Open("filename.txt")
//如果没有错误err就等于nil空
if err != nil {
fmt.Println(err)
}
func hello(name string) error {
if len(name) == 0 {
//通过errors.New返回自定义的错误类型
return errors.New("error: name is null")
}
fmt.Println("Hello,", name)
return nil
}
//这样就会出现无进入if里
if err := hello(""); err != nil {
fmt.Println(err)
}
在 Python、Java 等语言中有 try...catch
机制,在 try
中捕获各种类型的异常,在 catch
中定义异常处理的行为。Go 语言也提供了类似的机制 defer
和 recover
。
func get(index int) (ret int) {
defer func() {
if r := recover(); r != nil {
fmt.Println("Some error happened!", r)
ret = -1
}
}()
arr := [3]int{2, 3, 4}
return arr[index]
}
func main() {
fmt.Println(get(5))
fmt.Println("finished")
}
- 在 get 函数中,使用 defer 定义了异常处理的函数,在协程退出前,会执行完 defer 挂载的任务。因此如果触发了 panic,控制权就交给了 defer。
- 在 defer 的处理逻辑中,使用 recover,使程序恢复正常,并且将返回值设置为 -1,在这里也可以不处理返回值,如果不处理返回值,返回值将被置为默认值 0。
结构体类似于其他语言中的 class,可以在结构体中定义多个字段,为结构体实现方法,实例化等。接下来我们定义一个结构体 Student,并为 Student 添加 name,age 字段,并实现 hello()
方法。
type Student struct {
name string
age int
}
//`func` 和函数名`hello` 之间,加上该方法对应的实例名 `stu` 及其类型 `*Student`,可以通过实例名访问该实例的字段`name`和其他方法了。
//这里的实列名和后面的方法名都可以随便起,但是要注意不要和其他的变量名冲突
func (sut *Student) hello(person string) string {
//创建Student的一个方法,与普通函数的区别在于
//使用`Student{field: value, ...}`的形式创建 Student 的实例
return fmt.Sprintf("hello %s, I am %s", person, sut.name)
}
func main() {
//没有被赋值的变量会被赋予默认值
stu := &Student{name: "Tom",}
sstu := Student{name: "Jerry",}
fmt.Println(sstu.hello("Tom")) // Tom
msg := stu.hello("Jack")
fmt.Println(msg) // hello Jack, I am Tom
stu2 := new(Student)//也可以使用new
}
接口的实现
type Person interface {
getName() string
}
type Student struct {
name string
age int
}
func (s *Student) getName() string {
return s.name
}
type Worker struct {
name string
gender string
}
func (w *Worker) getName() string {
return w.name
}
func main() {
var _ Person = (*Student)(nil)
var _ Person = (*Worker)(nil)
var p *Student = &Student{
name: "Tom",
age: 18,
}
//一个空接口可以存储任何类型的值,包括指针、结构体、函数、切片、map等。
m := make(map[string]interface{})
m["name"] = "Tom"
m["age"] = 18
m["scores"] = [3]int{98, 99, 85}
fmt.Println(m) // map[age:18 name:Tom scores:[98 99 85]]
fmt.Println(p.getName()) // Tom
}