Go语言学习笔记

优点:

  1. 继承C,保留编译执行以及弱化的指针
  2. 引入包概念,一个文件属于一个包
  3. 垃圾回收机制,内存自动回收
  4. 天然并发,语言层面支持,goroutine,轻量级线程,可实现大并发处理,高校利用多核,基于CPS
  5. 吸收管道通信机制channel,不是用共享内存
  6. 函数返回多个值
  7. 切片slice,集合,动态数组,延时执行defer

安装:

  1. 下载:https://golang.org/dl/
  2. 点击pkg安装
  3. export PATH=$PATH:/usr/local/go/bin

单独运行:

go run main.go

编译:

go build main.go

./main

区分大小写

引入未使用的变量和包,会编译不过

gofmt -w main.go

官方document:https://golang.org/doc/

官方手册:Go 语言之旅

web端的运行版:https://play.golang.org/

go语言中文网:首页 - Go语言中文网 - Golang中文社区

中文库:Go语言标准库文档中文版 | Go语言中文网 | Golang中文社区 | Golang中国

使用:包名.函数名

变量:

//第一种
//变量 变量名 变量类型
var i int
//第二种,类型推导
var i = 10.1
//第三种,用:省略var
i := 102.3

//一次赋值多个变量
//第一种
var n1,n2,n3 int
//第二种
//按顺序对应
var n1,n2,n3 = 1,2,3 
//第三种
n1,n2,n3 := 1,2,3 
//第四种
var(
    n1 = 1
    n2 = 2
    n3 = 3
)

在函数外声明的就是全局变量

数据类型

bool

string

int  int8  int16  int32  int64
uint uint8 uint16 uint32 uint64 uintptr

byte // uint8 的别名

rune // int32 的别名
    // 表示一个 Unicode 码点

float32 float64

complex64 complex128

查看变量字节大小和数据类型

fmt.Printf("n1的字节长度 %d",unsafe.Sizeof(n1))

fmt.Printf("n1的类型 %T",n1)

浮点数可能有精度损失

默认是float64

 golang没有专门的字符类型,没有char,一般用byte,

格式化输出:

var c1 byte = 'a'

fmt.Printf("n1的类型 %c",c1)

大于byte的话,那就要用int或者更大来存

传统字符串由字符组成,golang的字符串是由字节组成

go语言使用的字符使用utf-8:比如"国"在go中存进去的是22269

utf-8转换:ip地址查询 ip查询 查ip 公网ip地址归属地查询 网站ip查询 同ip网站查询 iP反查域名 iP查域名 同ip域名

 bool只能取true或者false

string字符串不可更改具体字符,反引号``原样输出

 类型转换需要显示转换,原来的类型不会变化,只是将转换后的类型赋值给左边

 基本数据类型转string

//整形转string
//str = fmt.Sprintf("%参数",表达式)
var str string
str = fmt.Sprintf("%d",i)
fmt.Println("str: ",str)

//整形转string
str = strconv.Itoa(123)
fmt.Println("str = ",str)

//整形转string
//使用strconv函数,需要引入strconv包
//第一个参数要求int64,第二个参数是转换成几进制
//输出结果str:  1100100
str = strconv.FormatInt(int64(i),2)
fmt.Println("str: ", str)

//浮点转字符串
//第一个参数要求int64
//第二个参数是格式,详细看函数手册。
//第三个参数是控制精度
//第四个参数是表示float64
str = strconv.FormatFloat(float64(12.3),'f',10,64)
fmt.Println("str: ", str)

//布尔类型转字符串
str = strconv.FormatBool(true)
fmt.Println("str: ", str)

string转基本数据类型

  • string转布尔类型
  • string转整形
  • string转浮点
//string转布尔类型
var b bool
str = "true"
//ParseBool返回两个值,但是用_忽略掉第二个返回值
b,_ = strconv.ParseBool(str)
fmt.Printf("b类型:%T ,值:%v \n", b,b)

//string转整形
//结果用n1来接,结果是int64
var n1 int64
str = "1239400"
//第一参数是字符串
//第二个是转换的进制
//第三个是转换的数值能不能比int64大,大的话溢出,报错
n1,_ = strconv.ParseInt(str,10,64)
fmt.Printf("n1类型:%T ,值:%v ", n1,n1)

//string转浮点
var f1 float64
str = "1233.99209"
//第一个参数是字符串,第二个参数是期望float范围,超过则返回错误
f1,_ = strconv.ParseFloat(str,64)
fmt.Printf("f1类型:%T ,值:%v \n", f1,f1)
//返回错误类型
var f1 float64
str = "12312313213231231231231231231231231312313131313123122222222222222222222222222222222222222222222222222222222222222222222223.99209"
var e1 error
//第一个参数是字符串,第二个参数是期望float范围,超过则返回错误
f1,e1 = strconv.ParseFloat(str,32)
fmt.Printf("f1类型:%T ,值:%v \n", f1,f1)
fmt.Printf("e1类型:%T ,值:%v \n", e1,e1)

string转基本数据类型转换时,确保string类型能有效转换。转换不成功的话,会返回0或者false

指针

var i int =10

获取地址&i

指针变量存的是地址 var ptr *int = &i

解指针,取内容 *ptr 相当于i

指针格式:*数据类型

值类型,基本数据类型,int,float,bool,string,数组,结构体struct。变量直接存储值,在栈上分配

引用类型,指针,slice切片,map,管道chan,interface,变量存地址,在堆上分配,当没有任何变量引用这个地址时,该地址对应的数据空间成为一个垃圾,由GC回收

标识符命名规则 

25个系统保留关键字

36个预定义标志符:基本数据类型和系统内嵌函数

包名,保持package与目录文件夹名字保持一致

变量名、函数名、常量名,采用驼峰法,stuName,goodPrice,xxxYyyyZzzz

变量名、函数名、常量名首字母大写,可以被其他包调用,如果首字母小写,则只能在本包调用。首字母大写就是公有的,首字母小写就是私有的

