方法

方法定义

跟go一样:在函数名前面加接收者,就是方法。

接收者默认不可变,如果要修改接收者,要加上mut。

结构体方法可以定义在同一个模块目录的不同源文件中。

struct User {
mut:
	name string
	age  int
}

fn (u User) get_name() string {
	return u.name
}

fn (mut u User) set_name(name string) { //需要修改接收者,要加上mut
	u.name = name
}

fn (_ User) str() string { //如果不需要在方法中使用接收者,可使用下划线来忽略,当然也可以命名
	return 'User'
}

除了可以给结构体添加方法外,还可以给以下类型添加方法:

  • 枚举

  • 类型别名

  • 函数类型

  • 联合类型

  • 自定义数组类型

  • 自定义字典类型

module main

struct Point {
	x int
	y int
}

enum Color {
	white
	black
	blue
}

type Myint = int

type MyFn = fn (int, int) int

type Mysumtype = int | string

pub fn (c Color) str() string { // 枚举的方法
	return 'from color'
}

pub fn (m Myint) str() string { // 类型别名的方法
	return 'from myint'
}

pub fn (myfn MyFn) str2() string { // 函数类型的方法
	return 'from myfn'
}

pub fn (mysum Mysumtype) str() string { // 联合类型的方法
	return 'from mysumtype'
}

pub fn (myarr []Point) point_arr_method() string { // 自定义数组类型的方法,接收者是对应的数组类型
	return 'from []Point'
}

pub fn (mymap map[string]Point) point_map_method() string { // 自定义字典类型的方法,接收者是对应的字典类型
	return 'from map[string]Point'
}

fn fn2(f MyFn) {
	println(f(1, 3)) // 直接调用函数
	println(f.str2()) // 调用函数类型的方法
}

fn add(x int, y int) int {
	return x + y
}

fn main() {
	// 枚举方法
	c := Color.white
	println(c.str())
	// 类型别名方法
	i := Myint(11)
	println(i.str())
	// 联合类型方法
	mut m := Mysumtype{}
	m = int(11)
	println(m.str())
	// 函数类型方法
	fn2(add)
	// 直接定义函数类型
	f := MyFn(add)
	println(f.str2()) // 调用函数类型的方法
	// 自定义数组类型方法
	p := Point{
		x: 1
		y: 2
	}
	mut p_array := []Point{}
	p_array << p
	println(p_array.point_arr_method())
	// 自定义字典类型方法
	mut mp := map[string]Point{}
	println(mp.point_map_method())
}

方法作为变量

方法也可以像函数那样,作为变量,作为函数的参数和返回值,只要方法签名和函数类型的签名一致就可以。

module main

struct Foo {
	s string
mut:
	i int
}

fn (f Foo) get_s() string {
	return f.s
}

fn (f &Foo) get_s_ref() string {
	return f.s
}

fn (f Foo) add(a int) int {
	return a + f.i
}

fn (f &Foo) add_ref(a int) int {
	return a + f.i
}

fn main() {
	mut f := Foo{
		s: 'hello'
		i: 1
	}

	get_s := f.get_s // 方法作为变量
	get_s_ref := unsafe { f.get_s_ref } // 方法作为变量
	add := f.add // 方法作为变量
	add_ref := unsafe { f.add_ref } // 方法作为变量

	println(typeof(get_s).str())
	println(typeof(get_s_ref).str())
	println(typeof(add).str())
	println(typeof(add_ref).str())

	println(get_s()) // 方法作为变量,也可直接调用
	println(get_s_ref()) // 方法作为变量,也可直接调用
	println(add(2)) // 方法作为变量,也可直接调用
	println(add_ref(2)) // 方法作为变量,也可直接调用
}

更复杂的例子:

module main

import gg
import gx

[heap]
struct App {
mut:
	gg     &gg.Context = 0
	radius f64 = 100.0
}

fn (mut app App) frame(x voidptr) {
	app.gg.begin()
	app.gg.draw_circle_empty(150, 150, f32(app.radius), gx.blue)
	app.gg.draw_text(20, 20, 'radius: $app.radius')
	app.gg.end()
}

fn main() {
	mut app := &App{}
	app.gg = gg.new_context(
		bg_color: gx.white
		width: 300
		height: 300
		window_title: 'Circles'
		frame_fn: app.frame // 方法也可以跟函数一样,作为变量使用
		user_data: app
	)
	app.gg.run()
}

静态方法/类型方法

V语言支持静态方法,也叫类型方法,可以用来作为结构体的构造器,使用静态方法的好处是,静态方法的前缀默认是类名,这样就可以让调用时,静态方法名有了类名的限定,看起来更直观,更易理解。

不过,如果是跨模块调用,就会变为:模块名.类名.方法名,多了一个层级,除非使用短名称导入类。大规模使用短名称导入,又会导致导入方式不一致,看起来不是那么的统一。

module main

pub struct Point {
	x int
	y int
}

//静态方法或类型方法,作为构造器
pub fn Point.new(x int, y int) Point {
	return Point{x, y}
}

//普通函数作为构造器
pub fn new_point(x int, y int) Point {
	return Point{x, y}
}

pub fn main() {
	p := Point.new(1, 2) //静态方法作为构造器
	println(p)
	p2 := new_point(2, 3) //普通函数作为构造器
	println(p2)
}

方法链式调用

V语言支持方法链式调用。

只是新的V编译器增加了限制,如果方法的接收者是可变的话,也就是在方法中修改了结构体字段,就不允许链式调用,希望后续的版本能够重新支持。

以下示例代码可以编译通过:

module main

[heap]
struct DB {
mut:
	sql string
}

fn new() DB {
	return DB{
		sql: ''
	}
}

fn (db DB) table(name string) DB {
	// db.sql += name
	println('from table')
	return db
}

fn (db DB) where(condition string) DB {
	// db.sql += condition
	println('where')
	return db
}

fn (db DB) first() DB {
	// db.sql += 'limit 1'
	println('from first')
	return db
}

fn main() {
	mut db := new()
	// 链式调用
	db.table('select * from user ').where('where id=1 ').first()
	println(db.sql)
}

以下示例代码无法编译通过,因为方法的接收者是可变的:

module main

struct DB {
mut:
	sql string
}

fn new() &DB { // &表示取地址,引用
	return &DB{
		sql: ''
	}
}

fn (mut db DB) table(name string) &DB { // &表示取地址,引用
	db.sql += name
	return db
}

fn (mut db DB) where(condition string) &DB {
	db.sql += condition
	return db
}

fn (mut db DB) first() &DB {
	db.sql += 'limit 1'
	return db
}

fn main() {
	mut db := new()
	// 链式调用
	db.table('select * from user ').where('where id=1 ').first()
	println(db.sql) // 输出:select * from user where id=1 limit 1
}

最后更新于