Go 方法、协程、channel、继承、接口、异常、反射

方法
Golang的方法是作用在指定数据类型上的,因此自定义类型,都可以有方法而不仅仅是struct
type Person struct {
Name string
Age int
}
/**
*表示 Person 结构体有一方法,方法名为 hello
*/
func (p Person) hello() {
fmt.printf("hello, my name is %v", p.name)
}
func main() {
var p Person
p.name = "jerry"
p.Age=55
p.hello()
}
hello方法只能通过 Person 类型的变量来调用,而不能直接调用,也不能使用其它类型变量来调用。
与函数的区别
  1. 调用方式不一样
函数的调用方式: 函数名(实参列表)
方法的调用方式: 变量.方法名(实参列表)
  1. 对于普通函数,接收者为值类型时,不能将指针类型的数据直接传递,反之亦然
  2. 对于方法(如 struct 的方法),接收者为值类型时,可以直接用指针类型的变量调用方法,反过来同样也可以
文件操作
打开文件 file,err:=os.Open("filePath") filePath 是相对于执行文件来讲的
关闭文件 file.Close()
读取文件
reader :=bufio.NewReader(file)
reader.ReadLine() //读取一行
reader.ReadString('\n') //碰到第一个\n 就返回
bytes,error := ioutil.ReadFile(filePath) 读取文件的内容并显示在终端(使用 ioutil 一次将整个文件读入到内存中),这种方式适用于文件不大的情况。相关方法和函数(ioutil.ReadFile)
写入文件
OpenFile(name string, flag int, perm FileMode) (file *File, err error)
name 文件路径名称
flag 文件打开的模式
O_RDONLY 只读模式打开
O_WRONLY 只写模式打开
O_RDWR 读写模式打开文件
O_APPEND 写操作时将数据附加到文件尾部
O_CREATE 如果不存在将创建一个新文件
O_EXCL 和create配合使用,文件必须不存在
O_SYNC 打开文件用于同步I/O
O_TRUNC 如果可能 打开时清空文件
第三个参数perm:权限控制(系统)
权限取值范围:0~7
0:没有任何权限
1:执行权限()如果文件是可执行文件,是可以运行的
2:写权限
3:写权限和执行权限
4:读权限
5:读权限和执行权限
6:读权限和写权限
7:读权限、写权限和执行权限
创建一个只写文件
file, e := os.OpenFile(filePath, os.O_CREATE|os.O_EXCL|os.O_WRONLY, 0666)
writer :=bufio.NewWriter(file)
nn, e := writer.Write([] byte("hello golang")) //写入缓冲区 nn是写入的长度
e = writer.Flush() 刷入文件
//覆盖内容
file, e := os.OpenFile(filePath, os.O_WRONLY, 0666)
//追加方式
file, e := os.OpenFile(filePath, os.O_WRONLY | os.O_APPEND, 0666)
//删除文件
os.Remove(filePath)
//获取目录状态信息
info,_ := os.Stat(path)
//是否是文件夹
info.IsDir()
//所有文件夹信息
ioutil.ReadDir(path)
Go协程和Go主线程
Go主线程:一个Go线程上,可以起多个协程
Go协程的特点
1 有独立的栈空间
2 共享内存堆空间
3 调度由用户控制
4 协程是轻量级的线程
使用方法:
go funcName()
1 主线程是一个物理线程,直接作用在CPU上,是重量级的,非常耗费CPU资源。
2 协程从主线程开启的,是轻量级的线程,是逻辑态。对资源消耗相对小。
3 Golang 的协程机制是重要的特点,可以轻松的开启上万个协程。其它编程语言的并发机制是一般基于线程的,开启过多的线程,资源耗费大,这里就突显 Golang 在并发上的优势了
channel
channle 本质就是一个数据结构-队列,线程安全,多个协程操作同一个管道时,不会发生资源竞争问题。
  1. 数据是先进先出【FIFO : first in first out】
  2. 线程安全,多 goroutine 访问时,不需要加锁,就是说 channel 本身就是线程安全的
  3. channel 有类型的,一个 string 的 channel 只能存放 string 类型数据。
