Golang 基础之基础语法梳理 (三)
Python微信订餐小程序课程视频
https://edu.csdn.net/course/detail/36074
Python实战量化交易理财系统
https://edu.csdn.net/course/detail/35475
大家好,今天将梳理出的 Go语言基础语法内容,分享给大家。 请多多指教,谢谢。
本次《Go语言基础语法内容》共分为三个章节,本文为第三章节
- Golang 基础之基础语法梳理 (一)
- Golang 基础之基础语法梳理 (二)
- Golang 基础之基础语法梳理 (三)
本章节内容
- interface
- 反射
- 泛型
interface
介绍
在Go语言中接口 (interface) 是一种类型, 一种抽象的类型。
接口 (interface) 定义了一个对象的行为规范, 只定义规范不实现,由具体的对象来实现规范的细节。
接口做的事情就像是定义一个协议(规则)。
Interface 是一组method的集合, 是duck-type programming 的一种体现。
接口的定义
- 接口是一个或多个方法签名的集合
- 接口只有方法声明,没有实现,没有数据字段
- 接口可以匿名嵌入其他接口,或嵌入到结构中
- 接口调用不会做receiver的自动转换
- 接口同样支持匿名字段方法
- 接口也可实现类似OOP中的多态
- 任何类型的方法集中只要拥有该接口’对应的全部方法’签名
- 只有当接口存储的类型和对象都为nil时,接口才等于nil
- 用 interface{} 传递任意类型数据是Go语言的惯例用法,而且 interface{} 是类型安全的
- 空接口可以作为任何类型数据的容器
- 一个类型可实现多个接口
- 接口命名习惯以 er 结尾
使用
每个接口由数个方法组成,接口的定义如下
type 接口类型 interface {方法名1 (参数列表1) 返回值列表1方法名2 (参数列表2) 返回值列表2...
}
注意
- 接口名:使用type将接口定义为自定义的类型名。Go语言的接口在命名时,一般会在单词后面添加er,如有写操作的接口叫Writer,有字符串功能的接口叫Stringer等。接口名最好要能突出该接口的类型含义。
- 方法名:当方法名首字母是大写且这个接口类型名首字母也是大写时,这个方法可以被接口所在的包(package)之外的代码访问。
- 参数列表、返回值列表:参数列表和返回值列表中的参数变量名可以省略。
例子
type writer interface {Write([]byte) error
}
值接收者和指针接收接口
type Mover interface {move()
}type dog struct {}func (d dog) move() {fmt.Println("狗狗")
}func main() {var x Movervar wangcai = dog{}x = wangcai // x 可以接收dog类型var fugui = &dog{} // fugui是 *dog 类型 x = fugui // x可以接收*dog类型 指针接收x.move()
}
多个类型实现同一接口
// Mover 接口
type Mover interface {move()
}type dog struct {name string
}
type car struct {brand string
}// dog 类型实现 Mover 接口
func (d dog) move() {fmt.Printf("%s: mmmm", d.name)
}
// car 类型实现 Mover 接口
func (c car) move() {fmt.Printf("%s: mmmm", c.brand)
}func main() {var x Movervar a = dog{name: "旺财"}var b = car{brand: "虾米"}x = ax.move()x = bx.move()
}
一个接口的方法,不一定需要由一个类型完全实现,接口的方法可以通过在类型中嵌入其他类型或者结构体来实现。
接口嵌套
接口与接口间可以通过嵌套创造出新的接口。
type Sayer interface {say()
}
type Mover interface {move()
}// 接口嵌套
type animal interface {SayerMover
}// 嵌套得到的接口的使用与普通接口一样
type cat struct {name string
}func (c cat) say() {fmt.Println("ssss")
}func (c cat) move() {fmt.Println("mmmm")
}func main() {var x animalx = cat{name: "花花"}x.move()x.say()
}
空接口
空接口是指没有定义任何方法的接口,因此任何类型都实现了空接口。
空接口类型的变量可以存储任意类型的变量。
func main() {// 定义一个空接口 xvar x interface{}s := "test data"x = sfmt.Printf("type:%T value: %v\n", x, x)i := 100x = ifmt.Printf("type:%T value: %v\n", x, x)b := truex = bfmt.Printf("type:%T value: %v\n", x, x)
}
空接口作为函数的参数
使用空接口实现可以接收任意类型的函数对象。
func show(a interface{}){fmt.Printf("type:%T value: %v\n", a, a)
}
空接口作为map的参数
使用空接口实现可以保存任意值的字典
var Info = make(map[string]interface{})
Info["id"] = 1
Info["name"] = "帽儿山的枪手"
fmt.Println(Info)
获取空接口值
判断空接口中值,可以使用类型断言,语法如下
x.(T)
x
表示类型为 interface{} 的变量
T
表示断言 x 可能是的类型
该语法返回两个参数,第一个参数是 x 转化为 T 类型后的变量, 第二个值是一个布尔值, 若为 true 则表示断言成功, false 则表示失败。
func main() {var x interface{}x = "data"v, ok := x.(string)if ok {fmt.Println(v)} else {fmt.Println("类型断言失败")}
}
如果要断言多次,可以写 if
判断, 也可以用 switch
语句实现。
反射
介绍
什么是反射?
例如:有时候我们需要知道某个值是什么类型,才能用对等逻辑去处理它。
以下是常用的处理方法:
// 伪代码
switch value := value.(type){case string:// 处理操作case int:// 处理操作...
}
这样处理,会写的非常长,而且还可能存在自定的类型,也就是说这个判断日后可能还要一直改,因为无法知道未知值到底属于什么类型。
如果使用反射来处理,使用标准库 reflect
中的 TypeOf 和 ValueOf 函数从接口中获取目标对象的信息,就可以轻松处理这个问题。
更多介绍,可参考reflect 官方地址
https://pkg.go.dev/reflect
Go语言提供了一种机制,在编译时不知道类型的情况下,可更新变量、运行时查看值、调用方法以及直接对他们的布局进行操作的机制,称为反射。
使用
使用反射查看对象类型
package mainimport ("fmt""reflect"
)func main() {var name string = "帽儿山的枪手"nameType := reflect.TypeOf(name)nameValue := reflect.ValueOf(name)fmt.Println("name type: ", nameType)fmt.Println("name value: ", nameValue)
}
输出
name type: string
name value: 帽儿山的枪手
struct 类型反射用法
package mainimport ("fmt""reflect"
)type Info struct {Name stringDesc string
}func (i Info) Detail() {fmt.Println("detail info")
}func main() {i := Info{Name: "帽儿山的枪手", Desc: "技术分享"}t := reflect.TypeOf(i) // 获取目标对象v := reflect.ValueOf(i) // 获取value值for i := 0; i < v.NumField(); i++ { // NumField()获取字段总数key := t.Field(i) // 根据下标,获取包含的keyvalue := v.Field(i).Interface() // 获取key对应的值fmt.Printf("key=%s value=%v type=%v\n", key.Name, value, key.Type)}// 获取Info的方法for i := 0; i < t.NumMethod(); i++ {m := t.Method(i)fmt.Printf("方法 Name=%s Type=%v\n", m.Name, m.Type)}
}
输出
key=Name value=帽儿山的枪手 type=string
key=Desc value=技术分享 type=string
方法 Name=Detail Type=func(main.Info)
通过反射判断类型用法
package mainimport ("fmt""reflect"
)type Info struct {Name stringDesc string
}func main() {i := Info{Name: "帽儿山的枪手", Desc: "技术分享"}t := reflect.TypeOf(i)// Kind()函数判断值的类型if k := t.Kind(); k == reflect.Struct {fmt.Println("struct type")}num := 100switch v := reflect.ValueOf(num); v.Kind() {case reflect.String:fmt.Println("string type")case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:fmt.Println("int type")default:fmt.Printf("unhandled kind %s", v.Kind())}
}
输出
struct type
int type
通过反射修改内容
package mainimport ("fmt""reflect"
)type Info struct {Name stringDesc string
}func main() {i := &Info{Name: "帽儿山的枪手", Desc: "技术分享"}v := reflect.ValueOf(i)// 修改值必须是指针类型if v.Kind() != reflect.Ptr {fmt.Println("不是指针类型")return }v = v.Elem() // 获取指针指向的元素name := v.FieldByName("Desc") // 获取目标key的值name.SetString("好好工作")fmt.Printf("修改后数据: %v\n", *i)
}
输出
修改后数据: {帽儿山的枪手 好好工作}
通过反射调用方法
package mainimport ("fmt""reflect"
)type Info struct {Name stringDesc string
}func (i Info) Detail() {fmt.Println("detail info")
}func main() {i := Info{Name: "帽儿山的枪手", Desc: "技术分享"}v := reflect.ValueOf(i)// 获取方法控制权mv := v.MethodByName("Detail")mv.Call([]reflect.Value{}) // 这里是无调用参数 []reflect.Value{}
}
输出
detail info
泛型
介绍
泛型的概念,可以从多态看起,多态是同一形式表现出不同行为的一种特性,在编程语言中被分为两类,临时性多态和参数化多态。
根据实参生成不同的版本,支持任意数量的调用,即泛型,简言之,就是把元素类型变成了参数。
golang版本需要在 1.17版本或以上,才支持泛型使用。
(1.17版本泛型是golang推出的尝鲜版,1.18是正式版本)
举例:
func Add(a, b int) int{}
func AddFloat(a, b float64) float64{}
在泛型的帮助下,上面代码就可以简化成为:
func Add[T any](a, b T) T
Add后面的[T any],T表示类型的标识,any表示T可以是任意类型。
a、b和返回值的类型T和前面的T是同一个类型。
为什么用[],而不是其他语言中的<>,官方有过解释,大概就是<>会有歧义。曾经计划使用() ,因为太容易混淆,最后使用了[]。
泛型3大概念
- 类型参数
- 类型约束
- 类型推导
特性
- 函数可以通过type关键字引入额外的类型参数
(type parameters)列表:func F(type T)(p T) { ... }
- 这些类型参数可以像一般的参数一样在函数体中使用
- 类型也可以拥有类型参数列表:
type M(type T) []T
- 每个类型参数可以拥有一个约束:
func F(type T Constraint)(p T) { ... }
- 使用interface来描述类型的约束
- 被用作类型约束的interface可以拥有一个预声明类型列表,限制了实现此接口的类型的基础类型
- 使用泛型函数或类型时需要传入类型实参
- 类型推断允许用户在调用泛型函数时省略类型实参
- 泛型函数只允许进行类型约束所规定的操作
使用
对泛型进行输出
如果Go当前版本是1.17版本,运行时需要加参数 -gcflags=-G=3
# 完整命令
go run -gcflags=-G=3 example.go
示例
package mainimport ("fmt"
)func print[T any](s []T) {for _, v := range s {fmt.Printf("%v ", v)}fmt.Printf("\n")
}func main() {print[int]([]int{1,2,3,4})print[float64]([]float64{1.01, 2.02, 3.03, 4.04})print[string]([]string{"a", "b", "c", "d"})
}
输出
1 2 3 4
1.01 2.02 3.03 4.04
a b c d
Go1.18 中,any
是 interface{} 的别名
使用泛型约束,控制类型的使用范围
原先的语法中,类型约束会用逗号分隔的方式来展示
type int, int8, int16, int32, int64
在新语法中,结合定义为 union element(联合元素),写成一系列由竖线 ”|“ 分隔的类型或近似元素。
int | int8 | int16 | int32 | int64
示例
package mainimport ("fmt"
)type CustomType interface {int | int8 | int16 | int32 | int64 | string
}func add[T CustomType] (a, b T) T{return a + b
}func main() {fmt.Println(add(1, 2))fmt.Println(add("帽儿山的枪手", "技术分享"))
}
输出
3
帽儿山的枪手技术分享
上述 CustomType
接口类型也可以写成以下格式
type CustomType interface {~int | ~string
}
上述声明的类型集是 ~int,也就是所有类型为 int 的类型(如:int、int8、int16、int32、int64)都能够满足这个类型约束的条件。
泛型中自带 comparable 约束
因为不是所有的类型都可以==比较,所以Golang内置提供了一个comparable约束,表示可比较的。
官方说明
comparable是由所有可比较类型(布尔、数字、字符串、指针、通道、可比较类型的数组、字段均为可比较类型的结构)实现的接口。可比较接口只能用作类型参数约束,不能用作变量的类型。
https://pkg.go.dev/builtin@master#comparable
package mainimport ("fmt"
)func diff[T comparable](a []T, v T) {for _, e := range a {if e == v {fmt.Println(e)}}
}func main() {diff([]int{1, 2, 3, 4}, 3)
}
输出
3
泛型中操作指针
package mainimport ("fmt"
)func pointerOf[T any](v T) *T {return &v
}func main() {name := pointerOf("帽儿山的枪手")fmt.Println(*name)id := pointerOf(100)fmt.Println(*id)
}
输出
帽儿山的枪手
100
技术文章持续更新,请大家多多关注呀~~
搜索微信公众号【 帽儿山的枪手 】,关注我
Golang 基础之基础语法梳理 (三)相关推荐
- java 基础学习——基本语法(三)
十五.Java条件语句之 if 生活中,我们经常需要先做判断,然后才决定是否要做某件事情.例如,如果考试成绩大于 90 分,则奖励一个 IPHONE 5S .对于这种"需要先判断条件,条件满 ...
- Golang 基础之基础语法梳理 (一)
大家好,今天将梳理出的 Go语言基础语法内容,分享给大家. 请多多指教,谢谢. 本次<Go语言基础语法内容>共分为三个章节,本文为第一章节 Golang 基础之基础语法梳理 (一) Gol ...
- Java基础语法(三)——运算符
文章目录 Java基础语法(三)--运算符 一.算术运算符 1.基本四则运算符 (1)练习 (2)注意事项 2.复合运算符 3.自增自减操作符 二.关系运算符 三.逻辑操作符 1.逻辑与&&a ...
- Golang 笔记 1 基础、基本数据类型
一.Go语言基础 1. 基础 Go语言中的标识符必须以字母(Unicode字母,PHP/JS可以用中文作为变量名)下划线开头.大写字母跟小写字母是不同的:Hello和hello是两个不同的名字. ...
- python点操作符语法_最基础的python语法
最基础的pyth语法 1 python严格区分大小写 2 Python中的每一行就是一条语句,每条语句以换行结束 print(1233) print('eeeee') 3 多行编写时语句后边以\结尾 ...
- C++ 基础概念、语法和易错点整理
目录 基础知识 构造函数与析构函数 虚函数 继承 单例模式 重载.隐藏和重写(覆盖) vector 扩容机制应注意的问题 STL 迭代器 前言 快秋招了,专门用一篇博客整理一下 C++ 的一些基础概念 ...
- 华南理工专科计算机随堂联系,华南理工大学网络教育计算机基础随堂练习第三章...
<华南理工大学网络教育计算机基础随堂练习第三章>由会员分享,可在线阅读,更多相关<华南理工大学网络教育计算机基础随堂练习第三章(8页珍藏版)>请在人人文库网上搜索. 1.第三章 ...
- (54)FPGA基础编码D触发器(三)
(54)FPGA基础编码D触发器(三) 1 文章目录 1)文章目录 2)FPGA入门与提升课程介绍 3)FPGA简介 4)FPGA基础编码D触发器(三) 5)技术交流 6)参考资料 2 FPGA入门与 ...
- Python零基础入门(一)——Python基础关键字和语法[学习笔记]
Python零基础入门(一)--Python基础关键字和语法 目录 1. Hello World! 2. 字符串操作 3. 简单数学计算 4. if elif else 5. 循环 基础类型 pyth ...
最新文章
- 30-35岁职场规划深谈,字字戳心
- linux中普通用户用友所有权限,linux文件的特殊权限
- Nmap参数--探索网络
- Hadoop之Hadoop数据压缩
- HYSBZ - 1050(旅行comf 并查集Java实现)
- 任务调度(三)——Timer的替代品ScheduledExecutorService简介
- C++生成指定范围内的随机数
- centos7 oracle_Centos7主机名变成bogon的原因及解决方法
- 在公司群匿名吐槽后当场“掉马”?QQ回应:真这样程序猿要被祭天
- 和could的区别用法_have to 与 must 用法及区别
- 交换机抑制广播命令详解
- linux分区磁盘大小,Linux对超大容量磁盘进行分区
- 今天给同学写5个数据结构算法的题...感觉很有价值的几个题..感兴趣的坐下。。...
- jQuery - 选择器(五)
- Kafka在Linux下载安装及部署
- html日历页面节假日_基于jquery实现可查询节假日万年历代码
- frp服务端(frps) 安装及使用
- Linux主机安全加固方法使用开源软件fail2ban防护主机
- Android学习---zygote(上)
- Voronoi图(四):抛物线的妙用
热门文章
- 测试代理ip是否可用的方法
- java jpa自身关联查询_使用JPA进行数据查询和关联查询
- 卷积神经网络可视化理解
- 夕阳西下,行业内卷,土木转行Python的几个方向?
- android ios调试工具,Android Studio显示多个window和iOS模拟器显示RN调试工具
- 2022年保育员专业知识(初级)考试单选题专项训练及答案
- 直播间留住粉丝的套路
- win10 php5.5,win10.64位wnmp-nginx1.14.0 + PHP 5. 6.36 + MySQL 5.5.59 环境-站长资讯中心
- 向超百亿目标进发,再造一个“新”亚信科技
- vuecli配置babel-polyfill