Go语言学习笔记
1.写一个简单的Go程序并运行
编写简单的hello.go文件
package main
import "fmt"
func main(){
fmt.Println("Hello World!")
}
使用go run hello.go
运行
或者使用go build
命令生成二进制文件并运行
go build hello.go
./hello.exe
对上述代码hello.go语言结构进行分析:
1.package main定义的是包名,必须再源文件的非注释第一行指明属于哪个包。package main表示这是一个可独立运行的程序,每个Go应用程序都应该包含一个名为main的包。
2.import "fmt"表示这个程序需要使用fmt包,fmt中包含了格式化I/O的函数。
3.func main()是程序开始执行的函数,是每个可执行函数必须包含的。
4.fmt.Println()是输出控制,并且自动换行。
5.当标识符(包括常量、变量、类型、函数名、结构字段等等)以一个大写字母开头,如:Group1,那么使用这种形式的标识符的对象就可以被外部包的代码所使用(客户端程序需要先导入这个包),这被称为导出(像面向对象语言中的 public);标识符如果以小写字母开头,则对包外是不可见的,但是他们在整个包的内部是可见并且可用的(像面向对象语言中的 protected )。
2.Go基础语法
注释
如C/C++相同单行注释使用//
即可,多行注释使用/* */
变量(标识符)
Go中使用字母及符号或数字组成标识符,且标识符的开头只能以字母和下划线构成,不可以数字开头。
无效的标识符:case(关键字)、1ab(违背规则)、a+b(运算符不被允许)
定义变量的方式:
var a int = 1
//同 a :=1 :=相当于声明 因此之前a不应该被声明
var b string = "test"
var c = "%d&%s"
var d = fmt.Sprintf(c,a,b)//格式化字符串
fmt.Println(d)//1&test
//同时定义多个变量
var a,b int //未初始化的值为0 bool为false
var a,b int = 1,2
//同时声明多个不同类型变量 一般
var(
name1 type1,
name2 type2
)
a,b,c := "123",1,false //自动进行类型推导
常量
Go中,常量即表示无法修改,通过const定义
显式定义:const a int = 123
隐式定义: const a =123
多个相同类型可简写为:
const a, b = 1, 2
const枚举:
const(
a = "123"
b = len(a)
c = unsafe.Sizeof(a)
)
itoa 特殊的常量,可认为是能被编译器修改的常量
iota 在 const关键字出现时将被重置为 0(const 内部的第一行之前),const 中每新增一行常量声明将使 iota 计数一次(iota 可理解为 const 语句块中的行索引)。
const (
a = iota //0
b = iota //1 同样可直接简写为 b 即可
c = iota //2
)
const (
a = iota //0
b //1
c //2
d = "ha" //独立值,iota += 1
e //"ha" iota += 1
f = 100 //iota +=1
g //100 iota +=1
h = iota //7,恢复计数
i //8
)
如果当前常量未进行赋值,则其值默认为上一个常量的值;若上一个常量为iota,则当前表示为iota+1。
字符串连接
同Java相同,可使用+对字符串进行连接
fmt.Println("Google" + "Runoob")
格式化字符串
使用fmt.Sprintf格式化字符串,并且赋值给新串
func main() {
// %d 表示整型数字,%s 表示字符串
var stockcode=123
var enddate="2020-12-31"
var url="Code=%d&endDate=%s"
var target_url=fmt.Sprintf(url,stockcode,enddate)
fmt.Println(target_url)
}
声明未被使用
当在局部声明变量a且赋值后,如果不使用a,就会得到a declared and not used错误,即必须被使用,但是全局变量是被允许的
3.Go基础数据类型
布尔型
布尔类型的值只能是 true 或 false ,例如 var a bool = true
数字型
数字型分为整型int与浮点型float32,float64。
int有以下不同类型:
int -2^31~2^31-1
int8 -2^7~2^7-1
uint8 0~2^8
int16 -2^15~2^15-1
uint16 0~2^16
int32 -2^31~2^31-1
int64 -2^63~2^63-1
Go语言支持复数类型,即complex64(32位实数和虚数)和complex128(64位实数和虚数)
其他数字类型:
byte
rune
uint
int
uintptr
字符串型
字符串就是一串固定长度的字符连接起来的字符序列。Go 的字符串是由单个字节连接起来的。Go 语言的字符串的字节使用 UTF-8 编码标识 Unicode 文本。
4.Go运算符类型
同C++运算符使用方法基本相同,包含算术运算符、关系运算符、逻辑运算符、位运算符、赋值运算符。
且Go中同C++一样支持&和*
运算符优先级 由高到低排列:
1.* / % << >> & &^
2.+ - | ^
3.== != < <= > >=
4.&&
5.||
5.Go条件、循环语句
条件语句
1.if 判断,若为真 则执行下面的内容
2.if else 多个判断条件依次执行,选择执行一个
3.switch 根据不同条件执行不同动作
注意: Go中无C++类似的三目运算符?:
循环语句
列举三种不同的for使用方法:
//1.for init; condition; post {}
for i := 1; i <= 10; i++ {
a += i
}
//2.for condition {}
for true {//无限循环
}
//3.for { }
其中,init一般为赋值表达式,给变量赋初值,condition关系表达式,控制循环条件,post一般用于对循环变量进行自增/减。
6.Go数组
数组中的索引下标从0开始,地址位置连续。
声明数组
var array_name [size] array_type
//具体样例:
var a [100] int //定义整形数组a,长度为100
初始化数组
var a = [100] int{1,2,3}//后面的值默认为0
a := [100] int{1,2,3}//同上
若长度不确定可使用...代替,且长度之后不会发生变化
a := [...] int{1,2,3}
但此时不能访问长度外的索引下标
a[3] = 1 //错误
创建二维数组
var array_name [x][y] array_type
//将一维数组添加到二维数组
a := [][] int {}
var b = [3]int{1,2,3}
var c = [3]int{1,2,3}
a = append(a,b)
a = append(a,c)
初始化二维数组
a := [2][3] int {
{1,2,3},
{4,5,6},
}
//上面第三行的,为必须存在,若将最后一行的{上移,则可以去掉。
二维数组的遍历
for i := 0; i < 2; i++ {
for j := 0 ; j < 3; j++ {
fmt.Println(a[i][j])
}
}
7.Go函数
函数定义
func function_name( [parameter list] ) [return_type] {
...
}
定义一个返回两数最大值的函数:
func Max(a,b int) int {
if(a>b) {
return a
} else {
return b
}
}
函数参数传递方式
分为两种传递方式:
值传递: 值传递是指在调用函数时将实际参数复制一份传递到函数中,这样在函数中如果对参数进行修改,将不会影响到实际参数。
引用传递: 引用传递是指在调用函数时将实际参数的地址传递到函数中,那么在函数中对参数所进行的修改,将影响到实际参数。
不定函数参数传递方式:
Golang 可变参数本质上就是 slice。只能有一个,且必须是最后一个。
在参数赋值时可以不用一个一个的赋值,可以直接传递一个数组或者切片,特别注意的是在参数后加上“…”即可。
func myfunc(args ...int) { //0个或多个参数
}
func add(a int, args…int) int { //1个或多个参数
}
func add(a int, b int, args…int) int { //2个或多个参数
}
使用时,若使用slice传递参数,需要展开写法 即加上...
:
func main() {
s := []int{1, 2, 3}
res := test("sum: %d", s...) // slice... 展开slice
println(res)
}
函数闭包(匿名函数)
Go语言支持匿名函数,即内联的语句或表达式,匿名函数的优点是可以直接使用函数内部的局部变量。
func(x int) {
fmt.Printf("func 2, x is %d\n", x)
}(2)
//最后一个()直接跟参数列表是指函数直接调用
a := func(x int) int {
fmt.Printf("func 3, x is %d\n", x)
return 5
}
8.Go指针
指针定义
使用方式同 C++ 一样
声明一个指针
var ptr *int
指向某一元素
var a int = 1
ptr = &a
获取指针的值直接解引用即可,否则获取的就是指针指向的地址。
在Go语言中空指针使用nil表示,意义同其他语言的NULL、null,都代指零值或空值。
指针数组
本质是数组,数组中的每个元素皆为指针,指向其他的地址。
声明一个指针数组
var ptr [10] *int
多级指针
Go语言同C++一样,支持多级指针,即指向指针的指针。
var a int = 1
var ptr1, ptr2 *int
ptr1 = &a
ptr2 = ptr1
9.Go结构体
定义结构体
使用type和struct定义结构体
type struct_name struct{
number1 type1
number2 type2
...
}
//定义结构体
type book struct{
int a
string b
}
var p = book{1,"123"}
var g = book{a:1,b:"123"}
访问结构体成员
var p book
p.a = 1
p.b = "123"
结构体指针
var p *book
p = &g //g是已初始化的结构体
调用时,直接使用.访问结构体成员
构造函数
Go语言的结构体没有构造函数,我们可以自己实现。 例如,下方的代码就实现了一个person的构造函数。 因为struct是值类型,如果结构体比较复杂的话,值拷贝性能开销会比较大,所以该构造函数返回的是结构体指针类型。
func newPerson(name string, city string, age int8) *person {
return &person{
name: name,
city: city,
age: age,
}
}
调用构造函数
p9 := newPerson("pprof.cn", "测试", 90)
fmt.Printf("%#v\n", p9)
10.Go切片(Slice)
Go语言中切片是对数组的抽象,切片相对与数组而言,长度不是固定的,可在末尾追加元素,类似于C++中vector容器。
定义切片
var Slice_name []type
例如: var s [] int //不需要定义长度
使用make创建切片:
var Slice_name = make([]type, len)
可简写为: Slice_name := make([]type, len)
其中make 可以有第三个参数capacity,表示最大容量 可被省略
初始化切片
例如: s := []int{1, 2, 3} 其中len = capacity = 3
s := arr[:] //初始化切片s, 是数组 arr 的引用
s := arr[startIndex:endIndex] //将 arr 中从下标 startIndex 到 endIndex-1 下的元素创建为一个新的切片
s := arr[startIndex:] //默认 endIndex 时将表示一直到arr的最后一个元素
s := arr[:endIndex] //默认 startIndex 时将表示从 arr 的第一个元素开始
s1 := s[startIndex:endIndex] //通过切片s初始化切片s1
使用len()函数可获取切片的长度,使用cap()函数获取切片的容量。
注意:当切片被定义但未被初始化时,默认为nil,长度为0。
append()、copy()函数
//append()函数
var number []int
fmt.Println(number,len(number),cap(number))// [] 0 0
number = append(number,1)
fmt.Println(number,len(number),cap(number))// [1] 1 1
number = append(number,2,3,4)
fmt.Println(number,len(number),cap(number))// [1,2,3,4] 4 4
//copy()函数
number1 := make(int[],len(number),(cap(number)*2))
copy(number1,number)
Go范围(Range)
Go语言中使用Range关键字对for循环中数组array、切片slice、通道channel或集合map。
在数组和切片中返回两个值,即元素的索引的索引对应的值(key-val)。
如果不需要对对一个key进行访问,可使用 _
代替。
var arr = []int{1,2,3,4}//定义一个切片
sum := 0
for i, val := range arr {//遍历切片
sum += val
if(i == 1) {
fmt.Println(i)
}
}
fmt.Println(sum)
mp := map[int]int {1 : 2, 2 : 3, 3 : 4}//定义集合map
for _, val := range mp {
fmt.Println(val)
}
s := "123"
for _, val := range s {//遍历字符串
fmt.Println(val)
}
Go集合(Map)
Go语言中map是无序的键值对,使用hash表实现。
/* 声明变量,默认 map 是 nil */
var map_variable map[key_data_type]value_data_type
/* 使用 make 函数 */
map_variable := make(map[key_data_type]value_data_type)
//如果不初始化 map,那么就会创建一个 nil map。nil map 不能用来存放键值对
//实例:
var mp map[string]int
mp = make(map[string]int)
mp["1"] = 2
delete()函数
使用delete删除map中的键值对,删除时只需要表名删除的key即可
接上例:
delete(mp,"1")//删除mp[1]的内容
Go并发
goroutine
Go语言通过go关键字来开启goroutine即可。
goroutine 是轻量级线程,goroutine 的调度是由 Golang 运行时进行管理的。
goroutine语法格式:
go 函数名(参数列表)
例如: go f(x,y,z)
Go 允许使用 go 语句开启一个新的运行期线程, 即 goroutine,以一个不同的、新创建的 goroutine 来执行一个函数。 同一个程序中的所有 goroutine 共享同一个地址空间。
channel
管道,用于传递数据的一个数据结构。
通道可用于两个 goroutine 之间通过传递一个指定类型的值来同步运行和通讯。操作符 <- 用于指定通道的方向,发送或接收。如果未指定方向,则为双向通道。
ch <- v //把v发送到通信ch
v := <- ch //从ch接收数据 并赋值给v
声明channel
ch := make(chan int)
注意:默认情况下,通道是不带缓冲区的。发送端发送数据,同时必须有接收端相应的接收数据。
package main
import "fmt"
func sum(s []int, c chan int) {
sum := 0
for _, v := range s {
sum += v
}
c <- sum // 把 sum 发送到通道 c
}
func main() {
s := []int{7, 2, 8, -9, 4, 0}
c := make(chan int)
go sum(s[:len(s)/2], c)
go sum(s[len(s)/2:], c)
x, y := <-c, <-c // 从通道 c 中接收
fmt.Println(x, y, x+y)
}
//-5 17 12
协程具有无序性。
channel缓冲区
通道可以设置缓冲区,通过 make 的第二个参数指定缓冲区大小:
ch := make(chan int, 100)
Go遍历通道与关闭通道
Go 通过 range 关键字来实现遍历读取到的数据,类似于与数组或切片。
package main
import (
"fmt"
)
func fibonacci(n int, c chan int) {
x, y := 0, 1
for i := 0; i < n; i++ {
c <- x
x, y = y, x+y
}
close(c)
}
func main() {
c := make(chan int, 10)
go fibonacci(cap(c), c)
// range 函数遍历每个从通道接收到的数据,因为 c 在发送完 10 个
// 数据之后就关闭了通道,所以这里我们 range 函数在接收到 10 个数据
// 之后就结束了。如果上面的 c 通道不关闭,那么 range 函数就不
// 会结束,从而在接收第 11 个数据的时候就阻塞了。
for i := range c {
fmt.Println(i)
}
}
//0 1 1 2 3 5 8 13 21 34