运算符

算术运算符

如果运算的数是整数,那么结果也是整数

%的使用,a%b = a-a/b*b 

++和--只能独立使用

a = i++,错误

关系运算符

逻辑运算符

&&与 ||或 !非

运算符优先级

位运算符

 其他运算符

 不支持三元运算符

输入Scanln

    var s1 int
	fmt.Scanln(&s1)
	fmt.Println("s1", s1)

控制格式 

    var s2 string
	var s3 int
	fmt.Scanf("%s %d",&s2,&s3)
	fmt.Printf("%v 的数字是 %d\n", s2,s3)

进制及其转换

位运算

原码、补码、反码

正数的原码、补码、反码都一样

负数的反码=原码的符号位不变,其他位取反

负数的补码=反码+1

0的原码、补码、反码都一样

计算机运算时都是以补码方式运行

流程控制

顺序控制

分支控制

格式:

单分支语句

if 条件表达式 {

         执行代码块1

}else{

        执行代码块2

}

多分支语句

if 条件表达式 {

         执行代码块1

}else if{

        执行代码块2

}else{

        执行代码块3

}

switch语句,不需要加break

switch key {

case "a":

    fmt.Printf("aaaaa")

case "b":

    fmt.Printf("bbbbb")

default:

    fmt.Printf("default")

}

switch穿透fallthrought,默认只穿透一层

支持type switch

循环控制

 格式:

for

如果有中文字符,按照这种遍历方法会出错,乱码

因为这种遍历是一个一个字节遍历的,但是中文汉字utf8占3个字节

用切片解决

    for i:=1;i<=10;i++{
		fmt.Println("circulation")
	}
    str = "as阿斯顿"
	str2 := []rune(str)
	for i:=0;i<len(str2);i++{
		fmt.Printf("str2[%d] = %c\n",i,str2[i])
	}

for-range

按照字符遍历

    str = "asdfghjkl"
	for i,val :=range str{
		fmt.Printf("str[%d] = %c\n",i,val)
	}

go没有while和dowhile,要靠for实现

生成随机数

 包“time" "math/rand"

种子:rand.Seed(time.Now().Unix())

 产生随机数:rand.Intn(100)+1,产生1-100的数

break语句

可以用break跳出指定标签

continue

goto

 return

函数

语法

 举例:

func add(n1 float64,n2 float64)float64{

        return n1+n2

}

包的本质就是创建不同的文件夹,来存放程序文件

函数名要首字母大写才能被跨包使用,专业名叫该包名可导出

首先是添加项目路径,在vim ~/.zprofile(mac用zsh)中添加export GOPATH=~/Desktop/gostudy

那么包会在gostudy下面的src文件夹中寻找。因此我们引用时候只需引用go_code/project01/cal,不需要写src/go_code/proj.......前面的src

注意细节 

根据包名去区分函数,不是按照go文件区分

比如引用一个函数,先要import包,再包名.函数名

 取别名

在引用包名时写成,ccccc "go_code/project01/cal",那就直接用ccccc就行了,当然cal包也失效了

 同一个包名下,不能有同一个函数名

生成可执行文件,做成main包

go build -o mai go_code/project01/main

调用

函数入参,默认以值传递,也可以引用传递

func cite(n *int) {
	*n++
}
func main() {
	num := 10
	fmt.Printf("%d\n", num)
	cite(&num)
	fmt.Printf("%d\n", num)
}

Go不支持传统的函数重载,报重复定义,用另外形式实现函数重载

函数可以是一种数据类型,函数也可以作为参数入参,并且调用

 用type取别名,但是转换时候还是要显示转换 

 函数别名

 

 

 使用 _ 标识符,忽略返回值

函数支持可变参数

args... 要放在形参列表的最后

len(args) 获取参数长度

func sum(n1 float64, args... float64)float64{
	sum := n1
	for i:=0;i<len(args);i++{
		sum +=args[i]
	}
	return sum
}
func main(){
	fmt.Printf("%0.2f\n", sum(1.6,2.7,12,4.8))
}

init函数

在执行main函数前执行,完成初始化工作

变量定义->init函数->main函数

引入包时,即执行包的init函数 

 顺序如下:

匿名函数

没有名字的函数

求匿名函数结果

res2 := func (n1 int,n2 int)int{
	return n1+n2
}(10,20)
fmt.Printf("%d\n", res2)

重复利用匿名函数,将匿名函数赋给变量

f123 := func (n1 int,n2 int)int{
	return n1+n2
}
fmt.Printf("%d\n", f123(20,20))

全局匿名函数,将匿名函数赋给全局变量

闭包

闭包是一个函数与其相关的引用环境组合的一个整体

AddUpper()是一个函数,返回类型是func (int) int

func AddUpper() func (int) int {
	var n = 10
	return func (x int) int{
		n = n + x
		return n
	}
}
func main(){
	f := AddUpper()
	fmt.Println(f(1))
	fmt.Println(f(2))
	fmt.Println(f(3))
}

匿名函数引用到函数外的变量,因此这个匿名函数和n就形成了整体,构成闭包

闭包相当于是一个类,而函数是操作,变量是字段

因为变量n只初始化一次,当我们反复调用f函数时,因此每调用一次就进行累加。

闭包的关键,分析出返回的函数和使用哪些变量

func makeSuffix(suffix string) func (string) string{
	return func (name string) string{
		if !strings.HasSuffix(name,suffix) {
			return name+suffix
		}
		return name
	}
}
func main(){
	f2 := makeSuffix(".jpg")
	fmt.Println(f2("qewa"))
	fmt.Println(f2("asd.jpg"))
	fmt.Println("Hello, World!")
}

传统的话,每次都需要传入后缀名。但是用闭包,只需要传一次后缀名

函数中defer

目的:函数执行完后,可以及时释放资源

当执行到defer时,暂时不执行,会将defer后面的语句压入独立的栈(defer栈)

入栈时候,会将defer语句入栈,值也会拷贝一份压入

