Golang
简称 Go
,是一个开源的编程语言。
Go 语言被设计成一门应用于搭载 Web 服务器,存储集群或类似用途的巨型中央服务器的系统编程语言。
Go 语言在用于高性能分布式系统开发中,无疑比大多数其它语言有着更高的开发效率。此外,它提供了海量并行的支持,这对于游戏服务端的开发而言也是不错的选择。
err
判断bool
String
整数:
int8(-128 -> 127) int16(-32768 -> 32767) int32(-2,147,483,648 -> 2,147,483,647) int64(-9,223,372,036,854,775,808 -> 9,223,372,036,854,775,807)
无符号整数:
uint8(0 -> 255) uint16(0 -> 65,535) uint32(0 -> 4,294,967,295) uint64(0 -> 18,446,744,073,709,551,615)
浮点型(IEEE-754 标准):
float32(± 1e-45 -> ± 3.4 * 1e38) float64(± 5 * 1e-324 -> 107 * 1e308)
uintptr 同指针,32位平台为4字节,64位八字节
byte 等价于uint8
rune 等价于uint32,单个unicode字符
复数类型:complex64,complex128
int 型是计算最快的一种类型。
整型的零值为 0,浮点型的零值为 0.0
结构化的(复合的),如:struct、array、slice、map、channel;
只描述类型的行为的,如:interface
package main
import(
"fmt"
)
func main() {
//1.指定变量类型,若不赋值,使用默认值
var num int
var num1 int = 1
fmt.Println(num,num1)
//2.根据值自行判定变量类型(类型推导),其实就是不写类型,根据你赋的值判断
var num2 = 2
fmt.Println(num2)
//3.省略 var, 注意 :=左侧的变量不应该是已经声明过的,否则会导致编译错误
num3 := 3
fmt.Println(num3)
//4.批量定义
var n1, n2, n3 int
fmt.Println(n1,n2,n3)
fmt.Println("=======")
var n4, name, n5 = 100, "tom",888
fmt.Println("n4=",n4,"name=",name,"n5=",n5)
fmt.Println("======")
n6, name1, n7 := 6, "name1",7
fmt.Println("n6=",n6,"name1=",name1,"n7=", n7)
}
package main
import(
"fmt"
"unsafe"
)
func main() {
//如何在程序查看某个变量的字节大小和数据类型 (使用较多)
var i uint16 = 100
fmt.Printf("i 的类型%T,占用的字节数:%d\n",i,unsafe.Sizeof(i))
var i1 uint = 100
fmt.Printf("i 的类型%T,占用的字节数:%d",i1,unsafe.Sizeof(i1))
}
i 的类型uint16,占用的字节数:2
i 的类型uint,占用的字节数:8
package main
import(
"fmt"
)
func main() {
var i byte = 'a'
fmt.Printf("i对应的类型:%T,对应的字符:%c,对应的码值:%d\n", i, i, i)
var i1= ','
fmt.Printf("i对应的类型:%T,对应的字符:%c,对应的码值:%d\n", i1, i1, i1)
var i2= '南'
fmt.Printf("i对应的类型:%T,对应的字符:%c,对应的码值:%d\n", i2, i2, i2)
//var i3 byte= '经' //报错:constant 32463 overflows byte
}
i对应的类型:uint8,对应的字符:a,对应的码值:97
i对应的类型:int32,对应的字符:,,对应的码值:44
i对应的类型:int32,对应的字符:南,对应的码值:21335
package main
import "fmt"
func main() {
//字符串golang
//双引号
var str1 = "hello"
//单引号将字符串完整格式都打出
str2 := `
pachage
it is time;
lets go;
`
fmt.Println(str1)
fmt.Println(str2)
}
hello
pachage
it is time;
lets go;
表达式 T(v) 将值 v 转换为类型 T
T: 就是数据类型,比如 int32,int64,float32 等等
v: 就是需要转换的变量
在转换中,比如将 int64 转成 int8 【-128—127】 ,编译时不会报错,只是转换的结果是按
溢出处理,和我们希望的结果不一样。 因此在转换时,需要考虑范围
方式一:fmt.Sprintf("%参数", 表达式)
package main
import "fmt"
func main() {
var i = true
var i1 = 1
var i2 = 1.3
var s1 = fmt.Sprintf("%v",i)
fmt.Printf("i对应的类型:%T,对应的值%v\n",s1,s1)
var s2 = fmt.Sprintf("%v",i1)
fmt.Printf("i对应的类型:%T,对应的值%v\n",s2,s2)
var s3 = fmt.Sprintf("%v",i2)
fmt.Printf("i对应的类型:%T,对应的值%v\n",s3,s3)
fmt.Println(s1+s2+s3)
}
i对应的类型:string,对应的值true
i对应的类型:string,对应的值1
i对应的类型:string,对应的值1.3
true11.3
方式二:使用 strconv 包的函数
package main
import (
"fmt"
"strconv"
)
func main() {
var i = true
var i1 = 10
var i2 = 1.3
var s1 = strconv.FormatBool(i)
fmt.Printf("i对应的类型:%T,对应的值%v\n",s1,s1)
//整型需要转换成int64,还需指定对应的进制
var s2 = strconv.FormatInt(int64(i1),10)
fmt.Printf("i对应的类型:%T,对应的值%v\n",s2,s2)
//f表示格式; 10表示小数位保留10位; 64表示float64
var s3 = strconv.FormatFloat(i2,'f',10,64)
fmt.Printf("i对应的类型:%T,对应的值%v\n",s3,s3)
fmt.Println(s1+s2+s3)
}
i对应的类型:string,对应的值true
i对应的类型:string,对应的值10
i对应的类型:string,对应的值1.3000000000
true101.3000000000
使用时 strconv 包的函数
注:返回的都是int64和float64,如不满足需求需要自己转换
注意事项
在将 String 类型转成 基本数据类型时,要确保 String 类型能够转成有效的数据,比如 我们可以把 “123” , 转成一个整数,但是不能把 “hello” 转成一个整数,如果这样做,Golang 直接将其转成 0 ,其它类型也是一样的道理. float => 0 bool => false
数组可以存放多个同一类型数据。数组也是一种数据类型,在 Go 中,数组是值类型。
var 数组名 [数组大小]数据类型
var a [5]int
赋初值 a[0] = 1 a[1] = 30 …
对上图的总结:
func main() {
var arr [2]int = [2]int{1,2}
fmt.Println(arr)
//这里[...]是固定写法,省去了指定数组长度
var arr1 [2]int = [...]int{1,2}
fmt.Println(arr1)
var arr2 = [...]int{1,2}
fmt.Println(arr2)
//指定数组元素的顺序
var arr3 = [...]int{1:1,0:2}
fmt.Println(arr3)
//通过类型推导
arr4 := [...]string{1:"tom",0:"susan"}
fmt.Println(arr4)
}
方式一:
定义一个切片,然后让切片去引用一个已经创建好的数组。
slice :=arr[a:b:c]
a:起始位置
b:截取数据的结束位置 默认到末端)
c:截取后的容量位置 默认到末端)
slice的len = b - a
slice的cap = c - a
底层数组: 为原数组的下标a到下标b(不包括)的所有元素
func main() {
//定义一个数组
var arr = [...]string{"张学友","刘德华","黎明","郭富城","成龙","周星驰"}
//定义切片,参数分别为:起始位置,终止位置,切片的容量
slice :=arr[1:3:5]
fmt.Printf("slice容量:%d,长度:%d,值:%v\n",cap(slice),len(slice),slice)
fmt.Printf("slice地址:%p",slice)
}
slice容量:4,长度:2,值:[刘德华 黎明]
slice地址:0xc000084130
方式二:
通过 make 来创建切片
基本语法:var 切片名 []type = make([]type, len, [cap])
参数说明:
type: 就是数据类型
len : 大小
cap :指定切片容量,可选, 如果你分配了 cap,则要求 cap>=len.
func main() {
slice :=make([]int,3,5)
slice[0] = 100
slice[1] = 200
slice[2] = 300
fmt.Printf("slice容量:%d,长度:%d,值:%v\n",cap(slice),len(slice),slice)
fmt.Printf("slice地址:%p\n",slice)
//slice[3] = 400 //runtime error: index out of range 报错
slice = append(slice, 400)
slice = append(slice, 500)
fmt.Printf("slice容量:%d,长度:%d,值:%v\n",cap(slice),len(slice),slice)
fmt.Printf("slice地址:%p\n",slice)
slice = append(slice, 600)
fmt.Printf("slice容量:%d,长度:%d,值:%v\n",cap(slice),len(slice),slice)
fmt.Printf("slice地址:%p",slice)
}
slice容量:5,长度:3,值:[100 200 300]
slice地址:0xc000016120
slice容量:5,长度:5,值:[100 200 300 400 500]
slice地址:0xc000016120
slice容量:10,长度:6,值:[100 200 300 400 500 600]
slice地址:0xc000074000
与第一种方式的区别:
方式三:
定义一个切片,直接指定具体数组,相当于直接把切片当数组用。
func main() {
slice :=[]string{"张学友","刘德华","黎明","郭富城","成龙","周星驰"}
fmt.Printf("slice容量:%d,长度:%d,值:%v\n",cap(slice),len(slice),slice)
fmt.Printf("slice地址:%p",slice)
}
slice容量:6,长度:6,值:[张学友 刘德华 黎明 郭富城 成龙 周星驰]
slice地址:0xc000052180
func main() {
slice :=[]string{"张学友","刘德华","黎明","郭富城","成龙","周星驰"}
fmt.Println("常规方式")
for i:=0; i<len(slice) ; i++ {
fmt.Printf("%d ===> %v\n",i,slice[i])
}
fmt.Println("结构遍历")
for index,value := range slice{
fmt.Printf("%d ===> %v\n",index,value)
}
}
Go语言中的Map集合几乎和Java里面的差不多,但是Go语言里面,定义Map集合是不能使用的,需要手动用make方法分配内存。
var a map[string]string
var a map[string]int
var a map[int]string
var a map[string]map[string]string
分配空间
func main() {
var m = make(map[int]interface{},1)
m[1]="A"
m[2]="B"
fmt.Printf("m地址:%p;m的值:%v",m,m)
}
方式一:
用make函数分配内存,并且指定长度。
func main() {
var m = make(map[int]interface{},1)
m[1]="A"
m[2]="B"
fmt.Printf("m地址:%p;m的值:%v",m,m)
}
m地址:0xc00005c150;m的值:map[1:A 2:B]
方式二:
不指定长度。
func main() {
var m = make(map[int]interface{})
m[1]="A"
m[2]="B"
fmt.Printf("m地址:%p;m的值:%v",m,m)
}
m地址:0xc00005c150;m的值:map[1:A 2:B]
方式三:
直接声明并赋值,注意最后要加上,(逗号)。
func main() {
var m = map[int]interface{}{
1:"A",
2:"B",
}
fmt.Printf("m地址:%p;m的值:%v",m,m)
}
m地址:0xc000070150;m的值:map[2:B 1:A]
map[“key”] = value //如果 key 还没有,就是增加,如果 key 存在就是修改。
delete(map,“key”) ,delete 是一个内置函数,如果 key 存在,就删除该 key-value,如果 key 不存在,
不操作,但是也不会报错。
如果我们要删除 map 的所有 key ,没有一个专门的方法一次删除,可以遍历一下 key, 逐个删除
或者 map = make(…),make 一个新的,让原来的成为垃圾,被 gc 回收
value := m[key]
如果 m这个 map 中存在 key , 那么 返回 对应的value,否则返回 对应value类型的默认值
func main() {
var m = map[int]interface{}{
1:"A",
2:"B",
}
fmt.Printf("m地址:%p;m的值:%v\n",m,m)
//添加
m[3]="C"
fmt.Printf("m地址:%p;m的值:%v\n",m,m)
//更新
m[2]= "H"
//删除
delete(m,3)
//遍历
for key,value:= range m{
fmt.Printf("key:%v ; value:%v\n",key,value)
}
fmt.Printf("m地址:%p;m的值:%v\n",m,m)
}
m地址:0xc00005c150;m的值:map[1:A 2:B]
m地址:0xc00005c150;m的值:map[1:A 2:B 3:C]
key:1 ; value:A
key:2 ; value:H
m地址:0xc00005c150;m的值:map[2:H 1:A]
map 在使用前一定要 make;
map 的 key 是不能重复,如果重复了,则以最后这个 key-value 为准;
map 的 value 是可以相同的, map 的 key-value 是无序;
map 是引用类型,遵守引用类型传递的机制,在一个函数接收 map,修改后,会直接修改原来
的 map;
map 的容量达到后,再想 map 增加元素,会自动扩容,并不会发生 panic,也就是说 map 能动
态的增长 键值对(key-value);
map 的 value 也经常使用 struct 类型,更适合管理复杂的数据(比前面 value 是一个 map 更好),比如 value 为 Student 结构体。
Golang语言面向对象编程的说明:
type 结构体名称 struct {
field1 type
field2 type
}
示例:
type Student struct {
Name string //字段
Age int //字段
Score float32
}
在创建一个结构体变量后,如果没有给字段赋值,都对应一个零值(默认值),规则同前面讲的
一样:
方式一
var person Person
方式二
var person Person = Person{}
方式三
var person *Person = new (Person)
方式四
var person *Person = &Person{}
示例:
type Person struct {
Name string
Age int
}
func main() {
var p1 Person
p1.Name="jerry"
p1.Age=55
fmt.Printf("p1的值:%v\n",p1)
var p2 Person= Person{Name:"susan",Age:10}
fmt.Printf("p2的值:%v\n",p2)
var p3 *Person = new(Person)
p3.Name="jeck"
p3.Age=66
fmt.Printf("p3的值:%v\n",p3)
var p4 *Person= &Person{Name:"john",Age:88}
fmt.Printf("p4的值:%v\n",p4)
}
p1的值:{jerry 55}
p2的值:{susan 10}
p3的值:&{jeck 66}
p4的值:&{john 88}
总结:
第 3 种和第 4 种方式返回的是 结构体指针。
结构体指针访问字段的标准方式应该是:(*结构体指针).字段名 ,比如 (*person).Name = “tom” 3) 但 go 做了一个简化,也支持 结构体指针.字段名, 比如 person.Name = “tom”。更加符合程序员使用的习惯,go 编译器底层 对 person.Name 做了转化 (*person).Name。
结构体的所有字段在内存中是连续的
结构体是用户单独定义的类型,和其它类型进行转换时需要有完全相同的字段(名字、个数和类
型)
结构体进行 type 重新定义(相当于取别名),Golang 认为是新的数据类型,但是相互间可以强转
type Person struct {
Name string
Age int
}
type ps Person
func main() {
var p1 ps
p1.Name="jerry"
p1.Age=55
fmt.Printf("p1的值:%v\n",p1)
var p2 Person= Person(p1)
fmt.Printf("p2的值:%v\n",p2)
}
struct 的每个字段上,可以写上一个 tag, 该 tag 可以通过反射机制获取,常见的使用场景就是序
列化和反序列化。
Go语言里面也和java一样,对应的结构体可以有自己的方法。
type Person struct {
Name string
Age int
}
func (p Person) hello() {
fmt.Printf("hello ! my name is %v",p.Name)
}
func main() {
var p Person
p.Name="jerry"
p.Age=55
p.hello()
}
方法是可以有参数、返回值,比如:
func (p Person) getSum(a int, b int) (c int) {
c = a + b
return
}
也可以写成:
func (p Person) getSum(a int, b int) int {
return a + b
}
注意 不能返回==局部变量
的地址值
;因为函数调用结束,栈帧释放,局部变量的地址,不再受系统保护,随时可能分配给其他程序,可以返回局部变量
的值
==。
type Circle struct {
R float64
}
func (c Circle)getArea() float64 {
return math.Pi * math.Pow(c.R,2)
}
func main() {
var c = Circle{R: 2}
area := c.getArea()
fmt.Printf("面积:%v",area)
}
在Go中使用的是匿名属性,来实现继承的;即将父类作为子类的匿名属性。
package main
import "fmt"
type Person struct {
id int
name string
age int
}
type Student struct {
Person
id int
score int
className string
}
func main() {
var (
s Student
)
s = Student{
Person{name: "susan", age: 23,id:123},
456,
58,
"302班"}
fmt.Printf("ID:%d\n姓名:%s\n年龄:%d\n班级:%s\n分数:%d",
s.id,s.name,s.age,s.className,s.score)
}
注意:在Student对象中可以直接使用父类Person的属性,如果父类和子类中有重复字段,则优先使用子类自身的属性。
方法的继承和属性的继承是类似的,当一个类是另一个类的匿名属性时即可直接使用该类的方法
package main
import "fmt"
type Person struct {
id int
name string
age int
}
//父类say方法
func (p Person)say(msg string) {
fmt.Printf("[person] %s say: '%s'",p.name,msg)
}
//student子类say方法
func (s Student)say(msg string){
fmt.Printf("[student] %s say: '%s'",s.name,msg)
}
type Student struct {
Person
id int
score int
className string
}
func main() {
var (
s Student
)
s = Student{
Person{name: "susan", age: 23,id:123},
456,
58,
"302班"}
fmt.Printf("ID:%d\n姓名:%s\n年龄:%d\n班级:%s\n分数:%d\n",
s.id,s.name,s.age,s.className,s.score)
s.say("hello world")
}
ID:456
姓名:susan
年龄:23
班级:302班
分数:58
[student] susan say: 'hello world'
方法的重写,在语法格式上有一定的要求,即方法名,参数,返回值类型都必须一样,但绑定的对象不再是父类而是子类本身。
type annimal interface {
eat()
sleep()
run()
}
接口的继承与实现也是将被继承和实现的接口以匿名属性传入即可。
package main
import "fmt"
type annimal interface {
eat()
sleep()
run()
}
type cat interface {
annimal
Climb()
}
type HelloKitty struct {
cat
}
func (h HelloKitty)eat(){
fmt.Println("eat cake!!")
}
func main() {
var a annimal
a = HelloKitty{}
a.eat()
}
package main
import "fmt"
type annimal interface {
eat()
sleep()
run()
}
type cat interface {
annimal
Climb()
}
type dog interface {
annimal
}
type HelloKitty struct {
cat
}
type husky struct {
dog
}
func (h HelloKitty)eat(){
fmt.Println("eat cake!!")
}
func (h husky)eat(){
fmt.Println("eat bone!!")
}
func test(a annimal){
a.eat()
}
func main() {
var a annimal
a = HelloKitty{}
test(a)
a = husky{}
test(a)
}
test方法中传入了一个animal对象,在实际调用时会因传入对象的不同而得到不同的效果
结果:
eat cake!!
eat bone!!
判断具体的类型,在go中可以使用 对象.(指定的类型) 判断改对象是否时指定的类型。
func main() {
var a annimal
a = HelloKitty{}
var b annimal
b = husky{}
var animals [2]annimal =[...]annimal{a,b}
for _,v :=range animals{
//注意这里!================================================
if data,ok :=v.(husky);ok{
data.eat()
fmt.Println("this is wangcai : ")
}
if data,ok :=v.(HelloKitty);ok{
data.eat()
fmt.Println("this is HelloKitty : ")
}
}
}
==注意:==if条件中的data,ok :=v.(husky);ok,v.(husky)返回了两个值——v和bool类型,如果v是husky类型,那么将v的对象传递给data,true传递给ok,这样弥补了一个问题:问题的链接。
异常分为:
方法一:添加判断
这种是比较常见的作法,将每种异常都考虑到,然后做出响应的处理
package main
import (
"errors"
"fmt"
)
func test(a int,b int) (int,error){
var err error
if a == 0{
return 0,errors.New("a 不可以为0!!")
}
a = b / a
return a,err
}
func main() {
i,err := test(0, 1)
if err ==nil{
fmt.Println("====main方法正常结束!!====",i)
}else{
fmt.Println(err)
}
}
方法二:捕捉异常
异常的场景会有很多,会很难统计出来,或统计出来后出来很麻烦;此时我们就要对异常进行捕捉
这里将用到defer和recover
defer:延时执行,即在方法执行结束(出现异常而结束或正常结束)时执行
recover:恢复的意思,如果是异常结束程序不会中断,返回异常信息,可以根据异常来做出相应的处理
recover必须放在defer的函数中才能生效
package main
import (
"errors"
"fmt"
)
func test(a int,b int) (int,error){
var err error
if a == 0{
return 0,errors.New("a 不可以为0!!")
}
a = b / a
return a,err
}
func main() {
i,err := test(0, 1)
if err ==nil{
fmt.Println("====main方法正常结束!!====",i)
}else{
fmt.Println(err)
}
}
方法三:手动抛出异常
看到异常总觉得很讨厌,但有的时候它能帮助我们解决很多麻烦事,比如用户输入错误,就应该抛出异常,可以让这个异常一层层的返回给调用方的程序,使其不能继续执行,从而起到保护后面业务的目的,也简化了错误信息的传递,例如:
一个人取钱,账户只有100元,他输入了200元,从输入到账户扣款经过了20个方法调用,那总不能将这个err传递这么多次,传到起始的调用处吧?,后面的扣款业务也不能执行的
package main
import (
"errors"
"fmt"
)
func test(a int) int {
i:=100 - a
if i<0{
panic(errors.New("账户金额不足!!!!"))
}
fmt.Println("=======账户扣款=====")
return i
}
func main() {
i := test(101)
fmt.Println("====main方法正常结束!!====余额:",i)
}
panic: 账户金额不足!!!!
goroutine 1 [running]: