字符串
string 是 NUL 结尾的字节序列, 变量本身只保存指向它的指针 (64 位槽)。 字符串字面量存放在数据区, 拼接、strcpy、substr 等操作会产生新的堆块。
字面量与拼接
string s = "hello"
string t = s + " " + "world" // 拼接产生新堆块
println(t) // hello world
println(strlen(t)) // 11+ 任意一侧为 string 时, 另一侧自动字符串化:
println("n = " + 42) // n = 42
println("pi = " + 3.14) // pi = 3.14
println("ok = " + true) // ok = true
println("c = " + 'A') // c = 65 (字符字面量是整数编码)显式转换更可控
需要精确控制格式时用转换内建: int_to_str / float_to_str / bool_to_str (别名 itoa / ftoa), 例如 "x=" + int_to_str(x)。
print / println 可以一次给多个参数, 依次输出且不加分隔符:
int a = 3
string b = "cin"
println("a=", a, " b=", b) // a=3 b=cin
println() // 空行转义序列
字符串字面量除单字符转义 (\n \t \r \0 \" \\, 以及 \a \b \f \v) 外, 还支持字节与码点转义:
| 转义 | 含义 |
|---|---|
\xH / \xHH | 原始字节 (1~2 位十六进制): "\xE4\xB8\xAD" 即 "中" 的 UTF-8 三字节 |
\uHHHH | Unicode 码点 (正好 4 位十六进制), 按 UTF-8 编码写入: "\u4E2D" 与 "中" 等价 |
\UHHHHHHHH | Unicode 码点 (正好 8 位十六进制) |
string raw = "\xE4\xB8\xAD" // 3 个字节
string cp = "\u4E2D" // 也是 3 个字节
println(int_to_str(strlen(raw)) + " " + int_to_str(strlen(cp))) // 3 3详见 词法规则。
比较与复制
string a = "abc"
string b = "abc"
if (strcmp(a, b) == 0) { // 内容比较: 正确做法
println("equal")
}
string copy = strcpy(a) // 复制为新堆块不要用 == 比较字符串内容
== 比较的是 64 位指针值而不是字符串内容。实测结果:
string a = "ab" + "c" // 堆块
string b = "abc" // 数据区字面量
if (a == b) { println("true") } else { println("false") } // false
if (strcmp(a, b) == 0) { println("strcmp equal") } // strcmp equal同一个字面量地址的两次比较可能“碰巧为真”, 因此永远不要依赖 == 判断内容。
单字节访问
string s = "Code CIN"
int h = s[0] // 67 ('C')
int i = s[5] // 67 ('C')s[i]按字节下标, 返回0..255的整数编码;- 字符串不可原地修改:
s[i] = 65不能作为赋值左值; - 没有越界检查,
i必须落在0..strlen(s)之内。
字符串里不能有字节 0
string 是 NUL 结尾的, 所以字节 0 无法表示——字面量里的 \x00 会被当成 字符串结束, 后面的一切静默丢弃:
println(int_to_str(strlen("\x00\x01\x02"))) // 0 (整串变成空串!)
println(int_to_str(strlen("A\x00B"))) // 1 (在 NUL 处截断)
println(int_to_str(strlen("\xFF"))) // 1 (非 0 字节没问题)这条限制在字节处理里会咬人: 想用一张 256 字节的查表来做"整数 ↔ 单字节字符串" 是行不通的, 因为第 0 项就写不出来。后果是任何"能产出任意字节"的函数 (hex/base64 解码、XOR、压缩) 都不可能表示字节 0, 所以"解码后再编码得到原串"这类性质在含 NUL 的输入上必然不成立。
需要 0..255 全值域时请改用 int[] 传字节; 只要可打印/非 0 字节时才用 string。
字符串内建
| 函数 | 签名 | 说明 |
|---|---|---|
strlen(s) | int | 字节长度 (不含结尾 NUL) |
strcmp(a, b) | int | 字典序比较, <0 / 0 / >0 |
strcpy(s) | string | 复制为新堆块 |
substr(s, start, len) | string | 子串 (新堆块; 越界自动裁剪) |
indexof(hay, needle) | int | 首次出现位置, 未找到为 -1 |
upper(s) / lower(s) | string | ASCII 大小写转换 (新堆块) |
trim(s) / ltrim(s) / rtrim(s) | string | 去首尾 / 前导 / 尾部空白 (新堆块) |
atoi(s) | int | 字符串 → 十进制整数 (忽略前导空白, 失败为 0) |
int_to_str(n) / itoa(n) | string | 整数 → 十进制字符串 |
float_to_str(f) / ftoa(f) | string | 浮点 → 字符串 |
bool_to_str(b) | string | 布尔 → "true" / "false" |
完整列表与边界行为见 内建函数; 内置标准库 str.cin (s_upper / s_contains / s_starts_with / s_repeat …) 见 标准库参考。
完整示例
import "str.cin"
function main() -> int {
string raw = " Hello, CIN "
string t = trim(raw) // 新堆块
println("[" + t + "]") // [Hello, CIN]
println("len = " + int_to_str(strlen(t))) // len = 10
println("upper = " + upper(t)) // upper = HELLO, CIN
println("sub = " + substr(t, 0, 5)) // sub = Hello
println("idx = " + int_to_str(indexof(t, "CIN"))) // idx = 7
println("num = " + int_to_str(atoi(" 42 "))) // num = 42
println("rep = " + s_repeat("ab", 3)) // rep = ababab
if (strcmp(s_lower("HeLLo"), "hello") == 0) {
println("case-insensitive equal") // case-insensitive equal
}
return 0
}内存与性能注意
- 每次拼接、
strcpy、substr、upper等都会分配新的堆块; - 在长循环里反复拼接会持续占用堆, 必要时改用标准库工具或预先算好长度;
- 堆与栈在默认 1 GiB 内存里是共享的 (堆基址
0x20000000, 栈从0x3FFFFFF8往下, 按需提交, 只有真正写入的部分占物理内存), 堆栈相撞会报Stack overflow (collides with heap), 极端情况可用--mem-size扩容; - 内存布局细节见 寄存器与内存模型。
常见错误
| 现象 | 原因 | 处理 |
|---|---|---|
两个内容相同的字符串 == 为 false | == 比较指针 | 用 strcmp(a, b) == 0 |
s[i] = 65 编译/运行报错 | 字符串不可原地修改 | 生成新串 (upper/substr/拼接) |
| 打印出乱码或截断 | 字符串被越界写破坏 (s[i] 越界) | 检查下标范围; 打开 --bounds-check |
atoi("abc") 返回 0 | 解析失败返回 0, 不报错 | 先用 val_is_int 校验 (见标准库 validate) |
| 循环里拼接后内存耗尽 | 每步都产生新堆块 | 减少拼接次数或扩容 --mem-size |