当函数执行完毕后,再从defer栈,按照先入后出的方式出栈,执行

 让给函数释放时自动释放,defer ,关闭文件夹,释放链接这些场合好用

函数参数传递方式:值传递和引用传递(传递地址,效率高)

变量作用域

当全局和局部变量重复时,编译器采用就近原则

注意全局变量赋值方式,第一种才能成功,第二种不行。错误原因是Name = "tom"赋值语句不能在函数外

字符串函数

  1. 统计字符串长度,按字节,len(str),这是内建函数,不需要引用包。注意,有中文的话一个中文占三个字节
  2. 字符串遍历,如果用for i:=0;i<len(str);i++的话,遇到中文,会乱码。因此采用切片方式,先转成切换方式,r :=[]rune(str),for i:=0;i<len(r);i++
  3. 字符串转整数,n,err = strconv.Atoi("123"),如果err为nil,则出错。可以用来验证是不是数字
  4. 整数转字符串,str = strconv.Itoa(12345)
  5. 字符串转[]byte切片,var bytes = []byte("hello")
  6. []byte切片转字符串,str = string([]byte{97,98,99})
  7. 十进制转2 8 16进制,返回对应字符串,str = strconv.FormatInt(123,2)
  8. 查找子串是否在指定的字符串中,返回bool类型,strings.Contains("seafood","foo")
  9. 统计字符串中有几个子串,返回int类型,strings.Count("seaoooofooood","o")
  10. 区分大小写的字符串比较,用==,if("abd"=="Abd")
  11. 不区分大小写的字符串比较,返回bool类型,strings.EqualFold("abd","Abd")
  12. 返回子串在字符串第一次出现的index值,如果没有返回-1,strings.Index("YYhd_das","das")
  13. 返回子串在字符串最后一次出现的index值,如果没有返回-1,strings.LastIndex("YYhd_das_das_das_das","das")
  14. 将指定子串替换成另一个子串,strings.Replace("YYhd_das_das_das_das","das","das替换",n),n表示希望替换几个,如果n=-1表示全部替换。
  15. 分割字符串成数组,返回数组类型,strings.Split("hellasd,asasd,asd",",")
  16. 字符串字母大小写转换,strings.ToLower("Go"),得到go。strings.ToUpper("Go"),得到GO
  17. 字符串两边空格去掉,strings.TrimSpace("  asd    ")
  18. 字符串左右两边指定的字符去掉,strings.Trim("   ! aasd! "," !"),表示空格和!都去掉
  19. 字符串左边指定的字符去掉,strings.TrimLeft("   ! aasd! "," !")
  20. 字符串右边指定的字符去掉,strings.TrimRight("   ! aasd! "," !")
  21. 判断字符串是否以指定的字符开头,返回bool类型,strings.HasPrefix("ftp://192.168.0.1","ftp")
  22. 判断字符串是否以指定的字符结尾,返回bool类型,strings.HasSuffix("aa123.jpg","jpg")

时间函数time

  1. 引入时间包,time
  2. 获取当前时间,返回的类型是time.Time,函数:time.Now()
  3. 调用类型time.Time的方法,time.Now().Year(),time.Now().Month(),time.Now().Day(),time.Now().Hour(),time.Now().Minute(),time.Now().Second(),如果数字显示,用int(time.Now().Month())
  4. 格式化日期时间Printf(),fmt.Printf("当前年月日: %d-%d-%d %d:%d:%d\n",time.Now().Year(),time.Now().Month(),time.Now().Day(),time.Now().Hour(),time.Now().Minute(),time.Now().Second())
  5. 格式化日期时间SPrintf(),返回string类型,time1 := fmt.SPrintf("当前年月日: %d-%d-%d %d:%d:%d\n",time.Now().Year(),time.Now().Month(),time.Now().Day(),time.Now().Hour(),time.Now().Minute(),time.Now().Second())
  6. fmt.Printf(time.Now().Format("2006-01-02 15:04:05")),数字固定
  7. time.Format
  8. 时间常量
  9. Unix时间戳,Unixnano时间戳,获取执行时间

内置函数

  1. len
  2. new,分配内存,分配值类型,比如int、float32、struct,返回的是指针
  3. make,分配内存,分配引用类型,比如chan、map、slice

错误处理机制

 recover()内置函数,可以捕获到异常

defer func(){

        err := recover()

        if err != nil {

                fmt.Println("err: ",err)

        }

}()

errors.New()

panic(err)

数组

存放多个同一类型数据,数组是值传递

var hens[6] float64

hens[0] = 3.0

hens[1] = 2.1

hens[2] = 5.8

数组地址可以通过数组名来获取,&hens

数组的第一个元素的地址,就是数组的首地址,&hens 和&hens[0]一样,取地址格式用%p

数组初始化方式:

  1. 定义时候初始化,var arr1 [3]int = [3]int{1,2,3}
  2. var arr2 = [3]int{1,2,3}
  3. ...是规定的,var arr3 = [...]int{1,2,3}

  4. var arr4 = [...]int{1:800,0:900,2:222},根据下标指定顺序

  5. 类型推导arr5 := [...]int{1:800,0:900,2:222}

遍历方式

  1. for遍历
  2. for-range,index或者value都可以用下划线_忽略

    for index,value := range arr5 {

    fmt.Println(index," : ",value)

    }

数组长度固定,不能动态变化

数组默认情况下是值传递,因此会进行值拷贝

可以用引用传递,函数test(arr *[3]int),函数内(*arr)[0]使用,调用时test(&arr)

生成随机数,时间种子rand.Seed(time.Now().UnixNano()),rand.Intn(100)

切片slice

切片是数组的一个引用,因此切片是引用类型,在传递时,遵循引用传递的机制。

长度可以变化,定义:var a [] int,底层是一个结构体

slice := arr[1:3],从数组下标1开始,到下标为3但是不包含,就是只有两个元素

