外国网站建站,江苏住房和城乡建设厅官方网站6,网站开发费用构成,数字营销服务商1.面向对象
1.1.golang语言面向对象编程说明
Golang 也支持面向对象编程(OOP)#xff0c;但是和传统的面向对象编程有区别#xff0c;并不是纯粹的面向对象语言。所以我们说 Golang 支持面向对象编程特性是比较准确的。Golang 没有类(class)#xff0c;Go 语言的结构体(st…1.面向对象
1.1.golang语言面向对象编程说明
Golang 也支持面向对象编程(OOP)但是和传统的面向对象编程有区别并不是纯粹的面向对象语言。所以我们说 Golang 支持面向对象编程特性是比较准确的。Golang 没有类(class)Go 语言的结构体(struct)和其它编程语言的类(class)有同等的地位你可以理解 Golang 是基于 struct 来实现 OOP 特性的。Golang 面向对象编程非常简洁去掉了传统 OOP 语言的继承、方法重载、构造函数和析构函数、隐藏的 this 指针等等Golang仍然有面向对象编程的继承封装和多态的特性只是实现的方式和其它 OOP 语言不一样比如继承 : Golang 没有 extends 关键字继承是通过匿名字段来实现。Golang 面向对象(OOP)很优雅OOP 本身就是语言类型系统(type system)的一部分通过接口(interface)关联耦合性低也非常灵活。后面同学们会充分体会到这个特点。也就是说在 Golang 中面向接口编程是非常重要的特性。
1.2.结构体
将一类事物的特性提取出来(比如猫类) 形成一个新的数据类型 就是一个结构体 通过这个结构体我们可以创建多个变量(实例/对象)
1.2.1.结构体和结构体变量的区别
结构体是自定义的数据类型代表一类事物结构体变量(实例)是具体的实际的代表一个具体变量
1.2.2.声明结构体
type structName struct {field1 typefield2 type
}1.2.3.字段
从概念或叫法上看:结构体字段 属性 field (即授课中统一叫字段)
字段是结构体的一个组成部分一般是基本数据类型、数组也可是引用类型
注意
字段声明语法同变量示例:字段名 字段类型字段的类型可以为:基本类型、数组或引用类型在创建一个结构体变量后如果没有给字段赋值都对应一个零值(默认值)规则同前面讲的一样: 布尔类型是 false 数值是0字符串是 “”数组类型的默认值和它的元素类型相关比如 score [3]int 则为[0,0,0]指针slice和map 的零值都是 nil 即还没有分配空间。 不同结构体变量的字段是独立互不影响一个结构体变量字段的更改不影响另外一个,结构体 是值类型。
1.2.4.创建结构体变量和访问结构体字段
1.直接声明
var person Person2.{}
var person Person Person{}p2 : Person{mary, 20}
//p2.Name tom
//p2.Age 183.
var person *Person new(Person)var p3 *Person new(Person)
(*p3).Name smith
//p3.Name smith 也可以
(*p3).Age 30
//p3.Age 30 也可以4.{}
var Person *Person Person{}说明
第3种和第4种方式返回的是结构体指针
结构体指针访问字段的标准方式应该是:(*结构体指针).字段名比如(*person).Nametom
但 go 做了一个简化也支持结构体指针.字段名比如person.Nametom更加符合程序员使用的习惯go编译器底层 对 person.Name做了转化(*person).Name。
不能写成*p.Age因为.的运算符优先级比*高
1.2.5.结构体使用注意事项和细节
结构体的所有字段在内存中是连续的结构体是用户单独定义的类型和其它类型进行转换时需要有完全相同的字段(名字、个数和类型)结构体进行type 重新定义(相当于取别名)Golang认为是新的数据类型但是相互间可以强转struct 的每个字段上可以写上一个 tag该 tag 可以通过反射机制获取常见的使用场景就是序列化和反序列化
1.3.方法
Golang中的方法是作用在指定的数据类型上的(即:和指定的数据类型绑定)因此自定义类型都可以有方法而不仅仅是 struct
1.3.1.方法的定义
func (receiver type) methodName (参数列表) (返回值列表){方法体return 返回值
}参数列表:表示方法输入receiver type:表示这个方法和 type 这个类型进行绑定或者说该方法作用于 type 类型receiver type: type可以是结构体也可以其它的自定义类型receiver:就是 type 类型的一个变量(实例)比如 :Person 结构体 的一个变量(实例)返回值列表:表示返回的值可以多个方法主体:表示为了实现某一功能代码块return 语句不是必须的
1.3.2.方法的声明和调用
type A struct {Num int
}
func(a A) test() {fmt.Println(a.Num)
}对上面的语法的说明
func(a A) test() {} 表示 A 结构体有一方法方法名为 test(a A)体现test 方法是和 A 类型绑定的
说明
在通过一个变量去调用方法时其调用机制和函数一样不一样的地方时变量调用方法时该变量本身也会作为一个参数传递到方法(如果变量是值类型则进行值拷贝如果变量是引用类型则进行地址拷贝
1.3.3.方法的注意事项和细节
结构体类型是值类型在方法调用中遵守值类型的传递机制是值拷贝传递方式如程序员希望在方法中修改结构体变量的值可以通过结构体指针的方式来处理Golang 中的方法作用在指定的数据类型上的(即:和指定的数据类型绑定)因此自定义类型都可以有方法而不仅仅是 struct, 比如 int,foat32 等都可以有方法方法的访问范围控制的规则和函数一样方法名首字母小写只能在本包访问方法首字母大写可以在本包和其它包访问。如果一个类型实现了 String()这个方法那么 fmt.Println 默认会调用这个变量的 String()方法进行输出
1.3.4.方法和函数区别
调用方式不一样 函数的调用方式函数名(参数列表)方法的调用方式变量.方法名(实参列表) 对于普通函数接收者为值类型时不能将指针类型的数据直接传递反之亦然对于方法(如 struct 的方法)接收者为值类型时可以直接用指针类型的变量调用方法反过来同样也可以
总结
不管调用形式如何真正决定是值拷贝还是地址拷贝看这个方法是和哪个类型绑定
如果是和值类型比如(p Person)则是值拷贝 如果和指针类型比如是(p*Person) 则是地址拷贝
1.4.工厂模式
Golang 的结构体没有构造函数通常可以使用工厂模式来解决这个问题
如果结构体变量首字母小写引入后不能直接使用可以工厂模式解决
package modeltype student struct {Name stringscore float64
}func NewStudent(n string, s float64) *student {return student{Name: n,score: s,}
}
func (s *student) GetScore() float64 {return s.score
}
func main() {stu : model.NewStudent(xiaoming, 45)fmt.Println(*stu)fmt.Println(stu.GetScore())
}1.5.封装
封装(encapsulation)就是把抽象出的字段和对字段的操作封装在一起,数据被保护在内部,程序的其它包只有通过被授权的操作(方法),才能对字段进行操作
1.5.1封装的理解和好处
隐藏实现细节可以对数据进行验证保证安全合理
1.5.2如何体现封装
对结构体中的属性进行封装通过方法包 实现封装
1.5.3封装的实现步骤 将结构体、字段(属性)的首字母小写(不能导出了其它包不能使用类似 private) 给结构体所在包提供一个工厂模式的函数首字母大写类似一个构造函数 提供一个首字母大写的 Set 方法(类似其它语言的 public)用于对属性判断并赋值 func(var 结构体类型名) SetXxx(参数列表) (返回值列表) {//加入数据验证的业务逻辑var.字段 参数
}提供一个首字母大写的 Get 方法(类似其它语言的 public)用于获取属性的值 func (var 结构体类型名) GetXxx() {return var.age
}1.6.继承
继承可以解决代码复用让我们的编程更加靠近人类思维。
当多个结构体存在相同的属性(字段)和方法时可以从这些结构体中抽象出结构体在该结构体中定义这些相同的属性和方法。
其它的结构体不需要重新定义这些属性(字段)和方法,只需嵌套一个 Student匿名结构体即可。
也就是说:在 Golang 中如果一个 struct 嵌套了另一个匿名结构体那么这个结构体可以直接访问匿名结构体的字段和方法从而实现了继承特性。
1.6.1.嵌套匿名结构体的基本语法
type Goods struct {Name stringPrice int
}
type Book struct {Goods //这里就是嵌套匿名结构体Writer string
}1.6.2.继承的深入讨论 结构体可以使用嵌套匿名结构体所有的字段和方法即:首字母大写或者小写的字段、方法 都可以使用 匿名结构体字段访问可以简化 type A struct {Name stringage int
}func (a *A) sayOk() {fmt.Println(A SayOk,a.Name)
}
func (a *A) hello() {fmt.Println(A hello,a.Name)
}
type B struct {A
}
func main() {var b Bb.A.Name tomb.A.age 19b.A.sayOk()b.A.hello()//上面的写法可以简化b.Name smithb.age 20b.sayOk()b.hello()
}对上面的代码小结 当我们直接通过b访问字段或方法时其执行流程如下比如 b.Name 编译器会先看b对应的类型有没有 Name,如果有则直接调用 B 类型的 Name 字段 如果没有就去看B中嵌入的匿名结构体 A 有没有声明 Name 字段,如果有就调用.如果没 有继续查找.如果都找不到就报错 当结构体和匿名结构体有相同的字段或者方法时编译器采用就近访问原则访问如希望访问匿名结构体的字段和方法可以通过匿名结构体名来区分 结构体嵌入两个(或多个)匿名结构体如两个名结构体有相同的字段和方法(同时结构体本身没有同名的字段和方法)在访问时就必须明确指定匿名结构体名字否则编译报错。 如果一个 struct 嵌套了一个有名结构体这种模式就是组合如果是组合关系那么在访问组合的结构体的字段或方法时必须带上结构体的名字 嵌套匿名结构体后也可以在创建结构体变量(实例)时直接指定各个匿名结构体字段的值 如果一个结构体有 int类型的匿名字段就不能第二个。如果需要有多个int的字段则必须给 int 字段指定名字
1.6.3.多重继承
如果一个 struct 嵌套了多个匿名结构体那么该结构体可以直接访问嵌套的匿名结构体的字段和方法从而实现了多重继承。
type Goods struct {Name stringPrice float64
}
type Brand struct {Name stringAddress string
}
type TV struct {GoodsBrand
}细节说明
如嵌入的匿名结构体有相同的字段名或者方法名则在访问时需要通过匿名结构体类型名来区分。为了保证代码的简洁性建议大家尽量不使用多重继承
1.7.接口
interface 类型可以定义一组方法但是这些不需要实现。并且 interface 不能包含任何变量。到某个自定义类型(比如结构体 Phone)要使用的时候,在根据具体情况把这些方法写出来(实现)。
1.7.1.基本用法
type 接口名 interface {method1(参数列表) 返回值列表method2(参数列表) 返回值列表...
}
func (t 自定义类型) method1(参数列表) 返回值列表{//方法实现
}
func (t 自定义类型) method2(参数列表) 返回值列表{//方法实现
}
//...说明
接口里的所有方法都没有方法体即接口的方法都是没有实现的方法。接口体现了程序设计的多态和高内聚低偶合的思想。Golang 中的接口不需要显式的实现。只要一个变量含有接口类型中的所有方法那么这个变量就实现这个接口。因此Golang中没有implement这样的关键字
1.7.2.注意事项和细节
接口本身不能创建实例,但是可以指向一个实现了该接口的自定义类型的变量(实例)接口中所有方法都没有方法体即都是没有实现的方法在 Golang 中一个自定义类型需要将某个接口的所有方法都实现我们说这个自定义类型实现 了该接口。一个自定义类型只有实现了某个接口才能将该自定义类型的实例(变量)赋给接口类型只要是自定义数据类型就可以实现接口不仅仅是结构体类型。一个自定义类型可以实现多个接口Golang接口中不能有任何变量一个接口比如A接口可以继承多个别的几口比如BC接口这时如果要实现A接口须将BC接口的方法也全部实现interface 类型默认是一个指针(引用类型)如果没有对 interface 初始化就使用那么会输出 nil空接口 interface{} 没有任何方法所以所有类型都实现了空接口即我们可以把任何一个变量赋给空接口。
1.7.3.接口和继承的比较
实现接口可以看作是对继承的一种补充 当 A 结构体继承了 B结构体,那么 A 结构就自动的继承了B结构体的字段和方法并且可以直接使用当A 结构体需要扩展功能同时不希望去破坏继承关系则可以去实现某个接口即可因此我们可以认为:实现接口是对继承机制的补充实现接口可以看作是对 继承的一种补充 接口和继承解决的解决的问题不同 继承的价值主要在于解决代码的复用性和可维护性接口的价值主要在于设计设计好各种规范(方法)让其它自定义类型去实现这些方法。 接口比继承更加灵活 接口比继承更加灵活继承是满足 is-a 的关系而接口只需满足 like-a 的关系。 接口在一定程度上实现代码解耦
1.8.多态
变量(实例)具有多种形态。面向对象的第三大特征在 Go 语言多态特征是通过接口实现的。可以按照统一的接口来调用不同的实现。这时接口变量就呈现不同的形态。
1.8.1.接口体现多态的两种形式
多态参数 在前面的 Usb 接口案例Usb usb 即可以接收手机变量又可以接收相机变量就体现了 Usb 接口 多态。 多态数组 演示一个案例:给Usb数组中存放Phone结构体和Camera 结构体变量
1.9.类型断言
类型断言由于接口是一般类型不知道具体类型如果要转成具体类型就需要使用类型断言
在进行类型断言时如果类型不匹配就会报panic因此进行类型断言时要确保原来的空接口指向的就是断言的类型
如何在进行断言时带上检测机制如果成功就 ok,否则也不要报 panic
func main() {var x interface{}var b2 float32 2.1x b2if y, ok : x.(float32); ok {fmt.Println(convert success)fmt.Printf(y的类型是%T值是%v, y, y)} else {fmt.Println(convert fail)}fmt.Println(继续执行...)
}2.文件操作
文件在程序中是以流的形式来操作的
流数据在数据源(文件)和程序(内存)之间经历的路径输入流数据从数据源(文件)到程序(内存)的路径输出流数据从程序(内存)到数据源(文件)的路径
os.File 封装所有文件相关操作File 是一个结构体
2.1.打开和关闭文件
使用的函数和方法
func Open
func Open(name string) (file *File, err error)Open打开一个文件用于读取。如果操作成功返回的文件对象的方法可用于读取数据;对应的文件描述符具有O_RDONLY模式。如果出错错误底层类型是*PathError。
func (*File) Close
func (f *file) Close() errorClose关闭文件f使文件不能用于读写。它返回可能出现的错误。
案例演示
package mainimport (fmtos
)func main() {file, err : os.Open(C:\\Users\\ASUS\\Desktop\\GoCode\\examine-basic\\file\\test.text)if err ! nil {fmt.Println(open file err:, err)}fmt.Println(file)fmt.Printf(file %v, file)err file.Close()if err ! nil {fmt.Println(close file err:, err)}
}2.2.读文件操作
读取文件的内容并显示在终端(带缓冲区的方式)使用 os.Openfile.Closebufio.NewReader()reader.ReadString 函数和方法
package mainimport (bufiofmtioos
)func main() {file, err : os.Open(C:\\Users\\ASUS\\Desktop\\GoCode\\examine-basic\\file\\test.text)if err ! nil {fmt.Println(open file err:, err)}defer file.Close()//创建一个*Reader是带缓冲的默认的缓冲区为4096字节reader : bufio.NewReader(file)//循环读取文件的内容for {str, err : reader.ReadString(\n) //读到一个换行就结束if err io.EOF { //io.EOF表示文件的末尾fmt.Println(str)break}fmt.Print(str)}fmt.Println(文件读取结束···)
}
读取文件的内容并显示在终端(使用 ioutil 一次将整个文件读入到内存中)这种方式适用于文件不大的情况。相关方法和函数(ioutil.ReadFile)
func main() {//使用ioutil.ReadFile一次性将文件读取到位file : C:\\Users\\ASUS\\Desktop\\GoCode\\examine-basic\\file\\test.textcontent, err : ioutil.ReadFile(file)if err ! nil {return}fmt.Printf(%v, string(content))
}2.3.写文件操作
基本介绍-os.OpenFile函数
func OpenFile(name string,flag int,perm FileMode) (file *File,err error)第二个参数文件打开模式可以组合
第三个参数权限控制linux
说明: os.OpenFile是一个更一般性的文件打开函数它会使用指定的选项(如O_RDONLY等)、指定的模式(如0666等)打开指定名称的文件。如果操作成功返回的文件对象可用于I/0。如果出错错误底层类型是PathError
创建一个新文件写入内容5句hello, Gardon’
func main() {filePath : C:\\Users\\ASUS\\Desktop\\GoCode\\examine-basic\\file\\test.textfile, err : os.OpenFile(filePath, os.O_WRONLY|os.O_CREATE, 0666)if err ! nil {fmt.Println(open file err, err)}defer file.Close()str : hello,Gardon\nwriter : bufio.NewWriter(file)for i : 0; i 5; i {writer.WriteString(str)}//因为writer十带缓存的因此在调用WriteString方法时其实内容时先写入到缓存的//所以需要调用Flush将缓存的数据真正写入到文件中否则文件中会没有数据writer.Flush()
}打开一个存在的文件中将原来的内容覆盖成新的内容10句你好
func main() {filePath : C:\\Users\\ASUS\\Desktop\\GoCode\\examine-basic\\file\\test.textfile, err : os.OpenFile(filePath, os.O_WRONLY|os.O_TRUNC, 0666)if err ! nil {fmt.Println(open file err, err)}defer file.Close()str : 你好\r\nwriter : bufio.NewWriter(file)for i : 0; i 10; i {writer.WriteString(str)}writer.Flush()
}打开一个存在的文件在原来的内容追加内容’ABC!ENGLISH!’
func main() {filePath : C:\\Users\\ASUS\\Desktop\\GoCode\\examine-basic\\file\\test.textfile, err : os.OpenFile(filePath, os.O_WRONLY|os.O_APPEND, 0666)if err ! nil {fmt.Println(open file err, err)}defer file.Close()str : ABC!ENGLISH!\r\nwriter : bufio.NewWriter(file)for i : 0; i 10; i {writer.WriteString(str)}writer.Flush()
}打开一个存在的文件将原来的内容读出显示在终端并且追加5句hello,北京!
func main() {filePath : C:\\Users\\ASUS\\Desktop\\GoCode\\examine-basic\\file\\test.textfile, err : os.OpenFile(filePath, os.O_RDWR|os.O_APPEND, 0666)if err ! nil {fmt.Println(open file err, err)}defer file.Close()reader : bufio.NewReader(file)for {str, err : reader.ReadString(\n)fmt.Print(str)if err io.EOF {break}}str : hello,北京\r\nwriter : bufio.NewWriter(file)for i : 0; i 5; i {writer.WriteString(str)}writer.Flush()
}将文件内容写入另一个文件
func main() {//将test的内容写入test2filePath : C:\\Users\\ASUS\\Desktop\\GoCode\\examine-basic\\file\\test.textfilePath2 : C:\\Users\\ASUS\\Desktop\\GoCode\\examine-basic\\file\\test2.textdata, err : ioutil.ReadFile(filePath)if err ! nil {fmt.Println(read file err, err)return}err ioutil.WriteFile(filePath2, data, 0666)if err ! nil {fmt.Println(write file error, err)return}
}2.4.判断文件是否存在
golang判断文件或文件夹是否存在的方法为使用os.Stat()函数返回的错误值进行判断
如果返回的错误为ni,说明文件或文件夹存在如果返回的错误类型使用os.IsNotExist()判断为true,说明文件或文件夹不存在如果返回的错误为其它类型,则不确定是否在存在
写一个函数
func PathExists(path string) (bool, error) {_, err : os.Stat(path)if err nil {return true, nil}if os.IsNotExist(err) {return false, nil}return false, err
}2.5.拷贝文件
说明将一张图片/电影/mp3 拷贝到另外一个文件
func Copy(dst Writer, src Reader) (written int64, err error)注意;Copy 函数是 io 包提供的
package main
import (fmtosiobufio
)//自己编写一个函数接收两个文件路径 srcFileName dstFileName
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()//通过srcfile ,获取到 Readerreader : bufio.NewReader(srcFile)//打开dstFileNamedstFile, err : os.OpenFile(dstFileName, os.O_WRONLY | os.O_CREATE, 0666)if err ! nil {fmt.Printf(open file err%v\n, err)return }//通过dstFile, 获取到 Writerwriter : bufio.NewWriter(dstFile)defer dstFile.Close()return io.Copy(writer, reader)}func main() {//将d:/flower.jpg 文件拷贝到 e:/abc.jpg//调用CopyFile 完成文件拷贝srcFile : d:/flower.jpgdstFile : e:/abc.jpg_, err : CopyFile(dstFile, srcFile)if err nil {fmt.Printf(拷贝完成\n)} else {fmt.Printf(拷贝错误 err%v\n, err)}}2.6.统计不同字符的个数
package main
import (fmtosiobufio
)//定义一个结构体用于保存统计结果
type CharCount struct {ChCount int // 记录英文个数NumCount int // 记录数字的个数SpaceCount int // 记录空格的个数OtherCount int // 记录其它字符的个数
}func main() {//思路: 打开一个文件, 创一个Reader//每读取一行就去统计该行有多少个 英文、数字、空格和其他字符//然后将结果保存到一个结构体fileName : e:/abc.txtfile, err : os.Open(fileName)if err ! nil {fmt.Printf(open file err%v\n, err)return}defer file.Close()//定义个CharCount 实例var count CharCount//创建一个Readerreader : bufio.NewReader(file)//开始循环的读取fileName的内容for {str, err : reader.ReadString(\n)if err io.EOF { //读到文件末尾就退出break}//遍历 str 进行统计for _, v : range str {switch {case v a v z:fallthrough //穿透case v A v Z:count.ChCountcase v || v \t:count.SpaceCountcase v 0 v 9:count.NumCountdefault :count.OtherCount}}}//输出统计的结果看看是否正确fmt.Printf(字符的个数为%v 数字的个数为%v 空格的个数为%v 其它字符个数%v, count.ChCount, count.NumCount, count.SpaceCount, count.OtherCount)
}2.7.命令行参数
os.Args 是一个 string 的切片用来存储所有的命令行参数
C:\Users\ASUS\Desktop\GoCode\examine-basic\filemain.exe tom c:/aaa/bbb/test/log 99
命令行参数有 4
args[0]main.exe
args[1]tom
args[2]c:/aaa/bbb/test/log
args[3]99func main() {fmt.Println(命令行参数有, len(os.Args))for i, v : range os.Args {fmt.Printf(args[%v]%v\n, i, v)}
}说明前面的方式是比较原生的方式对解析参数不是特别的方便特别是带有指定参数形式的命令行。 比如cmdmain.exe -f c:/aaa.txt -p 200 -u root 这样的形式命令行go 设计者给我们提供了 flag包可以方便的解析命令行参数而且参数顺序可以随意
C:\Users\ASUS\Desktop\00\代码\chapter14\flagdemotest.exe -u root -pwd root -h 192.168.0.1 -port 3306
userroot pwdroot host192.168.0.1 port3306package main
import (fmtflag
)func main() {//定义几个变量用于接收命令行的参数值var user stringvar pwd stringvar host stringvar port int//user 就是接收用户命令行中输入的 -u 后面的参数值//u ,就是 -u 指定参数// , 默认值//用户名,默认为空 说明flag.StringVar(user, u, , 用户名,默认为空)flag.StringVar(pwd, pwd, , 密码,默认为空)flag.StringVar(host, h, localhost, 主机名,默认为localhost)flag.IntVar(port, port, 3306, 端口号默认为3306)//这里有一个非常重要的操作,转换 必须调用该方法flag.Parse()//输出结果fmt.Printf(user%v pwd%v host%v port%v, user, pwd, host, port)}3.JSON
JSON(avaScript Object Notation)是一种轻量级的数据交换格式。易于人阅读和编写。同时也易于机器解析和生成。key-valJSON是在2001年开始推广使用的数据格式目前已经成为主流的数据格式JSON易于机器解析和生成并有效地提升网络传输效率,通常程序在网络传输时会先将数据(结构体、map等)序列化成json字符串,到接收方得到json字符串时在反序列化恢复成原来的数据类型(结构体、map等)。这种方式已然成为各个语言的标准。在JS语言中一切都是对象。因此任何的数据类型都可以通过JSON来表示例如字符串、数字、对象、数组,map,结构体等.
JSON数据在线解析www.json.cn
序列化
ison 序列化是指将有 key-value 结构的数据类型(比如结构体、map、切片)序列化成ison 字符串的操作。
package main
import (fmtencoding/json
)//定义一个结构体
type Monster struct {Name string json:monster_name //反射机制Age int json:monster_ageBirthday string //....Sal float64Skill string
}func testStruct() {//演示monster : Monster{Name :牛魔王,Age : 500 ,Birthday : 2011-11-11,Sal : 8000.0,Skill : 牛魔拳,}//将monster 序列化data, err : json.Marshal(monster) //..if err ! nil {fmt.Printf(序列号错误 err%v\n, err)}//输出序列化后的结果fmt.Printf(monster序列化后%v\n, string(data))}//将map进行序列化
func testMap() {//定义一个mapvar a map[string]interface{}//使用map,需要makea make(map[string]interface{})a[name] 红孩儿a[age] 30a[address] 洪崖洞//将a这个map进行序列化//将monster 序列化data, err : json.Marshal(a)if err ! nil {fmt.Printf(序列化错误 err%v\n, err)}//输出序列化后的结果fmt.Printf(a map 序列化后%v\n, string(data))}//演示对切片进行序列化, 我们这个切片 []map[string]interface{}
func testSlice() {var slice []map[string]interface{}var m1 map[string]interface{}//使用map前需要先makem1 make(map[string]interface{})m1[name] jackm1[age] 7m1[address] 北京slice append(slice, m1)var m2 map[string]interface{}//使用map前需要先makem2 make(map[string]interface{})m2[name] tomm2[age] 20m2[address] [2]string{墨西哥,夏威夷}slice append(slice, m2)//将切片进行序列化操作data, err : json.Marshal(slice)if err ! nil {fmt.Printf(序列化错误 err%v\n, err)}//输出序列化后的结果fmt.Printf(slice 序列化后%v\n, string(data))}//对基本数据类型序列化对基本数据类型进行序列化意义不大
func testFloat64() {var num1 float64 2345.67//对num1进行序列化data, err : json.Marshal(num1)if err ! nil {fmt.Printf(序列化错误 err%v\n, err)}//输出序列化后的结果fmt.Printf(num1 序列化后%v\n, string(data))
}func main() {//演示将结构体, map , 切片进行序列号testStruct()testMap()testSlice()//演示对切片的序列化testFloat64()//演示对基本数据类型的序列化
}注意事项 对于结构体的序列化,如果我们希望序列化后的key的名字,又我们自己重新制定,那么可以给 struct指定一个 tag 标签.
反序列化
json 反序列化是指将json 字符串反序列化成对应的数据类型(比如结构体、map、切片)的操作
在反序列化一个json字符串时,要确保反序列化后的数据类型和原来序列化前的数据类型一致。如果 json 字符串是通过程序获取到的则不需要再对“ 转义处理。
package main
import (fmtencoding/json
)//定义一个结构体
type Monster struct {Name string Age int Birthday string //....Sal float64Skill string
}//演示将json字符串反序列化成struct
func unmarshalStruct() {//说明str 在项目开发中是通过网络传输获取到.. 或者是读取文件获取到str : {\Name\:\牛魔王~~~\,\Age\:500,\Birthday\:\2011-11-11\,\Sal\:8000,\Skill\:\牛魔拳\}//定义一个Monster实例var monster Monstererr : json.Unmarshal([]byte(str), monster)if err ! nil {fmt.Printf(unmarshal err%v\n, err)}fmt.Printf(反序列化后 monster%v monster.Name%v \n, monster, monster.Name)}
//将map进行序列化
func testMap() string {//定义一个mapvar a map[string]interface{}//使用map,需要makea make(map[string]interface{})a[name] 红孩儿~~~~~~a[age] 30a[address] 洪崖洞//将a这个map进行序列化//将monster 序列化data, err : json.Marshal(a)if err ! nil {fmt.Printf(序列化错误 err%v\n, err)}//输出序列化后的结果//fmt.Printf(a map 序列化后%v\n, string(data))return string(data)}//演示将json字符串反序列化成map
func unmarshalMap() {//str : {\address\:\洪崖洞\,\age\:30,\name\:\红孩儿\}str : testMap()//定义一个mapvar a map[string]interface{} //反序列化//注意反序列化map,不需要make,因为make操作被封装到 Unmarshal函数err : json.Unmarshal([]byte(str), a)if err ! nil {fmt.Printf(unmarshal err%v\n, err)}fmt.Printf(反序列化后 a%v\n, a)}//演示将json字符串反序列化成切片
func unmarshalSlice() {str : [{\address\:\北京\,\age\:\7\,\name\:\jack\}, {\address\:[\墨西哥\,\夏威夷\],\age\:\20\,\name\:\tom\}]//定义一个slicevar slice []map[string]interface{}//反序列化不需要make,因为make操作被封装到 Unmarshal函数err : json.Unmarshal([]byte(str), slice)if err ! nil {fmt.Printf(unmarshal err%v\n, err)}fmt.Printf(反序列化后 slice%v\n, slice)
}func main() {unmarshalStruct()unmarshalMap()unmarshalSlice()
}4.单元测试
Go 语言中自带有一个轻量级的测试框架 testing 和自带的 go test 命令来实现单元测试和性能测试,testing 框架和其他语言中的测试框架类似可以基于这个框架写针对相应函数的测试用例也可以基于该框架写相应的压力测试用例。通过单元测试可以解决如下问题:
确保每个函数是可运行并且运行结果是正确的确保写出来的代码性能是好的单元测试能及时的发现程序设计或实现的逻辑错误使问题及早暴露便于问题的定位解决,而性能测试的重点在于发现程序设计上的一些问题让程序能够在高并发的情况下还能保持稳定
案例
cal.go
package calfunc addNums(a int, b int) int {return a b
}
cal_test.go
package calimport testingfunc TestAddNums(t *testing.T) {res : addNums(3, 4)if res ! 7 {t.Fatalf(addNums(3,4) error)}t.Logf(test addNums success)
}
单元测试快速入门总结
\1) 测试用例文件名必须以 _test.go 结尾。 比如 cal_test.go , cal 不是固定的。
\2) 测试用例函数必须以 Test 开头一般来说就是 Test被测试的函数名比如 TestAddUpper
\3) TestAddUpper(t *tesing.T) 的形参类型必须是 *testing.T 【看一下手册】
\4) 一个测试用例文件中可以有多个测试用例函数比如 TestAddUpper、TestSub
\5) 运行测试用例指令
(1) cmdgo test [如果运行正确无日志错误时会输出日志]
(2) cmdgo test -v [运行正确或是错误都输出日志]
\6) 当出现错误时可以使用 t.Fatalf 来格式化输出错误信息并退出程序
\7) t.Logf 方法可以输出相应的日志
\8) 测试用例函数并没有放在 main 函数中也执行了这就是测试用例的方便之处[原理图].
\9) PASS 表示测试用例运行成功FAIL 表示测试用例运行失败
\10) 测试单个文件一定要带上被测试的原文件
go test -v cal_test.go cal.go
\11) 测试单个方法
go test -v -test.run TestAddUpper
5.协程和管道
5.1.进程和线程 进程就是程序程序在操作系统中的一次执行过程是系统进行资源分配和调度的基本单位 线程是进程的一个执行实例是程序执行的最小单元它是比进程更小的能独立运行的基本单位。 一个进程可以创建和销毁多个线程同一个进程中的多个线程可以并发执行。 一个程序至少有一个进程一个进程至少有一个线程
5.2.并发和并行
多线程程序在单核上运行就是并发多线程程序在多核上运行就是并行
5.3.go协程和主线程
Go 主线程(有程序员直接称为线程/也可以理解成进程):一个Go 线程上可以起多个协程你可以这样理解协程是轻量级的线程「编译器做优化]。
Go 协程的特点
有独立的栈空间共享程序堆空间调度由用户控制协程是轻量级的线程
5.4.goroutine快速入门
案例
func main() {go test() //开启了一个协程for i : 1; i 10; i {fmt.Println(main() , i)time.Sleep(time.Second / 4)}
}
func test() {for i : 1; i 10; i {fmt.Println(test() , i)time.Sleep(time.Second / 4)}
}如果主线程退出了则协程即使还没有执行完毕也会退出
当然协程也可以再主线程没有退出前就自己结束了比如完成了自己的任务
5.5.快速入门小结
主线程是一个物理线程直接作用在 cpu 上的。是重量级的非常耗费 cpu 资源。协程从主线程开启的是轻量级的线程是逻辑态。对资源消耗相对小。Golang 的协程机制是重要的特点可以轻松的开启上万个协程。其它编程语言的并发机制是一般基于线程的开启过多的线程资源耗费大这里就突显 Golang 在并发上的优势了
5.6.MPG 模式
M: 操作系统的主线程(是物理线程)P:协程执行需要的上下文G: 协程
MPG模式运行的状态1 MPG模式运行的状态2 设置Golang运行的cpu数
import (fmtruntime
)func main() {cpuNum : runtime.NumCPU()fmt.Println(cpuNum, cpuNum)//可以自己设置使用多少个cpuruntime.GOMAXPROCS(cpuNum)
}g01.8后默认让程序运行在多个核上,可以不用设置了
go1.8前还是要设置一下可以更高效的利益cpu
5.7.channel(管道)
5.7.1.使用全局变量加锁
package mainimport (fmtsynctime
)var (myMap make(map[int]int, 10)//声明一个全局的互斥锁//sync是包, synchornized 同步//Mutex 互斥lock sync.Mutex
)func main() {for i : 1; i 200; i {go test(i)}time.Sleep(time.Second * 10)lock.Lock()for i, v : range myMap {fmt.Printf(myMap[%v]%v, i, v)}lock.Unlock()
}
func test(n int) {res : 1for i : 1; i n; i {res * i}//加锁lock.Lock()myMap[n] res//解锁lock.Unlock()
}5.7.2.为什么需要channel
前面使用全局变量加锁同步来解决 goroutine 的通讯但不完美主线程在等待所有 goroutine 全部完成的时间很难确定我们这里设置 10 秒仅仅是估算。如果主线程休眠时间长了会加长等待时间如果等待时间短了可能还有 goroutine 处于工作状态这时也会随主线程的退出而销毁通过全局变量加锁同步来实现通讯也并不利用多个协程对全局变量的读写操作。上面种种分析都在呼唤一个新的通讯机制-channel
5.7.3.channel 的基本介绍
channle 本质就是一个数据结构-队列数据是先进先出【FIFO : first in first out】线程安全多 goroutine 访问时不需要加锁就是说 channel 本身就是线程安全的channel 有类型的一个 string 的 channel 只能存放 string 类型数据
channel是线程安全的多个协程操作一个管道时不会发生资源竞争问题
5.7.4.声明channel
var 变量名 chan 数据类型
举例
var intChan chan int //intChan 用于存放 int 数据
var mapChan chan map[int]string //mapChan用于存放map[int]string类型
var perChan chan Person
var perChan2 chan *Person
...说明
channel 是引用类型channel 必须初始化才能写入数据, 即 make 后才能使用管道是有类型的intChan 只能写入 整数 int
使用案例
package mainimport fmtfunc main() {//创建一个可以存放 3 个 int 类型的管道var intChan chan intintChan make(chan int, 3)//2. 看看 intChan 是什么fmt.Printf(intChan 的值%v intChan 本身的地址%p\n, intChan, intChan)//向管道写入数据intChan - 10num : 211intChan - numfmt.Printf(channel len%v, cap%v \n, len(intChan), cap(intChan))num2 : -intChanfmt.Println(num2)num3 : -intChanfmt.Println(num3)//在没有使用协程的情况下如果我们的管道数据已经全部取出再取就会报告 deadlocknum4 : -intChanfmt.Println(num4)fmt.Println(num3, num3, num4, num4)
}
5.7.5.channel 使用的注意事项
channel 中只能存放指定的数据类型channle 的数据放满后就不能再放入了如果从 channel 取出数据后可以继续放入在没有使用协程的情况下如果 channel 数据取完了再取就会报 dead lock 5.7.6.channel的关闭
使用内置函数close 可以关闭 channel, 当 channel 关闭后就不能再向 channel 写数据了但是仍然可以从该 channel 读取数据
func main() {intChan : make(chan int, 3)intChan - 100intChan - 200close(intChan)//这时不能够再写入数到channel//intChan-300fmt.Println(ok)//当管道关闭后读取数据时可以的n1 : -intChanfmt.Println(n1)
}
5.7.7.channel 的遍历
channel 支持 for–range 的方式进行遍历注意两个细节
在遍历时如果 channel 没有关闭则回出现 deadlock 的错误在遍历时如果 channel 已经关闭则会正常遍历数据遍历完后就会退出遍历
func main() {intChan : make(chan int, 100)for i : 0; i 100; i {intChan - i * 2}//遍历管道不能使用普通的for循环//在遍历时如果channel没有关闭则会出现deadlock的错误//在遍历时如果channel已经关闭则会正常遍历数据遍历完后就会退出遍历close(intChan)for v : range intChan {fmt.Println(v, v)}
}5.8.应用实例 package mainimport fmtfunc main() {intChan : make(chan int, 50)exitChan : make(chan bool, 1)go writeData(intChan)go readData(intChan, exitChan)for {_, ok : -exitChanif !ok {break}}
}// write Data
func writeData(intChan chan int) {for i : 1; i 50; i {intChan - ifmt.Printf(readData写入数据%v\n, i)}close(intChan)
}
func readData(intChan chan int, exitChan chan bool) {for {v, ok : -intChanif !ok {break}fmt.Printf(readData读到数据%v\n, v)}//读取数据后即任务完成exitChan - trueclose(exitChan)
}阻塞 求素数
package mainimport (fmttime
)func main() {intChan : make(chan int, 1000)primeChan : make(chan int, 2000)exitChan : make(chan bool, 4)go putNum(intChan)for i : 0; i 4; i {go primeNum(intChan, primeChan, exitChan)}go func() {for i : 0; i 4; i {-exitChan}close(primeChan)}()for {res, ok : -primeChanif !ok {break}fmt.Printf(素数%d\n, res)}fmt.Println(main线程退出)
}
func putNum(intChan chan int) {for i : 1; i 8000; i {intChan - i}close(intChan)
}
func primeNum(intChan chan int, primeChan chan int, exitChan chan bool) {var flag boolfor {time.Sleep(time.Millisecond * 10)num, ok : -intChanif !ok {break}flag truefor i : 2; i num; i {if num%i 0 {flag falsebreak}}if flag {primeChan - num}}fmt.Println(有一个primeNum协程因为取不到数据退出)exitChan - true
}5.9.channel 使用细节和注意事项
1.channel 可以声明为只读或者只写性质
//只写
var chan2 chan- int
chan2 make(chan- int, 3)
chan2 - 20
//只读
var chan3 -chan int
num : -chan3
fmt.Println(num)2.只读和只写的最佳实践案例 3.使用 select 可以解决从管道取数据的阻塞问题
func main() {intChan : make(chan int, 10)for i : 0; i 10; i {intChan - i}stringChan : make(chan string, 5)for i : 0; i 5; i {stringChan - hello fmt.Sprintf(%d, i)}for {select {//注意这里如果intChan一直没有关闭不会一直阻塞而deadlock//会自动到下一个case匹配case v : -intChan:fmt.Printf(%d\n, v)case v : -stringChan:fmt.Printf(%s\n, v)default:fmt.Printf(都取不到了)return}}
}4.goroutine 中使用 recover解决协程中出现 panic导致程序崩溃问题 package mainimport (fmttime
)// 函数
func sayHello() {for i : 0; i 10; i {time.Sleep(time.Second)fmt.Println(hello,world)}
}// 函 数
func test() {//这里我们可以使用 defer recoverdefer func() {if err : recover(); err ! nil {fmt.Println(test() 发生错误, err)}}()//定义了一个 mapvar myMap map[int]stringmyMap[0] golang //error
}func main() {go sayHello()go test()for i : 0; i 10; i {fmt.Println(main() ok, i)time.Sleep(time.Second)}
}6.反射
6.1.反射的基本介绍
反射可以在运行时动态获取变量的各种信息, 比如变量的类型(type)类别(kind)如果是结构体变量还可以获取到结构体本身的信息(包括结构体的字段、方法)通过反射可以修改变量的值可以调用关联的方法。使用反射需要 import (“reflect”)
示意图 6.2.反射的应用场景 
6.3.反射重要的函数和概念 3变量、interface{} 和 reflect.Value 是可以相互转换的这点在实际开发中会经常使用到。画出示意图 6.4.快速入门
请编写一个案例演示对(基本数据类型、interface{}、reflect.Value)进行反射的基本操作代码演示
package mainimport (fmtreflect
)func main() {var num int 100reflectTest01(num)
}
func reflectTest01(b interface{}) {tTyp : reflect.TypeOf(b)fmt.Println(tTyp)//获取reflect.ValuerVal : reflect.ValueOf(b)n2 : 2 rVal.Int()fmt.Println(n2, n2)fmt.Printf(rVal%v rVal type%T\n, rVal, rVal)//下面将rVal转成interface{}iV : rVal.Interface()//将interface{}通过断言转成需要的类型num2 : iV.(int)fmt.Println(num2, num2)
}
请编写一个案例演示对(结构体类型、interface{}、reflect.Value)进行反射的基本操作代码演示
func reflectTest02(b interface{}) {tTyp : reflect.TypeOf(b)fmt.Println(tTyp)//获取reflect.ValuerVal : reflect.ValueOf(b)//下面将rVal转成interface{}iV : rVal.Interface()fmt.Printf(%v,%T\n, iV, iV)stu, ok : iV.(Student)if ok {fmt.Println(stu.Name)}
}
6.5.反射的注意事项和细节
reflect.Value.Kind获取变量的类别返回的是一个常量 Type 和 Kind 的区别
Type 是类型, Kind 是类别 Type 和 Kind 可能是相同的也可能是不同的比如: var num int 10 num 的 Type 是 int , Kind 也是 int比如: var stu Student stu 的 Type 是 pkg1.Student , Kind 是 struct 通过反射的来修改变量, 注意当使用 SetXxx 方法来设置需要通过对应的指针类型来完成, 这样才能改变传入的变量的值, 同时需要使用到 reflect.Value.Elem()方法 6.reflect.Value.Elem() 应该如何理解 6.6.反射最佳实践
1.使用反射来遍历结构体的字段调用结构体的方法并获取结构体标签的值
package mainimport (fmtreflect
)// Monster 定义了一个 Monster 结构体
type Monster struct {Name string json:nameAge int json:monster_ageScore float32 json:成绩Sex string
}// Print 方法显示 s 的值
func (s Monster) Print() {fmt.Println(---start~ )fmt.Println(s)fmt.Println(---end~ )
}// GetSum 方法返回两个数的和
func (s Monster) GetSum(n1, n2 int) int {return n1 n2
}// Set 方法 接收四个值给 s 赋值
func (s Monster) Set(name string, age int, score float32, sex string) {s.Name names.Age ages.Score scores.Sex sex
}func TestStruct(a interface{}) {//获取 reflect.Type 类型typ : reflect.TypeOf(a)//获取 reflect.Value 类型val : reflect.ValueOf(a)//获取到 a 对应的类别kd : val.Kind()//如果传入的不是 struct就退出if kd ! reflect.Struct {fmt.Println(expect struct)return}//获取到该结构体有几个字段num : val.NumField()fmt.Printf(struct has %d fields\n, num) //4//变量结构体的所有字段for i : 0; i num; i {fmt.Printf(Field %d: 值为%v\n, i, val.Field(i))//获取到 struct 标签, 注意需要通过 reflect.Type 来获取 tag 标签的值tagVal : typ.Field(i).Tag.Get(json)//如果该字段于 tag 标签就显示否则就不显示if tagVal ! {fmt.Printf(Field %d: tag 为%v\n, i, tagVal)}}//获取到该结构体有多少个方法numOfMethod : val.NumMethod()fmt.Printf(struct has %d methods\n, numOfMethod)//var params []reflect.Value//方法的排序默认是按照 函数名的排序ASCII 码val.Method(1).Call(nil) //获取到第二个方法。调用它//调用结构体的第 1 个方法 Method(0)var params []reflect.Value //声明了 []reflect.Valueparams append(params, reflect.ValueOf(10))params append(params, reflect.ValueOf(40))res : val.Method(0).Call(params) //传入的参数是 []reflect.Value, 返回[]reflect.Valuefmt.Println(res, res[0].Int()) //返回结果, 返回的结果是 []reflect.Value*/
}
func main() {//创建了一个 Monster 实例var a Monster Monster{Name: 黄鼠狼精, Age: 400,Score: 30.8,}//将 Monster 实例传递给 TestStruct 函数TestStruct(a)
}7.TCP编程
18.1 网络编程基本介绍
Golang 的主要设计目标之一就是面向大规模后端服务程序网络通信这块是服务端 程序必不可少也是至关重要的一部分。
网络编程有两种:
TCP socket 编程是网络编程的主流。之所以叫 Tcp socket 编程是因为底层是基于 Tcp/ip 协议的. 比如: QQ 聊天 [示意图]b/s 结构的 http 编程我们使用浏览器去访问服务器时使用的就是 http 协议而 http 底层依旧是用 tcp socket 实现的。[示意图] 比如: 京东商城 【这属于 go web 开发范畴 】