V语言11写文档

类似go.从文档串中自动生成.函数/类型/常数的文档必须放在其上1行.

//清理所有
fn clearall() {
}

注释,必须以自己名字开头.多行用多个单行注释.注释最好是现在时.
模块简介,应是模块名后的第1个注释.v doc net.http来生成文档.

工具

v fmt 文件.v格式化文件.一次运行,非常便宜,v fmt -w(每次保存时运行)约30毫秒.
拉代码时,v fmt -w file.v.
v -profile profile.txt run file.v来检查性能.产生性能文件供你分析.有四列:调用次数|总时间|平均时间|函数名.
sort -n -k3 profile.txt|tail这样来按第3列排序.
用秒表显式测试:

import time

fn main() {
	sw := time.new_stopwatch({})
	println("你好")
	println("花费: ${sw.elapsed().nanoseconds()}ns")
}

高级主题

运行时转存表达式

dump(expr)转存表达式.

fn factorial(n u32) u32 {
	if dump(n <= 1) {
		return dump(1)
	}
	return dump(n * factorial(n - 1))//转存
}

fn main() {
	println(factorial(5))
}

这样:v run factorial.v.转存会跟踪源位置,表达式,表达式的值.

内存不安全代码

写低级但高效代码,V要求标记出不安全的代码.

序号可能不安全动作
1指针运算
2指针索引
3从不匹配类型转换为指针
4调用某些free,strlen和strncmp等C函数

unsafe来包装它.

//分配未初化2字节并返回到它的引用
mut p := unsafe { malloc(2) }
p[0] = `h` //指针索引,仅在`不安全`块允许
unsafe {
    p[0] = `h` // OK
    p[1] = `i`
}
p++ //指针算术,仅在`不安全`块允许
unsafe {
    p++ // OK
}
assert *p == `i`

内存安全的表达式不要放在不安全块中,这样用不安全的原因,就很清晰.不安全块代码中不应有安全代码.
你怀疑程序违反内存安全,可以检查不安全块行为.
正在进行中.

带引用字段构

带引用字段要求显式给引用字段赋初值,除非已定义初值.
未来不支持零值引用/空针.现在在注意置0是不安全的,会导致恐慌.

struct Node {
	a &Node
	b &Node = 0 //自动初化为0,使用时小心
}

//必须初化引用字段,除非如b一样,已声明
// Zero (0)没问题,但要小心,是空针.
foo := Node{
	a: 0
}
bar := Node{
	a: &foo
}
baz := Node{
	a: 0
	b: 0
}
qux := Node{
	a: &foo
	b: &bar
}
println(baz)
println(qux)

sizeof和__offsetof.前者sizeof(Type)字节给定大小,
后者__offsetof(Struct, field_name)像这样给定字段偏移.

struct Foo {
	a int
	b int
}

assert sizeof(Foo) == 8
assert __offsetof(Foo, a) == 0
assert __offsetof(Foo, b) == 4

V调用C:

#flag -lsqlite3
#include "sqlite3.h"
...C系接口...
//https://www.sqlite.org/quickstart.html,示例
fn my_callback(arg voidptr, howmany int, cvalues &&char, cnames &&char) int {
	unsafe {
		for i in 0 .. howmany {
			print("| ${cstring_to_vstring(cnames[i])}: ${cstring_to_vstring(cvalues[i]):20} ")
		}
	}
	println("|")
	return 0
}

fn main() {
	db := &C.sqlite3(0) 
	C.sqlite3_open(c"users.db", &db)
	query := "select count(*) from users"
	stmt := &C.sqlite3_stmt(0)
	C.sqlite3_prepare_v2(db, &char(query.str), -1, &stmt, 0)
	C.sqlite3_step(stmt)
	nr_users := C.sqlite3_column_int(stmt, 0)
	C.sqlite3_finalize(stmt)
	println("数据库中有$nr_users用户.")
	//
	error_msg := &char(0)
	query_all_users := "select * from users"
	rc := C.sqlite3_exec(db, &char(query_all_users.str), my_callback, voidptr(7), &error_msg)
	if rc != C.SQLITE_OK {
		eprintln(unsafe { cstring_to_vstring(error_msg) })
		C.sqlite3_free(error_msg)
	}
	C.sqlite3_close(db)
}

flag,传递C编译标志.在V文件开头.

标志意思
-I包含路径
-l库名
-LC库搜索路径
-D编译时变量

不同目标用不同标志:可用有:linux,darwin,freebsd,及windows
每个标志,必须单独一行:

#flag linux -lsdl2
#flag linux -Ivig
#flag linux -DCIMGUI_DEFINE_ENUMS_AND_STRUCTS=1
#flag linux -DIMGUI_DISABLE_OBSOLETE_FUNCTIONS=1
#flag linux -DIMGUI_IMPL_API=

控制台中,你可用-cflags来传递自定义标志,用-cc来改编译器,如-cc gcc-9 -cflags -fsanitize=thread.
可在终端中定义VFLAGS来存储-cc-cflags标志,避免重复定义他们.

属性

函数/构/枚声明前,作用于该声明.

//调用时,过时警告
[deprecated]
fn old_function() {
}

//自定义消息
[deprecated: "用新 new_function() 替代"]
fn legacy_function() {}

//会内联该函数 
[inline]
fn inlined_function() {
}