len(slice),获取长度。cap(slice),获取容量

 使用切片的三种方式

  1. slice := arr[1:3],数组事先可见
  2.  var slice []int = make([]int,2,4),注意,cap大于等于len,通过make维护,程序员不可见
  3. var slice []int = []int {1,2,3}

切片遍历和数组遍历差不多 

切片注意事项

 slice := arr[0:len(arr)],取数组全部元素,和 slice:=arr[]是一样的

切片可以再切, slice1 := slice2[1,2]

append内置函数扩容,对切片动态追加,

        slice1 := arr1[ : ]

        fmt.Println(slice1)

        slice1 = append(slice1,1,2,3)

        fmt.Println(slice1)

        slice1 = append(slice1,slice1...)

切片拷贝,copy(slice1,arr)

函数参数在传递时候,是引用类型,会有 

 string和slice

string底层是一个byte数组,按字节处理,因此string也可以进行切片处理

string 也是一个slice

如果想要修改字符串,可以先将string转成[]byte或者[]rune,修改,重新转成string 

	str123 := "hello"
	fmt.Println(str123)
	arr123 := []byte(str123)
	arr123[0] = 'q'
	str123 = string(arr123)
	fmt.Println(str123)

但是[]byte不能修改含有中文的,因此最好用[]rune

    str456 := "热气球"
	fmt.Println(str456)
	arr456 := []rune(str456)
	arr456[0] = '冷'
	str456 = string(arr456)
	fmt.Println(str456)

例子:

二维数组

 初始化

var arr [2][3]int = [2][3]int {{1,2,3},{4,5,6}} 

二维数组遍历方式

for 或者 for range

map

key-value结构,相当于集合

var a map[int]string

数组声明后会分配内存,但是map声明后不会分配内存,初始化需要make分配内存后才能赋值和使用 

a = make(map[int]string,10),10的意思是可以容纳10对map的意思

golang中,map无序

map使用方式

方式二三不受空间大小限制,推荐方式二

	var a map[string]string
	a = make(map[string]string,10)

	b := make(map[string]string)
	
	var c map[string]string = map[string]string{
		"111" : "qwe",
		"222" : "erw",
	}

	d := map[string]string{
		"111" : "qwe",
		"222" : "erw",
	}

两层map

 map增删改查curd

增加和修改,a["111"] = "222",不存在111这个key的话那就增加,存在的话那就覆盖

删除,内置函数delete(map,"key"),存在的话那就删除,不存在就忽略。golang删除所有key,只能遍历或者再make一次

查询,两个返回值,存在的话findres1为true,不存在的话findres2为false

val1, findres1 := d["111"]

val2, findres2 := d["222"]

fmt.Println(val1,findres1)

fmt.Println(val2,findres2)

map遍历,不能使用for循环,需要使用for-range

map长度,len(map)

map切片,切片可以理解成动态数组,[]int,int的数量可以增加,[]map,map的数量可以增加

下面的代码不能动态增肌

	monsters := make([]map[string]string,1,4)
	if(monsters[0] == nil){
		monsters[0] = make(map[string]string,2)
		monsters[0]["name"] = "aaa"
		monsters[0]["sex"] = "male"
	}
	fmt.Println(monsters)

 切片使用append函数,动态增加

	monsters := make([]map[string]string,1)
	if(monsters[0] == nil){
		monsters[0] = make(map[string]string,2)
		monsters[0]["name"] = "aaa"
		monsters[0]["sex"] = "male"
	}
	fmt.Println(monsters)

	newmonster := make(map[string]string,2)
	newmonster["name"] = "ccc"
	newmonster["sex"] = "male"

	monsters = append(monsters,newmonster)

	fmt.Println(monsters)

map排序

//对map就行排序

//1、先将map的key放入到切片中

//2、对切片排序,sort

//3、遍历切片,然后按照key来输出map的值

	map1 := make(map[int]int,4)
	map1[3] = 10
	map1[2] = 80
	map1[8] = 8
	map1[4] = 90
	fmt.Println(map1)
	var keys []int
	for k,_ := range map1{
		keys = append(keys, k)
	}
	sort.Ints(keys)
	for _,k := range keys{
		fmt.Printf("map1[%v] = %v\n", k,map1[k])
	}

map注意事项

map是引用类型,引用传递,修改会直接修改原来的map

map会动态扩容

map的value通常使用结构体

	student := make(map[string]stu)
	stu1 := stu{"tom",18,"北京"}
	stu2 := stu{"mary",28,"上海"}
	student["no1"] = stu1
	student["no2"] = stu2
	fmt.Println(student)
	for k,v := range student {
		fmt.Printf("学生的编号:%v\t", k)
		fmt.Printf("学生的姓名:%v\t", v.name)
		fmt.Printf("学生的年龄:%v\t", v.age)
		fmt.Printf("学生的地址:%v\t", v.address)
		fmt.Printf("\n")
	}

go没有类的概念,用的是结构体实现面向对象编程OOP概念,但是没有方法重载、构造析构、隐藏this指针,有封装继承多态的特性,实现方式和传统OOP语言不一样。通过接口interface关联,耦合性低,面向接口编程

结构体变量在内存的布局

声明结构体

概念:field=结构体字段=属性

type 结构体名称 struct{

        field1 type

        field2 type

}

举例:

type stu struct{

name string

age int

address string

}

如果结构体名为Stu,就是首字母大写,结构体可以被其他包使用。

属性可以是指针,切片,map

切片使用前要make,make([]int,10)

map使用前要make,make(map[int]int)

结构体变量实例化

	//方式一
	var p1 Person
	p1.Name = "aaa"
	p1.Age = 1
	fmt.Println(p1)
	//方式二
	p2 := Person{}
	p2.Name = "bbb"
	p2.Age = 2
	fmt.Println(p2)
	//方式三
	var p3 *Person = new (Person)
	(*p3).Name = "ccc"
	(*p3).Age = 3
	fmt.Println(*p3)
	//go里面也可以是直接赋值,go设计者为了程序员使用方便,
	//底层会对指针优化,会加上取值运算
	p3.Name = "ccchhh"
	p3.Age = 31
	fmt.Println(*p3)
	//方式四
	//赋值一个地址
	var p4 *Person = &Person{}
	p4.Name = "ddd"
	p4.Age = 4
	fmt.Println(*p4)

