具身智能篇---LLaVA (Large Language-and-Vision Assistant)
在Go中.变长参数函数使用的最多的就是fmt包 log包中的几个导出函数.源码位置:src/fmt/print.go// Println formats using the default formats for its operands and writes to standard output. // Spaces are always added between operands and a newline is appended. // It returns the number of bytes written and any write error encountered. func Println(a ...any) (n int, err error) { return Fprintln(os.Stdout, a...) } // Printf formats according to a format specifier and writes to standard output. // It returns the number of bytes written and any write error encountered. func Printf(format string, a ...any) (n int, err error) { return Fprintf(os.Stdout, format, a...) }源码位置:src/log/log.go// Printf calls l.Output to print to the logger. // Arguments are handled in the manner of [fmt.Printf]. func (l *Logger) Printf(format string, v ...any) { l.output(0, 2, func(b []byte) []byte { return fmt.Appendf(b, format, v...) }) } // Println calls l.Output to print to the logger. // Arguments are handled in the manner of [fmt.Println]. func (l *Logger) Println(v ...any) { l.output(0, 2, func(b []byte) []byte { return fmt.Appendln(b, v...) }) }
变长参数函数:变长参数就是指调用时可以接受零个 一个或多个实际参数的函数.示例如下:可以看到无论传入零个 两个还是多个实际参数.都传给了Print()方法的形式参数a(参考上面源码).形参a的类型是...any.这种接受...T类型形式参数的函数就被称为变长参数函数.一个变长参数函数只能有一个...T类型形式参数.并且该形式参数应该为函数参数列表中的最后一个形式参数.否则Go编译器会有错误提示.变长参数函数的...T类型形式参数在函数体内呈现为[]T类型的变量.可以将其理解为一个Go的语法糖.示例如下:func sum(arg ...int) int { var total int //arg的类型为[]int for _, v : range arg { total v } return total }在函数外部....T类型形式参数可以匹配和接受的实参类型有两种.
.多个T类型变量.
.t...(t为[]T类型变量).示例如下:func main() { a, b, c : 1, 2, 3 fmt.Println(sum(a, b, c)) nums : []int{1, 2, 3} fmt.Println(sum(nums...)) } func sum(arg ...int) int { var total int //arg的类型为[]int for _, v : range arg { total v } return total }我们只能选择上述两种实参类型的一种.要么是多个T类型变量.要么是t...(t为[]T类型变量).如果两种混用.会得到类似下面的编译错误.使用变长参数函数最容易出现的问题是实参与形参不匹配.示例如下:func main() { s : []string{a, b, c} dump(s...) } func dump(args ...interface{}) { for _, v : range args { fmt.Println(v) } }编译器给出了类型不匹配的错误.虽然string类型可以直接赋值给interface{}类型变量.但是[]string类型变量并不能直接赋值给[]interface{}类型变量.修改示例如下:func main() { s : []interface{}{a, b, c} dump(s...) } func dump(args ...interface{}) { for _, v : range args { fmt.Println(v) } }不过有个例外.就是Go的内置函数append函数.它支持通过下面的方式将字符串附加到一个字节后面.示例如下:func main() { b : []byte{} b append(b, hello...) fmt.Println(b) }string类型本身不满足类型要求(append本需要[]byte...).这算是Go的一个编译优化.编译器自动将string隐式转换为了[]byte.func main() { b : []byte{} b append(b, hello...) fmt.Println(b) fooTest(b) } func fooTest(b ...byte) { fmt.Println(b) }
模拟函数重载:Go语言不允许在同一个作用域下定义名字相同但函数原型不同的函数.如果定义这样的函数.Go编译器会提示下面代码的错误信息.func concat(a, b int) string { return fmt.Sprintf(%d %d, a, b) } func concat(x, y string) string { return fmt.Sprintf(%s %s, x, y) } func concat(s []string)string { return strings.Join(s, ) }要修复上面的错误.只能修改函数命名.但是在其他语言中.比如java.就支持这种名字相同.参数类型不同的重载函数.但Go语言并不支持函数重载.Go语言官方给出不支持的理由是:其他语言的经验告诉我们.使用具有相同名称但是函数签名不同的多种方法有时会很有用.但在实践中也可能会造成混淆和脆弱性.在Go的类型系统中.仅按名称进行匹配要求类型一致是一个主要的简化决策.变长参数解决.示例如下:func main() { fmt.Println(concat(-, 1,
) fmt.Println(concat(-, hello, gopher)) } func concat(sep string, args ...interface{}) string { var result string for i, v : range args { if i ! 0 { result sep } switch v.(type) { case int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64: result fmt.Sprintf(%v, v) case string: result fmt.Sprintf(%s, v) case []int: ints : v.([]int) for i, v : range ints { if i ! 0 { result sep } result fmt.Sprintf(%v, v) } case []string: strings : v.([]string) result fmt.Sprintf(%v, strings) default: fmt.Println(不支持此类型) return } } return result }
模拟实现函数的可选参数和默认参数:如果参数在传入时有隐式要求的固定顺序(调用者保证).还可以利用变长参数函数模拟实现可选参数和默认参数.示例如下:type record struct { name string gender string age int city string country string } func enroll(args ...interface{}) (*record, error) { if len(args) 5 || len(args) 3 { return nil, fmt.Errorf(the number of arguments passed to wrong) } r : record{ city: ShanXi, country: TaiYuan, } for i, v : range args { switch i { case 0: name, ok : v.(string) if !ok { return nil, fmt.Errorf(the first argument to enroll must be a string) } r.name name case 1: gender, ok : v.(string) if !ok { return nil, fmt.Errorf(the second argument to enroll must be a string) } r.gender gender case 2: age, ok : v.(int) if !ok { return nil, fmt.Errorf(the third argument to enroll must be an uint
} r.age age case 3: city, ok : v.(string) if !ok { return nil, fmt.Errorf(the third argument to enroll must be a string) } r.city city case 4: country, ok : v.(string) if !ok { return nil, fmt.Errorf(the fourth argument to enroll must be a string) } r.country country default: return nil, fmt.Errorf(unknown argument %d, i) } } return r, nil } func main() { r, _ : enroll(小明, male,
fmt.Printf(%v\n, *r) r1, _ : enroll(小明, male, 26, linfen) fmt.Printf(%v\n, *r
}
实现功能选项:
.通过参数暴露配置选项:type FinishedHouse struct { style int centralAirConditioning bool floorMaterial string wallMaterial string } func NewFinishedHouse(style int, centralAirConditioning bool, floorMaterial, wallMaterial string) *FinishedHouse { h : FinishedHouse{ style: style, centralAirConditioning: centralAirConditioning, floorMaterial: floorMaterial, wallMaterial: wallMaterial, } return h } func main() { fmt.Printf(%v\n, NewFinishedHouse(0, true, wood, paper)) }上面设计的唯一优点就是快速实现.不足之处很多.最致命的是接口无法扩展.
结构体封装配置项:type FinishedHouse struct { style int centralAirConditioning bool floorMaterial string wallMaterial string } type Options struct { style int centralAirConditioning bool floorMaterial string wallMaterial string } func NewFinishedHouse(options *Options) *FinishedHouse { var style 0 var centralAirConditioning true var floorMaterial wood var wallMaterial paper if options ! nil { style options.style centralAirConditioning options.centralAirConditioning floorMaterial options.floorMaterial wallMaterial options.wallMaterial } h : FinishedHouse{ style: style, centralAirConditioning: centralAirConditioning, floorMaterial: floorMaterial, wallMaterial: wallMaterial, } return h } func main() { fmt.Printf(%v\n, NewFinishedHouse(0, true, wood, paper)) }优点:
.后续添加配置项选项.Options结构体可以随着时间变迁而增长.但FinishedHouse创建函数本身的Api签名不变.
.允许调用者使用nil来表示他们希望使用默认值来创建.
.可以更好地记录文档.缺点:
.每次都要为Options中所有字段赋值.
.如果Options中的值在调用后变化了怎么办.
.使用功能选项:type FinishedHouse struct { style int centralAirConditioning bool floorMaterial string wallMaterial string } type Option func(h *FinishedHouse) func NewFinishedHouse(options ...Option) *FinishedHouse { h : FinishedHouse{ style: 0, centralAirConditioning: true, floorMaterial: wood, wallMaterial: paper, } for _, option : range options { option(h) } return h } func WithStyle(style int) Option { return func(h *FinishedHouse) { h.style style } } func WithCentralAirConditioning(centralAirConditioning bool) Option { return func(h *FinishedHouse) { h.centralAirConditioning centralAirConditioning } } func WithFloorMaterial(floorMaterial string) Option { return func(h *FinishedHouse) { h.floorMaterial floorMaterial } } func WithWallMaterial(wallMaterial string) Option { return func(h *FinishedHouse) { h.floorMaterial wallMaterial } } func main() { //默认. fmt.Printf(%v\n, NewFinishedHouse()) fmt.Printf(%v\n, NewFinishedHouse(WithStyle(
, WithFloorMaterial(title))) }我也曾像个疯子一样.如果大家喜欢我的分享的话.可以关注我的微信公众号念何架构之路
17.c路nom-17.c路应用