定义/声明 channel
channel 是引用类型
1 channel 必须初始化才能写入数据, 即 make 后才能使用。否则 all goroutines are asleep - deadlock!
2 channel 数据放满后,就不能再放入了
3 如果从channel取出数据后,可以继续放入
4 在没有使用协程的情况下,如果channel数据取完了,再取就会报deadlock
5 goroutine 中使用recover,解决协程中出现panic,导致程序崩溃问题
定义
name chan type
person chan string
写入
person<-jack
读取
name<-person //name 的值为jack
Go的继承
1 属性的继承
在go中使用匿名属性,来实现继承的,即将父类作为子类的匿名属性,如果父类和子类中重复字段,则优先使用子类自身的属性,继承多个类的属性,使用的时候要指明哪个父类的属性
package main
import "fmt"
type Human struct {
id int
name string
age int
}
type beings struct {
id int
name string
age int
}
func (b beings) say(msg string) {
fmt.Printf("[beings] %s say: '%s'", b.name, msg)
}
func (p Human) say(msg string) {
fmt.Printf("[Human] %s say: '%s'", p.name, msg)
}
func (s Stu) say(msg string) {
fmt.Printf("[student] %s say: '%s'", s.beings.name, msg)
}
type Stu struct {
Human
beings
id int
score int
className string
}
func main() {
var s Stu
s = Stu{
Human: Human{id:123, name:"jack", age:30},
beings: beings{id:111, name:"beings", age:50},
id: 321,
score: 75,
className: "203班"}
fmt.Printf("ID: %d\n姓名:%s\n年龄:%d\n班级:%s\n分数:%d", s.id, s.beings.name, s.beings.age, s.className, s.score)
s.say("hello world") //因为自己有定义自己的say方法所以say默认自己的。否则要指明,不会say方法主类不明确
}
接口 和 多态
Go 语言提供了另外一种数据类型即接口,它把所有的具有共性的方法定义在一起,任何其他类型只要实现了这些方法就是实现了这个接口。
关键字interface{} 空接口,表示可以接受任意对象,在go中空接口也是一种数据类型。它没有方法,如果编写一个函数以 interface{} 值作为参数,那么您可以为该函数提供任何值。
多态性是指相同的操作或函数、过程可作用于多种类型的对象上并获得不同的结果
多态最主要的就是接受的那个参数类型是一个接口类型,并且是多个结构体实现了的接口
接口的组合
type People interface{}
type Ghost interface{}
type Orc interface{
People
Ghost
}
接口的实现:任何其他类型只要实现了这些方法就是实现了这个接口。
package main
import "fmt"
type myth interface{
roar()
jump()
}
type dragon struct {
}
type tiger struct{
}
func( c dragon ) roar(){
fmt.Println("i can roar")
}
func( c dragon ) jump(){
fmt.Println("i can jump")
}
func( t tiger ) jump(){
fmt.Println("tiger i can jump")
}
func main(){
var a myth
a = dragon{}
a.roar()
a.jump()
a = tiger{} //注意这里会报错 因为tiger没有完全实现myth的方法 some method are missing
a.jump()
}
异常处理
1 编译时异常
在编译时抛出的异常,编译不通过,语法使用错误,符号填写错误等等
2 运行时异常
在程序运行时抛出的异常,输入类型不正确,数组下标越界,空指针。
解决方式:
a 每种都考虑到写n多异常情况
panic: runtime error: integer divide by zero
b 捕捉异常
defer :延时执行,即在方法执行结束(出现异常而结束或正常结束)时执行
recover: 恢复的意思,如果是异常结束程序不会中断,返回异常信息,可以根据异常来做出相应的处理,recover必须放在defer的函数中才能生效
package main
import "fmt"
func refed( a int, b int) int {
defer func(){
err:=recover() //恢复
fmt.Println(err)
}()
a = b / a
return a
}
func main() {
i := refed(0 ,1)
fmt.Println("====main方法正常结束!!====",i)
}
//结果
err: runtime error: integer divide by zero ====main方法正常结束!!==== 0
c 手动抛出异常
用panic
package main
import (
"errors"
"fmt"
)
func panic1( a int ) int {
i := 100 -a
if i < 0 {
panic(errors.New("账户余额不足"))
}
fmt.Println("--------账户扣款---------")
return i
}
func main() {
i := panic1(101)
fmt.Println("正常结束:余额:=>",i)
}
结果:
panic: 账户余额不足
goroutine 1 [running]:
main.panic1(0x65, 0x403d8c)
/mnt/hgfs/www/lj/go/Api/panic.go:11 +0xed
main.main()
/mnt/hgfs/www/lj/go/Api/panic.go:19 +0x2e
exit status 2
反射
1 反射可以在运行时动态获取变量的各种信息, 比如变量的类型(type),类别(kind)
2.如果是结构体变量,还可以获取到结构体本身的信息(包括结构体的字段、方法)
3 通过反射,可以修改变量的值,可以调用关联的方法。
4 使用反射,需要 import (“reflect”)
反射使用TypeOf和ValueOf函数从接口中获取目标对象的信息,轻松完成目的
字符串反射
var name string =" jack lee"
reflectType := reflect.TypeOf(name) //string 动态获取输入参数接口中的值的类型
reflectValue := reflect.ValueOf(name) // jack lee 获取输入参数接口中的数据的值
struct 反射
type jack struct {
Id int
Name string
}
func (s jack) Hello(){
fmt.Println("我是一个学生")
}
func (s jack) Hi(){
fmt.Println("晚上好")
}
func main() {
s := jack{Id: 1, Name: "jack lee"}
stype := reflect.TypeOf(s)
fmt.Println(stype)
svalue:= reflect.ValueOf(s)
for i:=0; i < svalue.NumField(); i++ {
key := stype.Field(i)
value := svalue.Field(i).Interface()
fmt.Printf("第%d个字段是:%s:%v = %v \n", i+1, key.Name, key.Type, value)
}
for i:=0; i < stype.NumMethod(); i++ {
m := stype.Method(i)
fmt.Printf("第%d个方法是:%s:%v\n", i+1, m.Name, m.Type)
}
}
结果
main.jack
第1个字段是:Id:int = 1
第2个字段是:Name:string = jack lee
第1个方法是:Hello:func(main.jack)
第2个方法是:Hi:func(main.jack)
判断类型
num := 1;
numType := reflect.TypeOf(num)
if k := numType.Kind(); k == reflect.Int {
fmt.Println("bingo")
}
修改内容
// 如果是整型的话 test := 333 testV := reflect.ValueOf(&test) testV.Elem().SetInt(666) fmt.Println(test) //666
修改struct
type judge struct {
Id int
Name string
}
S:=&judge{Id:1,Name:"coffee"}
V:=reflect.ValueOf(S)
if V.Kind()!=reflect.Ptr {
fmt.Println("不是指针类型,没法进行修改操作")
return
}
V = V.Elem() //指针指向的元素
name:=V.FieldByName("Name")
if name.Kind() == reflect.String {
name.SetString("小学生")
}
fmt.Printf("%#v \n", *S)
反射调用方法
type Student struct {
Id int
Name string
}
func (s Student) GodName(name string){
fmt.Println("我的名字是:", name)
}
func main() {
s := Student{Id: 1, Name: "不好色的jack"}
v := reflect.ValueOf(s)
// 获取方法控制权
mv := v.MethodByName("GodName")
// 拼凑参数
args := []reflect.Value{reflect.ValueOf("我是三好男人")}
// 调用函数
mv.Call(args)
}
Go 并发原理
并发(CONCURRENCY):两个或两个以上的任务在一段时间内被执行。我们不必care这些任务在某一个时间点是否是同时执行,可能同时执行,也可能不是,我们只关心在一段时间内,哪怕是很短的时间(一秒或者两秒)是否执行解决了两个或两个以上任务。
并行(PARALLELLISM):两个或两个以上的任务在同一时刻被同时执行。
并发说的是逻辑上的概念,而并行,强调的是物理运行状态。并发“包含”并行。
Go的CSP并发模型
Go实现了两种并发形式,第一种是大家普遍认知的:
1 多线程共享内存(java或C++等语言中的多线程)
2 CSP并发模型(communicating sequential process)
普通的线程并发模型,就像Java、C++或者Python,他们线程间通信都是通过共享内存的方式来进行的。非常典型的方式就是,在访问共享数据的时候,通过锁来访问。因此,在很多时候,衍生出一种方便操作的数据结构,叫做“线程安全的数据结构”。例如Java提供的包”java.util.concurrent”中的数据结构。Go中也实现了传统的线程并发模型。
Go的CSP并发模型,是通过goroutine和channel来实现的
  • goroutine 是Go语言中并发的执行单位。有点抽象,其实就是和传统概念上的”线程“类似,可以理解为”线程“。
  • channel 是Go语言中各个并发结构体(goroutine)之前的通信机制,通俗的讲,就是各个goroutine之间通信的“管道”,有点类似于Linux中管道
  • 生成一个goroutine的方式非常简单 Go一下,就生成了