结构体struct内存分配机制

结构体中的字段是连续分布的,

转换时候需要具有相同字段的名字、类型、数量

结构体进行type重新定义,相当于取别名,golang认为是新的数据类型,但是可以相互强转

通过tag实现大小写一致,序列化和反序列化,json.Marshal(monster)使用了反射

	type Monster struct{
		Name string `json:"name"`
		Age int `json:"age"`
		Skill string `json:"skill"`
	}
	monster := Monster{"麦",18,"qwer"}
	jsonstr, err := json.Marshal(monster)
	if err != nil {
		fmt.Println("json出错")
	}
	fmt.Println(string(jsonstr))

方法

数据类型绑定特有的方法

不能用其他类型调用

type Person struct{
	Name string
	Age int
}
func (p Person) test(){
	fmt.Println(p.Name)
}
func main() {
    p5 := Person{"aaa",18}
	p5.test()
}

方法内部调用的是传参,值拷贝,方法内修改不会改变对象变量和值

方法和函数的调用机制一样,但是方法调用时,变量本身也会作为一个参数传递到方法。如果变量是值类型,则值拷贝。如果变量是引用类型,则进行地址拷贝

方法的声明与定义

声明:

 func(person *Person)test()float64{

        return (*person).Age

        等价于return person.Age

}

调用:

(&p).test()等价于p.test()

方法和函数的区别

看的是函数绑定的是引用类型还是值类型,与调用方式无关 

工厂模式

由于golang没有构造函数,往往使用工厂模式解决

需求:结构体或者变量名的首字母是小写,但是我们又希望在外部可以引用。

工厂模式实现跨包创建结构体实例。工厂模式返回的是一个zhi ehn e

定义

调用:

返回的是一个结构体指针 

如果字段名首字母是小写,那就写一个结构体方法,绑定,返回首字母是小写的字段名

抽象,模版

封装,

继承。通过嵌入匿名结构体实现继承特性

继承语法

 代码举例:

 可以间接访问,也可以直接访问。就近原则访问变量或者方法

 嵌套匿名结构体时,可以在创建结构体变量时,直接指定各个匿名结构体字段的值

也可以是匿名指针

 
尽量不要多重继承

接口interface

实现Usb接口,就是指实现了Usb接口声明的所有方法

 定义和声明:

type Usb interface{
	Start()
	Stop()
}
type Phone struct{

}
func (phone Phone) Start(){
	fmt.Println("手机开始工作")
}
func (phone Phone) Stop(){
	fmt.Println("手机结束工作")
}
type Camera struct{

}
func (camera Camera) Start(){
	fmt.Println("相机开始工作")
}
func (camera Camera) Stop(){
	fmt.Println("相机结束工作")
}

type Computer struct{

}
//实现Usb接口,就是指实现了Usb接口声明的所有方法
func (computer Computer)Working(usb Usb){
	usb.Start()
	usb.Stop()
}

 调用:

func main(){
	//构建结构体变量
	computer := Computer{}
	phone := Phone{}
	camera := Camera{}
	//调用phone,实现多态
	computer.Working(phone)
	computer.Working(camera)
}

 golang中接口不能有变量,只能有方法声明,也不需要接口本身去实现这个方法。需要交给具体的自定义类型实现方法,而且是要自定义类型去实现所有方法

接口本身不能实例化,但是可以指向一个实现了该接口的自定义类型

自定义类型可以实现多个接口

接口之间也可以继承,比如A继承B、C,实现A的话也要实现B、C的接口

接口类型默认是一个指针(引用类型),如果没有对interface初始化就使用,那么会输出nil

空接口interface{}没有任何方法,所有类型都实现了空接口

绑定了指针类型的话,需要放入指针类型

实现一个学生结构体的排序

定义学生结构体,实现sort接口方法

type Student struct{
	Name string
	Age int
	Score float64
}

type StudentSlice []Student
func (ss StudentSlice) Len() int{
	return len(ss)
}
func (ss StudentSlice) Less(i, j int) bool{
	return ss[i].Age < ss[j].Age
}
func (ss StudentSlice) Swap(i, j int){
	ss[i] , ss[j] = ss[j] , ss[i]
}

调用

import(
	"fmt"
	"sort"
	"math/rand"
)
func main{
    var sss StudentSlice
	for i:=0;i<10;i++{
		ss := Student{
			Name : fmt.Sprintf("学生 %d",rand.Intn(100)),
			Age : rand.Intn(100),
			Score : rand.Float64(),
		}
		sss = append(sss,ss)
	}
	for _,v := range sss{
		fmt.Printf("学生的姓名:%v\t", v.Name)
		fmt.Printf("学生的年龄:%v\t", v.Age)
		fmt.Printf("学生的成绩:%v\t", v.Score)
		fmt.Printf("\n")
	}
	sort.Sort(sss)
	fmt.Println("-----------------------------")
	for _,v := range sss{
		fmt.Printf("学生的姓名:%v\t", v.Name)
		fmt.Printf("学生的年龄:%v\t", v.Age)
		fmt.Printf("学生的成绩:%v\t", v.Score)
		fmt.Printf("\n")
	}
}

不破坏继承的关系基础上,对结构体进行功能扩展。接口和继承是一种解藕的方式。

A结构体继承B结构体,那么A结构体就自动继承B结构体的字段和方法,并且可以直接使用

当A结构体需要扩展功能,同时不希望去破坏继承关系,则可以是实现某个接口即可。实现接口是对继承机制的补充

继承目的是解决代码复用性和可维护性

接口目的是设计,设计各种规范方法,让其它自定义类型实现

接口是对继承的一个补充。

