仓颉编程(8)标准库的应用
一、随机数库(std.random)的应用
1.1 随机数库的基本使用
在仓颉中,随机数的生成主要通过Random 类实现。Random 类位于 std.random 包中,提供了生成各种类型伪随机数的功能。要使用随机数库,首先需要导入相关包:
import std.random.Random
1.1.1 创建 Random 对象
Random 类提供了两种构造方法:
- 无参构造函数:创建一个使用系统时间作为种子的随机数生成器
let random = Random()
- 带种子的构造函数:使用指定的 UInt64 类型种子创建随机数生成器
let random = Random(12345) // 使用种子12345
使用相同种子创建的 Random 对象会生成相同的随机数序列,这在需要重现特定随机序列的场景(如测试)中非常有用。
1.1.2 生成基本类型随机数
Random 类提供了生成各种基本类型随机数的方法:
生成布尔值随机数:
let boolRandom = random.nextBool() // 返回true或false,概率各为50%
生成整数随机数:
Random 类提供了生成不同位数整数的方法:
- nextInt8():生成 Int8 类型随机数
- nextInt16():生成 Int16 类型随机数
- nextInt32():生成 Int32 类型随机数
- nextInt64():生成 Int64 类型随机数(最常用)
类似地,还有生成无符号整数的方法:
- nextUInt8()
- nextUInt16()
- nextUInt32()
- nextUInt64()
生成浮点数随机数:
- nextFloat16():生成 Float16 类型随机数
- nextFloat32():生成 Float32 类型随机数
- nextFloat64():生成 Float64 类型随机数(最常用)
这些方法生成的浮点数范围是 **[0.0, 1.0)**,即大于等于 0.0 且小于 1.0。
1.2 控制随机数范围
1.2.1 生成指定范围的整数
Random 类提供了带有参数的 nextInt 方法,可以生成指定范围内的随机整数。这些方法采用左闭右开区间的约定,即包含起始值,不包含结束值:
// 生成[0, 10)范围内的随机整数(0到9之间)
let int0_9 = random.nextInt64(10)
// 生成[5, 15)范围内的随机整数(5到14之间)
let int5_14 = random.nextInt64(10) + 5
如果需要生成包含结束值的区间,可以通过简单的数学运算实现:
// 生成[1, 10]范围内的随机整数(1到10之间)
let int1_10 = random.nextInt64(10) + 1
// 生成[a, b]范围内的随机整数
func randomIntRange(a: Int64, b: Int64): Int64 {return random.nextInt64(b - a + 1) + a
}
1.2.2 生成指定范围的浮点数
要生成指定范围的浮点数,可以使用基本的浮点数生成方法结合数学运算:
// 生成[0.0, 5.0)范围内的随机浮点数
let float0_5 = random.nextFloat64() * 5.0
// 生成[2.5, 7.5)范围内的随机浮点数
let float2_5_7_5 = random.nextFloat64() * 5.0 + 2.5
// 生成[a, b)范围内的随机浮点数
func randomFloatRange(a: Float64, b: Float64): Float64 {return random.nextFloat64() * (b - a) + a
}
如果需要包含结束值 b,可以使用以下方法:
// 生成[a, b]范围内的随机浮点数
func randomFloatRangeInclusive(a: Float64, b: Float64): Float64 {return random.nextFloat64() * (b - a) + a
}
1.2.3 生成高斯分布随机数
Random 类还提供了生成符合高斯分布(正态分布)的随机数方法,这些方法需要指定均值(mean)和标准差(sigma):
// 生成符合标准正态分布(均值0,标准差1)的Float64随机数
let gaussianFloat64 = random.nextGaussianFloat64()
// 生成均值为5.0,标准差为2.0的高斯分布随机数
let gaussianCustom = random.nextGaussianFloat64(5.0, 2.0)
// 生成高斯分布的Float32随机数
let gaussianFloat32 = random.nextGaussianFloat32(3.0, 1.5)
1.3 随机数生成示例
以下是一个完整的随机数生成示例,展示了各种随机数的生成方法:
package demo
import std.random.Random
main(): Int64 {let random = Random()// 生成布尔值println("布尔值随机数: ${random.nextBool()}")// 生成不同位数的整数println("Int8随机数: ${random.nextInt8()}")println("Int16随机数: ${random.nextInt16()}")println("Int32随机数: ${random.nextInt32()}")println("Int64随机数: ${random.nextInt64()}")// 生成无符号整数println("UInt8随机数: ${random.nextUInt8()}")println("UInt16随机数: ${random.nextUInt16()}")println("UInt32随机数: ${random.nextUInt32()}")println("UInt64随机数: ${random.nextUInt64()}")// 生成浮点数println("Float16随机数: ${random.nextFloat16()}")println("Float32随机数: ${random.nextFloat32()}")println("Float64随机数: ${random.nextFloat64()}")// 生成指定范围的整数println("0-9之间的随机整数: ${random.nextInt64(10)}")println("5-14之间的随机整数: ${random.nextInt64(10) + 5}")// 生成指定范围的浮点数println("0.0-5.0之间的随机浮点数: ${random.nextFloat64() * 5.0}")println("2.5-7.5之间的随机浮点数: ${random.nextFloat64() * 5.0 + 2.5}")// 生成高斯分布随机数println("标准正态分布随机数: ${random.nextGaussianFloat64()}")println("自定义高斯分布随机数: ${random.nextGaussianFloat64(mean: 5.0, sigma: 2.0)}")return 0
}
二、数学库(std.math)的应用
2.1 数学库概览
仓颉的数学库(std.math)提供了丰富的数学函数和常量,涵盖了基本算术运算、三角函数、指数对数函数、位运算等多个方面。要使用数学库,可以导入整个包或按需导入特定函数:
import std.math.* // 导入所有数学函数和常量
2.2 基本数学函数
2.2.1 绝对值函数
绝对值函数abs可以处理多种数值类型:
// 整数绝对值
let intAbs = abs(-12) // 12
let int8Abs = abs(Int8(-5)) // 5
// 浮点数绝对值
let floatAbs = abs(-3.14) // 3.14
let float32Abs = abs(Float32(-2.718)) // 2.718
2.2.2 幂运算和开方
数学库提供了多种幂运算和开方函数:
幂运算函数pow:
// 浮点数幂运算
let pow1 = pow(2.0, 3.0) // 8.0
let pow2 = pow(10.0, 2.0) // 100.0
// 整数指数
let pow3 = pow(3.0, 4) // 81.0
let pow4 = pow(2.0, Int64(5)) // 32.0
平方根函数sqrt:
let sqrt1 = sqrt(4.0) // 2.0
let sqrt2 = sqrt(2.0) // 1.41421356...
let sqrt3 = sqrt(100.0) // 10.0
立方根函数cbrt:
let cbrt1 = cbrt(8.0) // 2.0
let cbrt2 = cbrt(27.0) // 3.0
2.2.3 指数和对数函数
指数函数:
- exp(x):计算自然常数 e 的 x 次幂
- exp2(x):计算 2 的 x 次幂
import std.math.*
let eExp = exp(1.0) // e ≈ 2.71828
let eSquare = exp(2.0) // e² ≈ 7.38906
let twoPower3 = exp2(3.0) // 8.0
对数函数:
数学库提供了多种对数函数,包括自然对数(ln)、以 2 为底的对数(log2)等:
let ln2 = log(2.0) // 自然对数,约0.693147
let log2_8 = log2(8.0) // 以2为底,3.0
let log10_100 = log10(100.0) // 以10为底,2.0
2.3 三角函数
数学库提供了完整的三角函数支持,参数均为弧度制:
2.3.1 基本三角函数
// 正弦函数
let sin30 = sin(30.0 * Float64.getPI() / 180.0) // sin(30°) = 0.5
let sin90 = sin(90.0 * Float64.getPI() / 180.0) // sin(90°) = 1.0
// 余弦函数
let cos0 = cos(0.0) // 1.0
let cos45 = cos(45.0 * Float64.getPI() / 180.0) // cos(45°) = √2/2 ≈ 0.7071
// 正切函数
let tan45 = tan(45.0 * Float64.getPI() / 180.0) // tan(45°) = 1.0
let tan60 = tan(60.0 * Float64.getPI() / 180.0) // tan(60°) = √3 ≈ 1.732
2.3.2 反三角函数
// 反正弦函数(结果为弧度)
let asin0_5 = asin(0.5) // π/6
let asin1 = asin(1.0) // π/2
// 反余弦函数
let acos0_5 = acos(0.5) // π/3
let acos1 = acos(1.0) // 0
// 反正切函数
let atan1 = atan(1.0) // π/4
let atan0 = atan(0.0) // 0
2.3.3 双曲函数
数学库还提供了双曲函数和反双曲函数:
// 双曲正弦函数
let sinh1 = sinh(1.0) // (e^1 - e^-1)/2 ≈ 1.1752
let sinh0 = sinh(0.0) // 0
// 双曲余弦函数
let cosh0 = cosh(0.0) // 1
let cosh1 = cosh(1.0) // (e^1 + e^-1)/2 ≈ 1.5431
// 双曲正切函数
let tanh1 = tanh(1.0) // (e^1 - e^-1)/(e^1 + e^-1) ≈ 0.7616
let tanh0 = tanh(0.0) // 0
2.4 取整函数
数学库提供了多种取整函数:
2.4.1 向上取整(ceil)
ceil函数返回不小于给定值的最小整数:
let ceil1 = ceil(4.2) // 5.0
let ceil2 = ceil(4.7) // 5.0
let ceil3 = ceil(-4.2) // -4.0
let ceil4 = ceil(-4.7) // -4.0
2.4.2 向下取整(floor)
floor函数返回不大于给定值的最大整数:
let floor1 = floor(4.2) // 4.0
let floor2 = floor(4.7) // 4.0
let floor3 = floor(-4.2) // -5.0
let floor4 = floor(-4.7) // -5.0
2.4.3 四舍五入(round)
round函数执行四舍五入操作:
let round1 = round(3.4) // 3.0
let round2 = round(3.5) // 4.0
let round3 = round(3.6) // 4.0
let round4 = round(-3.5) // -4.0
2.4.4 截断取整(trunc)
trunc函数截断小数部分,返回整数部分:
let trunc1 = trunc(4.9) // 4.0
let trunc2 = trunc(-4.9) // -4.0
let trunc3 = trunc(4.1) // 4.0
let trunc4 = trunc(-4.1) // -4.0
2.5 数学常量
数学库提供了几个重要的数学常量:
圆周率 π:
import std.math.*
let pi = Float64.getPI() // π ≈ 3.141592653589793
let piShort = Float32.getPI() // π的Float32近似值
// 使用示例:计算圆的面积和周长
func circleArea(radius: Float64): Float64 {return getPI() * radius * radius
}
func circleCircumference(radius: Float64): Float64 {return 2.0 * getPI() * radius
}
自然常数 e:
let e = Float64.getE() // e ≈ 2.718281828459045
let eShort = Float32.getE() // e的Float32近似值
2.6 数学运算示例
以下是一个综合的数学运算示例:
package Study
import std.math.*
import std.convert.Formattable
main(): Int64 {// 基本运算println("绝对值:")println("abs(-12) = ${abs(-12)}")println("abs(-3.14) = ${abs(-3.14)}")println("\n幂运算和开方:")println("pow(2.0, 3.0) = ${pow(2.0, 3.0)}")println("pow(10.0, 2.0) = ${pow(10.0, 2.0)}")println("sqrt(4.0) = ${sqrt(4.0)}")println("sqrt(2.0) = ${sqrt(2.0).format(".10")}") // 保留10位小数println("\n三角函数(角度转弧度):")let degrees30 = 30.0let radians30 = degrees30 * getPI() / 180.0println("sin(30°) = ${sin(radians30)}")println("cos(30°) = ${cos(radians30)}")println("tan(30°) = ${tan(radians30).format(".10")}")println("\n取整函数:")println("ceil(4.2) = ${ceil(4.2)}")println("floor(4.7) = ${floor(4.7)}")println("round(3.5) = ${round(3.5)}")println("trunc(4.9) = ${trunc(4.9)}")println("\n数学常量:")println("π = ${getPI().format(".15")}") // 显示15位小数println("e = ${getE().format(".15")}")// 计算示例:勾股定理let a = 3.0let b = 4.0let c = sqrt(pow(a, 2.0) + pow(b, 2.0))println("\n勾股定理示例:")println("直角边a = ${a}, b = ${b}")println("斜边c = ${c}")return 0
}
三、格式化库(std.format)的应用
3.1 格式化库概述
仓颉的格式化库(std.format)提供了强大的格式化功能,主要用于将各种类型的数据转换为格式化的字符串。该库定义了Formatter接口,为内置类型(如 Int8、Float64 等)提供了默认实现,开发者也可以为自定义类型实现该接口以获得格式化能力。
3.2 基本格式化语法
格式化字符串的基本语法为:
format_spec := [flags][width][.precision][specifier]
其中:
- flags:可选,包括-(左对齐)、+(显示正负号)、#(添加进制前缀)、0(补零)
- width:可选,指定输出宽度
- precision:可选,指定精度(小数位数或有效数字)
- specifier:必选,指定输出格式(如整数、浮数、进制等)
3.3 数字格式化
3.3.1 整数格式化
整数可以格式化为二进制、八进制、十六进制等:
import std.format.*
let num = 20
// 基本进制格式
println("二进制: ${num.format("b")}") // 10100
println("八进制: ${num.format("o")}") // 24
println("十六进制: ${num.format("x")}") // 14
println("大写十六进制: ${num.format("X")}") // 14
// 带进制前缀
println("带前缀的十六进制: ${num.format("#x")}") // 0x14
println("带前缀的八进制: ${num.format("#o")}") // 0o24
// 宽度和对齐
println("宽度为10的右对齐: \"${num.format("10")}\"") // " 20"
println("宽度为10的左对齐: \"${num.format("-10")}\"") // "20 "
println("宽度为10补零: \"${num.format("010")}\"") // "0000000020"
// 带符号的整数
let negativeNum = -20
println("正号显示: ${negativeNum.format("+")}") // -20
println("正号显示: ${20.format("+")}") // +20
3.3.2 浮点数格式化
浮点数格式化支持多种格式,包括定点数、科学计数法等:
import std.format.*
let pi = 3.141592653589793
let e = 2.718281828459045
let largeNum = 123412341234.1
// 基本浮点格式
println("默认格式: ${pi}") // 3.141592653589793
// 控制小数位数(精度)
println("保留2位小数: ${pi.format(".2")}") // 3.14
println("保留10位小数: ${pi.format(".10")}") // 3.1415926536
// 科学计数法
println("科学计数法(小写e): ${pi.format("e")}") // 3.141593e+00
println("科学计数法(大写E): ${pi.format("E")}") // 3.141593E+00
println("指定宽度和精度: ${pi.format("20.2e")}") // " 3.1e+00"
// general格式(自动选择合适的表示方式)
println("general格式: ${largeNum.format("G")}") // 1.23412E+11
println("general格式: ${pi.format("G")}") // 3.141592653589793
// 宽度和对齐
println("宽度为20,右对齐: \"${pi.format("20")}\"") // " 3.141592653589793"
println("宽度为20,左对齐: \"${pi.format("-20")}\"") // "3.141592653589793 "
println("宽度为20,补零: \"${pi.format("020.2")}\"") // "000000000003.14"
3.4 日期和时间格式化
日期时间格式化需要结合std.time包使用。std.time包提供了DateTime结构体表示日期时间,支持灵活的格式化。
3.4.1 时间字符串格式说明
时间格式化使用特定的格式字符:
字符 | 含义 | 示例 |
a | 上下午 | AM/PM |
y | 公元年 | 2023 |
M | 月份 | 01-12 |
d | 日 | 01-31 |
H | 24 小时制小时 | 00-23 |
h | 12 小时制小时 | 01-12 |
m | 分钟 | 00-59 |
s | 秒 | 00-59 |
S | 毫秒 / 微秒 / 纳秒 | 000/000000/000000000 |
z | 时区名 | CST |
Z | 时区偏移(如 + 08:00) | +08:00 |
3.4.2 日期时间格式化示例
import std.time.*
// 获取当前日期时间
let now = DateTime.now()
// 格式化当前时间
println("完整日期时间(默认格式): ${now}")
// 不同格式示例
println("\n日期格式示例:")
println("年-月-日: ${now.format("yyyy-MM-dd")}")
println("月/日/年: ${now.format("MM/dd/yyyy")}")
println("完整日期: ${now.format("yyyy年MM月dd日")}")
println("\n时间格式示例:")
println("24小时制: ${now.format("HH:mm:ss")}")
println("12小时制: ${now.format("h:mm:ss a")}")
println("带毫秒: ${now.format("HH:mm:ss.SSS")}") // 毫秒
println("\n日期时间组合格式:")
println("标准格式1: ${now.format("yyyy-MM-dd HH:mm:ss")}")
println("标准格式2: ${now.format("MM/dd/yyyy h:mm:ss a")}")
println("完整格式: ${now.format("yyyy年MM月dd日 HH时mm分ss秒")}")
// 时区相关
println("\n时区信息:")
let tzShanghai = TimeZone.get("Asia/Shanghai")
let tzNow = now.withTimeZone(tzShanghai)
println("上海时间: ${tzNow.format("yyyy-MM-dd HH:mm:ss z")}")
println("时区偏移: ${tzNow.format("Z")}")
3.4.3 自定义日期时间格式化
std.time包还提供了预定义的格式样式(FormatStyle枚举),可以快速应用标准格式:
import std.time.*
let now = DateTime.now()
// 使用预定义格式
println("短日期格式: ${now.format(FormatStyle.ShortDate)}")
println("长日期格式: ${now.format(FormatStyle.LongDate)}")
println("短时间格式: ${now.format(FormatStyle.ShortTime)}")
println("长时间格式: ${now.format(FormatStyle.LongTime)}")
3.5 字符串插值格式化
仓颉支持使用${}语法进行字符串插值,并可在其中使用格式化指令:
// 基本字符串插值
let name = "Alice"
let age = 30
println("${name}今年${age}岁了。") // Alice今年30岁了。
// 在插值中使用格式化
let score = 95.5
let piValue = 3.141592653589793
println("成绩: ${score:.1f}分") // 成绩: 95.5分
println("圆周率: ${piValue:.5f}") // 圆周率: 3.14159
println("对齐演示: [${age:10}]") // 对齐演示: [ 30]
println("二进制: ${0b1010:b}") // 二进制: 1010
println("十六进制: ${0x1F:X}") // 十六进制: 1F
四、字符串操作库的应用
4.1 字符串拼接功能
在仓颉编程语言中,字符串拼接是最基础的操作之一。仓颉提供了多种字符串拼接方式,每种方式都有其特定的使用场景和性能特点。
基本拼接操作
最直观的字符串拼接方式是使用+操作符。这种方式简单直接,但需要注意的是,由于字符串在仓颉中是不可变对象,每次使用+操作符都会创建一个新的字符串对象。在频繁拼接的场景下,这种方式的性能会受到影响。
let str1 = "Hello" + " " + "World"
let str2 = "仓颉" + "编程语言"
let str3 = str1 + " - " + str2
println(str3) // 输出:Hello World - 仓颉编程语言
使用 StringBuilder 进行高效拼接
为了解决频繁拼接时的性能问题,仓颉提供了StringBuilder类。StringBuilder是一个可变对象,内部维护了一个字符数组缓冲区,能够高效地进行字符串拼接操作。
创建StringBuilder对象有两种方式:
- 无参构造函数:创建一个空的 StringBuilder
- 带参构造函数:使用指定字符串初始化
// 创建空的StringBuilder
let sb1 = StringBuilder()
sb1.append("Hello")
sb1.append(" ")
sb1.append("World")
println(sb1.toString()) // 输出:Hello World
// 使用字符串初始化
let sb2 = StringBuilder("仓颉")
sb2.append("编程")
sb2.append("语言")
println(sb2.toString()) // 输出:仓颉编程语言
StringBuilder还提供了一些便捷的方法:
- append:追加字符串或字符
- insert(index, str):在指定位置插入字符串
- delete(start, end):删除指定区间的字符
- replace(start, end, str):替换指定区间的字符
格式化拼接
仓颉还支持使用format包进行格式化字符串拼接。这种方式特别适合需要格式化输出的场景,如数字格式化、日期格式化等。
import std.format.*
let name = "Alice"
let age = 25
let score = 95.5
// 使用格式化字符串
let formattedStr = format("姓名:%s,年龄:%d,成绩:%.1f", name, age, score)
println(formattedStr) // 输出:姓名:Alice,年龄:25,成绩:95.5
4.2 字符串分割功能
字符串分割是将一个字符串按照指定的分隔符或模式分解成多个子字符串的操作。仓颉的String类提供了强大的split方法来实现这一功能。
基本分割操作
split方法的基本语法为:
str.split(separator: String, removeEmpty: Bool = true, limit: Int64 = -1)
其中:
- separator:分隔符字符串
- removeEmpty:是否移除空字符串(默认值为 true)
- limit:限制分割的子串数量(默认值为 - 1,表示不限制)
let str = "Hello|,|我叫钝子生"
let parts1 = str.split("|") // 以"|"为分隔符分割
println(parts1) // 输出:["Hello", "", "我叫钝子生"]
// 不移除空字符串
let parts2 = str.split("|", removeEmpty: false)
println(parts2) // 输出:["Hello", "", "我叫钝子生"]
// 限制分割为2个子串
let parts3 = str.split("|", limit: 2)
println(parts3) // 输出:["Hello", ",|我叫钝子生"]
按字符分割
除了按字符串分割外,还可以按单个字符进行分割:
let s = " java home this is a test "
let sList = s.split(" ", removeEmpty: false) // 按空格分割
println("====split string =====")
for (t in sList) {println(t)
}
输出结果:
====split string ====
java
home
this
is
a
test
复杂分割场景
在实际应用中,分割操作可能会遇到更复杂的需求。例如,分割 CSV 格式的数据时,需要考虑字段中可能包含分隔符的情况。这种情况下,可以结合正则表达式来实现复杂的分割逻辑。
4.3 字符串查找功能
字符串查找是在一个字符串中定位特定子串的操作。仓颉的String类提供了多个查找相关的方法,包括indexOf、lastIndexOf、contains等。
基本查找方法
indexOf方法用于查找子串在字符串中首次出现的位置:
let str = "Hello, 我叫钝子生"
let index1 = str.indexOf("钝") // 查找"钝"字
let index2 = str.indexOf("Hello") // 查找"Hello"
let index3 = str.indexOf("world") // 未找到,返回-1
println(index1) // 输出:7
println(index2) // 输出:0
println(index3) // 输出:-1
lastIndexOf方法用于查找子串在字符串中最后一次出现的位置:
let str = "Hello, Hello, Hello"
let lastIndex = str.lastIndexOf("Hello")
println(lastIndex) // 输出:13
contains方法用于判断字符串是否包含指定子串:
let str = "Hello, 我叫钝子生"
println(str.contains("钝子生")) // 输出:true
println(str.contains("世界")) // 输出:false
带起始位置的查找
indexOf方法还可以指定起始位置,从该位置开始向后查找:
let str = "Hello, 我叫钝子生"
let index = str.indexOf("l", 2) // 从索引2开始查找字符'l'
println(index) // 输出:3
字符数组查找
除了字符串查找外,仓颉还提供了toRuneArray方法将字符串转换为字符数组,便于进行更复杂的字符操作:
let str = "Hello, 我叫钝子生"
let runes = str.toRuneArray()
for (i in 0..runes.size) {if runes[i] == '生' {println("找到了'生'字,位置:\(i)")}
}
4.4 字符串替换功能
字符串替换是将字符串中的特定子串替换为新字符串的操作。仓颉的String类提供了replace方法来实现这一功能。
基本替换操作
replace方法的基本语法为:
str.replace(oldValue: String, newValue: String, ignoreCase: Bool = false)
其中:
- oldValue:要替换的旧字符串
- newValue:替换后的新字符串
- ignoreCase:是否忽略大小写(默认值为 false)
let str = "Hello, 我叫钝子生"
let replacedStr1 = str.replace("Hello", "Hi") // 替换"Hello"为"Hi"
let replacedStr2 = str.replace("生", "同学") // 替换"生"为"同学"
let replacedStr3 = str.replace("Hello", "hi", ignoreCase: true) // 忽略大小写替换
println(replacedStr1) // 输出:Hi, 我叫钝子生
println(replacedStr2) // 输出:Hello, 我叫钝同学
println(replacedStr3) // 输出:hi, 我叫钝子生
替换所有匹配项
需要注意的是,replace方法默认只替换第一个匹配项。如果需要替换所有匹配项,可以结合正则表达式使用:
let str = "Hello, Hello, Hello"
let replacedStr = str.replace("Hello", "Hi")
println(replacedStr) // 输出:Hi, Hello, Hello(只替换第一个)
// 使用正则表达式替换所有
import std.regex.*
let regex = Regex("Hello")
let allReplacedStr = regex.replaceAll(str, "Hi")
println(allReplacedStr) // 输出:Hi, Hi, Hi
复杂替换场景
在实际应用中,替换操作可能需要更复杂的逻辑。例如,替换 HTML 标签时,需要考虑标签的属性和内容。这种情况下,可以使用正则表达式的分组功能来实现复杂的替换逻辑。
4.5 字符串大小写转换
仓颉提供了三个方法用于字符串的大小写转换:toAsciiLower()、toAsciiUpper()和toAsciiTitle()。
基本转换方法
- toAsciiLower():将字符串转换为小写
- toAsciiUpper():将字符串转换为大写
- toAsciiTitle():将字符串转换为标题格式(每个单词的首字母大写)
let str = "Hello, 我叫钝子生"
let lowerStr = str.toAsciiLower() // 转换为小写
let upperStr = str.toAsciiUpper() // 转换为大写
let titleStr = str.toAsciiTitle() // 转换为标题格式
println(lowerStr) // 输出:hello, 我叫钝子生
println(upperStr) // 输出:HELLO, 我叫钝子生
println(titleStr) // 输出:Hello, 我叫钝子生
字符级别的大小写转换
除了字符串级别的转换外,仓颉还提供了字符级别的大小写转换方法:
let c1 = 'A'.toAsciiLowerCase() // 大写转小写
let c2 = 'b'.toAsciiUpperCase() // 小写转大写
println(c1) // 输出:a
println(c2) // 输出:B
4.6 正则表达式匹配功能
正则表达式是处理字符串模式匹配的强大工具。仓颉的std.regex包提供了完整的正则表达式支持,但需要注意的是,当前版本仅支持 ASCII 编码字符串。
正则表达式规则集
仓颉支持的正则表达式规则包括:
- 基础匹配符
-
- .:匹配除换行符外的任意单个字符
- ^:匹配输入字符串的开始位置(多行模式下也匹配\n或\r之后)
- $:匹配输入字符串的结束位置
- 重复匹配符
-
- *:匹配前面的子表达式零次或多次
- +:匹配前面的子表达式一次或多次
- ?:匹配前面的子表达式零次或一次
- {n}:匹配确定的 n 次
- {n,}:至少匹配 n 次
- {n,m}:最少匹配 n 次且最多匹配 m 次
- 字符类
-
- [xyz]:匹配所包含的任意一个字符
- [^xyz]:匹配未列出的任意字符
- [a-z]:匹配指定范围内的任意字符
- \d:匹配数字字符(等价于[0-9])
- \D:匹配非数字字符(等价于[^0-9])
- \s:匹配空白字符(等价于[\f\n\r\t\v])
- \S:匹配非空白字符(等价于[^\f\n\r\t\v])
- 分组和引用
-
- (pattern):匹配 pattern 并获取这一匹配的子字符串
- (?:pattern):匹配 pattern 但不获取匹配的子字符串
- \num:向后引用第 num 个捕获组
- 预查断言
-
- (?=pattern):正向肯定预查
- (?!pattern):正向否定预查
- (?<=pattern):反向肯定预查
- (?<!pattern):反向否定预查
Regex 类的使用
Regex类用于创建正则表达式对象。创建Regex对象时可以指定匹配选项:
import std.regex.*
// 创建基本正则表达式对象
let regex1 = Regex("\\d+") // 匹配一个或多个数字
// 创建带有选项的正则表达式对象
let options = RegexOption()
options.ignoreCase() // 忽略大小写
options.multiLine() // 多行模式
let regex2 = Regex("^[a-z]+", options)
Matcher 类的使用
Matcher类用于执行正则表达式匹配操作。通过Regex对象的matcher方法可以创建Matcher对象:
let regex = Regex("\\d+")
let text = "Hello 123 World 456"
let matcher = regex.matcher(text)
// 查找所有匹配项
while (matcher.find()) {let matchData = matcher.get()let matchedStr = matchData.matchStr()println("找到匹配:\(matchedStr)")
}
输出结果:
找到匹配:123
找到匹配:456
常见正则应用场景
- 验证邮箱地址
let emailPattern = Regex("^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$")
let email1 = "test@example.com"
let email2 = "invalid.email"
println(emailPattern.matches(email1)) // 输出:true
println(emailPattern.matches(email2)) // 输出:false
- 提取 URL 中的域名
let urlPattern = Regex("https?://([^/]+)")
let url = "https://www.example.com/path"
let matcher = urlPattern.matcher(url)
if (matcher.find()) {let domain = matcher.get().matchStr(1) // 获取第一个捕获组println("域名:\(domain)") // 输出:www.example.com
}
- 分割复杂字符串
let csvText = "Name, Age, Email\nAlice, 25, alice@example.com\nBob, 30, bob@example.com"
let rowPattern = Regex("^([^,]+),\\s*([^,]+),\\s*([^,]+)$", RegexOption().multiLine())
let rowMatcher = rowPattern.matcher(csvText)
while (rowMatcher.find()) {let name = rowMatcher.get().matchStr(1)let age = rowMatcher.get().matchStr(2)let email = rowMatcher.get().matchStr(3)println("姓名:\(name),年龄:\(age),邮箱:\(email)")
}
注意事项
- 正则表达式的捕获组最大个数为 63 个
- 编译后的正则表达式最大长度为 65535
- 不支持((pattern1){m1,n1}pattern2){m2,n2}这种嵌套量化模式
- 仅支持 ASCII 编码字符串,不支持 Unicode 字符匹配
五、IO 库的应用
5.1 IO 库概述
仓颉编程语言的 IO 系统基于 ** 数据流(Stream)** 的概念,将所有输入输出操作都抽象为流的形式。这种设计使得文件、网络、标准输入输出等各种 I/O 操作都能使用统一的接口进行处理。
流的分类
仓颉的 IO 系统将流分为两大类:
- 节点流(Node Stream)
-
- 直接提供数据源的流
- 包括:标准流(StdIn、StdOut、StdErr)、文件流(File)、网络流(Socket)等
- 处理流(Processing Stream)
-
- 不能直接提供数据源,而是代理其他数据流进行处理
- 包括:缓冲流(BufferedInputStream/BufferedOutputStream)、字符串流(StringReader/StringWriter)、链式流(ChainedInputStream)等
流的基本操作
所有流都支持以下基本操作:
- read():从流中读取数据
- write():向流中写入数据
- close():关闭流,释放资源
- flush():刷新缓冲区,确保数据被写入
5.2 文件读写操作
文件读写是 IO 操作中最常见的场景。仓颉通过std.fs包提供了文件系统相关的功能,File类是文件操作的核心类。
文件的创建与打开
File类提供了多种创建和打开文件的方式:
- 使用构造函数创建文件
import std.fs.*
// 创建新文件(如果文件已存在则抛出异常)
let file1 = File(Path("./test.txt"), OpenOption.Create())
// 打开现有文件(只读模式)
let file2 = File(Path("./test.txt"), OpenOption.Open())
// 追加模式打开(如果文件不存在则创建)
let file3 = File(Path("./test.txt"), OpenOption.Append())
// 截断模式打开(清空文件内容)
let file4 = File(Path("./test.txt"), OpenOption.Truncate())
- 使用静态方法创建文件
// 静态方法创建文件
let file = File.create(Path("./test.txt"))
文件写入操作
文件写入主要使用write方法,可以写入字节数组或字符串:
import std.fs.*
// 创建文件并写入内容
let file = File.create(Path("./test.txt"))
// 写入字节数组
let bytes = "Hello, 世界\n".toArray()
file.write(bytes)
// 写入字符串(自动转换为字节数组)
file.write("这是第二行内容\n")
// 追加模式写入
let appendFile = File(Path("./test.txt"), OpenOption.Append())
appendFile.write("这是追加的内容\n")
file.close()
appendFile.close()
文件读取操作
文件读取可以使用read、readLine或readToEnd方法:
- 读取指定字节数
let file = File(Path("./test.txt"), OpenOption.Open())
// 创建缓冲区
let buffer = Array<Byte>(1024, item: 0)
// 读取数据
let bytesRead = file.read(buffer)
if (bytesRead > 0) {let content = String.fromUtf8(buffer.slice(0, bytesRead))println("读取的内容:\(content)")
}
file.close()
- 逐行读取
let file = File(Path("./test.txt"), OpenOption.Open())
while (true) {let line = file.readLine()if (line == null) {break}println("行:\(line)")
}
file.close()
- 读取全部内容
let file = File(Path("./test.txt"), OpenOption.Open())
let content = file.readToEnd()
println("文件内容:\(String.fromUtf8(content))")
file.close()
文件的随机访问
File类支持随机访问,可以通过seek方法设置文件指针的位置:
import std.io.SeekPosition
let file = File(Path("./test.txt"), OpenOption.Open())
// 从文件开头偏移10个字节的位置开始读取
file.seek(SeekPosition.Begin(10))
let buffer = Array<Byte>(10, item: 0)
file.read(buffer)
println("从第10个字节开始的内容:\(String.fromUtf8(buffer))")
// 从文件末尾倒数5个字节的位置读取
file.seek(SeekPosition.End(-5))
file.read(buffer)
println("文件末尾5个字节的内容:\(String.fromUtf8(buffer))")
file.close()
文件的常规操作
File类还提供了一些静态方法用于文件的常规操作:
import std.fs.*
// 检查文件是否存在
if (File.exists(Path("./test.txt"))) {println("文件存在")// 删除文件File.delete(Path("./test.txt"))println("文件已删除")
}
// 复制文件
File.copy(Path("./source.txt"), Path("./dest.txt"))
// 移动/重命名文件
File.move(Path("./oldname.txt"), Path("./newname.txt"))
// 获取文件信息
let info = File.stat(Path("./test.txt"))
println("文件大小:\(info.size) 字节")
println("创建时间:\(info.ctime)")
5.3 标准输入输出操作
标准输入输出是程序与用户交互的主要方式。仓颉通过std.console包提供了标准输入输出功能。
标准输出操作
Console类提供了stdOut和stdErr两个静态属性,分别代表标准输出流和标准错误流:
- 基本输出方法
import std.console.*
// 向标准输出写入
Console.stdOut.write("Hello, 世界\n")
// 向标准错误输出写入
Console.stdErr.write("错误信息:文件不存在\n")
// 使用快捷方法print和println
print("Hello, 仓颉\n") // 等价于Console.stdOut.write
println("这是一个测试") // 自动添加换行符
- 格式化输出
import std.console.*
let name = "Alice"
let age = 25
// 使用格式化字符串
print("姓名:%s,年龄:%d\n", name, age)
// 输出不同类型的数据
print("布尔值:%b,浮点数:%f,整数:%d\n", true, 3.14, 123)
标准输入操作
标准输入主要使用stdIn属性进行读取:
- 读取一行输入
import std.console.*
print("请输入你的姓名:")
let name = Console.stdIn.readLine()
if (name != null) {println("你好,\(name)!")
}
- 读取字符输入
import std.console.*
print("请输入一个字符:")
let c = Console.stdIn.read()
if (c != null) {println("你输入的字符是:\(c)")
}
- 读取指定字节数
import std.console.*
print("请输入一些内容(最多100字节):")
let buffer = Array<Byte>(100, item: 0)
let bytesRead = Console.stdIn.read(buffer)
if (bytesRead > 0) {let input = String.fromUtf8(buffer.slice(0, bytesRead))println("你输入的内容是:\(input)")
}
标准流的重定向
仓颉支持标准流的重定向,可以将标准输出重定向到文件:
import std.console.*
import std.fs.*
// 保存原始的标准输出
let originalOut = Console.stdOut
// 创建文件并将标准输出重定向到文件
let file = File.create(Path("./output.log"))
Console.setOut(file.asOutputStream())
// 输出的内容将写入文件
print("这是写入文件的内容\n")
println("第二行内容")
// 恢复标准输出
Console.setOut(originalOut)
// 输出到控制台
print("标准输出已恢复\n")
file.close()
5.4 缓冲流与字符流
为了提高 IO 操作的性能,仓颉提供了缓冲流和字符流的支持。
缓冲流的使用
缓冲流通过在内存中建立缓冲区,减少实际的 I/O 操作次数,显著提高性能。
- 缓冲输入流
import std.io.*
import std.fs.*
let file = File(Path("./large_file.txt"), OpenOption.Open())
// 创建缓冲输入流
let bufferedIn = BufferedInputStream(file)
// 使用缓冲流读取数据
let buffer = Array<Byte>(4096, item: 0)
while (bufferedIn.read(buffer) > 0) {// 处理读取的数据
}
bufferedIn.close()
file.close()
- 缓冲输出流
import std.io.*
import std.fs.*
let file = File.create(Path("./output.txt"))
// 创建缓冲输出流
let bufferedOut = BufferedOutputStream(file)
// 使用缓冲流写入数据
for (i in 0..1000) {bufferedOut.write("这是第\(i+1)行\n".toArray())
}
// 刷新缓冲区,确保所有数据被写入
bufferedOut.flush()
bufferedOut.close()
file.close()
字符流的使用
字符流提供了更方便的字符处理能力,自动处理编码转换:
- StringReader 的使用
import std.io.*
let byteBuffer = ByteBuffer()
byteBuffer.write("Hello\nWorld\n".toArray())
// 创建StringReader
let reader = StringReader(byteBuffer)
// 逐行读取
while (true) {let line = reader.readLine()if (line == null) {break}println("读取的行:\(line)")
}
reader.close()
- StringWriter 的使用
import std.io.*
let byteBuffer = ByteBuffer()
// 创建StringWriter
let writer = StringWriter(byteBuffer)
// 写入字符串
writer.write("Hello, 仓颉\n")
writer.write("这是一个测试\n")
// 写入带格式的内容
writer.printf("数字:%d,浮点数:%f\n", 123, 3.14)
writer.close()
// 获取最终的字节数组
let content = byteBuffer.toArray()
println("写入的内容:\(String.fromUtf8(content))")
总结
通过本文的详细介绍,我们全面了解了仓颉编程语言标准库中字符串操作库和 IO 库的核心功能与使用方法。
字符串操作库的核心要点:
- 字符串拼接:使用+操作符进行简单拼接,使用StringBuilder进行高效拼接,使用format包进行格式化拼接
- 字符串分割:split方法支持按分隔符分割,可指定是否移除空字符串和分割限制
- 字符串查找:indexOf、lastIndexOf、contains等方法提供了灵活的查找功能
- 字符串替换:replace方法支持基本替换,结合正则表达式可实现复杂替换
- 大小写转换:toAsciiLower()、toAsciiUpper()、toAsciiTitle()提供了完整的大小写转换功能
- 正则表达式:std.regex包提供了强大的正则表达式支持,但仅支持 ASCII 编码字符串
IO 库的核心要点:
- 流的概念:基于数据流的抽象,将所有 I/O 操作统一为流的形式
- 文件操作:std.fs包提供了完整的文件系统操作,File类支持创建、打开、读写、关闭等操作
- 标准输入输出:std.console包提供了stdIn、stdOut、stdErr三个标准流
- 缓冲流:BufferedInputStream和BufferedOutputStream通过缓冲区提高 I/O 性能
- 字符流:StringReader和StringWriter提供了方便的字符处理能力
- 综合应用:通过组合不同的流,可以实现复杂的 I/O 操作,如文件复制、日志记录等