一 结构体
1 简介
1 结构体介绍
Go 语言通过自定义方式形成新的类型,结构体是类型中带有成员的符合类型,Go语言使用结构体和结构体成员来描述真实世界的实体和实体对应的各种属性。
2 字段
结构体成员是由一系列成员变量构成,这些成员变量称为"字段"
字段特征如下:
1 字段必须有自己的名称和类型
2 字段名必须唯一
3 字段的类型一般是基本数据类型,数组,也可以是引用类型,甚至可以是字段所在的结构体的类型。
Go 语言中不但结构体可以拥有自己的方法,且每种自定义类型都可以拥有自己的方法
2 定义结构体
1 基本定义
Go语言使用关键字type 可以定义各种类型,包括将各种基本类型定义为自定义类型,自然也可以定义结构体
2 基本格式
type 类型名称 struct {
字段1 字段1类型
字段2 字段2类型
}
类型名: 同一个包中不能重复,标识自定义结构体名称
struct{}: 标识其类型是结构体类型
字段: 表示字段名称,在该结构体中必须唯一
字段类型:该字段的类型,可以是多种类型
3 实例化结构体
结构体定义只是一种内存布局的描述,只有当结构体实例化后,才会真正的分配内存,因此必须在定义结构体并进行实例化后才能使用。
实例化:根据结构体定义的格式创建一份与格式一致的区域
结构体实例和实例之间内存是独立的,因为其值类型
1 初始化基本结构体
package main
import "fmt"
type A struct {
//定义一个结构体,其中包含三个字段
X int
Y string
Z int
}
func main() {
a := A{X: 10, Y: "234", Z: 10}
b := A{10, "123", 100}
c := A{}
c.X = 100
c.Y = "789"
c.Z = 1000
fmt.Println(a)
fmt.Println(b)
fmt.Println(c)
}
结果如下
2 初始化化匿名结构体
package main
import (
"fmt"
)
func PrintMsgType(msg *struct {
//此处定义入参类型为如此结构体
id int
data string
}) {
fmt.Printf("其类型为:%T", msg) // 此处打印其对应的类型
}
func main() {
msg := struct {
// 此处使用匿名指针
id int
data string
}{ // 此处调用该匿名指针,直接进行赋值操作
10,
"12234",
}
PrintMsgType(&msg)
}
结果如下
3 其他格式的实例化操作
结构体本身是一种类型,其也可通过var 方式声明并进行实例化,基本如下
package main
import "fmt"
type A struct {
//定义一个结构体,其中包含三个字段
X int
Y string
Z int
}
func main() {
var a A // 实例化结构体
a.X = 10 //结构体赋值,及其成员访问是通过"." 点号来访问的,其赋值方式和普通变量相同
a.Y = "abcd"
a.Z = 10
fmt.Println(a)
fmt.Printf("结构体实例地址:%p\n", &a) // 取出实例地址
fmt.Println("第一个参数地址:", &a.X)
fmt.Println(&a.Y)
fmt.Println(&a.Z)
}
结果如下
结构体注意事项和使用细节
1 结构体的所有字段在内存中都是连续的
2 结构体是用户单独定义的类型,和其他类型进行转换时需要有完全相同的字段(名称,个数和类型一一对应)
4 其他类型创建和初始化
package main
import "fmt"
type A struct {
X int //定义整形数据
Y string // 定义字符串类型数据
Z float64 //定义浮点类型数据
W []int //定义切片
M map[string]string //定义map
N [3]string //定义数组
P *int // 定义指针
}
func main() {
var a A
a.X = 10
a.Y = "abcd"
a.Z = 10.0000
a.W = []int{1, 2, 3, 4}
a.M = make(map[string]string)
a.M["a"] = "abcd"
a.M["b"] = "1234"
a.N = [3]string{"1", "2", "3"}
a.P = &a.X
a.W = append(a.W, 1, 3, 4, 6, 7, )
fmt.Println(a)
}
结果如下
4 指针类型结构体
1 基本初始化
Go语言中,也可使用new关键字对类型进行实例化,结构体在实例化后会形成指针类型的结构体
package main
import "fmt"
type A struct {
//定义一个结构体,其中包含三个字段
X int
Y string
Z int
}
func main() {
a := new(A) //此处返回为一个指针类型,其需要使用*a 来进行取值,其类型为*A
a.X = 10
a.Y = "123"
a.Z = 20
fmt.Printf("a的类型为%T,a的值为%v\n", a, *a)
b := new(int) // 其类型为*int
fmt.Printf("b的类型为%T,b的值为%v\n", b, *b)
}
结果如下
2 结构体指针基本使用
package main
import "fmt"
type A struct {
X int //定义整形数据
Y string // 定义字符串类型数据
}
func main() {
var a A
a = A{X: 10, Y: "abcd"}
fmt.Printf("a 的类型为:%T", a)
fmt.Println(a)
var b *A = new(A) //使用值传递进行处理
fmt.Printf("b 的类型为:%T", b)
(*b).X = 100 //此处应该使用值方式访问,其* 表示通过指针获取其值
(*b).Y = "1234"
fmt.Println(*b)
b.X = 200 // 此处也修改了,其实际上应该是(*b).X ,其是Go语言底层对其进行了优化和配置
b.Y = "mysql"
fmt.Println(*b)
var c = &A{}// 取地址操作,其可视为对该类型进行一次new的实例化操作。
fmt.Printf("c 的类型为:%T", c)
c.X = 300
c.Y = "abcd1234"
fmt.Println(*c)
}
结果如下
说明:
其中b和c 方式返回的是结构体指针,其标准的访问字段形式是(*x.field).Go语言为了进行简化,支持结构体指针.字段名,更加符合程序员的使用习惯,Go的底层进行了相关的优化操作
3 取地址实例化应用-工厂函数
某种情况下,需要调用某些结构体,但又不想对外暴露,此时便可使用工厂模式来解决此种情况
目录结构如下
test01.go中的配置
package test
type student struct {
Name string
Score float64
}
func NewStudent(n string, s float64) *student { //此处用于返回一个student的指针类型数据,其被封装在对应的函数中
return &student{
Name: n,
Score: s,
}
}
main.go 调用如下
package main
import (
"fmt"
"gocode/project01/test"
)
func main() {
t1 := test.NewStudent("zhangsan", 100.00)
fmt.Println(*t1)
}
结果如下
5 扩展
1 type重定义
结构体进行type 重新定义(相当于取别名),golang任务是新的数据类型,但是相互之间可强制转换
package main
import "fmt"
type A struct {
X int //定义整形数据
}
type B A // 重新定义
func main() {
var a A
var b B
a.X = 10
b = B(a) // 强制转换
fmt.Println(a, b)
}
结果如下
2 struct 反射机制
问题:
Json 要处理数据,其必须调用对应的Mashal方法,若结构体的各个字段都是小写,则其不能被调用,若是大写,则可能会导致返回给前端为大写,不符合规范,此时可使用tag 进行处理
package main
import (
"encoding/json"
"fmt"
)
type A struct {
X int //定义整形数据
}
func main() {
var a A
a.X = 10
data, err := json.Marshal(a)
if err != nil {
fmt.Println(err)
} else {
fmt.Printf("%c", data) //其默认返回的Json数据为大写
}
}
结果如下
通过tag 进行处理
package main
import (
"encoding/json"
"fmt"
)
type A struct {
X int `json:"x"` //定义整形数据,定义tag,其中不能有空格
}
func main() {
var a A
a.X = 10
data, err := json.Marshal(a)
if err != nil {
fmt.Println(err)
} else {
fmt.Printf("%c", data) //其默认返回的Json数据为大写
}
}
结果如下
4 struct 方法及构造函数
1 介绍
Go语言中的方法是一种作用于特定类型变量的函数,这种特定变量叫做接收器
如果将特定类型理解为结构体或"类"时,接收器的概念就类似于this 或 self
Go语言中,接收器的类型可以是任何类型,而不仅仅是结构体,任何类型都可拥有方法
面向对象语言中,类拥有的方法一般被理解为类可以做的事情,在Go语言中方法给概念和其他一致,只是Go语言建立的"接收器"强调方法作用对象是接收器,也就是类实例,而函数是没有作用对象的
Go语言的类型或结构体的类型没有构造函数的功能,结构体的初始化过程可以使用函数封装实现
每个类可以添加构造函数,多个构造函数使用函数重载实现
构造函数一般与类名同名,且没有返回值
构造函数有一个静态构造函数,一般用这个特性来调用父类的构造函数
对于C++来说,还有默认构造函数,拷贝构造函数等
在某些情况下,我们需要定义方法,及数据可以干什么操作,因此,自定义的不仅仅是类型,还可以是方法
2 方法的调用和传参机制原理
说明:方法的调用和传参机制和函数基本一样,不一样的地方是方法调用,会将调用方法的变量,当做实参传递给方法
1 方法的声明(定义)
func (receiver type) methodName(参数列表) (返回值列表) {
方法体
return 返回值
}
其中receiver和type统称为接收器。其类型有指针类型和非指针类型两种。一般的,非指针类型都有返回值的。
1 type: 表示这个方法和这个type类型进行绑定,或者说方法用于type类型,其type可以是结构体,也可以是其他自定义类型
2 receive: 就是type 类型的一个变量(实例),其称为接收器变量,其在命名时,官方建议使用接收器类型名的第一个小写字母,而不是self,this之类的名称。
3 methodName:表示该结构体绑定的方法名称
4 参数列表: 表示方法的输入
5 返回值列表,其可为多个
6 方法主体:表示为了实现某一功能的代码块
7 return: 返回值,非必须
2 基本实例
package main
import "fmt"
type Bag struct {
items []int
}
func (b Bag) Insert(n int) { // 非指针类型
b.items = append(b.items, n)
}
func (b *Bag) Test(n int) { // 指针类型
(*b).items = append((*b).items, n)
}
func main() {
bag := Bag{}
bag.Insert(10) // 此处调用方法
bag.Insert(20)
fmt.Println(bag)
bag1 := &Bag{} // 此处也可使用new(Bag) 进行创建
bag1.Test(30) // 此处调用方法
bag1.Test(40)
bag1.Test(40)
fmt.Println(*bag1)
}
结果如下
3 指针类型和非指针类型接收器的使用
在计算机中,小对象由于值复制时很快,因此适合非指针接收器,大对象因为复制性能较低,适用于指针接收器,在接收器和参数之间传递时不进行复制,只进行指针传递
具体的结构体的方法其第一个字段只有结构体
package main
import (
"fmt"
)
type A struct {
X int `json:"x"` //定义整形数据,定义tag,其中不能有空格
}
func (a A) Test() { //定义方法,其中a 表示A的形参,后面的Test表示值
a.X += a.X
fmt.Println(a.X)
}
func (a A) Test1(n1 int) int { //此中可在对应方法中传值,更加具体的描述其方法的灵活性
return a.X + n1
}
func main() {
a := A{10}
a.Test()
res := a.Test1(100)
fmt.Println(res)
}
结果如下
4 方法注意事项和细节
1 结构体类型是值类型,在方法调用过程中,遵循值传递机制,是值拷贝传递方式
2 若程序员希望在访问中修改结构体变量的值,可通过结构体指针方式来完成
3 golang中方法作用在指定的数据类型上,及和数据类型绑定,只要是数据类型,均可绑定方法,而不一定是struct
4 方法的访问访问控制规则和函数一样,方法首字母小写,只能在本包中使用,方法首字母大写,可在本包和其他包中访问使用
5 如果一个变量实现了string()这个方法,则fmt.Println() 会默认调用这个变量的string() 进行输出
5 方法和函数的区别
1 调用方式不同
函数的调用方式 函数名(实参列表) 方法的调用方式 变量.方法名(实参列表)
2 传递实参
对于普通函数,接受者为值类型,不能将指针类型的数据直接传递,反之亦然,对于方法,接受者为值类型,可使用指针类型的变量调用方法,反之也可以
package main
import "fmt"
type User struct {
Name string `json:"name"` //定义整形数据,定义tag,其中不能有空格
Age int `json:"age"`
}
func (user *User) Test() {
fmt.Printf("user 的类型为:%T,其对应的值为:%v\n", user, *user)
}
func (user User) Test1() {
fmt.Printf("user 的类型为:%T,其对应的值为:%v\n", user, user)
}
func main() {
user := User{"golang", 13}
user.Test()
user.Test1()
}
结果如下
3 总结
不管是任何形式,真正决定是值拷贝还是地址包括的,是看方法是哪种类型的绑定,若是值类型绑定(user User) 指针类型绑定(user *User),则是指针拷贝。
二 面向对象编程
1 基本介绍
1 Go 语言面向对象编程和其他语言区别
1 golang 也支持面向对象编程(OOP),但和传统的面向对象编程有区别,其并不是纯粹的面向对象语言,所以说golang支持面向对象编程特性是比较准确的
2 golang没有class,golang语言结构体(struct)和其他语言的class有同等的地位,可理解为使用struct实现了OOP特性
3 golang面向对象非常简洁,去掉了传统的继承,重载,构造和析构,隐藏了this和self等
4 golang仍然有面向对象编程的继承,封装和多态,只是其实现方式和其他OOP语言不同,如继承golang没有extends关键字,继承法是通过匿名字段来实现的
5 golang面向对象很优雅,OOP本身就是语言系统的一部分,通过接口interface实现,耦合性第,也非常灵活
golang仍然有面向对象编程的继承,封装和多态,只是实现方式和其他COP语言不同
2 面向对象编程---封装
1 简介
封装就是把抽象出的字段和堆字段的操作封装在一起,数据被保护在内部,程序的其他包只能通过被授权的操作,才能实现对字段的操作
2 封装实现的步骤
1 将结构体字段的首字母大写
2 给结构体所在的包提供一个工厂模式的函数,首字母大写,类似于一个构造函数
3 提供一个首字母大写的Set方法,用于对属性判断和赋值
4 提供一个Get方法,用于获取属性的值
3 封装的好处
1 隐藏实现细节
2 可对数据进行验证,保证安全合理
4 如何体现封装
1 对结构体中的方法进行封装
2 通过方法,包实现封装
3 面向对象--继承
1 简介
类型内嵌和结构体内嵌
结构体允许其成员字段在声明时没有字段名而只有类型,这种形式被称为类型内嵌或者匿名字段类型内嵌
写法如下
package main
import "fmt"
type Data struct {
int //定义匿名字段
float32
bool
}
func main() {
ins := &Data{
10,
3.14,
true,
}
fmt.Println(*ins)
ins1 := Data{
10,
20.0,
false,
}
fmt.Println(ins1)
ins2 := new(Data)
ins2.float32 = 30.20 // 类型内嵌其实也有自己的字段名,只是字段名就是其类型本身,结构体要求字段名必须唯一,因此结构体中同种类型的匿名字段只能有一个
ins2.int = 10
ins2.bool = false
fmt.Println(*ins2)
}
结果如下
2 继承作用和实例
继承可解决代码复用的问题
当多个结构体存在相同的属性和方法时,可以从这些结构体中抽象出基础结构体,该结构体中定义这些相同的属性和方法,其他的结构体不需要重新定义该属性和方法,只需要嵌套基本结构体即可,也就是说,在golang中,如果一个struct嵌套了另一个匿名结构体,那么这个结构体可以直接访问匿名结构体中的字段和方法,从而实现了继承的特性
代码如下
package main
import "fmt"
type Person struct {
//老师和学生都是人类
Name string
Age int
}
type Teacher struct {
//老师具有对应的职业
Person
Occupation string
}
type Student struct {
//学生具有对应的座位号
Person
Seat_Number map[int]int
}
func main() {
student := Student{}
student.Name = "小旺"
student.Age = 12
student.Seat_Number = make(map[int]int)
student.Seat_Number[10] = 20
teacher := Teacher{}
teacher.Name = "王老师"
teacher.Age = 30
teacher.Occupation = "teacher"
fmt.Println(student)
fmt.Println(teacher)
}
结果如下
3 继承讨论
1 结构体可以使用嵌套匿名结构体复用字段和方法
2 当结构体和匿名结构体具有相同的字段或方法时,则采用就近原则,及个该实例最接近的结构体的属性或方法就是其返回的结果的条件
package main
import "fmt"
type Person struct {
//老师和学生都是人类
Name string
Age int
}
type Teacher struct {
//老师具有对应的职业
Person
Occupation string
}
type Student struct {
//学生具有对应的座位号
Person
Seat_Number map[int]int
}
func (p Person) Get() {
fmt.Printf("名字为:%s的年龄为:%d\n", p.Name, p.Age)
}
func (s Student) Get() {
fmt.Printf("此方法只针对学生,名字为:%s的年龄为:%d\n", s.Name, s.Age)
}
func main() {
student := Student{}
student.Name = "小旺"
student.Age = 12
student.Seat_Number = make(map[int]int)
student.Seat_Number[10] = 20
teacher := Teacher{}
teacher.Name = "王老师"
teacher.Age = 30
teacher.Occupation = "teacher"
fmt.Println(student)
fmt.Println(teacher)
student.Get()
teacher.Get()
}
结果如下
3 结构体嵌套入两个匿名结构体时,若两个匿名结构体具有相同的字段和方法,则在访问时,就必须指明匿名结构体名称,否则会出现编译错误
package main
import (
"fmt"
)
type Person struct {
Name string
Age int
}
type Teacher struct {
Name string
Age int
}
type Student struct {
//此结构体继承了上面两个结构体
Person
Teacher
}
func (p Person) Get() {
fmt.Printf("名字为:%s的年龄为:%d\n", p.Name, p.Age)
}
func (s Teacher) Get() {
fmt.Printf("此方法只针对学生,名字为:%s的年龄为:%d\n", s.Name, s.Age)
}
func main() {
student := Student{}
student.Teacher.Name = "小王"
student.Teacher.Age = 20
student.Person.Get()
student.Teacher.Get()
}
结果如下
4 如果一个struct嵌套了一个有名称的结构体,此称为组合,若组合关联,那么在访问结构体字段或方法时,就必须有结构体的名称
5 嵌套名结构体后,也可以创建结构体变量时,直接指定各个匿名结构体字段的值
4 面向对象编程之多态
变量(实例)具有多种形态,面向对象的第三大特征,在Go语言中,多态特征是通过接口实现的,可以按照统一的接口来调用不同的实现,这时接口就会呈现不同的形态
三 接口(interface)
1 基本介绍
interface 类型可以定义一组方法,但是这些不需要实现,并且interface不能包含任何变量,到某个自定义类型的时候要使用,根据具体情况将这些方法写出来,接口本身调用方法和实现均需要遵守一种协议,大家按照统一的方法来命名参数类型和数量来协调逻辑处理的过程
Go语言中使用组合实现独享特性的描述,对象的内部使用结构体内嵌组合对象应该具有的特性,对外通过接口暴露能使用的特性。
2 声明接口
接口是双方约定的一种合作协议,是一种类型,也是一种抽象结构,不会暴露所有含数据的格式,类型及结构
1 基本语法
type 接口名称 interface {
method1 (参数列表) 返回值列表
method2 (参数列表) 返回值列表
}
func (t 自定义类型) method1(参数列表) 返回值列表 {
方法体
return 返回数据
}
接口名称: 使用type将接口定义为自定义的类型名,Go语言的接口命名时,一般在单词后面加上er,如写操作叫做writer,关闭叫做closer等
method1: 当方法名首字母是大写时,且这个接口类型名首字母也是大写时,这个方法可以被接口所在的包(package)之外的代码访问
参数列表,返回值列表:参数列表和返回值列表中的参数变量可被忽略
2 小结
1 接口中所有的方法都没方法体,及接口的方法都是没实现的方法,接口体现了程序设计的多态和高内聚低耦合的思想
2 golang中的接口,不需要显示实现,只要一个变量,含有接口类型中的所有方法,那么这个变量就实现了这个接口,因此,golang中没有implement这样的关键字
2 接口实现的条件
接口定义后,需要实现调用接口,调用方能正确编译并通过使用接口,接口的实现需要遵循两条规则才能让接口可用。
1 接口被实现的条件一
接口的方法与实现接口的类型方法一致,及在类型中添加与接口签名一致的方法就可以实现该方法,签名包括方法中的名称、参数列表、返回值列表,其实现接口中方法的名称,参数列表,返回参数列表中的任意一项和接口要实现的方法不一致,那么就扣的这个方法就不能被实现。
package main
import "fmt"
type DataWriter interface {
WriteData(data interface{}) error
}
type file struct {
}
func (d *file) WriteData(data interface{}) error {
fmt.Println("WriteData:", data)
return nil
}
func main() {
f := new(file) //实例化file
var write DataWriter // 声明一个接口,用于读取数据
write = f //将接口赋值f,也就是file类型,及关联接口和实例,虽然其变量类型不一致,但writer是一个接口,且f已经完全实现了DataWriter()的所有方法,因此赋值成功
write.WriteData("data")
}
结果如下
2 接口中的所有方法均被实现
当一个接口有多个方法时,只有这些方法都被实现了,接口才能被正确编译并使用
Go 语言实现的接口是隐式的,无需让实现接口的类型写出实现了那些接口,这种设计被称为非侵入式设计。
package main
import (
"fmt"
)
type Test interface {
Start()
Stop()
}
type A struct {
X string
Y int
}
type B struct {
A
}
type C struct {
}
func (a A) Start() {
fmt.Println(a.X)
}
func (a A) Stop() {
fmt.Println(a.Y)
}
func (b B) Start() {
fmt.Println(b.X)
}
func (b B) Stop() {
fmt.Println(b.Y)
}
func (c C) Usb(usb Test) {
usb.Start()
usb.Stop()
}
func main() {
a := A{"golang", 20}
b := B{}
b.X = "goland"
b.Y = 10
c := C{}
//接口调用对应实例a
c.Usb(a)
fmt.Println("-----------------------------")
c.Usb(b)
}
结果如下
3 类型和接口的关系
1 一个类型可以实现多个接口
package main
import (
"fmt"
)
type DataWriter interface {
WriteData(data interface{}) error
}
type DataRead interface {
ReadData(data interface{}) error
}
type file struct {
}
// 定义实现该方法的结构体
func (d *file) WriteData(data interface{}) error {
fmt.Println("WriteData:", data)
return nil
}
func (d *file) ReadData(data interface{}) error {
fmt.Println("READ DATA", data)
return nil
}
func main() {
f := new(file) //实例化file
var write DataWriter // 声明一个接口,用于读取数据
write = f //将接口赋值f,也就是file类型,及关联接口和实例,虽然其变量类型不一致,但writer是一个接口,且f已经完全实现了DataWriter()的所有方法,因此赋值成功
write.WriteData("write data")
var read DataRead
//
read = f
read.ReadData("read data")
}
上述中file类型实现了DdataWriter 和 DataRead 的两个接口,这两个接口之间没联系。
2 多个类型可实现相同的接口
一个接口的方法,不一定需要由一个类型完全实现,接口的方法可以通过在类型中嵌套其他类型或结构体实现,也就是说,使用者并不关系某个接口的方法是通过一个类型完全实现的,还是通过多个结构嵌套到一个结构体中拼凑起来共同实现的。
package main
import (
"fmt"
)
type DataWriter interface {
WriteData(data interface{}) error
ReadData(data interface{}) error
}
type fileread struct {
}
type file struct {
fileread //此类型包含此类型
}
// 定义实现该方法的结构体
func (d *file) WriteData(data interface{}) error {
fmt.Println("WriteData:", data)
return nil
}
func (d *fileread) ReadData(data interface{}) error { // 此类型实现了此方法
fmt.Println("READ DATA", data)
return nil
}
func main() {
f := new(file) //实例化file
var write DataWriter // 声明一个接口,用于读取数据
write = f //将接口赋值f,也就是file类型,及关联接口和实例,虽然其变量类型不一致,但writer是一个接口,且f已经完全实现了DataWriter()的所有方法,因此赋值成功
write.WriteData("write data")
write.ReadData("read data")
}
结果如下
4 接口的嵌套组合
在Go语言中,不仅结构体体和结构体体之间可以嵌套,接口和接口之间也可以通过创造出新的接口
接口与接口嵌套组合而形成了新接口,只要接口的所有方法都被实现,则这个接口中所有嵌套接口的方法均可以被调用
package main
import (
"fmt"
)
type Data interface {
DataWriter
DataRead
}
type DataWriter interface {
WriteData(data interface{}) error
}
type DataRead interface {
ReadData(data interface{}) error
}
type file struct {
}
// 定义实现该方法的结构体
func (d *file) WriteData(data interface{}) error {
fmt.Println("WriteData:", data)
return nil
}
func (d *file) ReadData(data interface{}) error { // 此类型实现了此方法
fmt.Println("READ DATA", data)
return nil
}
func main() {
f := new(file) //实例化file
var data Data // 声明一个接口,用于读取数据
data = f //将接口赋值f,也就是file类型,及关联接口和实例,虽然其变量类型不一致,但writer是一个接口,且f已经完全实现了DataWriter()的所有方法,因此赋值成功
data.WriteData("write data")
data.ReadData("read data")
}
结果如下
5 定义接口和注意细节
1 接口本身不能创建实例,但是可以指向一个实现了接口的自定义类型的变量
2 接口中所有的方法都没有方法体,及没实现该方法
3 在golang中,一个自定义类型需要将某个接口的所有方法倒都实现,我们才说这个自定义接口实现了该方法
4 一个自定义类型只有实现了某个接口,才能将该自定义类型赋值给接口类型
5 只要是自定义类型,都可以实现接口,而不仅仅是结构体
6 一个自定义类型可以实现多个接口
7 golang接口中不能有任何变量
8 一个接口可以继承多个个别接口,这时如果要实现接口A,则必须实现接口B和接口C
9 interface 类型默认是一个指针,如果没有对interface初始化就使用,则会出现输出nil
10 空接口interface{} 没有实现任何方法,所以所有类型都实现了空接口
6 应用
1 基本排序处理方式
package main
import (
"fmt"
"math/rand"
"sort"
"time"
)
func Test(n int) []int {
var l1 []int
for i := 1; i <= n; i++ {
rand.NewSource(time.Now().UnixNano())
num := rand.Intn(500)
l1 = append(l1, num)
}
return l1
}
func main() {
var l1 []int
l1 = Test(10)
fmt.Println("排序前的值:", l1)
sort.Ints(l1)
fmt.Println("排序后的值:", l1)
}
结果如下
2 通过接口实现
官网得知,欲使用Sort接口,需实现三个方法
Len() 获取数据长度,返回为int 类型数据
Less() 元素比大小,通过i,j 进行比较返回bool
Swap() 元素交换,通过i,j 交换来处理元素
具体代码实现如下
package main
import (
"fmt"
"math/rand"
"sort"
"time"
)
// 此处用于构造元数据
type Base struct {
Name string
Age int
}
type SliceBase []Base //此方法需实现对应的interface规定的功能,其才能调用具体的interface sort.Sort()接口
// 此函数用于返回列表,其列表中的元素是上述struct中的元素
func CreateSlice(n int, base SliceBase) []Base {
for i := 1; i <= n; i++ {
rand.NewSource(time.Now().UnixNano())
num := rand.Intn(100)
value := Base{"goland" + string(num), num}
base = append(base, value)
}
return base
}
func (t SliceBase) Len() int {
return len(t) //此处返回对应类型长度即可
}
func (t SliceBase) Less(i, j int) bool {
if t[i].Age > t[j].Age { //此处表示降序排列,且此处为age的比较
return true
} else {
return false
}
}
func (t SliceBase) Swap(i, j int) {
t[i], t[j] = t[j], t[i] //此处标识交换
}
func main() {
var l1 SliceBase
l1 = CreateSlice(10, l1)
fmt.Println("排序前的值:", l1)
sort.Sort(l1)
fmt.Println("排序后的值:", l1)
}
结果如下
7 类型断言
Go 语言使用接口端来将接口转换成另一个接口,也可以将接口转换成另外一种类型。
1 类型断言的格式
t:=i.(T)
i 代表接口变量
T 代表转换的目标类型
t 代表转换后变量如果i 没有完全实现T接口的方法,此语句将会导致触发宕机,因此可通过 t,ok:=i.(T),这种写法下,如果发生接口未实现,则将会把ok置为false,t置为T类型的值为0,ok的含义是i接口是否实现T类型的结果。
2 基本使用场景
应用场景:由于接口是一般类型,不知道具体类型,如果要转换成具体类型,就需要断言
package main
import "fmt"
// 此处用于构造元数据
func main() {
var a interface{}
var f float64 = 10.00
a = f
if y, ok := a.(float64); ok {
fmt.Println(y)
} else {
fmt.Println("失败")
}
}
结果如下
package main
import "fmt"
// 此处用于构造元数据
type Base struct {
Name string
Age int
}
func main() {
var a interface{}
base := Base{"golang", 30}
a = base
var b Base
b = a.(Base) //此处若不使用断言,则会报错
fmt.Println(b)
}
在进行断言时,需要确保类型匹配,否则会报错
可进行处理,不报panic
3 综合应用
package main
import (
"fmt"
)
type DataWriter interface {
WriteData(data interface{}) error
}
type DatawRead interface {
ReadData(data interface{}) error
}
type readwritefile struct {
}
// 定义实现该方法的结构体
func (d *readwritefile) WriteData(data interface{}) error {
fmt.Println("WriteData:", data)
return nil
}
func (d *readwritefile) ReadData(data interface{}) error { // 此类型实现了此方法
fmt.Println("READ DATA", data)
return nil
}
type readfile struct {
}
func (d *readfile) ReadData(data interface{}) error { // 此类型实现了此方法
fmt.Println("read dta:", data)
return nil
}
func main() {
//创建实例
file := map[string]interface{}{
"readfile": new(readfile),
"readwritefile": new(readwritefile),
}
//遍历映射
for name, obj := range file {
fmt.Println("-------------------")
rw, ok := obj.(DataWriter)
if ok {
rw.WriteData("write data")
fmt.Println(name)
}
r, ok := obj.(DatawRead)
if ok {
r.ReadData("read data")
fmt.Println(name)
}
}
}
结果如下
8 空接口类型 interface{}
1 简介
空接口是接口类型的特殊形式,空接口没有任何方法,因此任何类型都无需实现空接口,从实现的角度看,任何值都满足这个接口的需求,因此空接口类型可保存任何值,也可以从空接口中取出原值。
空接口的内部实现了保存对象的类型和指针,使用空接口保存一个数据的过程比直接使用数据对应类型的变量保存稍慢。
package main
import "fmt"
func main() {
var str interface{}
str = 1
fmt.Println(str)
str = "hello"
fmt.Println(str)
str = false
fmt.Println(str)
}
结果如下
2 空接口获取值
package main
import "fmt"
func main() {
var a int = 1
var i interface{} = a
fmt.Println(i)
var b int = i
fmt.Println(b)
}
结果如下
编译器返回接口,不能将i 变量视为int类型赋值给b。
修改结果如下
package main
import "fmt"
func main() {
var a int = 1
var i interface{} = a
fmt.Println(i)
var b int = i.(int)
fmt.Println(b)
}
结果如下
3 空接口值的比较
空接口在保存不同的值后,可以和其他变量值一样使用"==" 进行比较操作,空接口比较有以下几种特性
1 类型不同的空接口之间的比较结果不同
package main
import "fmt"
func main() {
var a interface{} = 100
var b interface{} = "abc"
fmt.Println(a == b)
}
结果如下
2 不能比较空接口中的动态值
package main
import "fmt"
func main() {
var c interface{} = []int{10}
var d interface{} = []int{20}
fmt.Println(c == d)
}
结果如下
9 接口和结构体 优缺点
1 接口的优点
1 结构体实现继承过程中不能选择性继承,而当基类结构体需要其他扩展功能不被子类继承时,则需要使用接口来补充
2 接口比继承更灵活 接口一定程度上实现了代码的解耦
2 接口和继承解决的问题
1 继承的价值在于解决代码中的复用性和可维护性问题
2 接口的价值在于设计好各种规范,让其他自定义类型实现这些方法接口提现的多态特性
1 多态参数
2 多态数组
四 文件介绍
1 简介
文件,数据源,其主要作用就是保存数据,其就是数据源
文件在程序中以流的方式操作的
流:数据在数据源和程序之间经历的路径
输入流:从数据源到程序的路径,读取文件
输出流:从程序到数据源的路径,写文件
2 基本打开文件操作
1 os.Open
1 基本形式
func Open(name string) (file *File, err error)
Open打开一个文件用于读取。如果操作成功,返回的文件对象的方法可用于读取数据;对应的文件描述符具有O_RDONLY模式。如果出错,错误底层类型是*PathError。
2 os.openFile
1 基本形式
func OpenFile(name string, flag int, perm FileMode) (*File, error) {
testlog.Open(name)
f, err := openFileNolog(name, flag, perm)
if err != nil {
return nil, err
}
f.appendMode = flag&O_APPEND != 0
return f, nil
}
2 相关解析如下
name :表示文件路径
flag:表示文件打开模式
const (
O_RDONLY int = syscall.O_RDONLY // 只读模式打开文件
O_WRONLY int = syscall.O_WRONLY // 只写模式打开文件
O_RDWR int = syscall.O_RDWR // 读写模式打开文件
O_APPEND int = syscall.O_APPEND // 写操作时将数据附加到文件尾部
O_CREATE int = syscall.O_CREAT // 如果不存在将创建一个新文件
O_EXCL int = syscall.O_EXCL // 和O_CREATE配合使用,文件必须不存在
O_SYNC int = syscall.O_SYNC // 打开文件用于同步I/O
O_TRUNC int = syscall.O_TRUNC // 如果可能,打开时清空文件
)
perm FileMode:解析结果如下
const (
// 单字符是被String方法用于格式化的属性缩写。
ModeDir FileMode = 1 << (32 - 1 - iota) // d: 目录
ModeAppend // a: 只能写入,且只能写入到末尾
ModeExclusive // l: 用于执行
ModeTemporary // T: 临时文件(非备份文件)
ModeSymlink // L: 符号链接(不是快捷方式文件)
ModeDevice // D: 设备
ModeNamedPipe // p: 命名管道(FIFO)
ModeSocket // S: Unix域socket
ModeSetuid // u: 表示文件具有其创建者用户id权限
ModeSetgid // g: 表示文件具有其创建者组id的权限
ModeCharDevice // c: 字符设备,需已设置ModeDevice
ModeSticky // t: 只有root/创建者能删除/移动文件
// 覆盖所有类型位(用于通过&获取类型位),对普通文件,所有这些位都不应被设置
ModeType = ModeDir | ModeSymlink | ModeNamedPipe | ModeSocket | ModeDevice
ModePerm FileMode = 0777 // 覆盖所有Unix权限位(用于通过&获取类型位)
)
3 文件读取操作
1 不带缓冲的文件读取ioutil.ReadFile
func ReadFile(filename string) ([]byte, error) {
f, err := os.Open(filename)
if err != nil {
return nil, err
}
defer f.Close()
package main
import (
"fmt"
"io/ioutil"
)
func main() {
str := "D:\\go\\project\\src\\gocode\\project01\\test\\test01.go"
l1, err := ioutil.ReadFile(str)
if err != nil {
fmt.Println(err)
} else {
fmt.Printf("%s", l1) //此处输出类型为字符串
}
}
结果和上述相同
2 带缓冲的文件读取bufio
package main
import (
"bufio"
"fmt"
"io"
"os"
)
func main() {
str := "D:\\go\\project\\src\\gocode\\project01\\test\\test01.go"
file, err := os.Open(str) //此处获取到一个file句柄,一个err错误,若有错误返回,则此处不为nil
if err != nil {
fmt.Println(err)
} else {
reader := bufio.NewReader(file) // 此处用于生成一个缓冲,默认是4096,其读取数据选择部分读取
for {
str, err := reader.ReadString('\n') // 此处是通过\n进行分割的
if err == io.EOF { // 此处若是io.EOF,则表明其进入文件底部
break
}
fmt.Println(str)
}
}
defer file.Close()
}
结果如下
3 写入操作
1 基本写操作 file.WriteString
package main
import (
"fmt"
"os"
)
func main() {
str := "D:\\go\\project\\src\\gocode\\project01\\test\\test02.txt"
file, err := os.OpenFile(str, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0640)
if err != nil {
fmt.Println(err)
} else {
_, err := file.WriteString(
`package test
type student struct {
Name string
Score float64
}
func NewStudent(n string, s float64) *student {
return &student{
Name: n,
Score: s,
}
}`)
if err == nil {
fmt.Println("数据写入成功")
} else {
fmt.Println("数据写入失败")
}
}
defer file.Close()
}
结果如下
2 带缓冲的数据写入
package main
import (
"bufio"
"fmt"
"os"
)
func main() {
str := "D:\\go\\project\\src\\gocode\\project01\\test\\test02.txt"
file, err := os.OpenFile(str, os.O_CREATE|os.O_WRONLY, 0777)
if err != nil {
fmt.Println(err)
return
} else {
writer := bufio.NewWriter(file)
for i := 'a'; i <= 'z'; i++ {
_, err := writer.WriteString("mysql" + string(i) + "\n")
if err != nil {
fmt.Println(err)
}
}
err := writer.Flush()
if err != nil {
fmt.Println(err)
} else {
fmt.Println("刷新成功")
}
}
}
结果如下
4 文件复制操作
1 基本形式复制文件
package main
import (
"fmt"
"io/ioutil"
)
func main() {
str1 := "D:\\go\\project\\src\\gocode\\project01\\test\\test02.txt"
str2 := "D:\\go\\project\\src\\gocode\\project01\\test\\test03.txt"
data, err := ioutil.ReadFile(str1) //此处使用读文件,然后再写文件的方式完成对应操作
if err != nil {
fmt.Println("read file error", err)
return
}
err = ioutil.WriteFile(str2, data, 0666)
if err != nil {
fmt.Println(err)
} else {
fmt.Println("文件写入成功")
}
}
结果如下
2 Copy 方式复制文件
func Copy(dst Writer, src Reader) (written int64, err error)
将src的数据拷贝到dst,直到在src上到达EOF或发生错误。返回拷贝的字节数和遇到的第一个错误。
对成功的调用,返回值err为nil而非EOF,因为Copy定义为从src读取直到EOF,它不会将读取到EOF视为应报告的错误。如果src实现了WriterTo接口,本函数会调用src.WriteTo(dst)进行拷贝;否则如果dst实现了ReaderFrom接口,本函数会调用dst.ReadFrom(src)进行拷贝。
package main
import (
"fmt"
"io"
"os"
)
func CopyTest(srcfilepath, desfilepath string) (n int64, err error) {
srcfile, err := os.Open(srcfilepath)
if err != nil {
fmt.Println(err)
return
}
defer srcfile.Close()
desfile, err := os.Create(desfilepath)
if err != nil {
fmt.Println(err)
return
}
defer desfile.Close()
return io.Copy(desfile, srcfile)
}
func main() {
srcfilepath := "D:\\go\\project\\src\\gocode\\project01\\test\\test02.txt"
desfilepath := "D:\\go\\project\\src\\gocode\\project01\\test\\test04.txt"
n, err := CopyTest(srcfilepath, desfilepath)
if err != nil {
fmt.Println(err)
} else {
fmt.Println("成功", n)
}
}
结果如下
结果如下
5 判断文件是否存在
golang判断文件或文件夹是否存在的方法是使用os.Stat() 函数返回错误进行判断
1 若返回错误为nil,表明文件或文件夹存在
2 若返回类型使用os.IsNotExist() 判断为true,则表明文件或文件夹不存在
3 若返回错误为其他类型,则不确定是否存在
func PathExists(path string) (bool, error) {
_, err := os.Stat(path) //此处返回错误为nil,则表示存在文件或文件夹
if err == nil {
return true, nil
}
if os.IsNotExist(err) { //若此处返回使用此判断为true,则表示为不存在
return false, nil
}
return false, nil
}
6 golang 命令行参数
1 os.Args 命令处理
os.Args 是一个string的切片,用来存储所有的命令行参数
package main
import (
"fmt"
"os"
)
func main() {
param1 := os.Args[1] // 此处表示获取命令行的第一个参数
param2 := os.Args[2] // 此处表示获取命令行的第二个参数
fmt.Printf("第一个参数为:%s,第二个参数为:%s", param1, param2)
}
2 flag 机制
flag包用来解析命令行参数
说明: 前面的方式比较原生,对解析参数不方便,特别是带有指定参数形式的命令行
如 mysql.exe -p 5000 -u root -proot -h localhost ,对于此种形式,Go语言提供了flag包,可以方便解析命令行参数,而且其顺序书写也可以随意。
相关代码如下
package main
import (
"flag"
"fmt"
)
var (
user string
password string
port int
hostip string
db string
)
func main() {
flag.StringVar(&user, "u", "", "用户名,默认为空")
flag.StringVar(&password, "p", "", "密码,默认为空")
flag.IntVar(&port, "P", 3306, "端口,默认为3306")
flag.StringVar(&hostip, "h", "localhost", "远端IP地址,默认为localhost")
flag.StringVar(&db, "db", "test", "数据库,默认为test")
flag.Parse() // 此处表示方法转换,必须存在
fmt.Printf("user=%v,password=%v,port=%v,hostip=%v,db=%v\n", user, password, port, hostip, db)
}
结果如下
五 Json简介
1 简介
2 JSON 序列化操作
package main
import (
"encoding/json"
"fmt"
)
type A struct {
Name string `json:"name"`
Age int `json:"age"`
Sal float32 `json:"sal"`
}
// 结构体反序列化操作
func SerA() {
a := A{
Name: "golang",
Age: 13,
Sal: 30.00,
}
data, err := json.Marshal(a)
if err != nil {
fmt.Println(err)
} else {
fmt.Printf("%s\n", data)
}
}
//Map 序列化
func SerM() {
m := make(map[string]interface{})
m["name"] = "golang"
m["age"] = 40
m["sal"] = 100.0
data, err := json.Marshal(m)
if err != nil {
fmt.Println(err)
return
} else {
fmt.Printf("%s\n", data)
}
}
//切片序列化
func SerS() {
var l1 []map[string]interface{}
m := make(map[string]interface{})
m["name"] = "golang"
m["age"] = 40
m["sal"] = 100.0
m1 := make(map[string]interface{})
m1["name"] = "goland"
m1["age"] = 10
m1["sal"] = 200.0
l1 = append(l1, m)
l1 = append(l1, m1)
data, err := json.Marshal(l1)
if err != nil {
fmt.Println(err)
return
} else {
fmt.Printf("%s\n", data)
}
}
func main() {
SerA()
SerM()
SerS()
}
结果如下
3 JSON 反序列化
将Json 字符串反序列化成对应的数据类型
package main
import (
"encoding/json"
"fmt"
)
type A struct {
Name string `json:"name"`
Age int `json:"age"`
Sal float32 `json:"sal"`
}
// 结构体反序列化操作
func SerA() []byte {
a := A{
Name: "golang",
Age: 13,
Sal: 30.00,
}
data, err := json.Marshal(a)
if err != nil {
fmt.Println(err)
} else {
}
return data
}
//Map 序列化
func SerM() []byte {
m := make(map[string]interface{})
m["name"] = "golang"
m["age"] = 40
m["sal"] = 100.0
data, err := json.Marshal(m)
if err != nil {
fmt.Println(err)
} else {
}
return data
}
func DesA(by []byte) A {
a := A{}
err := json.Unmarshal(by, &a)
if err != nil {
fmt.Println(err)
} else {
}
return a
}
func DesM(by []byte) map[string]interface{} {
m := make(map[string]interface{})
err := json.Unmarshal(by, &m)
if err != nil {
fmt.Println(err)
} else {
}
return m
}
func main() {
b := SerA()
b1 := DesA(b)
fmt.Println(b1)
m := SerM()
m1 := DesM(m)
fmt.Println(m1)
}
结果如下
4 注意事项
1 在反序列化一个json字符串时,要确保反序列化后的数据类型和原来序列化前的数据类型一致
2 如果json字符串是通过程序获取的,则不需要对其进行转义和处理
有疑问加站长微信联系(非本文作者)