接口更加灵活,继承是满足is-a的关系,接口是满足like-a的关系

接口体现多态poly的两种形式:多态参数和多态数组

 在多态数组中,有的结构体具有另外结构体所没有的方法,即有的结构体有特有的方法,我们要在接口调用处区分类型,防止结构体之间的方法混用。引出类型断言

以下问题:将point赋值给空接口,空接口赋值给point类型,会报错。需要写成b = a.(Point)

类型断言用于接口转具体类型时使用

带检查的类型断言,需要写成b,flag = a.(Point)

 

 也可以把if语句写在一起, 

 增加类型断言,可以区分结构体的接口实现

 

增加类型断言,判断传入参数的类型。x.(type)

文件操作

输入流、输出流

os包里面有一个结构体File,封装了所有文件相关操作,os.File

文件打开关闭

    file , err :=  os.Open("/Users/xiquanmai/Desktop/gostudy/src/go_code/project01/main/abc.txt")
	if err != nil {
		fmt.Println("打开文件失败,err = ",err)
	}
	fmt.Printf("file =%v ",file)
	defer file.Close()

 读取文件,带缓冲区读取,适合大文件

    reader := bufio.NewReader(file)
	for{
		str , err =reader.ReadString('\n')
		if err ==io.EOF{
			break
		}
		fmt.Print(str)
	}

 读取文件,一次读取,适合小文件。不需要显示打开和关闭文件,因为都在ReadFile里面实现了

	content , err123 := ioutil.ReadFile("/Users/xiquanmai/Desktop/gostudy/src/go_code/project01/main/abc.txt")
	if err123 != nil {
		fmt.Println("ioutil.ReadFile读取文件失败,err = ",err123)
	}
	fmt.Println(string(content))

写文件

OpenFile的第二个参数是文件打开模式,第三个参数是权限控制rwx421

const (
    // Exactly one of O_RDONLY, O_WRONLY, or O_RDWR must be specified.
    O_RDONLY int = syscall.O_RDONLY // open the file read-only.
    O_WRONLY int = syscall.O_WRONLY // open the file write-only.
    O_RDWR   int = syscall.O_RDWR   // open the file read-write.
    // The remaining values may be or'ed in to control behavior.
    O_APPEND int = syscall.O_APPEND // append data to the file when writing.
    O_CREATE int = syscall.O_CREAT  // create a new file if none exists.
    O_EXCL   int = syscall.O_EXCL   // used with O_CREATE, file must not exist.
    O_SYNC   int = syscall.O_SYNC   // open for synchronous I/O.
    O_TRUNC  int = syscall.O_TRUNC  // truncate regular writable file when opened.
)
const (
    // The single letters are the abbreviations
    // used by the String method's formatting.
    ModeDir        FileMode = 1 << (32 - 1 - iota) // d: is a directory
    ModeAppend                                     // a: append-only
    ModeExclusive                                  // l: exclusive use
    ModeTemporary                                  // T: temporary file; Plan 9 only
    ModeSymlink                                    // L: symbolic link
    ModeDevice                                     // D: device file
    ModeNamedPipe                                  // p: named pipe (FIFO)
    ModeSocket                                     // S: Unix domain socket
    ModeSetuid                                     // u: setuid
    ModeSetgid                                     // g: setgid
    ModeCharDevice                                 // c: Unix character device, when ModeDevice is set
    ModeSticky                                     // t: sticky
    ModeIrregular                                  // ?: non-regular file; nothing else is known about this file

    // Mask for the type bits. For regular files, none will be set.
    ModeType = ModeDir | ModeSymlink | ModeNamedPipe | ModeSocket | ModeDevice | ModeCharDevice | ModeIrregular

    ModePerm FileMode = 0777 // Unix permission bits
)

示例程序:注意要写Flush()函数

	filewrite := "/Users/xiquanmai/Desktop/gostudy/src/go_code/project01/main/abc.txt"
	filefd, errwrite := os.OpenFile(filewrite, os.O_WRONLY | os.O_CREATE,0666)
	if errwrite != nil {
		fmt.Println("打开文件失败,errwrite = ",errwrite)
		return
	}
	defer filefd.Close()
	strwrite := "hellowrold\n"
	writer := bufio.NewWriter(filefd)
	for i:=0;i<5;i++{
		writer.WriteString(strwrite)
	}
	//flush,缓冲区写入磁盘
	writer.Flush()

一次写入

ioutil.WriteFile()

文件是否存在os.Stat()

拷贝文件

io包里面的func Copy(dst Writer, src Reader) (written int64, err error)

可以拷贝大文件,因为源代码中先读一部分再写一部分

重写复制文件函数

func CopyFile(dstFileName string, srcFileName string) (written int64, err error){
	srcFile,err := os.Open(srcFileName)
	if(err != nil){
		fmt.Printf("open file err=%v\n", err)
	}
	defer srcFile.Close()
	reader := bufio.NewReader(srcFile)

	dstFile, err :=os.OpenFile(dstFileName,os.O_WRONLY | os.O_CREATE,0666)
	if(err != nil){
		fmt.Printf("open file err=%v\n", err)
		return
	}
	defer dstFile.Close()
	writer := bufio.NewWriter(dstFile)

	return io.Copy(writer,reader)
}

调用

    dstFileName := "./cpoy111.jpeg"
	srcFileName := "./pictest.jpeg"
	_,err111 := CopyFile(dstFileName,srcFileName)
	if err111 == nil{
		fmt.Println("复制图片成功")
	}else{
		fmt.Println("复制图片失败")
	}

为了兼容中文字符,可以将str转成 []rune

str = []rune(str)

命令行参数基本使用,os.Args,得到的是一个切片,获得所有命令行输入参数值,但是需要按照顺序,使用下标调用即可

fmt.Println(os.Args[1])

flag包,提供命令行参数flag.StringVar()、flag.IntVar()、flag.Parse()解析,顺序可以调换

json字符串,

web中b/s,tcp中c/s 的传输

