怎么自己建一个网站吗,php网页设计培训,258做网站怎么样,备案时网站建设方案书golang基础学习golang1. 认识golang和goland使用1.1 goland插件2. 数据类型2.1 结构体3. 流程控制ifswitchselectforrangeGoto、Break、Continue4. 函数4.1 匿名函数4.2 闭包、递归4.3 延迟调用什么是deferdefer的常见用途释放已经获取的资源从panic中恢复延迟闭包参数即时求值…
golang基础学习golang1. 认识golang和goland使用1.1 goland插件2. 数据类型2.1 结构体3. 流程控制ifswitchselectforrangeGoto、Break、Continue4. 函数4.1 匿名函数4.2 闭包、递归4.3 延迟调用什么是deferdefer的常见用途释放已经获取的资源从panic中恢复延迟闭包参数即时求值延迟调用多个函数延迟调用对象的方法并发时释放共享资源锁延迟释放文件延迟关闭tcp连接延迟关闭数据库连接4.4 异常处理异常处理思想自定义异常详细的异常信息panicpanic recover4.5 单元测试压力测试5. 方法什么是方法方法与普通函数的区别匿名字段方法集6. 面向对象接口什么是接口实现接口接口的作用值接收者和指针接收者实现接口的区别**值接收者**实现接口**指针接收者**实现接口类型和接口的关系一个类型实现多个接口多个类型实现一个接口接口嵌套空接口空接口的应用使用空接口实现map、slice存储任意类型值类型断言关于接口需要注意的7. 标准库反射空接口和反射结构体与反射golang
1. 认识golang和goland使用
1.1 goland插件
File - Settings - Plugins - Marketplace
Gopher将进度条变成golang的标志更好看Rainbow Brackets多彩括号Go Linter代码规范CodeGlance在编辑区右侧生成一个代码缩略区
2. 数据类型
2.1 结构体
如果结构体中字段首字母小写该字段将无法正常解析。如果需要字段名首字母小写可以使用tag的方式。
3. 流程控制
if
switch
select
select语句类似于switch语句但是select会随机执行一个可运行的case如果没有case可运行它将阻塞直到有case可运行
for
range
Goto、Break、Continue
4. 函数
4.1 匿名函数
匿名函数是指不需要定义函数名的一种函数实现方式
func main() {sum : func(a,b int) int {return ab}fmt.Println(sum(1,2))
}4.2 闭包、递归
函数b嵌套在函数a内部函数a返回函数b
当函数a的内部函数b被函数a外的一个变量引用时就创建了一个闭包。
从此可以看出闭包的作用就是在函数a执行完并返回后闭包使得垃圾回收机制GC不会回收函数a所占用的资源。
4.3 延迟调用
什么是defer
通过使用defer修饰一个函数使其在外部函数返回后才被执行即使外部的函数返回的是panic异常。这类函数被称作延迟调用函数 打印结果先first后later
defer和finally类似但是两者的不同是finally的作用域在其**异常块中而defer的作用域限于包围它的那个函数**。
defer的常见用途
释放已经获取的资源 不管NewFromFile函数是否返回了错误这个延迟函数都将关闭已经打开的文件。
从panic中恢复
如果defer和被触发的**panic**位于同一个goroutine1中defer能够使程序从panic中恢复避免整个程序终止。 其中recover()函数可以返回panic()函数的参数这就使得我们能自行处理panic同时也可以向panic中传入错误或其他类型来判断引发panic的究竟是哪一个值。
延迟闭包
一个使用了 defer 的延迟调用函数可以是任意类型的函数因此当 defer 作用于一个匿名函数的时候这个函数就能够获取到外围函数体中的变量的最新状态。 参数即时求值
Go 的运行时会在延迟调用函数声明时保存任何传递到延迟调用函数中的参数而不是当它被运行的时候。
func count(i int) (n int) {defer func(i int) {n n i}(i)i i * 2n ireturn
}func main(){count(10)
}// 输出结果为30在延迟调用函数声明时就已经保存了传入到延迟调用函数中的i的值也就是10。所以最后n 20 10。
延迟调用多个函数
如果有多个延迟函数那么这些延迟调用函数会按照**声明顺序的反序**执行。
func stacked(){defer func(){fmt.Println(last) }()defer func(){fmt.Println(first)}
}func main(){stacked()
}// 输出结果为
// first
// last延迟调用对象的方法
没有使用指针作为接收者
type Car struct {model string
}func (c Car) PrintModel() {fmt.Println(c.model)
}func main() {c : Car{model: DeLorean DMC-12}defer c.PrintModel()c.model Chevrolet Impala
}// 输出结果
// DeLorean DMC-12使用指针作为接收者
type Car struct {model string
}func (c *Car) PrintModel() {fmt.Println(c.model)
}func main() {c : Car{model: DeLorean DMC-12}defer c.PrintModel()c.model Chevrolet Impala
}// 输出结果
// Chevrolet Impala当外围函数还没有返回的时候Go 的运行时就会立刻将传递给延迟函数的参数保存起来。
因此当一个以值作为接收者的方法被 defer 修饰时接收者会在声明时被拷贝在这个例子中那就是 Car 对象此时任何对拷贝的修改都将不可见例中的 Car.model 因为接收者也同时是输入的参数当使用 defer 修饰时会立刻得出参数的值(也就是 “DeLorean DMC-12” )。
在另一种情况下当被延迟调用时接收者为指针对象此时虽然会产生新的指针变量但其指向的地址依然与上例中的 “c” 指针的地址相同。因此任何修改都会完美地作用在同一个对象中。
并发时释放共享资源锁
延迟释放文件
延迟关闭tcp连接
延迟关闭数据库连接
4.4 异常处理
异常处理思想
在go语言中没有try catch因为try catch会消耗更多的资源。
所以go语言主张
如果一个函数可能出现异常那么应该把异常作为返回值没有异常就返回nil每次调用可能出现异常的函数时都应该主动进行检查并做出反应这个if语句术语叫做卫述语句
所以异常应该总是掌握在我们自己的手上保证每次操作产生的影响达到最小保证程序及时部分地方出现问题也不会影响整个程序的运行。
及时的处理异常这样就可以减轻上层处理异常的压力。同时也不要让未知的异常使你的程序崩溃。
自定义异常
比如程序有一个功能为除法的函数除数不能为0否则程序出现异常我们就要提前判断除数如果为0则返回一个异常。
func divisionInt(a, b int) (int, error) {if b 0 {return -1, errors.New(除数不能为0)}return a / b, nil
}func main(){a, b : 4, 0res, err : divisionInt(a, b)if err ! nil {fmt.Println(err.Error())return}fmt.Println(a, 除以, b, 的结果是 , res)
}注意
创建一个异常errors.New(字符串)获取异常信息err.Error()
errors.New(字符串)的形式不支持字符串格式化功能所以可以使用fmt.Errorf()
err fmt.Errorf(产生了一个%v的异常,除数为0)详细的异常信息
errors实现了一个error的接口这个接口里只有一个Error方法且返回一个string
type error interface{Error() string
}只要结构体实现了这个方法即可源码的实现方式如下
type errorString struct {s string
}func (e *errorString) Error() string {return e.s
}// 多一个函数当作构造函数
func New(text string) error {return errorString{text}
}所以我们只要扩充下自定义error的结构体字段就行了。
type FileError struct{Op stringName stringPath string
}func (f *FileError) Error() string{return fmt.Sprintf(路径为%v的文件%v,在 %v 操作时出错,f.Path,f.Name,f.Op)
}func NewFileError(op string,name string,path string) *FileError{return FileError(Op:op,Name:name,Path:path)
}调用
func main(){f : NewFileError(读,README.md,/home/README.md)fmt.Println(f.Error())
}// 输出结果为
// 路径为/home/README.md的文件README.md,在 读 操作时出错panic
程序在什么情况下会崩溃呢
go的类型系统会在编译时捕获很多异常但是有些异常只能在运行时检查如**数组访问越界、空指针引用**等。这些运行时异常会引起panic异常(程序直接崩溃退出)。然后在退出的时候调用当前goroutine1的defer延迟调用语句。
有时候在程序运行缺少必要的资源的时候应该手动触发宕机比如**配置文件解析出错、依赖某种独有库但该操作系统没有**的时候。
defer fmt.Println(关闭文件句柄)
panic(人工创建的运行时异常)// 输出结果为
// 关闭文件句柄
// panic人工创建的运行时异常panic recover
出现panic以后程序会终止运行所以我们应该在测试阶段就发现这些问题然后进行规避但是如果在程序中产生不可预料的异常比如在线的web或者rpc服务一般框架层即使出现问题一般是遇到不可预料的异常数据也不应该直接崩溃应该打印异常日志关闭资源跳过异常数据部分然后继续运行下去不然线上容易出现大面积血崩。
func divisionIntRecover(a, b int) (ret int, e error) {defer func() {if err : recover(); err ! nil {ret 0e fmt.Errorf(出现一个错误)}}()return a / b, nil
}func main(){if i, v : divisionIntRecover(4, 0); i ! 2 || v ! nil {fmt.Println(v.Error())} else {fmt.Printf(a/b%v\n, i)}if i, v : divisionIntRecover(4, 2); i ! 2 || v ! nil {fmt.Println(v.Error())} else {fmt.Printf(a/b%v\n, i)}
}// 输出结果为
// 出现了一个错误
// a/b2注意 调用panic后当前函数从调用点直接退出 捕获函数recover只有在延迟调用内直接调用才会终止错误否则总是返回**nil**。任何未捕获的错误都会沿调用堆栈向外传递。 无效示例没有在延迟调用内直接调用 func test(){defer fmt.Printf(recover:%v\n, recover())panic(test panic)
}func main(){test()
}// 输出结果为
// recover:nil
// panic: test panic无效示例在延迟调用内但是没有直接调用 func test(){defer func(){func(){fmt.Printf(recover1:%v\n, recover())}()}()panic(test panic)
}func main(){test()
}// 输出结果为
// recover1:nil
// panic: test panic正确示例在延迟调用内直接调用 func test(){defer func(){fmt.Printf(recover2:%v\n, recover())}()panic(test panic)
}func main(){test()
}// 输出结果为
// recover2:test panic4.5 单元测试
测试文件名以_test.go的文件是go test测试的一部分*_test.go文件中有三个类型的函数
类型格式作用测试函数函数名前缀为Test测试程序的一些逻辑行为是否正确基准函数函数名前缀为Benchmark测试函数的性能示例函数函数名前缀为Example为文档提供示例文档
go单元测试对文件名、方法名和参数都有很严格的要求
文件名必须以xx_test.go命名
方法必须是Test[^a-z]开头
方法参数必须是t *testing.T
使用go test执行单元测试压力测试
压力测试是用来检测函数方法的性能。
func Benchmark[^a-z](b *testing.B){}5. 方法
什么是方法
方法就是一个包含了接收者的函数接收者可以是**命名类型或者结构体类型的一个值或者是一个指针**。所有给定类型的方法属于该类型的方法集。
func (recevier type) methodName(参数列表)(返回值列表){}注意 接收者类型不能是接口类型因为接口是一个抽象定义而方法却是具体实现。 接收者类型也不能是指针类型但是它可以是任何其他允许类型的指针。 参数recevier可以任意命名参数、返回值可以省略。 实例value或者pointer可以调用全部的方法编译器会自动转换。 因为方法是函数所以方法不可以重载一个类型只能有一个给定名称的方法。但是同样名字的方法可以在多个不同的接收者类型上存在。 一个类型只能有一个给定名称的方法 type stu struct {name stringage string
}func (s stu) method() {s.name zhangsan
}func (s stu) method() {}// 报错同样名字的方法可以在多个不同的接收者类型上存在 type stu struct {name stringage string
}
type teacher struct {name stringgender string
}func (s stu) method() {s.name zhangsan
}
func (t teacher) method() {}方法与普通函数的区别
对于普通函数接收者为值类型时不能将指针类型的数据直接传递反之亦然。对于方法接收者为值类型时可以直接用指针类型的变量调用方法反之亦可。
匿名字段
go语言支持只提供类型而不写字段名的方式也就是匿名字段。
当匿名字段是一个struct时那么这个struct所拥有的全部字段以及方法都被隐式地引入了当前定义的这个struct
type person struct{name stringage intgender string
}
type student struct{person // 匿名字段那么student就包含了person的所有字段class stringid string
}func main(){stu : student{person{zhangsan,23,男},data191,001}fmt.Printf(stu.name is %v\n,stu.name)fmt.Printf(stu.gender is %v\n,stu.gender)fmt.Printf(stu.class is %v\n,stu.class)stu.age 22fmt.Printf(stu.age is %v\n,stu.age)
}// 输出信息
// stu.name is zhangsan
// stu.gender is 男
// stu.class is data191
// stu.age is 22stu还可以访问person作为字段名
stu.person person{lisi,21,男}
stu.person.age - 1但不仅仅是struct字段所有的内置类型和自定义类型都可以作为匿名字段
type skills []string
type person struct{name stringage intgender string
}
type student struct{person // 匿名字段那么student就包含了person的所有字段skills // 匿名字段自定义类型string sliceint // 内置类型作为匿名字段class stringid string
}func main(){stu : student{person{zhangsan,23,男},data191,001}stu.skills []string{吃,喝,玩}stu.skills append(stu.skills,乐)fmt.Printf(zhangsans skills are %v\n,stu.skills)stu.int 100fmt.Printf(stu.int:%v\n,stu.int)
}// 输出结果
// zhangsans skills are [吃,喝,玩,乐]
// stu.int:100但是如果在person中有一个字段name在student中也有一个字段name那么如何处理呢
go语言采用**最外层的优先访问**这也就允许我们去重载通过匿名字段继承的一些字段。如果我们想访问重载后匿名类型里面的字段也可以通过匿名字段来访问。
type person struct{name stringage intgender string
}
type student struct{person // 匿名字段那么student就包含了person的所有字段class stringid stringname string
}func main(){p : person{zhangsan, 23, 男}s : student{p, data191, 001, lisi}fmt.Printf(s.name: %v\n, s.name)fmt.Printf(s.person.name: %v\n, s.person.name)
}// 输出结果
// s.name: lisi
// s.person.name: zhangsan关于对方法的继承对比两个代码 方法集
在go语言中每个类型都有与之关联的方法把**这个类型的所有方法**称之为类型的方法集
type student struct{name stringage int
}func (s student) showName(){fmt.Println(s.name)
}func (s *student) setName(newName string){s.name newName
}类型student方法集包含了showName()方法
类型*student方法集包含了showName()方法和setName()方法
因为
类型T方法集包含所有receiver T方法类型*T方法集包含所有receiver T方法和receiver *T方法
6. 面向对象
接口
什么是接口
go语言中的接口是一些方法的集合(method set)它指定了对象的行为。
接口是一种类型。
实现接口
一个对象只要实现了接口中的所有方法那么就实现了这个接口。换句话说接口就是一个需要实现的方法列表。
如下student实现了People接口中的所有方法所以student就实现了这个People接口
type People interface {Speak()
}type student struct {
}func (stu student) Speak() {fmt.Println(hello)
}接口的作用
那么实现了接口有什么用呢
接口类型变量可以存储**任何**实现了该接口的实例。
type People interface {Speak()
}type student struct {
}type teacher struct {
}func (stu student) Speak() {fmt.Println(im a student)
}func (tea teacher) Speak() {fmt.Println(im a teacher)
}func main(){var p Peoplestu : student{}tea : teacher{}p stup.Speak()p teap.Speak()
}// 输出结果为
// im a student
// im a teacher值接收者和指针接收者实现接口的区别
**值接收者**实现接口
type People interface {Speak()
}type student struct {
}func (stu student) Speak() {fmt.Println(im a student)
}func main(){var p Peoplestu : student{}p stu // 接收student类型p.Speak()p stu // 接收*student类型p.Speak()
}// 输出结果
// im a student
// im a student使用**值接收者实现接口之后不管是student结构体类型的变量还是student结构体指针类型的变量都可以赋值给People接口**变量。
**指针接收者**实现接口
type People interface {Speak()
}type student struct {
}func (stu *student) Speak() {fmt.Println(im a student)
}func main(){var p Peoplestu : student{}p stu // 接收student类型不可以接收p stu // 接收*student类型可以接收p.Speak()
}使用**指针接收者实现接口之后student结构体类型的变量无法赋值给People接口变量student结构体指针类型的变量可以复制给People接口**变量。
此时People接口变量就只能存储**student结构体指针**类型的值
类型和接口的关系
一个类型实现多个接口
一个类型可以实现多个接口而接口之间彼此独立不知道对方的实现。
type People interface {Speak()
}type children interface {Play()
}type student struct {
}func (stu student) Speak() {fmt.Println(im a student)
}func (stu student) Play() {fmt.Println(i like playing)
}func main(){s : student{}s.Speak()s.Play()
}// 输出结果
// im a student
// i like playing多个类型实现一个接口
type People interface {Speak()
}type student struct {
}type teacher struct {
}func (stu student) Speak() {fmt.Println(im a student)
}func (tea teacher) Speak() {fmt.Println(im a teacher)
}func main(){var p Peoplestu : student{}tea : teacher{}p stup.Speak()p teap.Speak()
}// 输出结果为
// im a student
// im a teacher一个接口的方法不一定是一个类型完全实现接口的方法可以通过在类型中嵌入其他类型或者结构体来实现。
如下一个洗衣机包含了洗衣和烘干的功能我们通过两个类型来将其实现
type WashingMachine interface {washing()dry()
}type wash truct {
}type Haier truct {wash
}func (w wash) washing() {fmt.Println(function is wash)
}func (h Haier) dry() {fmt.Println(function is dry)
}接口嵌套
接口和接口之间可以通过嵌套创建新的接口
type Sayer interface {say()
}type Mover interface {move()
}type Animal interface {SayerMover
}空接口
空接口是指没有定义任何方法的接口因此任何类型都实现了空接口。
func main() {var x interface{}s : nihaoi : 1x sx i
}空接口的应用
使用空接口可以接收任意类型的函数参数
func show(a interface{}) {}使用空接口实现map、slice存储任意类型值
func main() {sli2 : []interface{}{1, 2, 3}map1 : map[string]interface{}{name: zhangsan,age: 2,}
}类型断言
空接口可以存储任何类型的值那么我们怎么获取我们存储的具体数据呢
一个接口的值由**具体类型和具体类型的值**两部分组成。这两部分分别称为接口的动态类型和动态值。
想要判断空接口中的值这个时候就可以使用类型断言格式为x.(T)具体如下
func main() {sli2 : []interface{}{1, 2, 3}_, ok : sli2[2].(string)fmt.Println(ok)
}关于接口需要注意的
关于接口需要注意的是只有当有两个或两个以上的具体类型必须以相同的方式进行处理时才需要定义接口。不要为了接口而写接口那样只会增加不必要的抽象导致不必要的运行时损耗。
7. 标准库
反射
反射是指在程序运行期间对程序本身进行访问和修改的能力
获取类型reflect.TypeOf(a)
获取值reflect.ValueOf(a)
空接口和反射
设置值(函数接收者为指针类型)reflect.ValueOf(a).Elem().SetInt()
func reflect_getTypeAndValue(a interface{}) {t : reflect.TypeOf(a)fmt.Printf(类型是:, t)println()k : t.Kind() // 可以获取具体的类型fmt.Printf(具体类型是:, k)println()v : reflect.ValueOf(a)fmt.Printf(值是:, v)println()fmt.Printf(值是:, v.Int())
}
func reflect_setValue(a interface{}) {v : reflect.ValueOf(a)k : v.Kind() // 可以获取具体的类型switch k {case reflect.Int64:v.SetInt(12)println()fmt.Printf(修改后的值是, v)case reflect.Ptr:v.Elem().SetInt(15)println()fmt.Printf(修改后的值是, v)}
}func main(){reflect_getTypeAndValue(num)reflect_setValue(num)
}结构体与反射
获取所有字段的个数reflect.TypeOf(o).NumField()获取字段reflect.TypeOf(o).Field(index)获取字段的名字reflect.TypeOf(a).Field(index).Name获取字段的类型reflect.TypeOf(a).Field(index).Type获取字段的值reflect.ValueOf(o).Field(index).Interface()修改字段的值(函数接收者为font colorred指针类型/font)reflect.ValueOf(0).Elem().FieldByName(fieldName).SetString()获取字段的tagreflect.ValueOf(o).Type().Elem().Field(index).Tag.Get(tagName)获取方法的个数reflect.TypeOf(o).NumMethod()获取方法reflect.TypeOf(o).Method(index)获取方法的名字reflect.TypeOf(o).Method(index).Name获取方法的调用类型和参数类型reflect.TypeOf(o).Method(index).Type调用方法reflect.ValueOf(o).MethodByName(methodName).Call( []reflect.Value{reflect.ValueOf(value) )type User struct {Id intName stringAge int
}type Boy struct {UserAddr string
}func (u User) Hello(name string) {fmt.Println(hello:, name)
}
// 查看所有字段的名字、类型和值
func Poni(o interface{}) {t : reflect.TypeOf(o)fmt.Println(类型, t)fmt.Println(字符串类型, t.Name())v : reflect.ValueOf(o)fmt.Println(值, v)for i : 0; i t.NumField(); i {f : t.Field(i)fmt.Printf(%s : %v\t, f.Name, f.Type)val : v.Field(i).Interface()fmt.Println(val:, val)}fmt.Println()for i : 0; i t.NumMethod(); i {m : t.Method(i)fmt.Print(m.Name)fmt.Println( , m.Type)}
}
// 查看匿名字段
func Poni2(b interface{}) {t : reflect.TypeOf(b)v : reflect.ValueOf(b)//fmt.Println(v)fmt.Printf(v.Field(0):%v\n, v.Field(0))for i : 0; i t.NumField(); i {f : t.Field(i)fmt.Printf(%s %s\t, f.Name, f.Type)val : v.Field(i).Interface()fmt.Printf(%s \n, val)}fmt.Println()for i : 0; i t.NumMethod(); i {m : t.Method(i)fmt.Printf(%s %s\n, m.Name, m.Type)}
}
// 修改结构体的值
func struct_SetValue(o interface{}) {v : reflect.ValueOf(o)v v.Elem()f : v.FieldByName(Name)if f.Kind() reflect.String {f.SetString(wangwu)}
}
// 调用方法
func struct_methodXfer(o interface{}) {v : reflect.ValueOf(o)m : v.MethodByName(Hello)args : []reflect.Value{reflect.ValueOf(kugou)}m.Call(args)
}
// 获取字段的tag
func struct_getFieldTag(o interface{}) {t : reflect.TypeOf(o)f : t.Elem().Field(0)fmt.Println(f.Tag.Get(json))fmt.Println(f.Tag.Get(db))
}goroutine是Go语言中的轻量级线程实现。 ↩︎ ↩︎