Go入门笔记 (超精简版)

275 阅读8分钟

本文是一个超级基础的Go入门笔记,仅涉及入门级别的基本使用,预计阅读时间10分钟。

1 Linux安装

//Centos
sudo yum install golang

//Ubutun
sudo add-apt-repository ppa:longsleep/golang-backports
sudo apt update
sudo apt install golang-go

如下说明安装成功

$ go version
go version go1.15.6 linux/amd64

可能各个公司为了保持一致的开发环境,会规定使用公司的一键安装脚本,或者分配已经安装好的开发机使用。

新建main.go

package main

import "fmt"

func main() {
    fmt.Println("Hello, World!")
}

执行

$ go build main.go // 跟C语言一样 -o 自定义编译后的可执行文件名
$ ./main  //Hello, World!
//或者
$ go run main.go //Hello, World!

2 数据类型

数据类型有布尔,数字,字符串,指针,数组,结构体,Channel,函数,切片,结构,Map,接口

2.1 数字整型

类型描述
uint8无符号 8 位整型 (0 到 255)
uint16无符号 16 位整型 (0 到 65535)
uint32无符号 32 位整型 (0 到 4294967295)
uint64无符号 64 位整型 (0 到 18446744073709551615)
int8有符号 8 位整型 (-128 到 127)
int16有符号 16 位整型 (-32768 到 32767)
int32有符号 32 位整型 (-2147483648 到 2147483647))
int64有符号 64 位整型 (-9223372036854775808 到 9223372036854775807)

2.2 数字浮点型

类型描述
float32IEEE-754 32位浮点型数
float64IEEE-754 64位浮点型数
complex6432 位实数和虚数
complex12864 位实数和虚数

2.3 其他数字类型

类型描述
byte类似 uint8
rune类似 int32
uint32 或 64 位
int与 uint 一样大小
uintptr无符号整型,用于存放一个指针

2.4 字符串

字符串就是一串固定长度的字符连接起来的字符序列。Go 的字符串是由单个字节连接起来的。Go 语言的字符串的字节使用 UTF-8 编码标识 Unicode 文本。本质是一个结构体,其中一个属性记录了字符长度,一个指针指向了堆中字符串数组的地址

//字符串替换
str := "这里是 www\n.runoob\n.com"  
fmt.Println("-------- 原字符串 ----------")  
fmt.Println(str)  
// 去除空格  
str = strings.Replace(str, " ", "", -1)  
// 去除换行符  
str = strings.Replace(str, "\n", "", -1)  

3 基础语法

3.1 常见规则

  1. 大括号不能另起一行
  2. 一个或是多个字母(A~Z和a~z)数字(0~9)、下划线_组成,第一个字符不能是数字
  3. 保留关键字一般ide会帮你标识,不需要特别注意

3.2 语言变量 & 常量

iota :Golang 中优雅的常量 入门暂时不用了解这个

常规用法:

var   identifier [type] = value
const identifier [type] = value


//先声明
var age int //未赋值,则默认是该类型的默认值,数值类型(包括complex64/128)为0 布尔未false,字符串为空,有些类型是nil

//可以一次声明多个变量
var age,height int
age, height = 11, 170 //也可以多次赋值
age, height = height, age//快速交换两个变量的值

//先声明 再赋值
var age int
age = 11

//声明的同时赋值 会根据值自行判定变量类型
var age = 11

//使用冒号省略var
age :=11 

//如果变量已经使用 var 声明过了,再使用 := 声明变量,就产生编译错误
var age int 
age := 11 // 这时候会产生编译错误,因为 age 已经声明,不需要重新声明

age int := 11 //编译错误

3.3 变量地址

常量、字符串和字典不可寻址,原因Google下,很好理解

age :=11
fmt.Println(&age) //0xc0000180a8

3.4 获取长度

var name string = "zhangsan"
fmt.Println( len(name) ) //8
fmt.Println( unsafe.Sizeof(name) ) //16

name = "zhangsan123456789abcdefg" //修改字符 长度变了 size不变
fmt.Println( len(name) ) //24
fmt.Println( unsafe.Sizeof(name) ) //16 

随便怎么改变字符内容,但size不变,是因为string类型底层实现也是一个结构体,一个属性记录长度,一个指针记录字符数组在堆上的内存地址,所以size其实是该字符结构体的占用空间

3.5 条件语句

Go开发者明确表示不支持三元表达式

   a := 10
   if a < 20 { //if后 条件语句用括号包括与否都可以,具体取决于公司编码规范
	fmt.Printf("a 小于 20\n")
    } else if a == 20 {
	fmt.Printf("a 等于 20\n")
    } else {
	fmt.Printf("a 大于 20\n")
    }

switch用法与其他常见语言一致, select用户入门暂时不用