json键值对,保存数据。js中一切皆对象

json访问以下网站JSON在线解析及格式化验证 - JSON.cnJson中文网致力于在中国推广Json,并提供相关的Json解析、验证、格式化、压缩、编辑器以及Json与XML相互转换等服务。https://www.json.cn/json/jsononline.html序列化:将key-value结构的数据类型(结构体、map、切片)序列化成json字符串操作

	stu123 := Student{
		Name : "amai",
		Age : 20,
		Score : 90.0,
	}
	datajson, err := json.Marshal(&stu123)
	if err != nil{
		fmt.Printf("序列化错误 err = %v\n",err)
	}
	fmt.Printf("序列化结果%v\n",string(datajson))
	
	var ajson map[string]interface{}
	ajson = make(map[string]interface{})
	ajson["name"] = "asd"
	ajson["address"] = 123
	ares, err := json.Marshal(ajson)
	if err != nil{
		fmt.Printf("序列化错误 err = %v\n",err)
	}
	fmt.Printf("序列化结果%v\n",string(ares))

	//切片map
	var aslice []map[string]interface{}
	var m1 map[string]interface{}
	m1 = make(map[string]interface{})
	m1["name"] = "iuujs"
	m1["address"] = 9882
	aslice = append(aslice,m1)
	var m2 map[string]interface{}
	m2 = make(map[string]interface{})
	m2["name"] = "iid"
	m2["age"] = 1000
	aslice = append(aslice,m2)
	sliceres, err := json.Marshal(aslice)
	if err != nil{
		fmt.Printf("序列化错误 err = %v\n",err)
	}
	fmt.Printf("序列化结果%v\n",string(sliceres))

反序列化:将json字符串反序列化成key-value结构的数据类型(结构体、map、切片)操作

确保反序列化后的数据类型和原来的序列化前的数据类型一致

如果json字符串是通过程序获取的,则不需要对"转义处理。

json.Unmarshal([]byte(str666), &Stu555)

	//反序列化
	str666 := "{\"Name\":\"amai\",\"Age\":20,\"Score\":90}"
	var Stu555 Student
	err1234 := json.Unmarshal([]byte(str666), &Stu555)
	if err1234 != nil {
		fmt.Printf("反序列化错误 err1234 = %v\n",err1234)
	}
	fmt.Printf("Stu555: %+v\n", Stu555)

单元测试

测试框架testing,test命令实现单元测试和性能测试

go test -v启动test框架

cal_test.go文件,注意cal下划线后面一定要带test,函数TestAddUpper1(t *testing.T)的Test后要不能接a-z字母

package main
import (
	_ "fmt"
	"testing"
)
//测试用例
func TestAddUpper1(t *testing.T) {
	res := AddUpper1(10)
	if res != 55 {
		t.Fatalf("AddUpper1(10)错误,期望值:%v,实际值:%v\n",55,res)
	}
	// tests := []struct {
	// 	name string
	// }{
	// 	// TODO: Add test cases.
	// 	
	// }
	// for _, tt := range tests {
	// 	t.Run(tt.name, func(t *testing.T) {
	// 		AddUpper1()
	// 	})
	// }
}

 cal.go文件,插入需要测试的函数即可

package main
//被测函数
func AddUpper1(n int)int{
	res := 0
	for i := 0;i <= n;i++{
		res += i
	}
	return res
}

goroutine协程和channel管道

进程和线程关系

并行(空间上,多线程在多核上)并发 (时间上,多线程在单核上)

  • 单核 CPU 多任务:并发(不必等上一个任务完成才开始下一个任务)、串行(只有一个实际执行任务的 CPU 核)
  • 多线程:并发、串行(所有线程都在同一个核上执行);并发、并行(不同线程在不同的核上执行)

协程(逻辑态)和主线程(物理级)

go协程特点:

  • 有独立的栈空间,数据独立
  • 共享程序堆空间,可以用线程的内容
  • 调度由用户控制,进程线程由系统调用
  • 协程是轻量级的线程

如果主线程退出了,则即使协程没有执行完毕,协程也会退出

MPG模式,M是操作系统的主线程,P是协程执行需要的上下文,G协程

go设置运行cpu数量,go1.8之前需要设置,1.8之后不需要

runtime包

	cpunum := runtime.NumCPU()
	fmt.Println("cpunum: ",cpunum)
	//设置cpu数目
	//runtime.GOMAXPROCS(cpunum)

资源竞争问题

goroutine协程使用时会出现并发/并行问题,因此协程间需要通讯。

查看程序是否有竞争关系,编译时增加一个参数-race,

比如 go build -race main.go

解决方法:

  • 全局变量加锁,sync包,Mutex是一个结构体,定义var lock sync.Mutex

lock.Lock(),lock.Unlock()

  • 或者channel,本质是一个队列,先进先出FIFO,线程安全,协程并发并行时不需要加锁。管道channel是有类型的,只能放固定的数据类型

管道channel

声明:var 变量名 chan 数据类型

是引用类型,因此需要make

定义管道var intchan chan int

管道初始化intchan = make(chan int,3)

往管道注入数据intchan <- 20 

长度len(intchan),容量cap(intchan)

从管道读取数据 num2 := <-intchan

任意数据类型

注意空接口需要类型断言

channel关闭,关闭后不能写,但是还能读

close(channel)

遍历,使用for-range方式遍历,遍历前需要关闭管道

    //遍历管道
	close(intchan)
	for v := range intchan{
		fmt.Println("v=: ",v)
	}

读管道数据v,ok<-chan,如果读不到数据,ok为false 

 读写操作

