闽公网安备 35020302035485号
3.以c#为代表,代码里表现的像类型擦除,但运行的时候实际上和c++一样采用模板实例化对每个不同的类型都生成一份代码
func Output[T any]() {
var t T
fmt.Printf("%#v\n", t)
}
// 堆代码 duidaima.com
type A struct {
a,b,c,d,e,f,g int64
h,i,j string
k []string
l, m, n map[string]uint64
}
type B A
func main() {
Output[string]()
Output[int]()
Output[uint]()
Output[int64]()
Output[uint64]() // 上面每个都underlying type都不同,尽管int64和uint64大小一样,所以生成5份不同的代码
Output[*string]()
Output[*int]()
Output[*uint]()
Output[*A]() // 所有指针都是同一个shape,所以共用一份代码
Output[A]()
Output[*B]()
Output[B]() // B的underlying tyoe和A一样,所以和A共用代码
Output[[]int]()
Output[*[]int]()
Output[map[int]string]()
Output[*map[int]string]()
Output[chan map[int]string]()
}
验证也很简单,看看符号表即可:
2.正常来说类型参数是可以当成普通的类型来用的,但golang里有很多时候不能
type A struct {
num uint64
num1 int64
}
func(a * A) Add() {
a.num++
a.num1 = int64(a.num / 2)
}
type B struct {
num1 uint64
num2 int64
}
func(b * B) Add() {
b.num1++
b.num2 = int64(b.num1 / 2)
}
type Adder interface {
Add()
}
func DoAdd[T Adder](t T) {
t.Add()
}
func DoAddNoGeneric(a Adder) {
a.Add()
}
func BenchmarkNoGenericA(b * testing.B) {
obj: = & A {}
for i: = 0;i < b.N;i++{
obj.Add()
}
}
func BenchmarkNoGenericB(b * testing.B) {
obj: = & B {}
for i: = 0;i < b.N;i++{
obj.Add()
}
}
func BenchmarkGenericA(b * testing.B) {
obj: = & A {}
for i: = 0;i < b.N;i++{
DoAdd(obj)
}
}
func BenchmarkGenericB(b * testing.B) {
obj: = & B {}
for i: = 0;i < b.N;i++{
DoAdd(obj)
}
}
func BenchmarkGenericInterfaceA(b * testing.B) {
var obj Adder = & A {}
for i: = 0;
i < b.N;
i++{
DoAdd(obj)
}
}
func BenchmarkGenericInterfaceB(b * testing.B) {
var obj Adder = & B {}
for i: = 0;
i < b.N;
i++{
DoAdd(obj)
}
}
func BenchmarkDoAddNoGeneric(b * testing.B) {
var obj Adder = & A {}
for i: = 0;
i < b.N;
i++{
DoAddNoGeneric(obj)
}
}
猜猜结果,是不是觉得引入了泛型可以解决很多性能问题?答案揭晓:
func Search[T Equaler[T]](slice[] T, target T) int {
index: = -1
for i: = range slice {
if slice[i].Equal(target) {
index = i
}
}
return index
}
type MyInt int
func(m MyInt) Equal(rhs MyInt) bool {
return int(m) == int(rhs)
}
type Equaler[T any] interface {
Equal(T) bool
}
func SearchMyInt(slice[] MyInt, target MyInt) int {
index: = -1
for i: = range slice {
if slice[i].Equal(target) {
index = i
}
}
return index
}
func SearchInterface(slice[] Equaler[MyInt], target MyInt) int {
index: = -1
for i: = range slice {
if slice[i].Equal(target) {
index = i
}
}
return index
}
var slice[] MyInt
var interfaces[] Equaler[MyInt]
func init() {
slice = make([] MyInt, 100)
interfaces = make([] Equaler[MyInt], 100)
for i: = 0;
i < 100;
i++{
slice[i] = MyInt(i * i + 1)
interfaces[i] = slice[i]
}
}
func BenchmarkSearch(b * testing.B) {
for i: = 0;
i < b.N;
i++{
Search(slice, 99 * 99)
}
}
func BenchmarkInterface(b * testing.B) {
for i: = 0;
i < b.N;
i++{
SearchInterface(interfaces, 99 * 99)
}
}
func BenchmarkSearchInt(b * testing.B) {
for i: = 0;
i < b.N;
i++{
SearchMyInt(slice, 99 * 99)
}
}
这是结果:
func F[T any]() T {
var ret T
// 如果需要指针,可以用new(T),但有注意事项,下面会说
return ret
}
So far, so good。那么我要把T的类型约束换成一个有方法的interface呢?type A struct {
i int
}
func( * A) Hello() {
fmt.Println("Hello from A!")
}
func(a * A) Set(i int) {
a.i = i
}
type B struct {
i int
}
func( * B) Hello() {
fmt.Println("Hello from B!")
}
func(b * B) Set(i int) {
b.i = i
}
type API interface {
Hello()
Set(int)
}
func SayHello[PT API](a PT) {
a.Hello()
var b PT
b.Hello()
b.Set(222222)
fmt.Println(a, b)
}
func main() {
a: = new(A)
a.Set(111)
fmt.Println(a)
SayHello( & A {})
SayHello( & B {})
}
运行结果是啥?啥都不是,运行时会奖励你一个大大的panic:
var a API a.Set(1)a没绑定任何东西,那么调Set百分百空指针错误。同理,SayHello里的b也没绑定任何数据,一样会空指针错误。为什么b.Hello()调成功了,因为这个方法里没对接收器的指针解引用。同样new(T)这个时候是创建了一个type parameter的指针,和原类型的关系就更远了。但对于像这样~int、[]int的有明确的core type的约束,编译器又是双标的,可以正常创建实例变量。
func Set[T * int | * uint](ptr T) { * ptr = 1
}
func main() {
i: = 0
j: = uint(0)
Set( & i)
Set( & j)
fmt.Println(i, j)
}
输出是啥,是编译错误:$ go build a.go # command-line-arguments ./a.go:6:3: invalid operation: pointers of ptr (variable of type T constrained by *int | *uint) must have identical base types这个意思是T不是指针类型,没法解引用。猜都不用猜,肯定又是type parameter作怪了。是的。T是type parameter,而type parameter不是指针,不支持解引用操作。不过比起前一个问题,这个是有解决办法的,而且办法很多,第一种,明确表明ptr是个指针:
func Set[T int|uint](ptr *T) {
*ptr = 1
}
第二种,投机取巧:func Set[T int|uint, PT interface{*T}](ptr PT) {
*ptr = 1
}
第二种为什么行,因为在类型约束里如果T的约束有具体的core type(包括any),那么在这里就会被当成实际的类型用而不是type parameter。所以PT代表的意思是“有一个类型,它必须是T代表的实际类型的指针类型”。因为PT是指针类型了,所以第二种方法也可以达到目的。但我永远只推荐你用第一种方法,别给自己找麻烦。type A struct {
i int
}
func( * A) Hello() {
fmt.Println("Hello from A!")
}
type B struct {
i int
}
func( * B) Hello() {
fmt.Println("Hello from B!")
}
func SayHello[T~ * A | ~ * B](a T) {
a.Hello()
}
func main() {
SayHello( & A {})
SayHello( & B {})
}
输出是啥?又是编译错误:$ go build a.go # command-line-arguments ./a.go:17:4: a.Hello undefined (type T has no field or method Hello)你猜到了,因为T是类型参数,而不是(*A),所以没有对应的方法存在。所以你这么改了:
func SayHello[T A|B](a *T) {
a.Hello()
}
这时候输出又变了:$ go build a.go # command-line-arguments ./a.go:17:4: a.Hello undefined (type *T is pointer to type parameter, not type parameter)这个报错好像挺眼熟啊,这不就是取了interface的指针之后在指针上调用方法时报的那个错吗?对,两个错误都差不多,因为type parameter有自己的数据结构,而它没有任何方法,所以通过指针指向type parameter后再调用方法会报一模一样的错。难道我们只能建个interface里面放上Hello这个方法了吗?虽然我推荐你这么做,但还有别的办法,我们可以利用上一节的PT,但需要给它加点method:
func SayHello[T A|B, PT interface{*T; Hello()}](a PT) {
a.Hello()
}
原理是一样的,但现在a还同时支持指针的操作。直接用interface{Hello()}不好吗?绝大部分时间都可以,但如果我只想限定死某些类型的话就不适用了。type A struct {
i int
}
func( * A) Hello() {
fmt.Println("Hello from A!")
}
func(a * A) Set(i int) {
a.i = i
}
type B struct {
i int /*j*/
}
func( * B) Hello() {
fmt.Println("Hello from B!")
}
func(b * B) Set(i int) {
b.i = i
}
type API[T any] interface { * T
Set(int)
}
func DoCopy[T any, PT API[T]](a PT) {
b: = * a(PT( & b)).Set(222222) // 依旧是浅拷贝
fmt.Println(a, b)
}
PT是指针类型,所以可以解引用得到T的值,然后再赋值给b,完成了一次浅拷贝。注意,拷贝出来的b是T类型的,得先转成*T再转成PT。想深拷贝怎么办,那只能定义和实现这样的接口了:CloneAble[T any] interface{Clone() T}。这倒也没那么不合理,为了避免浅拷贝问题一般也需要提供一个可以复制自身的方法,算是顺势而为吧。4.类型约束的core type直接影响被约束的类型可以执行哪些操作,要当心