go f()
通信机制channel也很方便,传数据用channel<-data 取数据用<-channel
在通信过程中,传数据channel<-data 和 取数据<-channel 必然会成对出现,因为这边传,那边取,两个goroutine之间才会实现通信。不管传还是取 必阻塞,直到另外的goroutine传或者取为止
如 大写字母代表goroutine
A ----->channel ------>B
A------>channel<-------B
A<------channel------->A
有两个goroutine,其中一个发起了向channel中发起了传值操作
左边A开始阻塞,等待有人接收
这时候 右边的B goroutine发起了接收操作 右边的goroutine也开始阻塞,等待别人传送
当两边goroutine都发现了对方,于是两个goroutine开始传和收。
Go并发模型的实现原理
我们先从线程讲起,无论语言层面何种并发模型,到了操作系统层面,一定是以线程的形态存在的。而操作系统根据资源访问权限的不同,体系架构可分为用户空间和内核空间;内核空间主要操作访问CPU资源、I/O资源、内存资源等硬件资源,为上层应用程序提供最基本的基础资源,用户空间呢就是上层应用程序的固定活动空间,用户空间不可以直接访问资源,必须通过“系统调用”、“库函数”或“Shell脚本”来调用内核空间提供的资源
线程模型的实现:1 用户级线程模型
2 内核级线程模型【kse操作系统本身内核态的线程】
3 两级线程模型
这种模型是介于用户级线程模型和内核级线程模型之间的一种线程模型。这种模型的实现非常复杂,和内核级线程模型类似,一个进程中可以对应多个内核级线程,但是进程中的线程不和内核线程一一对应;
这种线程模型会先创建多个内核级线程,然后用自身的用户级线程去对应创建的多个内核级线程,自身的用户级线程需要本身程序去调度,内核级的线程交给操作系统内核去调度。
Go线程实现模型MPG
Go语言的线程模型就是一种特殊的两级线程模型。暂且叫它“MPG”模型吧。
为了完成任务调度,GO Scheduler 使用3个主要实体
M、P、G
M指Machine,代表OS线程。它是由OS管理的执行线程,其工作方式与标准的POSIX线程非常相似。在运行时代码中,被称为M for machine
P 指 processor,表示调度的上下文。可以将其视为调度程序的本地化版本,该调度程序在单个线程上运行Go代码。这是让我们从N:1调度程序转到M:N调度程序的重要部分。在运行时代码中,它被称为P for processor。
G指的是Goroutine,代表一个goroutine。它包括堆栈,指令指针和其他对调度goroutine很重要的信息,就像它可能被阻塞的任何通道一样。其实本质上也是一种轻量级的线程。在运行时代码,它被称为G。
TCP网络编程
1 tcp socket
2 b/s http编程(底层已久用的是tcp socket)
撸一个利用go的tcp网络编程Api实现一个简单的http服务器案例
package main
import (
"fmt"
"net"
"strconv"
)
type Int int
func (i Int) toStr() string {
return strconv.FormatInt(int64(i), 10)
}
func testConn(conn net.Conn) {
addr := conn.RemoteAddr()
fmt.Println(addr)
var respBody = "<h1> hello world </h1>"
i := (Int)(len(respBody))
fmt.Println("响应的消息体长度: "+i.toStr())
//返回的http消息头
var respHeader = "HTTP/1.1 500 OK\n"+
"Content-Type:text/html;charset=ISO-8859-1\n"+
"Content-Length: "+i.toStr()
//拼装http返回的消息
resp := respHeader + "\n\r\n" + respBody
fmt.Println(resp)
n, _ := conn.Write([]byte(resp))
fmt.Printf("发送的数据数量: %s\n", (Int)(n).toStr())
}
func main() {
listen,err := net.Listen("tcp", "0.0.0.0:8888")
if err != nil {
fmt.Println(err)
return
}
defer listen.Close()
for {
conn, err :=listen.Accept()
if err !=nil {
fmt.Println(err)
}
go testConn(conn) //协程开启
}
}
单元测试
1 测试用例文件名必须以_test.go结尾,比如jack_test.go ,jack不是固定的
2 测试用例函数必须以Test开头,就是Test+被测试的函数名(开头首字母大写)
3 TestPow(t *testing.T)的形参类型必须是*tesing.T
4 一个测试用例文件中,可以有多个测试用例函数
5 运行测试用例指令
go test -v
6 当出现错误时,用t.FataIf 来格式化输出错误信息,并退出程序
7 t.Logf方法可以输出相应的日志
8 测试用例函数,并没有放在main函数中,也执行了,这就是测试用例的方便之处
9 PASS 表示测试用例运行成功,FAIL表示测试用例运行失败
10 测试单个文件,一定要带上被测试的源文件
go test -v jack_test.go jack.go
11 测试单个方法
go test -v -test.run TestPow

发表评论

您的电子邮箱地址不会被公开。 必填项已用*标注