基础语法 第一个程序 package mainimport "fmt" func main () { fmt.Println("Hello Go" ) }
格式化字符串 Go 语言中使用 fmt.Sprintf 或 fmt.Printf 格式化字符串并赋值给新串:
Sprintf 根据格式化参数生成格式化的字符串并返回该字符串。
Printf 根据格式化参数生成格式化的字符串并写入标准输出。
package mainimport "fmt" func main () { 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) }
package mainimport "fmt" func main () { var stockcode=123 var enddate="2020-12-31" var url="Code=%d&endDate=%s" fmt.Printf(url,stockcode,enddate) }
占位符 %v
:只输出所有的值。%+v
:先输出结构体字段类型,在输出字段值。%#v
:先输出结构体名称,再输出结构体字段类型+值。%T
:输出结构体名称,或输出目标的类型。%%
:输出字面上的百分号。%b
:二进制表示%c
:相应的Unicode码所表示的字符。%d
:十进制表示%o
:八进制表示%x
:十六进制表示,字母形式a-f%X
:十六进制,字母形式A-F%q
:双引号围绕的字符串%e
:科学计数法1.020000e+01%E
:科学计数法1.020000E+01%f
:小数输出,有小数点而无指数%p
:十六进制输出,输出指针类型。%g
:末尾无零的小数输出。%G
:末尾无零的小数输出。%t
:布尔占位符。
%p
跟%x
区别:%x
打印的是十六进制数,%p
也是十六进制,但%p以0x作为前缀,也就是说%p等于0x拼接%x。
例如: 同一个指针,用%x输出:c00000a0c8 用%p输出:0xc00000a0c8
变量 var identifier type var identifier1, identifier2 type
package mainimport "fmt" func main () { var a string = "Runoob" fmt.Println(a) var b, c int = 1 , 2 fmt.Println(b, c) }
变量声明 指定变量类型,如果没有初始化,则数值类型变量默认为零值 。
数值类型(包括complex64/128)为 0
布尔类型为 false
字符串为 **””**(空字符串)
以下几种类型为 nil :
var a *int var a []int var a map [string ] int var a chan int var a func (string ) int var a error
intVal := 1 相等于:
多变量声明 var vname1, vname2, vname3 type vname1, vname2, vname3 = v1, v2, v3 var vname1, vname2, vname3 = v1, v2, v3 vname1, vname2, vname3 := v1, v2, v3 var ( vname1 v_type1 vname2 v_type2 )
var a, b int var c string a, b, c = 5 , 7 , "abc"
空白标识符 在函数返回值时的使用
package mainimport "fmt" func main () { _,numb,strs := numbers() fmt.Println(numb,strs) } func numbers () (int ,int ,string ){ a , b , c := 1 , 2 , "str" return a,b,c }
常量
显式类型定义: const b string = "abc"
隐式类型定义: const b = "abc"
常量可用len(), cap(), unsafe.Sizeof()计算表达式的值。常量表达式中, 函数必须是内置函数, 否则编译不过:
package mainimport "unsafe" const ( a = "abc" b = len (a) c = unsafe.Sizeof(a) ) func main () { println (a, b, c) }
iota ota,特殊常量,可以认为是一个可以被编译器修改的常量。
iota 在 const关键字出现时将被重置为 0(const 内部的第一行之前),const 中每新增一行常量声明将使 iota 计数一次(iota 可理解为 const 语句块中的行索引)。
iota 可以被用作枚举值:
package mainimport "fmt" func main () { const ( a = iota b c d = "ha" e f = 100 g h = iota i ) fmt.Println(a,b,c,d,e,f,g,h,i) }
package mainimport "fmt" const ( i=1 <<iota j=3 <<iota k l ) func main () { fmt.Println("i=" ,i) fmt.Println("j=" ,j) fmt.Println("k=" ,k) fmt.Println("l=" ,l) }
运算符 位运算符
运算符
描述
实例
&
按位与运算符”&”是双目运算符。 其功能是参与运算的两数各对应的二进位相与。
(A & B) 结果为 12, 二进制为 0000 1100
|
按位或运算符”|”是双目运算符。 其功能是参与运算的两数各对应的二进位相或
(A | B) 结果为 61, 二进制为 0011 1101
^
按位异或运算符”^”是双目运算符。 其功能是参与运算的两数各对应的二进位相异或,当两对应的二进位相异时,结果为1。
(A ^ B) 结果为 49, 二进制为 0011 0001
<<
左移运算符”<<”是双目运算符。左移n位就是乘以2的n次方。 其功能把”<<”左边的运算数的各二进位全部左移若干位,由”<<”右边的数指定移动的位数,高位丢弃,低位补0。
A << 2 结果为 240 ,二进制为 1111 0000
>>
右移运算符”>>”是双目运算符。右移n位就是除以2的n次方。 其功能是把”>>”左边的运算数的各二进位全部右移若干位,”>>”右边的数指定移动的位数。
A >> 2 结果为 15 ,二进制为 0000 1111
赋值运算符
运算符
描述
实例
=
简单的赋值运算符,将一个表达式的值赋给一个左值
C = A + B 将 A + B 表达式结果赋值给 C
+=
相加后再赋值
C += A 等于 C = C + A
-=
相减后再赋值
C -= A 等于 C = C - A
*=
相乘后再赋值
C *= A 等于 C = C * A
/=
相除后再赋值
C /= A 等于 C = C / A
%=
求余后再赋值
C %= A 等于 C = C % A
<<=
左移后赋值
C <<= 2 等于 C = C << 2
>>=
右移后赋值
C >>= 2 等于 C = C >> 2
&=
按位与后赋值
C &= 2 等于 C = C & 2
^=
按位异或后赋值
C ^= 2 等于 C = C ^ 2
|=
按位或后赋值
C |= 2 等于 C = C | 2
其他运算符
运算符
描述
实例
&
返回变量存储地址
&a; 将给出变量的实际地址。
*
指针变量。
*a; 是一个指针变量
运算符优先级 由上至下代表优先级由高到低:
优先级
运算符
5
* / % << >> & &^
4
+ - | ^
3
== != < <= > >=
2
&&
1
||
条件语句 Go 语言提供了以下几种条件判断语句:
语句
描述
if 语句
if 语句 由一个布尔表达式后紧跟一个或多个语句组成。
if…else 语句
if 语句 后可以使用可选的 else 语句 , else 语句中的表达式在布尔表达式为 false 时执行。
if 嵌套语句
你可以在 if 或 else if 语句中嵌入一个或多个 if 或 else if 语句。
switch 语句
switch 语句用于基于不同条件执行不同动作。
select 语句
select 语句类似于 switch 语句,但是select会随机执行一个可运行的case。如果没有case可运行,它将阻塞,直到有case可运行。
函数 函数定义 Go 语言函数定义格式如下:
func function_name ( [parameter list] ) [return_types] { 函数体 }
例子
func max (num1, num2 int ) int { var result int if (num1 > num2) result = num1 else result = num2 return result }
函数返回多个值 Go 函数可以返回多个值,例如:
package mainimport "fmt" func swap (x, y string ) (string , string ) { return y, x } func main () { a, b := swap("Google" , "Runoob" ) fmt.Println(a, b) }
函数参数 调用函数,可以通过两种方式来传递参数:
传递类型
描述
值传递
值传递是指在调用函数时将实际参数复制一份传递到函数中,这样在函数中如果对参数进行修改,将不会影响到实际参数。
引用传递
引用传递是指在调用函数时将实际参数的地址传递到函数中,那么在函数中对参数所进行的修改,将影响到实际参数。
默认情况下,Go 语言使用的是值传递,即在调用过程中不会影响到实际参数。
函数用法
数组 var arr_name [SIZE] arr_type
以上为一维数组的定义方式。例如以下定义了数组 balance 长度为 10 类型为 float32:
初始化数组 以下演示了数组初始化:
var balance = [5 ]float32 {1000.0 , 2.0 , 3.4 , 7.0 , 50.0 }
我们也可以通过字面量在声明数组的同时快速初始化数组:
balance := [5 ]float32 {1000.0 , 2.0 , 3.4 , 7.0 , 50.0 }
如果数组长度不确定,可以使用 … 代替数组的长度,编译器会根据元素个数自行推断数组的长度:
var balance = [...]float32 {1000.0 , 2.0 , 3.4 , 7.0 , 50.0 }或 balance := [...]float32 {1000.0 , 2.0 , 3.4 , 7.0 , 50.0 }
如果设置了数组的长度,我们还可以通过指定下标来初始化元素:
balance := [5 ]float32 {1 :2.0 ,3 :7.0 }
初始化数组中 {} 中的元素个数不能大于 [] 中的数字。
如果忽略 [] 中的数字不设置数组大小,Go 语言会根据元素的个数来设置数组的大小:
二维数组 package mainimport "fmt" func main () { values := [][]int {} row1 := []int {1 , 2 , 3 } row2 := []int {4 , 5 , 6 } values = append (values, row1) values = append (values, row2) fmt.Println("Row 1" ) fmt.Println(values[0 ]) fmt.Println("Row 2" ) fmt.Println(values[1 ]) for i := range values { fmt.Printf("Row: %v\n" , i) fmt.Println(values[i]) } }
指针 var ip *int var fp *float32
package mainimport "fmt" func main () { var a int = 20 ip := &a; fmt.Printf("a 变量的地址是: %p\n" , &a ) fmt.Printf("ip 变量储存的指针地址: %p\n" , ip ) fmt.Printf("*ip 变量的值: %d\n" , *ip ) }
Go 空指针 当一个指针被定义后没有分配到任何变量时,它的值为 nil。
nil 指针也称为空指针。
nil在概念上和其它语言的null、None、nil、NULL一样,都指代零值或空值。
一个指针变量通常缩写为 ptr。
package mainimport "fmt" func main () { var ptr *int fmt.Printf("ptr 的值为 : %x\n" , ptr ) }
以上实例输出结果为:
空指针判断:
if (ptr != nil ) if (ptr == nil )
结构体 type struct_variable_type struct { member definition member definition }
variable_name := structure_variable_type {value1, value2...valuen} variable_name := structure_variable_type { key1: value1, key2: value2..., keyn: valuen}
type Books struct { title string author string subject string book_id int } mybook := Books{"Go 语言" , "www.runoob.com" , "Go 语言教程" , 6495407 }
访问结构体成员 如果要访问结构体成员,需要使用点号 . 操作符,格式为:
结构体指针 你可以定义指向结构体的指针类似于其他指针变量,格式如下:
var book *Booksbook = &Book1
使用结构体指针访问结构体成员,使用 “.” 操作符:
切片(Slice) Go 语言切片是对数组的抽象。
Go 数组的长度不可改变,在特定场景中这样的集合就不太适用,Go 中提供了一种灵活,功能强悍的内置类型切片(“动态数组”),与数组相比切片的长度是不固定的,可以追加元素,在追加时可能使切片的容量增大。
定义切片 你可以声明一个未指定大小的数组来定义切片:
切片不需要说明长度。
或使用 make() 函数来创建切片:
var slice1 []type = make ([]type , len )或 slice1 := make ([]type , len )
也可以指定容量,其中 capacity 为可选参数。
make ([]T, length, capacity)
这里 len 是数组的长度并且也是切片的初始长度。
切片初始化
直接初始化切片,**[]** 表示是切片类型,**{1,2,3}** 初始化值依次是 1,2,3 ,其 cap=len=3 。
初始化切片 s ,是数组 arr 的引用。
s := arr[startIndex:endIndex]
将 arr 中从下标 startIndex 到 endIndex-1 下的元素创建为一个新的切片。
默认 endIndex 时将表示一直到arr的最后一个元素。
默认 startIndex 时将表示从 arr 的第一个元素开始。
s1 := s[startIndex:endIndex]
通过切片 s 初始化切片 s1。
通过内置函数 make() 初始化切片s ,**[]int** 标识为其元素类型为 int 的切片。
len() 和 cap() 函数 切片是可索引的,并且可以由 len() 方法获取长度。
切片提供了计算容量的方法 cap() 可以测量切片最长可以达到多少。
以下为具体实例:
package mainimport "fmt" func main () { var numbers = make ([]int ,3 ,5 ) printSlice(numbers) } func printSlice (x []int ) { fmt.Printf("len=%d cap=%d slice=%v\n" ,len (x),cap (x),x) }
空(nil)切片 一个切片在未初始化之前默认为 nil,长度为 0,实例如下:
package mainimport "fmt" func main () { var numbers []int printSlice(numbers) if (numbers == nil ) fmt.Printf("切片是空的" ) } func printSlice (x []int ) { fmt.Printf("len=%d cap=%d slice=%v**\n**" ,len (x),cap (x),x) }
切片截取 可以通过设置下限及上限来设置截取切片 *[lower-bound:upper-bound]*,实例如下:
package mainimport "fmt" func main () { numbers := []int {0 ,1 ,2 ,3 ,4 ,5 ,6 ,7 ,8 } printSlice(numbers) fmt.Println("numbers ==" , numbers) fmt.Println("numbers[1:4] ==" , numbers[1 :4 ]) fmt.Println("numbers[:3] ==" , numbers[:3 ]) fmt.Println("numbers[4:] ==" , numbers[4 :]) numbers1 := make ([]int ,0 ,5 ) printSlice(numbers1) number2 := numbers[:2 ] printSlice(number2) number3 := numbers[2 :5 ] printSlice(number3) } func printSlice (x []int ) { fmt.Printf("len=%d cap=%d slice=%v\n" ,len (x),cap (x),x) }
执行以上代码输出结果为:
len =9 cap=9 slice=[0 1 2 3 4 5 6 7 8 ]numbers == [0 1 2 3 4 5 6 7 8] numbers [1 :4 ] == [1 2 3] numbers [:3 ] == [0 1 2] numbers [4 :] == [4 5 6 7 8] len =0 cap=5 slice=[]len =2 cap=9 slice=[0 1 ]len =3 cap=7 slice=[2 3 4 ]
append() 和 copy() 函数 如果想增加切片的容量,我们必须创建一个新的更大的切片并把原分片的内容都拷贝过来。
下面的代码描述了从拷贝切片的 copy 方法和向切片追加新元素的 append 方法。
package mainimport "fmt" func main () { var numbers []int printSlice(numbers) numbers = append (numbers, 0 ) printSlice(numbers) numbers = append (numbers, 1 ) printSlice(numbers) numbers = append (numbers, 2 ,3 ,4 ) printSlice(numbers) numbers1 := make ([]int , len (numbers), (cap (numbers))*2 ) copy (numbers1,numbers) printSlice(numbers1) } func printSlice (x []int ) { fmt.Printf("len=%d cap=%d slice=%v\n" ,len (x),cap (x),x) }
以上代码执行输出结果为:
len =0 cap=0 slice=[]len =1 cap=1 slice=[0 ]len =2 cap=2 slice=[0 1 ]len =5 cap=6 slice=[0 1 2 3 4 ]len =5 cap=12 slice=[0 1 2 3 4 ]
Map 定义 Map 可以使用内建函数 make 或使用 map 关键字来定义 Map:
map_variable := make (map [KeyType]ValueType, initialCapacity)
m := make (map [string ]int ) m := make (map [string ]int , 10 ) m := map [string ]int { "apple" : 1 , "banana" : 2 , "orange" : 3 , } v1 := m["apple" ] v2, ok := m["pear" ]
修改元素:
m["apple" ] = 5 len := len (m) delete (m, "banana" ) for k, v := range m { fmt.Printf("key=%s, value=%d\n" , k, v) }
循环读取:
for key, value := range map1 { fmt.Printf("key is: %d - value is: %f\n" , key, value) } for key := range map1 { fmt.Printf("key is: %d\n" , key) } for _, value := range map1 { fmt.Printf("value is: %f\n" , value) }
接口 type interface_name interface { method_name1 [return_type] method_name2 [return_type] method_name3 [return_type] ... method_namen [return_type] } type struct_name struct { } func (struct_name_variable struct_name) method_name1() [return_type] { } ... func (struct_name_variable struct_name) method_namen() [return_type] { }
例子:
package mainimport "fmt" type Phone interface { call() } type NokiaPhone struct {}func (nokiaPhone NokiaPhone) call() { fmt.Println("I am Nokia, I can call you!" ) } type IPhone struct {}func (iPhone IPhone) call() { fmt.Println("I am iPhone, I can call you!" ) } func main () { var phone Phone phone = new (NokiaPhone) phone.call() phone = new (IPhone) phone.call() }
类型转换 var a int = 10 var b float64 = float64 (a)
字符串转换为整数 将一个字符串转换成另一个类型,可以使用以下语法:
var str string = "10" var num int num, _ = strconv.Atoi(str)
以上代码将字符串变量 str 转换为整型变量 num。
注意,strconv.Atoi 函数返回两个值,第一个是转换后的整型值,第二个是可能发生的错误,我们可以使用空白标识符 _ 来忽略这个错误
整数转换为字符串 package mainimport ( "fmt" "strconv" ) func main () { num := 123 str := strconv.Itoa(num) fmt.Printf("整数 %d 转换为字符串为:'%s'\n" , num, str) }
字符串转换为浮点数 package mainimport ( "fmt" "strconv" ) func main () { str := "3.14" num, err := strconv.ParseFloat(str, 64 ) if err != nil { fmt.Println("转换错误:" , err) } else { fmt.Printf("字符串 '%s' 转为浮点型为:%f\n" , str, num) } }
浮点数转换为字符串 package mainimport ( "fmt" "strconv" ) func main () { num := 3.14 str := strconv.FormatFloat(num, 'f' , 2 , 64 ) fmt.Printf("浮点数 %f 转为字符串为:'%s'\n" , num, str) }
接口类型转换 接口类型转换有两种情况:类型断言 和类型转换 。
类型断言 类型断言用于将接口类型转换为指定类型,其语法为:
value.(type ) 或者 value.(T)
package mainimport "fmt" func main () { var i interface {} = "Hello, World" str, ok := i.(string ) if ok { fmt.Printf("'%s' is a string\n" , str) } else { fmt.Println("conversion failed" ) } }
类型转换 类型转换用于将一个接口类型的值转换为另一个接口类型,其语法为:
package mainimport "fmt" type Writer interface { Write([]byte ) (int , error ) } type StringWriter struct { str string } func (sw *StringWriter) Write(data []byte ) (int , error ) { sw.str += string (data) return len (data), nil } func main () { var w Writer = &StringWriter{} sw := w.(*StringWriter) sw.str = "Hello, World" fmt.Println(sw.str) }
以上实例中,我们定义了一个 Writer 接口和一个实现了该接口的结构体 StringWriter。然后,我们将 StringWriter 类型的指针赋值给 Writer 接口类型的变量 w。接着,我们使用类型转换将 w 转换为 StringWriter 类型,并将转换后的值赋值给变量 sw。最后,我们使用 sw 访问 StringWriter 结构体中的字段 str,并打印出它的值。
go并发 Go 语言支持并发,我们只需要通过 go 关键字来开启 goroutine 即可。
goroutine 是轻量级线程,goroutine 的调度是由 Golang 运行时进行管理的。
Go 允许使用 go 语句开启一个新的运行期线程, 即 goroutine,以一个不同的、新创建的 goroutine 来执行一个函数。 同一个程序中的所有 goroutine 共享同一个地址空间。
package mainimport ( "fmt" "time" ) func say (s string ) { for i := 0 ; i < 5 ; i++ { time.Sleep(10000 * time.Millisecond) fmt.Println(s) } } func main () { go say("world" ) say("hello" ) }
channel 通道(channel)是用来传递数据的一个数据结构。
通道可用于两个 goroutine 之间通过传递一个指定类型的值来同步运行和通讯。操作符 <-
用于指定通道的方向,发送或接收。如果未指定方向,则为双向通道
声明一个通道很简单,我们使用chan关键字即可,通道在使用前必须先创建:
以下实例通过两个 goroutine 并行来计算数字之和,在 goroutine 完成计算后,它会计算两个结果的和
package mainimport "fmt" func sum (s []int , c chan int ) { sum := 0 for _, v := range s { sum += v } c <- sum } 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 fmt.Println(x, y, x+y) }
通道缓冲区 通道可以设置缓冲区,通过 make 的第二个参数指定缓冲区大小:
ch := make (chan int , 100 )
带缓冲区的通道允许发送端的数据发送和接收端的数据获取处于异步状态,就是说发送端发送的数据可以放在缓冲区里面,可以等待接收端去获取数据,而不是立刻需要接收端去获取数据。
不过由于缓冲区的大小是有限的,所以还是必须有接收端来接收数据的,否则缓冲区一满,数据发送端就无法再发送数据了。
注意 :如果通道不带缓冲,发送方会阻塞直到接收方从通道中接收了值。如果通道带缓冲,发送方则会阻塞直到发送的值被拷贝到缓冲区内;如果缓冲区已满,则意味着需要等待直到某个接收方获取到一个值。接收方在有值可以接收之前会一直阻塞。
package mainimport "fmt" func main () { ch := make (chan int , 2 ) ch <- 1 ch <- 2 fmt.Println(<-ch) fmt.Println(<-ch) }
遍历通道与关闭通道 Go 通过 range 关键字来实现遍历读取到的数据,类似于与数组或切片。格式如下:
如果通道接收不到数据后 ok 就为 false,这时通道就可以使用 close() 函数来关闭。
package mainimport "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) for i := range c { fmt.Println(i) } }
执行输出结果为: