声明:本文为b站大佬刘丹冰Aceld 的8小时转职Golang工程师 视频学习笔记,如有侵权,联系我速删
大佬的传送门:https://www.bilibili.com/video/BV1gf4y1r79E/?spm_id_from=333.999.0.0&vd_source=95a9bb0b0759f8d391968411c1a6f007
在b站发现了大佬的宝藏视频,在此记录下学习过程。我在这里着重学习记录的是go语言的特性,也就是go与其他后端语言有区别的地方。并且在文章的末尾附上我使用go语言开发的即时通信系统的github地址。
一、目录 1.初识go语言
2.变量与常量
3.函数的返回值与defer
4.golang中的数组
5.golang中的map
6.golang面向对象基础
7.golang中的反射
8.goroutine和channel
二、初识go语言 1 2 3 4 5 6 7 8 9 10 11 package main import ( "fmt" "time" ) func main () { fmt.Println("Hello,go!!!" ) time.Sleep(2 * time.Second) }
运行结果:
知识点:
1.程序的第一行是包名,使用package 关键字
2.使用import 关键字进行导包
3.go语言中函数声明行末必须是左花括号
三、变量与常量 1.声明变量的四种方式 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 package mainimport "fmt" func main () { var a int fmt.Println("a =" , a) var b string = "字符串" fmt.Println("b =" , b) var c = 100.00 fmt.Printf("c is %T\n" , c) d := 60 fmt.Printf("the type of c is %T\n" , d) }
运行结果:
知识点:
(1) 我们通常使用fmt包中的Println函数来输出一行内容,相当于python中的print函数,其中的逗号表示使用空格连接,会自动在末尾加上换行符。
(2) fmt包中的Printf函数为格式化输出,不会自动加换行符,需要手动添加。其中的格式化参数:
%T
打印变量的类型.%v
以默认的方式打印变量的值.例如在结构体中{jack {12345 6789}}%+v
带字段名称.例如在结构体中{name:jack phone:{mobile:12345 office:6789}%t
打印true或false…… (3) 我们通常在函数体内使用冒等(:=)的写法来初始化变量,并给它赋值
2.声明多个变量 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 package mainimport "fmt" func main () { var m, n int = 100 , 200 var j, k = 300 , "abc" c, v := 1.5 , true fmt.Println(m, n, j, k, c, v) var ( z bool = true x int = 1 ) fmt.Println(z, x) }
运行结果:
3.const与iota 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 package mainimport "fmt" const ( MONDAY = iota +1 TUESDAY WEDNESDAY ) const ( a,b = iota +1 ,iota +2 c,d e,f g,h = iota *2 , iota *3 m,n ) func main () { const length int = 10 fmt.Println(MONDAY,TUESDAY,WEDNESDAY) fmt.Println(a,b,c,d,e,f,g,h,m,n) }
运行结果:
知识点:
(1) 常量的定义格式和变量的声明语法类似
(2) iota是常量计数器,在第一行为0,之后每新增一行常量声明将使iota计数一次
四、函数的返回值与defer 1.函数多返回值的几种写法 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 package mainimport "fmt" func foo1 (a string , b string ) int { c := 11111 fmt.Println("进入了函数1,并输入两个参数:" , a, b) return c } func foo2 (a string , b string ) (int , string ) { c := 2222222 d := "222222" fmt.Println("进入了函数2,并输入两个参数:" , a, b) return c, d } func foo3 (a string , b string ) (r1 int , r2 string ) { fmt.Println("进入了函数3,并输入两个参数:" , a, b) r1 = 333 r2 = "333333333" return } func foo4 (a string , b string ) (r1, r2 int ) { fmt.Println("进入了函数4,并输入两个参数:" , a, b) r1 = 444 r2 = 444444 return } func main () { fmt.Println(foo1("1" , "1" )) ret1, ret2 := foo2("2" , "2" ) fmt.Println(ret1, ret2) ret3, ret4 := foo3("3" , "3" ) fmt.Println(ret3, ret4) ret5, ret6 := foo4("4" , "4" ) fmt.Println(ret5, ret6) }
运行结果:
知识点:
(1) 函数声明语法: func 函数名(形参 形参数据类型,……)(返回值参数 返回值类型,…){
(2) 大多数 a int,b int,c int
这种形式都可以简写为a, b, c int
(3) 函数名首字母小写表示只能在当前文件内调用,像我们常用的fmt.Println()首字母大写,可以在其他文件内调用
2.defer语句调用流程 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 package mainimport "fmt" func deferAndReturn () int { defer fmt.Println("func: 调用了defer" ) return returnFunc() } func returnFunc () int { fmt.Println("func: 调用了return" ) return 1 } func main () { defer fmt.Println("main: main end1" ) defer fmt.Println("main: main end2" ) fmt.Println("main: main go 1" ) fmt.Println("main: main go 2" ) deferAndReturn() }
运行结果:
知识点:
(1) 使用defer后会将当前语句入栈 ,在当前函数结束之前依次出栈
(2) 注意defer是在return之后执行的
五、golang中的数组 1.静态数组 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 package mainimport "fmt" func printArray (myArray [10]int ) { for index,value := range myArray{ fmt.Println(index,value) } } func main () { var myarray1 [10 ]int myarray2 := [10 ]int {1 ,2 ,3 ,4 } for i:=0 ; i<len (myarray1); i++ { fmt.Println(myarray1[i]) } printArray(myarray2) }
运行结果:
知识点:
(1) 声明数组后,其中元素默认值为0
(2) 函数的形参和传递实参的长度一定要对应
2.动态数组 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 package mainimport "fmt" func printArray (myArray []int ) { for _, value := range myArray { fmt.Println(value) } } func main () { slice1 := []int {1 , 2 , 3 } printArray(slice1) fmt.Printf("len=%d,slice=%v\n" , len (slice1), slice1) var slice2 []int slice2 = make ([]int , 3 ) }
运行结果:
3.动态数组的容量 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 package mainimport "fmt" func main () { var numbers = make ([]int , 3 , 5 ) fmt.Printf("len=%d,cap=%d,slice=%v\n" , len (numbers), cap (numbers), numbers) numbers = append (numbers, 1 ) fmt.Printf("len=%d,cap=%d,slice=%v\n" , len (numbers), cap (numbers), numbers) numbers = append (numbers, 2 ) numbers = append (numbers, 3 ) fmt.Printf("len=%d,cap=%d,slice=%v\n" , len (numbers), cap (numbers), numbers) }
运行结果:
知识点:
(1) 使用make为数组开辟空间时,可以指定数组的cap,即数组的最大容量
(2) 当向slice添加的元素超过cap时,会自动扩充一个原始cap的大小
六、golang中的map 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 package mainimport "fmt" func main () { var myMap1 map [string ]string myMap1 = make (map [string ]string , 10 ) myMap1["one" ] = "java" myMap1["two" ] = "c++" fmt.Println(myMap1) myMap2 := map [string ]string { "a" : "aa" , "b" : "bb" , } fmt.Println(myMap2) }
运行结果:
七、golang面向对象基础 1.struct的使用 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 package mainimport "fmt" type myint int type Book struct { title string auth string } func printBook (book Book) { fmt.Printf("%v\n" , book) } func changeBook (book *Book) { book.title = "已经被改变了" } func main () { var book1 Book book1.title = "Golang" book1.auth = "lisi" fmt.Printf("%v\n" , book1) changeBook(&book1) fmt.Printf("%v\n" , book1) }
运行结果:
2.创建一个对象 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 package mainimport "fmt" type Hero struct { Name string Ad int Level int } func (this *Hero) Show() { fmt.Println("Name =" , this.Name) fmt.Println("Ad =" , this.Ad) fmt.Println("Level =" , this.Level) } func (this *Hero) GetName() string { return this.Name } func (this *Hero) SetName(newName string ) { this.Name = newName } func main () { hero := Hero{Name: "zhangsan" , Ad: 100 , Level: 2 } fmt.Println(hero.GetName()) hero.SetName("已经改变" ) hero.Show() }
运行结果:
知识点:
(1) 注意成员方法的写法,要在func和函数名之间加上**(变量名 *类名)**,变量名通常用this
3.类的继承 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 package mainimport "fmt" type Human struct { name string sex string } func (this *Human) Eat() { fmt.Println("开饭了" ) } func (this *Human) Walk() { fmt.Println("走起来了" ) } type SuperMan struct { Human level int } func (this *SuperMan) Eat() { fmt.Println("超人开饭了" ) } func (this *SuperMan) Fly() { fmt.Println("超人飞起来了" ) } func main () { human := Human{"zhangsan" , "female" } human.Eat() var s SuperMan s.name = "lisi" s.sex = "male" s.level = 99 s.Walk() s.Eat() s.Fly() }
运行结果:
4.创建一个接口 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 package mainimport "fmt" type AnimalIF interface { Sleep() GetColor() string GetType() string } type Cat struct { color string } func (this *Cat) Sleep(){ fmt.Println("猫在睡觉" ) } func (this *Cat) GetColor() string { return this.color } func (this *Cat) GetType() string { return "cat" } type Dog struct { color string } func (this *Dog) Sleep(){ fmt.Println("狗在睡觉" ) } func (this *Dog) GetColor() string { return this.color } func (this *Dog) GetType() string { return "dog" } func showAnimal (animal AnimalIF) { animal.Sleep() fmt.Println("color = " ,animal.GetColor()) fmt.Println("type = " ,animal.GetType()) } func main () { cat := Cat{"Green" } dog := Dog{"Yellow" } showAnimal(&cat) showAnimal(&dog) }
运行结果:
知识点:
(1) 接口使用interface关键字
(2) 当一个类实现了接口的所有方法后,就相当于是实现了这个接口
5.interface{}万能数据类型 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 package mainimport "fmt" func myFunc (arg interface {}) { fmt.Println(arg) value, ok := arg.(string ) if !ok { fmt.Println("arg is not string" ) } else { fmt.Println("arg is string,value =" , value) fmt.Printf("arg type is %T\n" , value) } } type Book struct { auth string } func main () { book := Book{"Golang" } myFunc(book) myFunc("1234" ) }
运行结果:
知识点:
(1) 感觉interface{}类似于java中的Object
八、golang中的反射 1.变量的内置pair结构 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 package mainimport "fmt" func main () { var a string a = "abcd" var allType interface {} allType = a str, _ := allType.(string ) fmt.Println(str) }
运行结果:
知识点:
(1) 每个变量都内置一个键值对(type, value)
(2) 变量不管给谁赋值,变量的pair都不会改变(pair连续传递 )
一些例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 package mainimport ( "fmt" "io" "os" ) func main () { tmp, err := os.OpenFile("D:\\Temp\\tmp.txt" , os.O_RDWR, 0 ) if err != nil { fmt.Println("error" , err) return } var r io.Reader r = tmp var w io.Writer w = r.(io.Writer) w.Write([]byte ("Hello world!!\n" )) }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 package mainimport "fmt" type Reader interface { ReadBook() } type Writer interface { WriteBook() } type Book struct {}func (this *Book) ReadBook() { fmt.Println("Read..." ) } func (this *Book) WriteBook() { fmt.Println("Write..." ) } func main () { b := &Book{} var r Reader r = b r.ReadBook() var w Writer w = r.(Writer) w.WriteBook() }
2.使用reflect获取变量的value和type 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 package mainimport ( "fmt" "reflect" ) func reflectNum (arg interface {}) { fmt.Println("type: " , reflect.TypeOf(arg)) fmt.Println("value: " , reflect.ValueOf(arg)) } func main () { var num float64 = 1.2345 reflectNum(num) }
运行结果:
3.使用reflect获取类对象的字段和方法 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 package mainimport ( "fmt" "reflect" ) type User struct { Id int Name string Age int } func (this User) Call() { fmt.Println("user is called" ) fmt.Printf("%v\n" , this) } func main () { user := User{1 , "zhangsan" , 8 } DoFileAndMethod(user) } func DoFileAndMethod (input interface {}) { inputType := reflect.TypeOf(input) fmt.Println("inputType: " , inputType.Name()) inputValue := reflect.ValueOf(input) fmt.Println("inputValue: " , inputValue) for i := 0 ; i < inputType.NumField(); i++ { field := inputType.Field(i) value := inputValue.Field(i).Interface() fmt.Printf("%s: %v = %v\n" , field.Name, field.Type, value) } for i := 0 ; i < inputType.NumMethod(); i++ { m := inputType.Method(i) fmt.Printf("%s: %v\n" , m.Name, m.Type) } }
运行结果:
九、goroutine和channel 1.创建一个goroutine 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 package mainimport ( "fmt" "time" ) func newTask () { i := 0 for { i++ fmt.Printf("new goroutine : i = %d\n" , i) time.Sleep(1 * time.Second) } } func main () { go newTask() i := 0 for { i++ fmt.Printf("main goroutine : i = %d\n" , i) time.Sleep(1 * time.Second) } }
运行结果:
知识点:
(1) 使用go 关键字可以开启一个go程
(2) 可以使用go加匿名函数来创建一个go程,详情见下面的代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 package mainimport ( "fmt" "time" ) func main () { go func (a int , b int ) bool { fmt.Println("a =" ,a,"b = " ,b) return true }(10 ,20 ) for { time.Sleep(1 * time.Second) } }
注意:使用go+func创建go程后要在最后使用括号调用函数
2.创建一个channel 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 package mainimport "fmt" func main () { c := make (chan int ) go func () { defer fmt.Println("go1结束" ) fmt.Println("go1正在运行...." ) c <- 666 }() num := <-c fmt.Println("num = " , num) fmt.Println("main gorountine结束" ) }
运行结果:
知识点:
(1) 使用**make(chan int)**声明并初始化一个int类型的channel
(2) 在channel中读数据: num := <-c ,写数据:c <- 666
3.带缓冲的channel 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 package mainimport ( "fmt" "time" ) func main () { c := make (chan int , 3 ) fmt.Println("len(c) = " , len (c), "cap(c)" , cap (c)) go func () { defer fmt.Println("子go程结束" ) for i := 0 ; i < 5 ; i++ { c <- i fmt.Println("子go程正在运行 len(c) = " , len (c), "cap(c)" , cap (c)) } }() time.Sleep(2 * time.Second) for i := 0 ; i < 3 ; i++ { num := <-c fmt.Println("num =" , num) } fmt.Println("main结束" ) }
运行结果:
知识点:
(1) 当子go程向channel写了3个元素之后,由于主go程的Sleep,并没有人读channel,导致子go程阻塞。随后,主go程睡醒,读取三个元素然后结束,就导致了子go程的defer语句没有输出
(2) 当channel满了之后,再想写入就会阻塞当前go程
4.使用close关闭channel 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 package mainimport ( "fmt" ) func main () { c := make (chan int ) go func () { for i := 0 ; i < 5 ; i++ { c <- i } close (c) }() for { if data, ok := <-c; ok { fmt.Println(data) } else { break } } fmt.Println("main结束" ) }
运行结果:
知识点:
(1) channel关闭就不可以写了,但是可以接着读
5.使用range来不断迭代操作channel 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 package mainimport ( "fmt" ) func main () { c := make (chan int ) go func () { for i := 0 ; i < 5 ; i++ { c <- i } close (c) }() for data := range c { fmt.Println(data) } fmt.Println("main结束" ) }
6.使用select 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 package mainimport ( "fmt" ) func fabonacii (c, quit chan int ) { x, y := 1 , 1 for { select { case c <- x: t := y y, x = x+y, t case <-quit: fmt.Println("quit" ) return } } } func main () { c := make (chan int ) quit := make (chan int ) go func () { for i := 0 ; i < 6 ; i++ { fmt.Println(<-c) } quit <- 0 }() fabonacii(c, quit) }
运行结果:
十、即时通信系统 跟着老师的课程,使用go语言写了一个即时通信系统的小例子,放在了我的github上
项目地址:https://github.com/feiweiliang/-golang-
实现的功能:
更改用户名 公聊模式 私聊模式 查看当前在线用户 超时强踢 项目截图:
未来打算学完gin之后,改成web界面版的即时通信系统