学习go语言一篇就够了(持续更新)
前言:写博客也写了差不多一年了,我更多的时候是记录自己学习的情况,有时也有自己工作上遇到的bug,自己有时候也比较迷茫,不知道怎么去写博文,我也很想别人跟我提提建议,但是有时候觉得写写博客还是很有成就感的,我有时候觉得快速的把知识点过一遍然后进行大量的练习和补充每个知识点的不足,让自己写的博文能更加完善些(关于这个博文是一套视频和两本连起来总结的,所有有时候会感觉知识点有点混乱)
学习方向
区块链研发工程师
Go服务器端/游戏软件工程师
golang分布式/云计算软件工程师
学习方法
先know how
,在know why
Golang的语言特点
注意事项
以”go” 为扩展名
执行入口是main()函数
严格区分大小写
每个语句后面不带分号
定义的变量和import的包如果没有使用到,代码不能编译通过
shift+tab整体向左移动(编辑器)
使用一个表达式形容Go语言: Go=C+Python
print()
println() //print+line
printf() //print+format
Sprintln()//获取字符串
Sprint()
Sprintf()
%T , 打印变量的数据类型
%d , 打印整数,十进制
%f , 打印浮点,小数
%t , 打印字符串
%s , 打印字符串
%v , 原样输出
%c , 打印对应的unicode字符
%p , 打印地址
变量的定义
var 变量 数据类型
变量名=赋值
缩写成一行
var 变量名 数据类型=赋值
类型推断
var 变量名=赋值
省略var
变量: =赋值
b:=2 //简短声明不支持全局变量
在同一区域同一数据类型内不断变化的
var num int //默认为0 跟js差不多 undefined
_
只写,不能读
常量
const 常量 数据类型=赋值
const 常量名=赋值
所有的字母大写
如果私有 前加小写字符c
一组常量中,如果某个常量没有初始值,那么默认和上一行一致
iota
:计数器 默认为0const( e=iota f g ) //每当定义一个常量累加1
整型
int
uint
和操作系统有关
浮点
float32
单精度float64
双精度
字符串
一个字符byte
Go语言字符使用UTF-8 英文字符1个字节 汉字3个字节
字符串一旦赋值了,就不能在修改
反引号是直接输出“
布尔(bool)
只能取true false不能取0,1 占用一个字节
Go在不同类型的变量之间赋值是需要显式转换,也就是说Golang中数据类型不能自动转换
var i int32 = 100
var n1 float32 = float32(i)
var n2 int8 = int8(i)
var n3 int64 = int64(i)
fmt.Print("i=%v n1=%v n2=%v n3=%v", i, n1, n2, n3)
%v 需要转换的变量
基本数据类型 的默认值
基本类型转string类型
-
fmt.Sprintf
会返回转换后的字符串 strconv.FormatFloat
strconv.FormatInt
strconv.formatbool
string类型转基本数据类型
-
strconv ParseInt() ParseFloat() ParseBool() ParseUint() n1,_=strconv.ParseInt(str2,10,64) f1,_=strconv.ParseFloat(str3,64)
算术运算符
- 对于除号”/” 只保留整数部分而舍弃小数部分
Golang的自增自减只能当作一个独立语言使用
例如
i++
i--
++和--只能写在变量的后面,不能写在变量的前面
go 语言明确不支持三元运算符
键盘输入语句
fmt.Scanln()
fmt.Scanf()
fmt.scanf
可以按照指定的格式输入fmt.Scanf("%s,%d,%f,%t",&name,&age,&sal,&ispass)
通过&变量保存数据
fmt.Scanln(&name)
if x>y {
fmt.Println("true")
}
if else if
/*希望函数内的变量能改变函数外的数值,可以传入变量的地址&
函数内以指针的方式操作变量*/
func test03(n1 *int) {
*n1=*n1+10
fmt.Println("test03 n1=",*n1)
}
func main(){
num:=20
test03(&num)
fmt.Println("main() num",num)
}
go函数不支持重载
自定义数据类型
type 自定义数据类型 数据类型
type myInt int
var num1 myInt
var num2 int
num1=400
num2=int(num1) //这里依然需要显示转换,go认为myInt和int两个类型
args
是slice
切片,通过args[index]
可以访问各个值
func sum(n1 int,args... int)int{
sum:=n1
for i:=0;i<len(args);i++{
sum+=args[i]//args[0] 表示取出args切片的第一个元素之
}
return sum
}
func main(){
res:=sum(1,2,3,4,5,5,6,6)
fmt.Println("res=",res)
}
每一个源文件都可以 包含一个init函数,init会在main函数前调用
执行顺序
bufio包
reader:=NewReader(os.Stuio)
reader.ReadLine()====>[]byte
reader.ReadString(\'\n\')-->String
内置包
math包提供数学计算的函数
math.Abs()
go语言特有
fallthrough
穿透
当某个case匹配成功后执行,如果有fallthrough,那么后面紧邻的case不再匹配,直接执行
//math/rand包下
//时间
t1:=time.Now().Unix()
fmt.Println(t1)
//设置种子数
rand.Seed(t1) //设置获取随机数的种子数
num1:=rand.Intn(10) //[0,10)
fmt.Println(num1)
数组
var arr [4]int
var b=[4]int{1,2,3,4}
f:=[...] int{1,2,3,4,5}
len() 长度
cap() 容量
//因为数组是定长的容器,长度和容量是相同的
range 对数组 取数组的下标和value
for index,value :=range arr{
fmt.Println("下标是",index,"数值是",value)
}
for _,v :=range arr{
fmt.Println(v)
}
二维数组
arr:=[4][3]int{{1,2,3},{1,2,3},{1,2,3},{1,2,3}}
切片
切片slice
同数组类似,也叫做变长数组
是一个引用类型的容器,指向了一个底层数据
因为切片是引用数据的数据,直接拷贝的是地址
浅拷贝:拷贝的数据地址 copy()
深拷贝: 拷贝的数据本身
s2:=[]int{1,2,3,4}
内置函数
make()
s1:=make([] 类型 ,len,cap) 第一个参数:切片的类型,第二个参数:len,第三个参数容量,如果省略,默认跟第二个一样
----------------
s2:=make([] int,3)
s2=append(s2,1,2,3,4)
a[start:end] //包左不包右
当向切片添加数据时,如果没有超过容量,直接添加,如果超过容量,自动扩容(成倍增加)
/* 数组传递的是数据
切片传递的是地址*/
s1:=[] int{1,2,3,4,5}
fmt.Println(s1)
s2:=s1
fmt.Println(s2)
s2[0]=100
fmt.Println(s1)
fmt.Println("--------")
s3:=[5] int{1,2,3,4,5}
fmt.Println(s3)
s4:=s3
fmt.Println(s4)
s4[0]=100
fmt.Println(s4)
fmt.Println(s3)
copy copy(s1,s2) 把s2拷贝到s1,但是不会改变长度,能装多少装多少
s1 := []int{1, 2, 3, 4, 5}
s2 := []int{6, 7, 8, 9}
copy(s1,s2)
fmt.Println(s1)
字符串操作
-
一个字节的切片
-
strings包下的字符串函数
strings.Contains() 是否包含指定的内容
strings.ContainsAny() 是否包含任意一个字符
Repeat 自己拼接自己count次
大小写转换 ToLower() ToUpper()
切割Split() SplitN()
Index IndexAnyLastIndex() LastIndexAny()
Replace(s,old,new,n)
Trim()
HasPerfix() 以xx前缀开头
HasSuffix() 以xx后缀结尾
查找
EqualFold不区分大小写的字符串比较
index 找不到返回-1
TrimSpace() 将字符串左边两边的空格去掉
Trim(“元字符串”,指定要去除的) 将指定的字符串左右两边空格去掉(有区别哦)
…
-
字符串遍历,同时处理有中文的问题
r:[]rune(str)
str:="hello被" r:=[]rune(str) for i:=0;i<len(r);i++{ fmt.Printf("%c\n",r[i]) }
-
字符串转整数
n,err=strconv.Atoi("12")
b:="65" s2, _ :=strconv.Atoi(b) fmt.Printf("%v",s2)
-
整数转字符串
str=strconv.Itoa(1234)
-
字符串 转[]byte:
var bytes=[]byte("hello go")
-
[]byte转字符串
str=string([]byte{\'a\',\'b\',\'c\'})
-
十进制转2,8,16进制
str=strconv.FormatInt(123,2)
//2->8,16
map, 映射
key ,value 也是一个容器,存储的无序键值对
key不重复,重复就覆盖
map声明是不会分配内存的,初始化需要make,分配内存后才能赋值和使用
类型
%T
m:=map[string]int{"one":1,"two":2} m:=make(map[string]int) nil 空 将map存进切片中 s1:=make([] map[string]string,0,3)
通过key获取value,当key如果不存在的时候,我们会得到value的默认值
map1[key]=value,根据key获取map中对应的value
value,ok:=map[key]
如果键值对存在,value就是对应的数据,ok为true如果key不存在获取,获取的是 值的默认值,ok为false,默认key为0
value,ok:=map[key] f ok{ fmt.Println("对应的数值是:",v1) }else{ fmt.Println("操作的key不存在",vl) }
内置函数delete
- delete(map1,key)
sort排序
sort.Ints(key) 字符按照字符串的编码值排序
str="FKJKJFJdfjakfjakKSKJW" map1 := make(map[byte]int) for i := 0; i < len(str); i++ { val, ok := map1[str[i]] if ok { val++ } else { val = 1 } map1[str[i]] = val } keys := make([]byte, 0, len(map1)) for k := range map1 { keys = append(keys, k) } for _, k := range keys { fmt.Printf("%c,%d\n", k, map1[k]) }
map1:=make(map[string]map[string]string) //需要定义长度 map1["name"]=make(map[string]string,3) map1["liming"]=make(map[string]string,3) map1["name"]["name3"]="张三" map1["name"]["name1"]="李四" map1["name"]["name2"]="王五" map1["liming"]["xiaoming1"]="小明" map1["liming"]["xiaoming2"]="小红" map1["liming"]["xiaoming3"]="小白" for k,v :=range map1{ fmt.Println(k,v) for k2,v2:=range v{ fmt.Println(k2,v2) } }
time
time.new 获取当前时间
格式化事件
fmt.Printf
fmt.SPrintf
now:=time.Now() fmt.Printf("now=%v,now\n",now) fmt.Printf("%d-%d-%d %d:%d:%d\n",now.Year(),now.Month(),now.Day(),now.Hour(),now.Minute(),now.Second())
time.Format()
fmt.Printf(now.Format("2018-12-12 14:34:23))
Unix() 1970到现在的毫秒值
UnixNano 1970到现在的纳秒值
内置函数
len
new
异常处理
//使用defer+recover 来捕获异常 defer func() { err := recover() //recover()内置函数,可以捕获到异常 if err != nil { //说明捕获到错误 fmt.Println("err=", err) } }() errors.New("错误说明") 会返回一个err类型的值表示一个错误 //自定义错误 // 定义一个函数,用于求矩形的面积 func getArea(wid,len float64)(float64,error){ errorMsg:="" if wid<=0{ errorMsg="宽度为负数" } if len<0{ if errorMsg==""{ errorMsg="长度为负数" }else{ errorMsg+=",长度也为负数" } } if errorMsg !=""{ return 0,&errorRect{errorMsg,wid,len} } area:=wid*len return area,nil }
函数
go语言支持函数式编程+面向对象
- 函数:独立功能,直接调用
- 方法:对象的功能,对象来调用
可变参数: 参数名…
函数中,可变参数想当于切片
位置参数>可变参数 (顺序)
写返回值的时候,必须在函数上声明返回值的类型
函数式编程
一个函数可以作为另一个函数的参数或返回值
递归函数
自己调用自己
func getgui(n int) int{
if n==1||n==2{
return 1
}
return getgui(n-1)+getgui(n-2)
}
匿名函数
func(a,b int){
fmt.Println(a,b)
}(1,2)
高阶函数(回调函数)
根据go语言函数的数据类型的特点,可以将函数作为另一个函数的参数
func add(a,b int) int{
return a+b
}
func oper(m,n int,fun func(int,int)int)int{
res:=fun(m,n)
return res
}
func main() {
fmt.Println(
oper(10,20,add))
}
闭包
引用外部变量的匿名函数
支持将函数作为另一个函数的返回值
func f(i int) func() int{
return func() int{
i++
return i
}
}
func main() {
str:=f(10)
fmt.Println(str())
fmt.Println(str())
}
-----------------------------
func ExFunc(n int) func () {
sum:=n
a:=func(){
fmt.Println(sum+1)
}
return a
}
func main() {
myFunc:=ExFunc(10)
myFunc()
}
函数的defer(延时机制)
在执行到defer是,暂时不执行,会将defer后面的压入独立的栈,按照先入后出的方式出栈,执行
先defer再return
获取变量的地址,用&
获取指针类型所指向的值*
fmt.Println(&num)
var ptr *int=&num
值类型,都有对应的指针类型 新式为*数据类型
结构体
//定义一个类,就是定义一个结构体
//类的定义,字段属性,行为方法
type 结构体名称 struct{
name type
age int
}
//定义结构体变量
var p1 结构体名称
p2:=Person{"mary",20}
p6:=new(Person)
var p1 Person
p1.Age=10
p1.Name="小明"
var p2 *Person=&p1
fmt.Println((*p2).Age)
注意不能*p2.Age写,因为.的优先级比* 高
// 实现结构体的浅拷贝
d4:=new(dog)
d4.color="黄色"
d4.age=2
d4.kind="中华田园犬"
fmt.Println(d4)
d5:=d4
d5.kind="张三"
fmt.Println(d5,d4)
d6:=&d1//*dog
d6.kind="金毛"
fmt.Println(d6,d1)
//匿名结构体:没有名字的结构体,在创建匿名结构体,同时创建对象
p1:=struct {
name string
age int
}{
name: "zhangsan",
age: 30,
}
//匿名字段
type student struct{
string
int//匿名字段(不能有重复字段)
}
s1:=student{"zhgsan",12}
fmt.Println(s1.string,s1.int)
type Book struct{
bookName string
price float64
auther string
}
//嵌套struct的名称冲突
有以下两个名称冲突的规则(报错)
* 外部struct覆盖内部struct的同名字段,同名方法(1)
* 同级别的struct出现同名字段,方法将报错(2)
type A struct{
a int
b int
}
type B struct{
b int
c string
d string
}
type C struct {
A //继承
B
a string
c string
}
(1) C.a和C.c分别覆盖A.a和B.c
(2) A.b和B.b
为什么呢? 继承的"提升字段"
//结构体嵌套
//模拟面向对象:聚合关系
//一个类作为另一个类的属性
//定义一个书的结构体
type Book struct{
bookName string
price float64
auther string
}
//定义一个人的结构体
type Person struct{
name string
age int
book Book //聚合
}
func main() {
p3:=Person{
name:"Jerry",
age:26,
book:Book{
bookName:"Go语言是怎么练成的",
price:55.0,
auther:"张三",
},
}
fmt.Println(p3.book.auther,p3.book.price,p3.book.auther)
p4:=Person{"李晓华",20,Book{"四万个为什么",44.8,"张三"}}
fmt.Println(p4.book.auther,p4.book.price,p4.book.auther)
}
- struct 的数据类型:值类型:默认深拷贝
- 如果结构体的字段类型是:指针,slice,和map的零值都是nil,还没有分配空间
- 如果需要使用这样的字段 ,需要先make,才能使用
- 结构体是值类型
方法
type Person struct{
name string
age int
}
type Student struct {
Person
school string
}
//方法
func (p Person) eat(){
fmt.Println("父类的方法,吃我我头")
}
func (s Student) study(){
fmt.Println("子类的方法..")
}
func (s Student) eat(){
fmt.Println("子类重写父类的方法")
}
多态继承
接口(interface
):功能的描述的集合
- 定义有哪些功能
接口的意义
- 解耦合:程序和程序之间的关联程度,降低耦合性
- 继承关系:增加耦合
注意点:
- 当需要接口类型的对象时,那么可以使用任意实现类对象代替
- 接口对象不能访问实现类的属性
/* 多态*/
//定义一个接口
type Shape interface{
peri() float64 //周长
area() float64 //面积
}
//定义实现类:三角形
type Triangle struct {
a,b,c float64//三个边
}
func (t Triangle)peri() float64{
return t.a+t.b+t.c//周长
}
func (t Triangle)area() float64{
p:=t.peri()
s:=math.Sqrt(p*(p-t.a)*(p-t.b)*(p-t.c)) //面积
return s
}
type Circle struct {
radius float64//半径
}
func (c Circle)peri()float64{
return c.radius*2*math.Pi //面积
}
func (c Circle)area()float64{
return math.Pow(c.radius,2)*math.Pi
}
func teshShape(s Shape) {
fmt.Println("周长",s.peri(),"面积",s.area())
}
//转型
func getType(s Shape) {
/* 方法一:instance,ok:=接口对象.(实际类型)
如果该接口对象是对应的实际类型,那么instance就是转型之后对象
*/
ins,ok:=s.(Triangle)
if ok{
fmt.Println("是三角形",ins.a,ins.b,ins.c)
}else if ins,ok:=s.(Circle);ok{
fmt.Println("是圆形,半径是:",ins.radius)
}
}
//2 转型(向下转型)
func getType2(s Shape) {
/*方法二:接口对象.(type),配合switch和case语句使用*/
switch ins := s.(type) {
case Triangle:
fmt.Println("三角形", ins.a, ins.b, ins.c)
case Circle:
fmt.Println("圆形", ins.radius)
//case int:
// fmt.Println("整型数据...")
}
}
func main() {
/*
多态:一个事物的多种形态
go语言:通过接口模拟多态性
一个实现类的对象:
看作是一个实现类类型:能够访问实现类中的方法和属性
还可以看作是对应接口类型:只能够访问接口中定义的方法
用法一:一个函数如果接受接口类型作为参数,那么实际上可以传入接口的任意实现类对象作为参数
*/
t1:=Triangle{3,4,5}
fmt.Println(t1.peri())
fmt.Println(t1.area())
fmt.Println(t1.a,t1.b,t1.c)
var s1 Shape
s1=t1
fmt.Println(s1.peri())
fmt.Println(s1.area())
var c1 Circle
c1=Circle{4}
fmt.Println(c1.peri())
fmt.Println(c1.area())
fmt.Println(c1.radius)
var s2 Shape=Circle{5}
fmt.Println(s2.area(),s2.peri())
//定义一个接口类型的数组
arr:=[4]Shape{t1,s1,c1,s2}
fmt.Println(arr)
//接口类型的对象-->对应实现类类型
getType(c1)
}
空接口
也是一个接口,但是该接口中没有任何方法
所有可以将任意类型作为该接口的实现
//定义一个map:string作为key,任意类型作为value
map1:=make(map[string]interface{})
map1["name"]="王二狗"
map1["age"]=12
//空接口
type A interface {}
var a1 A=Cata{"花猫",1}
var a2 A=Persona{"王二狗","男性"}
练习1
/*
三维坐标,求两点的距离
*/
type Point struct{
x,y,z float64
}
func (p Point) PrintInfo(){
fmt.Println(p.x,p.y,p.z)
}
func (p Point) getDisance3(p2 Point) float64{
dis:=math.Sqrt(math.Pow(p.x-p2.x,2)+math.Pow(p.y-p2.y,2)+math.Pow(p.z-p2.z,2))
return dis
}
func main() {
p1:=Point{1,2,4}
p2:=Point{0,0,0}
p1.PrintInfo()
p2.PrintInfo()
fmt.Println(p1.getDisance3(p2))
}