//构在堆中分配只能用作引用
[heap]
struct Window {
}

//如果标志为假,则不编译
// 用`v -d flag`
[if debug]
fn foo() {
}

fn bar() {
	foo() //未传递`-d debug`,则不调用
}

//函数返回前,垃集不释放内存 
[keep_args_alive]
fn C.my_external_function(voidptr, int, voidptr) int

//必须在不安全块,
[unsafe]
fn risky_business() {
	//检查代码及前条件
	unsafe {
		// 不检查,指针算术,访问联,调用`不安全`
		// 最好最小化不安全块代码
	}
	//仍检查这里
}

//本函数,手动释放,程序员负责
[manualfree]
fn custom_allocations() {
}

//C互操作,C方式
[typedef]
struct C.Foo {
}

// 传递Win32 API回调
[windows_stdcall]
fn C.DefWindowProc(hwnd int, msg int, lparam int, wparam int)

//导入gg,ui图形库时,图窗占优势,不创建控制台
//有效禁止`打印行`语句,显式创建控制台,仅在`主`前有效.
[console]
fn main() {
}

允许goto跳至标签.要求不安全,要尽量少用.

交叉编译

v -os windows ./v -os linux .目前不支持macOS.

热加载

module main

import time
import os

[live]
fn print_message() {
	println("运行本程序时修改")
}

fn main() {
	for {
		print_message()
		time.sleep(500 * time.millisecond)
	}
}

v -live message.v.目前,运行程序时不能修改类型.

转换C到V

#include "stdio.h"

int main() {
	for (int i = 0; i < 10; i++) {
		printf("你好,世界\n");
	}
        return 0;
}//转成下面
fn main() {
	for i := 0; i < 10; i++ {
		println("你好,世界")
	}
}

C库中生成包装器:v wrapper c_code/libsodium/src/libsodium.优秀C代码,可转译至V.

序号好处
1用1种语言开发代码,更安全,更容易
2交叉编译更简单
3不需要各种标志

内联汇编

a := 100
b := 20
mut c := 0
asm amd64 {
    mov eax, a
    add eax, b
    mov c, eax
    ; =r (c) as c // output
    ; r (a) as a // input
      r (b) as b
}
println("a: $a") // 100
println("b: $b") // 20
println("c: $c") // 120

有限操作符重载:

struct Vec {
	x int
	y int
}

fn (a Vec) str() string {
	return "{$a.x, $a.y}"
}

fn (a Vec) + (b Vec) Vec {
	return Vec{a.x + b.x, a.y + b.y}
}

fn (a Vec) - (b Vec) Vec {
	return Vec{a.x - b.x, a.y - b.y}
}

fn main() {
	a := Vec{2, 3}
	b := Vec{4, 5}
	mut c := Vec{1, 2}
	println(a + b) // "{6, 8}"
	println(a - b) // "{-2, -2}"
	c += a
	println(c) // "{3, 5}"
}

用来改进可读性,为了安全性/维护性,限制:

序号限制
1仅能重载+,-,*,/,%,<,>,==,!=,<=,>=
2编译器重载==/!=,但你可覆盖
3禁止符号函数中调用其他函数
4符号函数,不能修改参数
5</==时,必须返回.
6定义了==和<后,自动生成!=,>,<=及>=
7两边参数类型必须相同
8定义操作符时,自动生成*=,+=,/=,且必须返回相同类型

编译时反射

允许你对任何数据格式创建序化器.V有编译时if/for.

// TODO: 未完全实现

struct User {
    name string
    age  int
}

// T应仅传递构名
fn decode<T>(data string) T {
    mut result := T{}
    // 编译时`for`循环
    // T.fields给出字段元数据类型数组
    $for field in T.fields {
        $if field.typ is string {
            // $(string_expr)产生标识符
            result.$(field.name) = get_string(data, field.name)
        } $else $if field.typ is int {
            result.$(field.name) = get_int(data, field.name)
        }
    }
    return result
}

// `decode<User>` 生成:
fn decode_User(data string) User {
    mut result := User{}
    result.name = get_string(data, "name")
    result.age = get_int(data, "age")
    return result
}

优化

-prod编译时,如果给定提示,还可进一步优化:

属性意思
[inline]内联
[direct_array_access]转为C数组操作来直接访问,省略检查边界,但不安全.
if _likely_(bool expression) {该分支很可能真
if _unlikely_(bool expression) {很可能假

编译时伪变量,编译时替换,类似宏.

伪变量意思
@FN当前函数名
@METHODReceiverType.MethodName来替换,方法
@MOD模块名
@STRUCT构名
@FILE源码文件路径
@LINE行号,串表示
@COLUMN列号
@VEXEV编译器路径
@VEXEROOTV编译器路径根,
@VHASHV编译器的哈希
@VMOD_FILE最近v.mod内容
@VMODROOT最近v.mod根目录

调试/记录/跟踪时有用:

eprintln("file: " + @FILE + " | line: " + @LINE + " | fn: " + @MOD + "." + @FN)
//或嵌入版本号
import v.vmod
vm := vmod.decode( @VMOD_FILE ) or { panic(err.msg) }
eprintln("$vm.name $vm.version\n $vm.description")
//后面为环境特殊变量
posted @   zjh6  阅读(21)  评论(0编辑  收藏  举报  
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现
点击右上角即可分享
微信分享提示