本文是一个超级基础的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 数字浮点型
类型 | 描述 |
---|---|
float32 | IEEE-754 32位浮点型数 |
float64 | IEEE-754 64位浮点型数 |
complex64 | 32 位实数和虚数 |
complex128 | 64 位实数和虚数 |
2.3 其他数字类型
类型 | 描述 |
---|---|
byte | 类似 uint8 |
rune | 类似 int32 |
uint | 32 或 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 常见规则
- 大括号不能另起一行
- 一个或是多个字母(A~Z和a~z)数字(0~9)、下划线_组成,第一个字符不能是数字
- 保留关键字一般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
里的foreach
,Js
里的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 int = 20 // 声明实际变量
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]
append
和copy
都是可以将元素内容拷贝给另一个slice
(用于复制而不是引用)
3.11 Map
Map
是Go
里面的哈希,字典
//定义 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
defe
r和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