结构体
- Golang也支持面向对象编程,但是和传统的面向对象编程有区别,并不是纯粹的面向对象语言
- Golang没有类,Go语言的结构体和其它编程语言的类有同等的地位,可以理解为Golang是基于struct来实现OOP特性的
- Golang面向对象编程非常简单,去掉了传统OOP语言的继承,方法重载,析构函数和构造函数,隐藏的this指针等等
- Golang实现继承是通过匿名字段来实现的
定义结构体的四种方式
- var person Person
- var person Person = Person{}
- var person *Person = new(Person)
- var person *Person = &Person{}
注意第三种和第四种方式返回的是结构体指针
结构体指针访问字段的标准方式应该是:(*结构体指针).字段名
go做了一个优化,也支持结构体指针.字段名
结构体的注意事项
- 结构体的所有字段在内存中是连续的
- 结构体是用户单独定义的类型,和其它类型进行转换时需要有完全相同的字段(字段、个数、类型)
- 结构体进行type重新定义(相当于起别名),GoLand认为新的数据类型,但是相互之间可以强转
- struct的每个字段上,可以写上一个tag,改tag可以通过反射机制获取,常见的使用场景是序列化和反序列化
字段(属性)注意事项
- 字段声明语法同变量,实例:字段名 字段类型
- 字段的类型可以为:基本类型、数组、引用类型
- 不同结构体变量的字段是独立,互不影响,一个结构变量字段的更改,不影响另一个
- 在创建一个结构体变量后,如果没有给字段赋值,都对应一个默认值
- 布尔值默认为false
- 字符串是“ ”
- 指针,slice,map默认值为nil
- 数组类型的默认值和它的元素类型相关
方法
import "fmt"
type A struct {
Num int
}
func(a A) Tefst() {
a.Num = 100
fmt.Println(a.Num)
}
func main() {
var a method.A
a.Test()
}
方法的调用和传参机制
方法传参机制和函数基本一样,不一样的地方是方法调用时会将调用方法的变量,当做实参传递给方法
方法的定义
func (receiver type) methodName (参数列表) (返回值列表) {
方法体
return 返回值
}
方法注意事项
- 结构体类型是值类型,在方法调用中,遵守值类型的传递机制,是值拷贝传递方式
- 如果希望在方法中修改结构体变量的值,可以通过结构体指针的方式来处理
- Golang中的方法作用在指定的数据结构上的因此自定义类型,都可以有方法,而不仅仅是struct
- 方法的访问范围控制的规则和函数一样方法名首字母小写只能在本包中访问,方法首字母大写,可以在本包和其它包访问
- 如果一个变量实现了String()这个方法,那么fmt.Println默认会调用这个变量的String()进行输出
方法和函数的区别
- 调用方式不一样
- 函数的调用方式:函数名(参数列表)
- 方法的调用方式:变量.方法名(参数列表)
- 对于普通函数.接受者为值类型时,不能将指针 的数据直接传递,反之亦然
- 对于方法,接受者为值类型时,可以直接使用指针类型的变量调用方法,反过来同样也可以
**注意:**不管调用形式如何,真正决定是值拷贝的还是地址拷贝,要看方法和哪个类型绑定;如果是和值类型绑定,比如(p Person)则是值拷贝;如果是和指针类型绑定比如是(p *Person)则是地址拷贝
工厂设计模式
一个结构体的声明是这样的:
package model
type Student struct {
Name string...
}
因为这里的Student的首字母为大写,如果我们想在其他包中创建Student实例,引入model包后可以直接创建,但是当首字母为小写的时候,要想创建实例可以借助工厂模式
package factory
type student struct {
Name string
Score float32
}
func NewStudent(name string, score float32) *student {
return &student{
Name : name,
Score: score,
}
}
func main() {
var stu = factory.NewStudent("阿修罗", 88.9)
fmt.Println(*stu)
}
当结构体的字段为小写时可以通过以下方法获取
type student struct {
Name string
score float32
}
func (s *student) GetScore() float32 {
return s.score
}
func main() {
var stu = factory.NewStudent("阿修罗", 88.9)
fmt.Printf("%v成绩为%v",stu.Name, stu.GetScore())
}
三大特征
封装
封装就是把抽象出的字段和对字段的操作封装在一起,数据被保护在内部,程序的其他包只有通过被授权的操作才能对字段进行操作
如何实现封装?
- 对结构体中的属性进行封装
- 通过方法,包实现封装
封装的实现步骤
func (var 结构体类型名) SetXxx(参数列表)(返回值列表) {
var.字段 = 参数
}
func (var 结构体类型名) GetXxx() {
return var.字段;
}
特殊说明:在Golang开发并没有特别强调封装
快速入门
type person struct {
Name string
age int
sal float32
}
// 写一个工厂模式的函数.相当于构造函数
func NewPerson(name string) *person {
return &person{
Name: name,
}
}
// 编写Get个Set方法
func (p *person) SetAge(age int) {
if age > 0 && age < 100 {
p.age = age
} else {
fmt.Println("年龄超出正常范围...")
}
}
func (p *person) GetAge() int {
return p.age
}
func (p *person) SetSal(sal float32) {
if sal >=3000 && sal <=30000 {
p.sal = sal
} else {
fmt.Printf("薪水不正常,贪污了...")
}
}
func (p *person) GetSal() float32 {
return p.sal
}
func main() {
p := encapsulate.NewPerson("阿修罗")
p.SetAge(15)
p.SetSal(15000)
fmt.Println(p.Name, "age = ", p.GetAge(), "sale = ", p.GetSal())
}
继承
在Golang中,如果一个struct嵌套了另一个匿名结构体,那么这个结构体可以直接访问匿名结构体的字段和方法从而实现继承特性
嵌套匿名结构体的基本语法
type Goods struct {
Name string
Price int
}
type Book struct {
// 嵌套匿名结构体
Goods
Writer string
}
入门案例
type Student struct {
Name string
Age int
score int
}
func (stu *Student) ShouInfo(){
fmt.Printf("学生名=%v 年龄=%v 成绩=%v\n", stu.Name, stu.Age, stu.score)
}
func (stu *Student) SetScore(score int) {
stu.score = score
}
type Pupil struct {
Student
}
func (p *Pupil) Testing() {
fmt.Println("小学生正在考试...")
}
type Grandute struct {
Student
}
func (g *Grandute) testing() {
fmt.Println("大学生正在考试")
}
func main() {
pupil := &inheritance.Pupil{}
pupil.Student.Name = "阿修罗"
pupil.Student.Age = 1500
pupil.Testing()
pupil.Student.SetScore(70)
pupil.Student.ShouInfo()
}
继承注意细节
func main() {
pupil := &inheritance.Pupil{}
pupil.Name = "阿修罗"
pupil.Age = 1500
pupil.Testing()
pupil.SetScore(70)
pupil.ShouInfo()
}
// 简化之前
func main() {
pupil := &inheritance.Pupil{}
pupil.Student.Name = "阿修罗"
pupil.Student.Age = 1500
pupil.Testing()
pupil.Student.SetScore(70)
pupil.Student.ShouInfo()
}
多态
案例:给USB数组中,存放Phone结构体和Camera结构体变量
type Usb interface {
Start()
Stop()
}
type Phone struct {
Name string
}
func (p Phone) Start() {
fmt.Println("手机开始工作...")
}
func (p Phone) Stop() {
fmt.Printf("手机停止工作...")
}
type Camera struct {
Name string
}
func (c Camera) Start() {
fmt.Println("相机开始工作...")
}
func (c Camera) Stop() {
fmt.Printf("相机停止工作...")
}
func main() {
var usbArr [3]poly.Usb
usbArr[0] = poly.Phone{"vivo"}
usbArr[1] = poly.Phone{"华为"}
usbArr[2] = poly.Camera{"索尼"}
fmt.Println(usbArr)
}
接口
入门
type Usb interface {
Start()
Stop()
}
type Phone struct {
}
func (p Phone) Start() {
fmt.Println("手机开始工作...")
}
func (p Phone) Stop() {
fmt.Printf("手机停止工作...")
}
type Camera struct {
}
func (c Camera) Start() {
fmt.Println("相机开始工作...")
}
func (c Camera) Stop() {
fmt.Printf("相机停止工作...")
}
type Computer struct {
}
func (c Computer) Working(usb Usb) {
usb.Start()
usb.Stop()
}
func main() {
computer := _interface.Computer{}
phone := _interface.Phone{}
camera := _interface.Camera{}
computer.Working(phone)
computer.Working(camera)
}
基本介绍
interface类型可以定义一组方法,但是这些不需要实现,并且interface不能包含任何变量,到某个自定义类型要使用的时候,在根据具体情况把这些方法写出来
基本语法
type 接口名 interface{
methods1(参数列表) 返回值列表
methods2(参数列表) 返回值列表
}
func (t 自定义类型) methods1(参数列表) 返回值列表{
...
}
接口注意事项和细节
- 接口本身不能创建实例,但是可以指向一个实现了该接口的自定义类型的变量
- 接口中所有的方法都没有方法体,即都是没有实现的方法
- 在Golang中一个自定义数据类型需要将某个接口的所有方法都实现,我们说这个自定义类型实现了该接口
- 一个自定义类型只有实现了某个接口才能将该自定义类型的实例赋给接口类型
- 只要是自定义数据类型,就可以实现接口,不仅仅是结构体类型
- 一个自定义类型可以实现多个接口
- Golang接口中不能有任何变量
- 一个接口可以继承多个别的接口,这时如果要实现A接口,也必须要将B,C的接口方法全部实现
- interface类型默认是一个指针(引用类型),如果没有对interface初始化就使用那么会输出为nil
- 空接口interface{}没有任何方法,所以所有类型都实现了空接口
断言
入门
func main() {
var a interface{}
var point Point = Point{1, 2}
a = point
b = a.(point) // 类型断言
fmt.Println(b)
}
b = a.(point)就是类型断言,表示判断a是否指向Point类型的变量,如果是就转成Point类型并赋值给b变量,否则报错
基本介绍
类型断言,由于接口是一般类型,不知道具体类型,如果要转成具体类型,就需要使用类型断言
var t float32
var x interface{}
x = t
y := x.(float32)
对前面的Usb接口案例进行改进:给Phone结构体增加一个特有的方法call(),当Usb接口接收的是phone变量时,需要调用call方法
package assertion
import "fmt"
type Usb interface {
Start()
Stop()
}
type Phone struct {
Name string
}
func (p Phone) Start() {
fmt.Println("手机开始工作...")
}
func (p Phone) Stop() {
fmt.Printf("手机停止工作...")
}
func (p Phone) Call() {
fmt.Printf("打电话...")
}
type Camera struct {
Name string
}
func (c Camera) Start() {
fmt.Println("相机开始工作...")
}
func (c Camera) Stop() {
fmt.Printf("相机停止工作...")
}
type Computer struct {
}
func (c Computer) Working(usb Usb) {
usb.Start()
if phone, ok := usb.(Phone); ok {
phone.Call()
}
usb.Stop()
}
func main() {
var usbArr [2]assertion.Usb
usbArr[0] = assertion.Phone{"vivo"}
usbArr[1] = assertion.Camera{"索尼"}
var computer assertion.Computer
for _, v := range usbArr{
computer.Working(v)
fmt.Println()
}
}
文件操作
源:数据在数据源(文件)和程序(内存)之间经历的路径
输入流:数据从数据源(文件)到程序(内存的路径)
输出流:数据从程序(内存)到数据源(文件)的路径
打开文件
func OpemFile(name string) {
file, err := os.Open(name)
if err != nil {
fmt.Println("open file err =", err)
}
fmt.Printf("file = %v", file)
err1 := file.Close()
if err != nil {
fmt.Println("close file err=", err1)
}
}
func main() {
file.OpemFile("F:/test.txt")
}
读文件操作应用实例
func OpemFile(name string) {
file, err := os.Open(name)
if err != nil {
fmt.Println("open file err =", err)
}
reader := bufio.NewReader(file)
for {
str, err1 := reader.ReadString('\n')
if err1 == io.EOF { // io.EOF表示文件的结尾
break
}
// 输出内容
fmt.Print(str)
}
fmt.Println("文件读取结束...")
defer file.Close()
}
func main() {
file.OpemFile("F:/test.txt")
}
func OpenFileByIoUtil(name string) {
file := "F:/test.txt"
content, err := ioutil.ReadFile(file)
if err != nil {
fmt.Println("read file err =", err)
}
fmt.Printf("%v", string(content))
}
func main() {
file.OpenFileByIoUtil("F:/test.txt")
}
写入文件操作
func OpenFile(name string, flag int, perm FileMode)(file *File,err error)
说明;os.OpenFile是一个更一般性得到文件打开函数,它会使用指定的选项、指定的模式打开指定的文件,若果操作成功返回文件对象可用于I/O
第二个参数:文件打开模式
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 // 如果可能,打开时清空文件
)
第三个参数:权限控制
代码实现;
// 创建文件
func CreateFile(filePath string) {
file, err := os.OpenFile(filePath, os.O_WRONLY | os.O_CREATE, 0666)
if err != nil {
fmt.Println("open file err=", err)
return
}
defer file.Close()
str := "Hello World!\n"
// 写入时, 使用带缓存的 *writer
writer := bufio.NewWriter(file)
writer.WriteString(str)
// 内容是先写入缓存中的,所以需要调用flush方法,将缓冲的数据写到文件中
writer.Flush()
}
func main() {
filePath := "F:/test.txt"
file.CreateFile(filePath)
}
判断文件是否存在
GoLand判断文件或者文件夹是否存在的方法为使用os.Stat()函数返回的错误值进行判断:
- 如果返回的错误为nil,说明文件或文件夹存在
- 如果返回的错误类型使用os.IsNotExits()判断为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 // 其它错误
}
json序列化
json序列化是指将有key-value结构的数据类型(比如结构体、map、切片)序列化成json字符串的操作
案例:结构体, map, 切片的序列化
结构体序列化:
type Monster struct {
Name string
Age int
Birthday string
Sal float32
Skill string
}
func StructToJson() {
monster := Monster{
Name: "阿修罗",
Age: 101,
Birthday: "2000-01-01",
Sal: 5000.0,
Skill: "大暗黑天",
}
// 将结构体序列化
data, err := json.Marshal(monster)
if err != nil {
fmt.Println("出错了..")
}
fmt.Printf("monster序列化后=%v", string(data))
}
func main() {
json.StructToJson()
}
map序列化:
func MapToJson() {
// 定义一个map
var a map[string]interface{}
a = make(map[string]interface{})
a["name"] = "红孩儿"
a["age"] = 1500
a["address"] = "红岩洞"
data, err := json.Marshal(a)
if err != nil {
fmt.Println("出错了..")
}
fmt.Printf("monster序列化后=%v", string(data))
}
func main() {
json.MapToJson()
}
反序列化
json饭序列化是指将json字符串反序列化为对应的数据类型(结构体, map, 切片)
结构体反序列化
func JsonToStruct(str string) {
var monster Monster
err := json.Unmarshal([]byte(str), &monster)
if err!= nil {
fmt.Println("出错了...")
}
fmt.Printf("反序列化后 = %v", monster)
}
func main() {
// {"Name":"阿修罗","Age":101,"Birthday":"2000-01-01","Sal":5000,"Skill":"大暗黑天"}
str := "{\"Name\":\"阿修罗\",\"Age\":101,\"Birthday\":\"2000-01-01\",\"Sal\":5000,\"Skill\":\"大暗黑天\"}"
json.JsonToStruct(str)
}
map反序列化
func JsonToMap(str string) {
var a map[string]interface{}
er := json.Unmarshal([]byte(str), &a)
if er != nil {
fmt.Println("出错了...")
}
fmt.Printf("反序列化后 = %v", a)
}
func main() {
str := "{\"address\":\"红岩洞\",\"age\":1500,\"name\":\"红孩儿\"}"
json.JsonToMap(str)
}
单元测试
Go语言自带有一个轻量级的测试框架testing和自带的go test命令来实现单元测试和性能测试,testing框架和其它语言中的测试框架类似,可以基于这个框架写针对响应函数的测试用例,也可以基于该框架写相应的压力测试用例,通过单元测试可以解决以下问题:
- 确保每个函数都可以运行,并且运行结果是正确的
- 确保写出来的代码性能是好的
- 单元测试及时的发现程序设计或实现的逻辑错误,使问题及早暴露
测试快速入门
- 文件必须以“__test.go”结尾
- 方法名必须以“Test”打头,并且形参为(t *testing.T)
- 运行测试用例指令
- go test: [如果运行正确,无日志,错误是会输出日志]
- go test -v:[运行正确或是错误,都会输出日志]
- 当出现错误时可以使用t.Fatalf来格式化输出错误信息,并退出程序
- t.Logf方法可以输出相应的日志
测试用例
import (
"Test/unittest"
"testing"
)
func TestAddUpper(t *testing.T) {
res := unittest.AddUpper(10)
if res != 55 {
t.Fatalf("AddUpper(10) 执行错误,期望值=%v 实际值 = %v\n", 55, res)
}
t.Logf("AddUpper(10) 执行正确...")
}
// 累加器
func AddUpper(n int) int {
res := 0
for i := 1; i <= n-1; i++ {
res += i
}
return res
}
综合案例
案例要求:
- 编写一个Monster结构体,字段为Name, Age,Skill
- 给Monster绑定方法store,可以奖结构体序列化后保存在文件中
- 给Monster绑定方法ReStore,可以将一个序列化的Monster从文件中读取,并反序列化为Monster对象,检查反序列化名字是否正确
- 编写测试文件,进行测试
type Monster struct {
Name string
Age int
Skill string
}
// 将结构体序列化并保存
func (this *Monster) Store() bool{
// 序列化
data, err := json.Marshal(this)
if err != nil {
fmt.Println("Marshal err = ", err)
return false
}
// 保存到文件中
filePath := "F:/monster.ser"
err1 := ioutil.WriteFile(filePath, data, 0666)
if err1 != nil {
fmt.Println("writer is err =", err)
return false
}
return true
}
// 读取文件并且反序列化
func (this *Monster) ReStore() bool {
filePath := "F:/monster.ser"
data, err := ioutil.ReadFile(filePath)
if err != nil {
fmt.Println("read is err = ", err)
return false
}
err1 := json.Unmarshal(data, this)
if err1 != nil {
fmt.Println("Unmarsha is err = ", err1)
return false
}
return true
}
func TestStore(t *testing.T) {
// 创建结构体实例
monster := unittest.Monster{
Name: "阿修罗",
Age: 100,
Skill: "吐火",
}
res := monster.Store()
if !res {
t.Fatalf("错误,希望为 = %v\t实际为 = %v\n", true, res)
}
t.Logf("测试成功")
}
func TestReStore(t *testing.T) {
var monster = &unittest.Monster{}
res := monster.ReStore()
if !res {
t.Fatalf("错误,希望为 = %v\t实际为 = %v\n", true, res)
}
t.Logf("测试成功")
}
协程
入门
- 主线程是一个物理线程,直接作用在CPU上,是重量级的,非常消耗CPU资源
- 协程从主线程开启的是轻量级的线程,是逻辑态,对资源的消耗相对较少
func Goroutine() {
for i :=1; i <= 10; i++ {
fmt.Println("goroutine() hello world " + strconv.Itoa(i))
time.Sleep(time.Second)
}
}
func main() {
go goroutine.Goroutine() // go代表开启了一个协程
for i :=1; i <= 10; i++ {
fmt.Println("main() hello world " + strconv.Itoa(i))
time.Sleep(time.Second)
}
}
MPG模式介绍
M : 操作系统的主线程(物理线程)
P : 协程执行需要的上下文
G : 协程
设置Golang运行的CPU数量
为了充分利用多CPU的优势,在Golang程序中,设置运行的CPU数目
func main() {
num := runtime.NumCPU()
runtime.GOMAXPROCS(num -1)
fmt.Println(num)
}
在go1.8后默认让程序运行在多个核上,可以不设置
管道
介绍
- channel本质就是一个数据结构-队列
- 数据是先进先出的
- 线程是安全的,多goroutine访问时,不需要加锁,就是说channel本身是数据安全的
- channel是有数据类型的,一个string的channel只能存放string类型数据
定义声明channel
var 变量名 chan 数据类型
1. channel是引用类型
2. channel必须初始化才能写入数据,即make后才可以使用
3. 管道是有数据类型的, intChan 只能写入整数 int
4. 在没有使用协程的情况下,如果channel数据取完了,再取,就会报错
func Channel() {
// 创建一个管道
var intChan chan int
intChan = make(chan int, 3)
// 向管道中写入数据
intChan<- 3
intChan<- 9
intChan<- 12
fmt.Printf("channel len=%v\n", len(intChan))
// 从管道中取出数据
num := <-intChan
fmt.Println(num)
fmt.Printf("Now channel len = %v", len(intChan))
}
func main() {
channel.Channel()
}
channel的遍历和关闭
channel的关闭
使用内置函数close可以关闭channel,当channel关闭后,就不能再向channel写数据了,但是仍然可以从该channel读取数据
channel遍历
channel支持for-range的方式进行遍历,需要注意两个细节:
- 在遍历时,如果channel没有关闭,则会出现deadlock的错误
- 在遍历时,如果channel已经关闭,则会正常遍历数据,遍历完后就会退出遍历
- 遍历管道的时候不能使用普通的 for 循环
协程和管道结合的案例
基本要求:
- 开启一个writeData协程,向管道intChan中写入五十个整数
- 开启一个readData协程,从管道intChan中读取writeData写入的数据
- 注意:writerData和readData操作的是同一个管道
- 主线程需要等待协程完成后工作后才可以退出
// 向管道中添加数据
func WriteData(intChan chan int) {
for i := 1; i <= 50; i++ {
intChan <- i
fmt.Println("写入数据:", i)
time.Sleep(time.Second)
}
close(intChan)
}
// 从管道中读取数据
func ReafData(intChan chan int, exitChan chan bool) {
for {
v, ok := <- intChan
if !ok {
break
}
time.Sleep(time.Second)
fmt.Println("读到数据:", v)
}
exitChan <- true
close(exitChan)
}
func main() {
intChan := make(chan int, 50)
exitChan := make(chan bool, 1)
go channel.WriteData(intChan)
go channel.ReafData(intChan, exitChan)
time.Sleep(time.Second * 10)
}
管道使用的细节和注意事项
// 声明为只写
var intChan chan <- int
intChan = make(chan int, 3)
// 声明为只读
var intChan <- chan int
intChan = make(chan int, 3)
func Select() {
// 定义一个管道
intChan := make(chan int, 10)
for i := 1; i < 10; i++ {
intChan <- i
}
stringChan := make(chan string, 5)
for j := 1; j < 5; j++ {
stringChan <- "hello" + fmt.Sprintf("%d", j)
}
for {
select {
case v := <-intChan:
fmt.Printf("从intChan读取的数据%d\n", v)
time.Sleep(time.Second)
case v := <- stringChan:
fmt.Printf("从stringChan读取的数据%s\n", v)
time.Sleep(time.Second)
default:
fmt.Println("不玩了...")
time.Sleep(time.Second)
return
}
}
}
func main() {
channel.Select()
}
反射
反射的基本介绍
- 反射可以在运行时动态的获取变量的各种信息,比如变量的类型, 类别
- 如果是结构体变量,还可以获取到结构体本身的信息
- 通过反射可以修改变量的值,可以调用关联的方法
- 使用反射需要导入“reflect”包
反射中重要的函数
reflect.TypeOf(变量名)reflect.ValueOf(变量名)
快速入门
基本数据类型、interface{}、reflect.Value进行反射的基本操作
func ReflectDemo_01(param interface{}) {
// 先获取reflect.Type
rtyp := reflect.TypeOf(param)
fmt.Println("rtyp=", rtyp)
// 获取 reflect.Value
rval := reflect.ValueOf(param)
fmt.Println("rval =", rval)
}
func main() {
num := 100
reflect.ReflectDemo_01(num)
}
结构体类型、interface{}、reflect.Value进行反射的基本操作
func ReflectDemo_02(param interface{}) {
// 先获取reflect.Type
rtyp := reflect.TypeOf(param)
fmt.Println("rtyp=", rtyp)
// 获取 reflect.Value
rval := reflect.ValueOf(param)
iV := rval.Interface()
stu, ok := iV.(Student) // 类型断言
if ok {
fmt.Printf("stu name is %v", stu.Name)
}
}
func main() {
stu := reflect.Student{
Name: "阿修罗",
Age: 100,
}
reflect.ReflectDemo_02(stu)
}
反射注意事项
-
reflect.Value.Kind,获取变量的类别,返回的是一个常量
-
Type是类型,kind是类别,二者可能相同也可能不同,要看变量的类型
-
通过反射可以让变量在interface{}和Reflect.Value之间相互转换
-
使用反射的方式来获取变量的值(并返回对应的类型),要求数据类型匹配,比如x是int,那么就应该使用reflectValue(x).Int(),而不是使用其它类型的
-
通过反射来修改变量,注意使用Setxxx方法来设置需要通过对应的指针类型来完成,这样才能改变传入的变量的值,同时需要使用到reflect.Value.elem()方法
-
如何理解reflect.Value.Elem()
num := 9
ptr *int = &num
num2 := *ptr
最佳实践
type Monster struct {
Name string `json:"name"`
Age int `json:"monster_age"`
Score float32
Sex string
}
func (m Monster) Print() {
fmt.Println("---start---")
fmt.Println(m)
fmt.Println("---end---")
}
func (m Monster) GetSum(n1, n2 int) int {
return n1 + n2
}
func (m Monster) Set(name string, age int, score float32, sex string) {
m.Name = name
m.Age = age
m.Score = score
m.Sex = sex
}
func TestStruce(a interface{}) {
typ := reflect.TypeOf(a)
val := reflect.ValueOf(a)
kd := val.Kind()
if kd != reflect.Struct {
fmt.Println("expcet struct")
return
}
num := val.NumField()
fmt.Printf("struct has %d fileds\n", num)
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")
if tagVal != "" {
fmt.Printf("Field %d: tag为=%v\n", i, tagVal)
}
}
numMethod := val.NumMethod()
fmt.Printf("struct has %d methods\n", numMethod)
val.Method(1).Call(nil)
// 调用结构体的第一个方法Method
var params []reflect.Value
params = append(params, reflect.ValueOf(10))
params = append(params, reflect.ValueOf(40))
res := val.Method(0).Call(params)
fmt.Print("res = ", res[0].Int())
}
func main() {
monster := reflect.Monster{
Name: "爱修罗",
Age: 100,
Score: 99.9,
Sex: "man",
}
reflect.TestStruce(monster)
}
网络编程
网络编程有两种:
- TCP scoket编程,是网络编程的主流,底层是基于TCP/IP协议
- B/S结构是http编程,我们使用浏览器去访问服务器时,使用的HTTP协议.而HTTP底层依旧是用tcp scoket实现的
TCP scoket快速入门
服务端的处理流程
- 监听端口
- 接受客户端的tcp链接,建立客户端和服务端的链接
- 创建goroutine处理链接的请求(通常用户端会通过链接发送请求包)
客户端的处理流程
- 建立与服务端的链接
- 发送请求数据,接受服务器的结果数据
- 关闭连接