3.6 循环

Go中没有while循环,只有for循环

    var i int = 0
    for i < 10 {
        fmt.Println("i: ", i)
        i++
    }
    //或者
    for i := 0; i < 10; i++ {
        fmt.Println("i: ", i)
    }

range遍历数组,Map,切片, 类似php里的foreachJs里的for v in obj之类的,没啥特别

3.7 函数

函数可以多个返回值

//Go 语言函数定义格式
func function_name(param1 [type], param2 [type]) return_types_1, return_types_2 {
   //函数体
}
//示例
func main() {
    sum, sub, err := calFun(4, 2) //其中如果有不想接收的返回值,可以用_接收
    fmt.Println(sum, sub, err)// 6 2 nil
}
func calFun(num1 int, num2 int) (int, int, error) {
    sum := num1 + num2
    sub := num1 - num2
    return sum, sub, nil
}

函数也可以可变参数

func sum(vals ...int) int {
    total := 0
    for _, val := range vals {
        total += val
    }
    return total
}
func main() {
    fmt.Println(sum())           // 0
    fmt.Println(sum(3))          // 3
    fmt.Println(sum(1, 2, 3, 4)) // 10
    
    values := []int{1, 2, 3, 4}
    fmt.Println(sum(values...))  // 10
}

3.8 变量作用域

常规,函数内局部,函数外全局

3.8 数组

Go里面数组是大括号包裹

//声明数组
var variable_name [SIZE] variable_type
var ages [3] int

//初始化数组
var ages = [3]int{}

//多维数组
var variable_name [SIZE1][SIZE2]...[SIZEN] variable_type

3.9 指针

var var_name *var-type //默认值为nil
var ip *int            //指向整型
var fp *float32        // 指向浮点型
var a   int20       // 声明实际变量
var ip *int            // 声明指针变量
ip = &a                //指针变量的存储地址
fmt.Printf("a 变量的地址是: %x\n", &a  )       //a 变量的地址是: 20818a220
//指针变量的存储地址
fmt.Printf("ip 变量储存的指针地址: %x\n", ip ) //ip 变量储存的指针地址: 20818a220
//使用指针访问值
fmt.Printf("*ip 变量的值: %d\n", *ip )        //*ip 变量的值: 20

3.10 切片

//未指明长度的数组定义,就是定义切片
var identifier []type

//也可以用make创建切片
var slice []type = make([]type, len)
//也可以简写为
slice := make([]type, len)
//从数组初始化切片
s := [3]int{1, 2, 3}
s1 := s[start_index:end_index] //会初始化start_index到end_index - 1为切片

切片可以动态扩容, 使用append添加元素,len获取长度,cap获取容量

s1 := {1,2,3}
s1 = append(s1, 1)
s1 = append(s1, 1)
fmt.Println(s1, len(s1), cap(s1))//[1 2 3 1 1] 5 6

切片可以使用copy函数复制,

//copy(目标切片, 源切片)
slice1 := []int{10, 20, 30, 4, 5}
slice2 := []int{5, 4, 3}
copy(slice2, slice1)
fmt.Println(slice2)//[10 20 30]

appendcopy都是可以将元素内容拷贝给另一个slice(用于复制而不是引用)

3.11 Map

MapGo里面的哈希,字典

//定义 Map
var map_variable map[key_data_type]value_data_type
//使用 make 函数
map_variable := make(map[key_data_type]value_data_type)

//set k-v
var countryCapitalMap map[string]string
countryCapitalMap           = make(map[string]string)
countryCapitalMap["France"] = "巴黎"
countryCapitalMap["Japan"]  = "东京"
for country := range countryCapitalMap {
    fmt.Println(country, "首都是", countryCapitalMap[country])
}
//删除k-v delete函数没有返回值,删除一个不存在的key也不会报错
delete(countryCapitalMap, "France")
//查看元素在集合中是否存在
capital, ok := countryCapitalMap[ "American" ]
if (ok) {
   fmt.Println("American 的首都是", capital)
} else {
   fmt.Println("American 的首都不存在")
}

3.12 类型转换

Go不支持隐式类型转换, 仅支持显式/强制类型转换

//float 转int  反之亦然
var a float32 = 1.626
var b int
b = int(a)
fmt.Println(b)//1 丢了精度

//string转int
var str = "1100"
c, err := strconv.Atoi(str)
if nil == err {
    fmt.Println(c) //1100
    fmt.Printf("%T", c) //int
}
//int转string
d := strconv.Itoa(1123) //也可以使用fmt.Sprintf("%d", 111) 这个函数功能更强大
fmt.Println(d)//1123
fmt.Printf("%T\n", d)//string