func main(){
	var intchan1 chan int
	var exitchan chan bool
	intchan1 = make(chan int, 50)
	exitchan = make(chan bool,1)
	go WriteData(intchan1)
	go ReadData(intchan1,exitchan)
	for{
		_, ok := <-exitchan
		if !ok {
			break
		}
	}
}
func WriteData(intchan chan int){
	for i := 1; i < 10; i++ {
		intchan <- i
		fmt.Println("WriteData写入数据=: ",i)
		time.Sleep(time.Second/10.0)
	}
	close(intchan)
}
func ReadData(intchan chan int, exitchan chan bool){
	for{
		v, ok := <-intchan
		if !ok {
			break
		}
		fmt.Println("ReadData读到数据=: ",v)
	}
	exitchan <- true
	close(exitchan)
}

管道阻塞机制

系统会分析有没有读进程,没有的话就会阻塞,报错。读写的速度快慢没所谓。

当前时间time.Now().Unix()

管道可以声明为只读或者只写。有效防止误操作

默认管道是双向的,var chan1 chan int

声明为只写,var chan1 chan<- int

声明为只读,var chan1 <-chan int

传统的方法在遍历管道时,如果不关闭管道,会阻塞而导致deadlock

但是不好确定什么时候关闭管道。使用select可以解决管道阻塞问题

反射

应用:

  • 结构体标签tag的应用
  • 编写函数的适配器,桥连接

反射可以在运行时动态获取变量的各种信息,比如变量的类型type,类别kind

通过反射,可以修改变 量的值,引用传递,调用关联的方法

引入reflect包 ,

reflect.TypeOf(变量名),获取变量类型,返回reflect.Type类型,

reflect.ValueOf(变量名),获取变量值,返回reflect.Value类型,reflect.Value是一个结构体类型

变量、空接口类型interface{}和reflec.Value三个相互转换

var Student Stu

直接把Student赋值给函数func

将空接口类型interface{}转成reflec.Value,rVal := reflec.ValueOf(b)

将reflec.Value转成空接口类型interface{},iVal := rVal.Interface()

将空接口类型interface{}转成原来的变量类型,使用类型断言,v := iVal.(Stu)

 反射的本质是运行时确定

func main(){
	var num998 int = 100
	testreflect(num998)
}
func testreflect(b interface{}){
	rType := reflect.TypeOf(b)
	fmt.Println("rType: ",rType)
	rVal := reflect.ValueOf(b)
	n1 := 20 + rVal.Int()
	fmt.Println("n1: ",n1)
	iV := rVal.Interface()
	num3 := iV.(int)
	fmt.Println("num3: ",num3)
}

reflect.kind本质是一个常量

常量在定义时必须初始化,const tax int = 100,常量只修饰bool、int、string这几个基本数据类型

Type和Kind有可能相同也有可能不同

 

如果传入的是指针类型,需要增加rVal.Elem().SetInt(20),可以实现函数改变即改变外面的值

反射最佳实践

使用反射来遍历结构体的字段,调用结构体的方法,并获取结构体标签的值

 遍历结构体的字段

Type中Field(i int) StructField,返回的是StructField

type StructField struct {
    // Name is the field name.
    Name string

    // PkgPath is the package path that qualifies a lower case (unexported)
    // field name. It is empty for upper case (exported) field names.
    // See https://golang.org/ref/spec#Uniqueness_of_identifiers
    PkgPath string

    Type      Type      // field type
    Tag       StructTag // field tag string
    Offset    uintptr   // offset within struct, in bytes
    Index     []int     // index sequence for Type.FieldByIndex
    Anonymous bool      // is an embedded field
}

 

调用结构体方法

Method按照函数名的acsii大小排序,call(切片)  ,返回的结果是切片

如果传的是地址,那就只需要增加.Elem()获取指针即可

调用时传递进去的是地址:testreflect(& stu)

下面是修改字段的代码

 

适配器函数,有点像重载函数

 

网络编程

tcp socket编程,底层基于tcp/ip

b/s结构的http编程,基于http协议,底层基于 tcp socket实现,属于go web开发

 服务端server

  1. 监听端口,不如8888
  2. 接收客户端tcp链接,建立客户端和服务端的链接
  3. 创建goroutine,处理链接的请求

客户端client

  1. 建立与服务端的链接
  2. 发送请求数据,接收服务端返回的结果数据
  3. 关闭链接

net包,用于网络连接的包

client客户端

package main
import (
	"fmt"
	"net"
	"bufio"
	"os"
	"strings"
)

func main(){
	conn,err1 := net.Dial("tcp","127.0.0.1:8888")
	if err1 != nil {
		fmt.Println("Dial err = ",err1)
		return
	}
	for{
		//从终端拿到信息
		reader := bufio.NewReader(os.Stdin)
		str, err2 := reader.ReadString('\n')
		if err2 != nil{
			fmt.Println("readString err = ",err2)
		}
		line := strings.Trim(str," \r\n")
		if line =="exit"{
			fmt.Println("客户端退出......")
			return
		}
		_,err3 := conn.Write([]byte(str))
		if err3 != nil {
			fmt.Println("conn.Write err = ",err3)
		}
	}
}

 server服务端

package main
import (
	"fmt"
	"net"
	"io"
)

func process(conn net.Conn){
	defer conn.Close()
	for{
		//创建新的切片
		buf := make([]byte,1024)
		n,err := conn.Read(buf)
		fmt.Println("等待连接")
		if err != io.EOF {
			fmt.Println("conn.Read err = ",err)
			return
		}
		//打印时候不用带ln
		fmt.Print(string(buf[:n]))
	}
}

func main(){
	fmt.Println("服务器开始监听。。。。。。")
	//tcp指的是网络协议是tcp
	//0.0.0.0:8888,本地监听,8888端口
	listen , err := net.Listen("tcp","0.0.0.0:8888")
	if err != nil {
		fmt.Println("listen err = ",err)
		return
	}
	fmt.Printf("listen = %v \n",listen)
	//延时关闭
	defer listen.Close()
	//循环等待连接
	for{
		conn, err := listen.Accept()
		if err != nil {
			fmt.Println("Accept() err = ",err)
		}else{ 
			fmt.Printf("Accept() 成功, conn = %v\n", conn)
		}
		//启动一个协程
		go process(conn)
	}
}

 

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值