结构体
结构体定义
结构体命名必须是大写字母开头,如果是小写字母开头会编译报错。
struct Point {
x int
y int
}
fn main() {
p := Point{
x: 10
y: 20
}
println(p.x) // 结构体字段通过点号来访问
}
结构体被分配到内存的栈中,引用类型。
使用&来取结构体的地址
p2 := &Point{10, 10}
println(p2.x)
空结构体:
struct Empty {}
fn main() {
println(sizeof(Empty)) //空结构体占用内存的大小为0
}
sizeof( )可以返回结构体占用内存字节大小。
字段
结构体字段默认也是不可变,使用mut为可变。
结构体字段的可变性和访问控制,参考访问控制章节。
如果结构体字段名需要是关键字,可以通过使用@作为前缀也可以编译通过。
这一点在跟C库集成时比较常用,一些C库的struct的字段有些刚好是V的关键字,可以使用@前缀,编译成C代码时@前缀会被去掉,刚好就是C的struct中的字段名。
struct Foo {
@type string
}
fn main() {
foo := Foo{ @type: 'test' }
println(foo.@type)
}
结构体字段支持默认值:
struct Foo {
a int
b int = 7 //默认值7
}
fn main() {
foo := Foo{
a: 1
}
println(foo.a) //输出1
println(foo.b) //输出默认值7
foo2 := Foo{
a: 1
b: 2
}
println(foo2.b) //输出2
}
结构体字段的类型可以是任何类型,甚至可以是函数类型:
module main
struct Abc {
f1 int
f2 int
f3 fn (int, int) int //结构体字段类型为函数类型
}
fn add(x int, y int) int {
return x + y
}
fn main() {
a1 := Abc{
f1: 123
f3: fn (x int, y int) int {
return x + y
}
}
a2 := Abc{
f1: 123
f2: 789
f3: add
}
println(a1)
println(a2)
}
结构体字段初始化时必录:
struct Point {
x int
y int
z int [required] //字段注解required表示必须初始化赋值
}
fn main() {
a := Point{
// x: 1 //不会报错,x不是必须初始化赋值
y: 3
z: 5
}
b := Point{
x: 2
y: 4
// z: 6 //报错: field `Point.z` must be initialized
}
println(a)
println(b)
}
结构体变量可以基于另一个变量创建,同时合并新的字段值:
module main
struct City {
name string
population int
}
struct Country {
name string
capital City
}
fn main() {
c := Country{
name: 'test'
capital: City{
name: 'city'
}
}
c2 := Country{
...c // 在c的基础上创建c2变量
capital: City{
name: 'city2'
population: 200
}
}
println(c2)
}
当函数的参数是结构体变量时,这个语法可以简化结构体变量的创建,这个语法在ui模块比较常用到,用来简化函数参数。
module main
struct User {
name string
age int
}
fn add(u User) {
println(u)
}
fn main(){
add(User{name:'jack',age:22}) //标准方式
add(name:'tt',age:33) //简短的方式,省略类型和大括号,有命名参数调用的感觉
}
选项字段:
跟函数的返回值类似:字段类型前加问号?表示:字段可能返回正常的类型或返回空值,即 T | none,如果字段返回none,则会触发空值错误,进入or代码块。
module main
struct Foo {
mut:
x ?int = 1 //字段可以返回正常类型或者none
y ?int = none //字段默认值为none,触发空值错误,进入or代码块
}
fn main() {
mut f := Foo{}
println(f.x?) //输出1
f = Foo{
x: 2
y: none
}
a := f.x or { 123 }
println(a) // 输出2
b := f.y or { 9999 } // f.y返回空值none,触发空值错误,进入or代码块
println(b) // 输出9999
mut sum := f.x? + 1
sum = f.x or { 123 } + 1
println(sum) // 输出3
}
访问控制
结构体默认是模块级别的,使用pub关键字定义公共级别。
pub struct Point { //公共级别,可以被别的模块使用
x int
y int
}
组合
跟go一样,V的结构体没有继承,但是有组合,可以用组合来实现继承一样的效果,多个组合也是可以的。
module main
struct Widget {
mut:
x int
y int
}
pub fn new_widget(x int, y int) Widget {
return Widget{
x: x
y: y
}
}
pub fn (mut w Widget) move(x_step int, y_step int) {
w.x += x_step
w.y += y_step
}
struct Widget2 {
mut:
z int
}
pub fn new_widget2(z int) Widget2 {
return Widget2{
z: z
}
}
pub fn (mut w Widget2) move_z(z_step int) {
w.z += z_step
}
struct Button {
Widget //组合
Widget2 //多个组合
title string
}
fn main() {
mut button := Button{
title: 'Click me'
}
button.x = 3 // Button的x字段来自Widget
button.z = 4 // Button的z字段来自Widget2
println('x:$button.x,y:$button.y,z:$button.z')
button.move(3, 4) // Button的move方法来自Widget
println('x:$button.x,y:$button.y,z:$button.z')
button.move_z(5) // Button的move_z方法来自Widget2
println('x:$button.x,y:$button.y,z:$button.z')
}
匿名结构体
在定义结构体字段时,除了使用已经定义好的结构体作为类型,也可以使用匿名结构体。
module main
struct Book {
x string
author struct { //匿名结构体
name string
age int
}
author2 struct { //匿名结构体也可以是空结构体
}
title string
}
fn main() {
book := Book{
author: struct {'sdf', 23} // 初始化匿名结构体字段时,也需要使用struct关键字
author2: struct {}
}
println(book)
println(book.author.age)
}
结构体初始化
常用的结构体初始化有2种:
字面量初始化
构造函数初始化
//字面量初始化
mut button := Button{title: 'Click me'}
//构造函数:就是一个普通的函数,一般都是new或者new_xxx,返回对应的结构体
pub fn new_button(title string) Button {
return Button{
title: title
}
}
带组合的结构体初始化
struct Button {
Widget //组合1
Widget2 //组合2
pub:
title string
}
// 带组合的结构体初始化
mut button3 := Button{
Widget: Widget{
x: 1
y: 2
}
Widget2: Widget2{
z: 3
}
title: 'button3'
}
// 使用构造函数也可以
mut button4 := Button{
Widget: new_widget(1, 2)
Widget2: new_widget2(3)
title: 'button4'
}
空值初始化引用类型字段
引用类型的结构体字段必须进行初始化:
module main
struct Point {
x int
y int
}
struct Window {
pub:
p &Point = unsafe { nil } // 空值初始化引用类型字段,替代原来的voidptr(0)值
}
fn main() {
p := &Point{1,3}
w := Window { p:p }
println(w)
}
泛型结构体
参考泛型章节。
结构体注解
结构体像函数那样支持注解,可以针对结构体,结构体字段,结构体方法进行注解。
@[typedef]
@[typedef]
struct Point {
}
typedef注解目前主要用在集成C代码库,详细参考集成C代码章节。
@[heap]
heap表示该结构体只能在内存的堆上创建,只能以引用的形式被使用。
@[heap]
struct Window { //只能通过引用的形式(&Window)来使用这个结构体
}
可以参考标准库sync中WaitGroup的实际使用:
vlib/sync/waitgroup.v
@[heap]
struct WaitGroup {
mut:
task_count int
task_count_mutex &Mutex = &Mutex(0)
wait_blocker &Waiter = &Waiter(0)
}
pub fn new_waitgroup() &WaitGroup { //不能被复制,只能以引用的方式被使用
return &WaitGroup{
task_count_mutex: new_mutex()
wait_blocker: new_waiter()
}
}
@[noinit]
使用@[noinit]标志后,结构体只能在本模块内使用Foo{ }来创建变量,在其他模块中禁止直接使用字面量Foo{ }来初始化变量,只能使用模块提供的构造函数来创建。
module mymodule
@[noinit]
pub struct Result {
}
//模块内的构造函数
pub fn new_result() Result {
return Result{}
}
module main
import mymodule
fn main() {
// 报错:it cannot be initialized with `mymodule.Result{}`
// res := mymodule.Result{}
res := mymodule.new_result() //调用模块提供的构造函数是可以的
println(res)
}
@[params]
使用结构体的参数注解,可以用来实现函数参数的默认值和命名参数。
//参数注解
@[params]
struct ButtonConfig {
text string
is_disabled bool
width int = 70
height int = 20
}
struct Button {
text string
width int
height int
}
fn new_button(c ButtonConfig) &Button {
return &Button{
width: c.width
height: c.height
text: c.text
}
}
fn main() {
button := new_button(text: 'button1', width: 100)
println(button.height) // height没有初始化,使用默认值
button2 := new_button(width: 100, text: 'button2') //结构体参数的字段顺序无所谓
println(button2)
//结构体参数加了params注解,函数的参数可以什么都不传,直接使用结构体的默认值
button3 := new_button()
println(button3) //所有参数都使用默认值
}
@[table]
在内置的orm中使用,用于定义模型结构体对应的数据库表名
@[table: 'userlist'] //自定义表名
struct User {
id int [primary; sql: serial]
age int
name string [sql: 'username']
is_customer bool
skipped_string string [skip]
}
@[minify]
对结构体的内存布局进行优化,占用内存空间更小,会使用C语言的位字段,把布尔类型从占用1个字节精简为1个位,把枚举占用的内存字节数也降低。这个参数已被使用到V的编译器代码中,编译器的内存占用大约降低了8M。
@[minify]
struct Point {
x int
y int
}
@[packed]
不让编译器进行结构体内存对齐。
关于内存对齐可参考:https://www.zhihu.com/question/27862634
module main
struct Foo {
a i64 // 8字节
b int // 4字节
}
@[packed]
struct Bar {
a i64
b int
}
pub fn main() {
println(sizeof(Foo)) // 16,编译器会进行内存对齐
println(sizeof(Bar)) // 12,不让编译器进行内存对齐
}
@[autostr: allowrecurse]
这个注解告诉编译器,允许解析嵌套自身的结构体,而不用担心循环引用,确保结构体的print相关函数正确输出。
@[autostr: allowrecurse]
struct Node {
a &Node = unsafe { nil }
b &Node = unsafe { nil }
}
fn main() {
abc := &Node{
a: &Node{
b: &Node{}
}
b: &Node{
a: &Node{}
}
}
println(abc)
}
输出:
&Node{
a: &Node{
a: &nil
b: &Node{
a: &nil
b: &nil
}
}
b: &Node{
a: &Node{
a: &nil
b: &nil
}
b: &nil
}
}
如果没有这个这个注解,则会输出:
&Node{
a: &<circular>
b: &<circular>
}
结构体字段注解
@[required]
要求结构体字段在初始化的时候必须赋值
用于内置json解析支持,详细参考json章节。
结构体字段必须初始化赋值注解。
struct Point {
x int
y int
z int [required] //字段注解required表示必须初始化赋值
}
fn main() {
a := Point{
// x: 1 //不会报错,x不是必须初始化赋值
y: 3
z: 5
}
b := Point{
x: 2
y: 4
// z: 6 //报错: field `Point.z` must be initialized
}
println(a)
println(b)
}
@[deprecated]
结构体字段作废
可以将结构体的某个字段标注为作废字段,并且可以加上作废开始日期。
只有在其他模块中直接访问作废字段,才会出发过期作废的提示或警告。
abc.v
module abc
pub struct Point {
pub mut:
x_new int
x int [deprecated: 'use Point.x_new instead'; deprecated_after: '2999-03-01']
}
main.v
module main
import abc
fn main() {
a := abc.Point{
x: 1
}
println(a.x) //直接访问了结构体的作废字段,就会提示或警告
}
结构体方法注解
跟函数注解一样,也可以对结构体方法进行注解。
@[inline] //方法注解
pub fn (p Point) position() (int, int) {
return p.x, p.y
}
结构体反射
可以通过编译时反射,实现动态获取结构体的所有字段,所有方法,并且可以动态设置字段值。
具体可以参考:编译时反射章节。
结构体字段内存位置偏移
__offsetof函数实现C的offsetof函数那样,返回结构体中某个字段的内存偏移量。
module main
struct User {
name [50]u8
age int
desc string
}
fn main() {
offset_name:=__offsetof(User,name)
offset_age:=__offsetof(User,age)
offset_desc:=__offsetof(User,desc)
println(offset_name)
println(offset_age)
println(offset_desc)
}
最后更新于
这有帮助吗?