//string转float
var str2 = "1.234567"
f, err := strconv.ParseFloat(str2, 32)if nil == err {
    fmt.Println(f) //1.2345670461654663 精度变大 怎么处理这个问题我也还不知道
    fmt.Printf("%T\n", f)//float64
}
//float转string
fmt.Sprintf("%f", 3.12344) // 3.123440
//控制输出小数位数
fmt.Sprintf("%.2f", 323.12344) // 323.12

4 面向对象

4.1 类

Go中的类可以通过结构体实现,支持继承,不支持重载

type Person struct {
	Name string
	Age  int
}
type Student struct {
	Person
	grade string
	id    int
}

//定义一个结构体,并给该结构体添加方法
func (p Person) printPerson() {
	fmt.Println("Person Name =", p.Name, "Age =", p.Age)
}
func main() {
	var p = Person{
		Name: "HaiCoder",
		Age:  10,
	}
	var s = Student{
		Person: p,
		grade:  "一年级",
		id:     1001,
	}
	p.printPerson()
	fmt.Println(s.grade, s.id, s.Person.Name, s.Person.Age)
}

4.2 接口

接口(interface)定义了一个对象的行为规范,只定义规范不实现,由具体的对象来实现规范的细节。

type 接口类型名 interface{
        方法名1( 参数列表1 ) 返回值列表1
        方法名2( 参数列表2 ) 返回值列表2
        …
    }

只要一个结构体实现了某个接口的全部方法,就认为该结构体实现了该接口,一个结构体可以同时实现多个接口。

4.2 私有/公有

Go中根据首字母的大小写来确定可以访问的权限。无论是方法名、常量、变量名还是结构体的名称,如果首字母大写,则可以被其他的包访问;如果首字母小写,则只能在本包中使用。可以粗暴的理解为首字母大写是公有的,首字母小写是私有的。 类属性如果是小写开头,则其序列化会丢失属性对应的值,同时也无法进行Json解析。

5 并发编程

5.1 协程

例子里必须休眠,因为我们用数组模拟了异步写操作,如果不休眠,数组是连续地址,同步写比异步写还快

//同步
func main() {
	start := time.Now()
	var arr [100]int
	for i := 0; i < 100; i++ {
		time.Sleep(time.Millisecond) // 假设这个异步写操作耗时1毫秒
		arr[i] = i
	}
	end := time.Now()
	fmt.Println(end.Sub(start))//1.55s
} 

//Go并发
var wg sync.WaitGroup
func main() {
	start := time.Now()
	var arr [100]int
	for i := 0; i < 100; i++ {
		wg.Add(1) // 启动一个goroutine就登记+1
		go func(i int) {
			time.Sleep(time.Millisecond)
			defer wg.Done() // goroutine结束就登记-1
			arr[i] = i
		}(i)
	}
	wg.Wait() // 等待所有登记的goroutine都结束
	end := time.Now()
	fmt.Println(end.Sub(start))//507.5µs
}

5.2 锁

sync.Mutex //互斥锁
sync.RWMutex //读写锁

5.3 channel

//声明通道
var 变量 chan 元素类型
var ch1 chan int   // 声明一个传递整型的通道
var ch2 chan bool  // 声明一个传递布尔型的通道
var ch3 chan []int // 声明一个传递int切片的通道

//创建通道
make(chan 元素类型, [缓冲大小])

5.3.1 无缓冲通道

无缓冲通道上的发送操作会阻塞,直到另一个goroutine在该通道上执行接收操作,这时值才能发送成功,两个goroutine将继续执行。相反,如果接收操作先执行,接收方的goroutine将阻塞,直到另一个goroutine在该通道上发送一个值。 使用无缓冲通道进行通信将导致发送和接收的goroutine同步化。因此,无缓冲通道也被称为同步通道。

//无缓冲通道
var ch chan int
ch = make(chan int)
defer close(ch)
go func() {
	ch <- 10
}()
x := <-ch       // 从ch中接收值并赋值给变量x
fmt.Println(x) //10

5.3.2 有缓冲通道

内置的len函数获取通道内元素的数量,使用cap函数获取通道的容量

func main() {
    ch := make(chan int, 1) // 创建一个容量为1的有缓冲区通道
    ch <- 10
    fmt.Println("发送成功")
}

6 defer

defer和go一样都是Go语言提供的关键字。defer用于资源的释放,会在函数返回之前进行调用,如果有多个defer表达式,调用顺序类似于栈,越后面的defer表达式越先被调用。一般采用如下模式:

f,err := os.Open(filename)
if err != nil {
    panic(err)
}
defer f.Close() //这行会在return前执行

//函数return过程
1. 生成return = result
2. 执行defer函数 如果defer函数修改了return,这返回修改后的return;如果只是修改了result,不影响return的值
3. 返回return