Go语言
2019/11/25 Chenxin
参考 https://www.runoob.com/go/go-ide.html
变量
变量声明
1.指定变量类型,如果没有初始化,则变量默认为0值.
var v_name v_type = value //第一种
var v_name v_type //第二种
v_name = value
var ( //第三种
vname1 v_type1
vname2 v_type2
)
vname1, vname2 = value1, value2
常见的v_type有: 数值类型/布尔型/字符串/nil型.
以下几种类型为 nil:
var a *int //指针
var a []int
var a map[string] int
var a chan int
var a func(string) int
var a error // error 是接口
2.go自动根据值判断变量类型
var v_name = value
3.省略var关键字,使用":=".
前提是:=左侧之前没有声明过才行.否则编译报错.
:= 是一个声明语句(同时可以赋值).
限制: 该方式,只限于在函数体内使用.全局变量不可以使用此方式.
多变量声明
var vname1, vname2 type //只声明.或
var vname1, vname2 = v1, v2 //同时赋值.或
var ( //这种一般用在全局变量声明里
vname1 v_type1
vname2 v_type2
)
值类型(拷贝赋值)和引用类型(指针)
关键字"&"符号
简短方式 :=
略
其他注意
1.非全局变量不允许声明后不使用(否则编译报错).全局变量可以.
2."_"单个下划线,表示抛弃值,只允许写,不能读该变量.
常量
定义
const identifier [type] = value
const (
a = 1
b //1;b,c,d没有初始化,所以使用上一行的值
c //1
d //1
)
iota
iota,特殊常量,可以认为是一个可以被编译器修改的常量。
iota 在 const关键字出现时将被重置为 0(const 内部的第一行之前),const 中每新增一行常量声明将使 iota 计数一次(iota 可理解为 const 语句块中的行索引)。
const (
a11 = iota //0
b11 //1
c11 //2
d11 = "ha" //独立值,iota+=1
e11 //ha
f11 = 100
g11 //100
h11 = iota //7,恢复计数
i11 //8
)
双目运算符<< >>
<< 左移, x2的n次方.超过了,就删除高位,低位自动补"0"
>> 右移,/2的意思.
运算符
自增自减
a=21
a++ //22
a-- //21
Go 的自增,自减只能作为表达式使用,而不能用于赋值语句。
a++ // 这是允许的,类似 a = a + 1,结果与 a++ 相同
a-- //与 a++ 相似
a = a++ // 这是不允许的,会出现变异错误 syntax error: unexpected ++ at end of statement
指针与地址
& 返回变量存储地址 &a; 将给出变量的实际地址。
* 指针变量 *a; 是一个指针变量
指针变量 * 和地址值 & 的区别:指针变量保存的是一个地址值,会分配独立的内存来存储一个整型数字。当变量前面有 * 标识时,才等同于 & 的用法,否则会直接输出一个整型数字。
func main() {
var a int = 4
var ptr *int
ptr = &a
println("a的值为", a); // 4
println("*ptr为", *ptr); // 4
println("ptr为", ptr); // 824633794744
}
条件语句
注意: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可运行。
类型一
if 100 > number {
number += 3
}
类型二
if number := 4; 100 > number {
number += 3
} else if 100 < number {
number -= 2
} else {
fmt.Println("OK!")
}
循环语句
支持以下几种循环控制语句
break 经常用于中断当前 for 循环或跳出 switch 语句
continue 跳过当前循环的剩余语句,然后继续进行下一轮循环。
goto 将控制转移到被标记的语句.如 A: for ... goto A
函数
Go 语言标准库提供了多种可动用的内置的函数。例如,len() 函数.
函数定义
func function_name( [parameter list] ) [return_types] { //返回值,比如 int,比如 (string, string)
函数体
}
调用函数,可以通过两种方式来传递参数
值传递 值传递是指在调用函数时将实际参数复制一份传递到函数中,这样在函数中如果对参数进行修改,将不会影响到实际参数。
引用传递 引用传递是指在调用函数时将实际参数的地址传递到函数中,那么在函数中对参数所进行的修改,将影响到实际参数。
默认情况下,Go语言使用的是值传递,即在调用过程中不会影响到实际参数.
函数用法
函数用法 描述
1.函数作为另外一个函数的实参 函数定义后可作为另外一个函数的实参数传入
2.闭包 闭包是匿名函数,可在动态编程中使用
3.方法 方法就是一个包含了接受者的函数
数组
声明
var balance [10] float32
初始化
var 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}
指针
声明
var var_name *var-type
var ip *int /* 指向整型*/
var fp *float32 /* 指向浮点型 */
使用
在指针类型前面加上 * 号(前缀)来获取指针所指向的内容。
空指针
一个指针变量通常缩写为 ptr.
空指针判断:
if(ptr != nil) /* ptr 不是空指针 */
if(ptr == nil) /* ptr 是空指针 */
更多的指针应用
内容 描述
Go指针数组 你可以定义一个指针数组来存储地址
Go指向指针的指针 Go 支持指向指针的指针
Go向函数传递指针参数 通过引用或地址传参,在函数调用时可以改变其值
结构体
Go 语言中数组可以存储同一类型的数据,但在结构体中我们可以为不同项定义不同的数据类型。
结构体是由一系列具有相同类型或不同类型的数据构成的数据集合。有点类似于Java中的类.
结构体定义
type Books struct {
title string
author string
subject string
book_id int
}
func main() {
// 创建一个新的结构体
fmt.Println(Books{"Go 语言", "www.runoob.com", "Go 语言教程", 6495407})
// 也可以使用 key => value 格式
fmt.Println(Books{title: "Go 语言", author: "www.runoob.com", subject: "Go 语言教程", book_id: 6495407})
// 忽略的字段为 0 或 空
fmt.Println(Books{title: "Go 语言", author: "www.runoob.com"})
}
结构体成员的访问
如果要访问结构体成员,需要使用点号 . 操作符
var Book1 Books
Book1.title = "Go"
Book1.author= "chen"
fmt.Println(Book1,Book1.title)
结构体作为函数的参数
func printBook( book Books ) {
fmt.Printf( "Book title : %s\n", book.title)
fmt.Printf( "Book author : %s\n", book.author)
fmt.Printf( "Book subject : %s\n", book.subject)
fmt.Printf( "Book book_id : %d\n", book.book_id)
}
切片slice(未指定大小的数组)
先看下数组
var array1 [10] int 或
var array1 = [5]int{1,2,3,4,5} 或
var array1 = [...]int{1,2,3,4,5}
思考: Go为何不使用Python列表那种,将数组跟切片合为1个概念呢?其实本质上,还是同一个东西.
定义
var array1 [10] int //声明的是一个数组(指定了大小)
var array1 = [5]int{1,2,3,4,5} //声明并创建一个数组
var array1 = [...]int{1,2,3,4,5} //声明并创建一个数组
var slice1 []int //可以声明一个未指定大小的数组-切片(切片不需要说明长度)
var slice1 []int = make([]type, len) //声明切片:使用make()函数,这里定义有初始长度
var slice1 = make([]int, len) //省略了slice1的定义类型,由go自动检测
如 slice1 := make([]int, len) //如 slice1 := make([]int,3,5) 为int类型,初始长度3,最长5.连var关键字也省略了.
make([]type, length, capacity) //如 slice1 := make([]int,3,5) 为int类型,初始长度3,最长5.这里 len 是数组的长度并且也是切片的初始长度.capacity指定容量,为可选参数
切片初始化
1.通过make函数
2.通过字面量方式
3.对源数组或源切片使用identifier[start:end] 语法生成切片
s := [] int {1,2,3} //s是个切片
println(s[2]) //3
s2 := make([]int, 10, ) //s2是个切片
s2[1] = 5
println(s2[0],s2[1],s2[:9]) //0,5,[9/10]0xc00007ae58
var s4 = [...]int{1,2,3,4} //s4是个数组
println(s4[0]) //1
var s5 = s4[2:] //s5是个数组,也是切片
println(s5[0], len(s5)) //3,2
len() 和 cap() 函数
切片是可索引的,并且可以由 len() 方法获取长度。
切片提供了计算容量的方法 cap() 可以测量切片最长可以达到多少。
append() 和 copy() 函数
var numbers []int
numbers = append(numbers, 2,3,4) //追加多个元素.当我们一次性追加多个元素时,capacity会更新为一个接近但大于切片长度的数值,而不是简单地基于原来的值乘以某个正整数.
numbers1 := make([]int, len(numbers), (cap(numbers))*2) //创建切片 numbers1 是之前切片的两倍容量
copy(numbers1,numbers) //拷贝 numbers 的内容到 numbers1
关于切片容量,还可以参考 https://blog.csdn.net/weixin_36094484/article/details/82052049
范围(Range)
Go 语言中 range 关键字用于 for 循环中迭代数组(array)、切片(slice)、通道(channel)或集合(map)的元素。
在数组和切片中它返回元素的索引和索引对应的值,在集合中返回 key-value 对。
nums := [] int {2,3,4} //使用range求slice的和
fmt.Println(nums) //[2 3 4]
sum := 0
for _, num := range nums { //无需知道索引,所以赋值给"_"
sum += num
}
fmt.Println(sum) //9
for i,j := range nums{ //i取索引
if j==3 {
fmt.Println(i) //1
}
}
kvs := map[string]string{"a":"apple", "b":"banana"} //range用在map的键值对上
for k,v := range kvs {
fmt.Println(k,v)
}
for i,j := range "go" { //range也可以用来枚举Unicode字符串。第一个参数是字符的索引,第二个是字符(Unicode的值)本身。
fmt.Println(i,j) //0 103 和 1 111
}
集合(Map)
Map 是一种无序的键值对的集合。Map 最重要的一点是通过 key 来快速检索数据,key 类似于索引,指向数据的值。
Map 是一种集合,所以我们可以像迭代数组和切片那样迭代它。不过,Map 是无序的,我们无法决定它的返回顺序,这是因为 Map 是使用 hash 表来实现的。
跟Python的字典几乎一样吧.
定义 Map
分2步骤
/* 1.声明变量,默认 map 是 nil */
var map_variable map[key_data_type]value_data_type
/* 2.使用 make 函数 */
map_variable := make(map[key_data_type]value_data_type) //如果不初始化 map,那么就会创建一个 nil map。nil map 不能用来存放键值对
一步搞定
countryCapitalMap2 := map[string]string{"France":"Paris",}
fmt.Println(countryCapitalMap2)
示例
var countryCapitalMap map[string]string //Map声明
countryCapitalMap = make(map[string]string) //Map初始化
countryCapitalMap["France"]="Bali"
countryCapitalMap["Italy"]="Luoma"
for country := range countryCapitalMap{
fmt.Println(country, "ShouDuShi:", countryCapitalMap[country])
}
capital,ok := countryCapitalMap["American"]
fmt.Println("Capital:",capital) //Capital:
fmt.Println("True or False:",ok) //True or False: false
if (ok){
fmt.Println("Amercian ShouDu:", capital)
} else {
fmt.Println("American ShouDu is Null") //American ShouDu is Null
}
delete() 函数
delete() 函数用于删除集合的元素, 参数为 map 和其对应的 key.
delete(countryCapitalMap,"France")
递归
递归,就是在运行的过程中调用自己。
语法格式如下:
func recursion() {
recursion() /* 函数调用自身 */
}
func main() {
recursion()
}
Go 语言支持递归。但我们在使用递归时,开发者需要设置退出条件,否则递归将陷入无限循环中。
数据类型转换
type_name(expression)
示例
var sum int = 17
var count int = 5
var mean float32
mean = float32(sum)/float32(count) //类型转换
接口(需要结构体/方法的支持)
Go 语言提供了另外一种数据类型即接口,它把所有的具有共性的方法定义在一起,任何其他类型只要实现了这些方法就是实现了这个接口。
定义
/* 定义接口 */
type interface_name interface {
method_name1 [return_type]
method_name2 [return_type]
...
method_namen [return_type]
}
/* 定义结构体 */
type struct_name struct {
/* variables */
}
/* 实现接口方法 */
func (struct_name_variable struct_name) method_name1() [return_type] {
/* 方法实现 */
}
...
func (struct_name_variable struct_name) method_namen() [return_type] {
/* 方法实现*/
}
示例
package main
import (
"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()
}
在上面的例子中,我们定义了一个接口Phone,接口里面有一个方法call()。然后我们在main函数里面定义了一个Phone类型变量,并分别为之赋值为NokiaPhone和IPhone。然后调用call()方法.
错误处理
Go 语言通过内置的错误接口提供了非常简单的错误处理机制。
error类型是一个接口类型,这是它的定义:
type error interface {
Error() string
}
我们可以在编码中通过实现 error 接口类型来生成错误信息。
函数通常在最后的返回值中返回错误信息。使用errors.New 可返回一个错误信息:
func Sqrt(f float64) (float64, error) {
if f < 0 {
return 0, errors.New("math: square root of negative number")
}
// 实现
}
在下面的例子中,我们在调用Sqrt的时候传递的一个负数,然后就得到了non-nil的error对象,将此对象与nil比较,结果为true,所以fmt.Println(fmt包在处理error时会调用Error方法)被调用,以输出错误,请看下面调用的示例代码:
result, err:= Sqrt(-1)
if err != nil {
fmt.Println(err)
}
并发
func say(s string) {
for i := 0; i < 5; i++ {
time.Sleep(100 * time.Millisecond)
fmt.Println(s)
}
}
func main() {
go say("world")
say("hello")
}
Go 允许使用 go 语句开启一个新的运行期线程, 即 goroutine,以一个不同的、新创建的 goroutine 来执行一个函数。 同一个程序中的所有 goroutine 共享同一个地址空间。
通道(channel)
通道(channel)是用来传递数据的一个数据结构
通道可用于两个 goroutine 之间通过传递一个指定类型的值来同步运行和通讯。操作符 <- 用于指定通道的方向,发送或接收。如果未指定方向,则为双向通道。
ch <- v // 把 v 发送到通道 ch
v := <-ch // 从 ch 接收数据
// 并把值赋给 v
声明一个通道很简单,我们使用chan关键字即可,通道在使用前必须先创建:
ch := make(chan int)
注意:默认情况下,通道是不带缓冲区的。发送端发送数据,同时必须有接收端相应的接收数据。
通道缓冲区
通道可以设置缓冲区,通过 make 的第二个参数指定缓冲区大小:
ch := make(chan int, 100)
带缓冲区的通道允许发送端的数据发送和接收端的数据获取处于异步状态,就是说发送端发送的数据可以放在缓冲区里面,可以等待接收端去获取数据,而不是立刻需要接收端去获取数据。
不过由于缓冲区的大小是有限的,所以还是必须有接收端来接收数据的,否则缓冲区一满,数据发送端就无法再发送数据了。
注意:如果通道不带缓冲,发送方会阻塞直到接收方从通道中接收了值。如果通道带缓冲,发送方则会阻塞直到发送的值被拷贝到缓冲区内;如果缓冲区已满,则意味着需要等待直到某个接收方获取到一个值。接收方在有值可以接收之前会一直阻塞。
ch := make(chan int, 2)
ch <- 1
ch <- 2
fmt.Println(<-ch) //1
fmt.Println(<-ch) //2
Go 遍历通道与关闭通道
Go 通过 range 关键字来实现遍历读取到的数据,类似于与数组或切片。格式如下:
v, ok := <-ch
如果通道接收不到数据后 ok 就为 false,这时通道就可以使用 close() 函数来关闭。
问题
1.包,文件,package,import,不同文件函数间的调用方法