第14章 字符串类
系统掌握String创建、格式化、长度/索引、连接比较、分割、检索替换、Enumerator与encode/nkf编码转换,明确bytes、characters和shell边界。
学习目标
- 能解释%Q、%q与Here Document与sprintf方法与反引号命令在“第14章 字符串类”中的责任边界
- 能围绕“怎样区分字符串内容、字节、编码、可变性与外部命令结果?”运行正常与故障轨迹并定位首个分岔
- 能用“第14章 字符串类的输入样本、接收者与方法、关键状态前后值、正常与失败输出、异常或退出状态,以及复位后的再次运行记录。”证明“每次连接、切片、比较和转换都保留明确的编码与数据来源。”
来源、版次与运行边界
“第14章 字符串类”以作者维护的第 5 版支持页核定 2016 年 3 月 12 日首刷、两位作者、松本行弘监修以及四部分 23 章目录;逐章程序清单、练习答案和勘误只作为公开支持材料,不被冒充为原书全文。
对“第14章 字符串类”而言,中文解释、示例、交互、练习和答案均为独立教学重写;站内中文章名是与官方 23 章顺序对应的课程映射,不宣称是日文小节的逐字翻译,也不从公开程序清单复制整段实现。
“第14章 字符串类”固定在 Ruby 2.3 语境;Ruby 2.3.0 官方文档与稳定版发布说明用于核对当时可用的语言和标准库行为。现代 Ruby 的差异只能另列迁移说明,不能静默改变本页示例的版本结论。
围绕“怎样区分字符串内容、字节、编码、可变性与外部命令结果?”,本页验收“第14章 字符串类的输入样本、接收者与方法、关键状态前后值、正常与失败输出、异常或退出状态,以及复位后的再次运行记录。”。先预测正常轨迹,再只注入“把反引号命令输出当成可信字符串,忽略命令失败和外部编码”;若无法定位第一处状态分岔,就拒绝当前解释。
从“String就是字符数组”这个近似开始
Ruby String同时持有byte sequence与Encoding标签,并且可变。所谓length、index、slice、Regexp与output都受encoding影响;它不是简单的Unicode code point Array,更不是用户感知grapheme集合。贯穿全章。
先预测:text.length与text.bytesize为何可能不同?UTF-8中文一个character通常占多个bytes;协议限制10 bytes与UI限制10 characters不是同一问题。先命名单位再调用API。
字符串的创建:%Q、%q与Here Document
Double-quoted String处理escape与interpolation,single-quoted处理更少escape;%Q(delimiter)近似double,%q近似single,可避开大量quote escaping。Delimiter可用配对括号或单字符,但nested与terminator规则要清楚。
name = "Ruby"
expanded = %Q(hello #{name}\nnext)
literal = %q(hello #{name}\nnext)
p expanded
p literalHere Document用terminator包多行文本,quoted terminator控制interpolation;<<-等形式影响terminator indentation,现代squiggly heredoc在Ruby 2.3可用性需按版本确认。Template中的user input仍需目标context escaping,interpolation不提供HTML/SQL/shell安全。
message = <<MESSAGE
title: Ruby
status: ready
MESSAGE
puts message应根据内容选择,不以“最短写法”为唯一标准。
使用sprintf方法与反引号命令
sprintf/format按format string控制width、precision、base和type;format与arguments不匹配会失败或产生意外输出。适合日志字段和报告,但machine serialization用JSON/CSV。
p format("%04d %.2f", 23, 3.14159)
p format("%<name>s:%<score>.1f", name: "A", score: 91.25)Backticks执行shell并返回stdout,exit status在$?,stderr处理另行决定。它把command parsing交给shell,interpolate user input会注入;使用Open3.capture3或system(command, arg1, arg2)的argument-vector形式,并设置timeout/output上限。Command output仍是带encoding的String。
获取字符串的长度与索引
length/size按当前encoding解释characters,bytesize按bytes;each_byte、each_char提供不同unit。Grapheme cluster(emoji组合、附加音标)可能包含多个codepoints,用户界面截断需Unicode segmentation library/规则。
string[index]、slice、Range与Regexp forms返回String/nil的版本语义需验证;Ruby 2.x早期版本曾有整数codepoint差异。不允许把byte offset误作character index。
text = "Ruby中文"
p length: text.length
p bytes: text.bytesize
p first: text[0]
p slice: text[0, 4]Binary protocol使用ASCII-8BIT与byteslice,文本使用valid encoding与character APIs。Regexp match offsets在目标version/encoding下也要实测,跨系统locator最好同时记录byte offset和line/column生成规则。
字符串的连接与比较
+返回new String,<</concat修改receiver,interpolation创建结果,Array#join适合多片段组合。影响aliasing与performance。
parts = ["ruby", "array", "string"]
joined = parts.join("/")
buffer = String.new
parts.each { |part| buffer << part << "\n" }
p joined: joined, buffer: buffer示例用String.new明确创建可变buffer;较新Ruby常见unary plus复制frozen String,但目标版本需确认。重复result = result + piece产生中间Strings;buffer mutation需确保receiver不被共享/frozen。
==比较内容并考虑encoding compatibility,<=>用于lexicographic ordering,casecmp做case-insensitive比较但Unicode/locale规则有限。用户语言排序、case folding与normalization是独立i18n问题,不能用downcase后<=>替代locale collation。
字符串的分割与换行符的使用方法
Split的separator可String/Regexp,limit决定最大fields和trailing empty处理;默认whitespace有特殊规则。是parser的一部分。
line = "a,b,,c,"
p line.split(",")
p line.split(",", -1)
p line.linesStructured CSV含quote、embedded comma/newline,必须用CSV library。Line endings可能是LF、CRLF或CR;each_line/lines按record separator,chomp移除separator而strip还删除其它whitespace。保留原文件时不要chomp后统一补LF。
字符串的检索与替换
include?给boolean,index给position/nil,Regexp#match给MatchData;选择caller需要的evidence。避免先boolean后重复搜索。
sub替换首个match,gsub替换全部;可传replacement String或block。Backreference和escape规则使dynamic replacement复杂,block形式更安全清楚。Bang variants修改receiver且无变化时可能返回nil。
source = "id=12 id=34"
result = source.gsub(/\d+/) { |digits| "[#{digits}]" }
p source: source
p result: result大量替换应评估Regexp backtracking与output expansion上限;untrusted input先限制length。Normalization(NFC等)会改变codepoint sequence,搜索前是否normalize属于product policy。
Enumerator对象:连接与reverse
each_line、each_char、each_byte无block时返回Enumerator,可with_index/lazy组合。区分record、character和byte processing。
Reverse按String字符解释,但grapheme cluster可能被拆;面向用户text reverse不是简单String#reverse。Array of Strings连接优先join,减少separator和最后一个元素的branch。Binary bytes转换先force_encoding/validate的具体责任要在boundary明确。
encode方法与nkf库
String#encoding是标签,force_encoding只改解释不转换bytes;encode(target, source, options)执行transcoding。是可靠文本系统的核心。
raw = File.binread(path)
text = raw.force_encoding("Windows-31J")
raise EncodingError, "invalid source" unless text.valid_encoding?
utf8 = text.encode("UTF-8")实际source encoding来自protocol/metadata,不应猜测。Invalid/undefined conversion可raise或使用replace option;替换会丢信息,必须计数/记录policy。NKF标准库处理日文编码、MIME等转换,但新代码优先String#encode明确source/target,只有需要NKF特有功能时使用并测试。
正式节点与章专属证据
- ↡在“第14章 字符串类”中,%Q、%q与Here Document必须连接输入、状态变化与可复核结果。 :第 1 个正式节点要能回到“每次连接、切片、比较和转换都保留明确的编码与数据来源。”,并说明故障发生前后的第一处差异。
- ↡在“第14章 字符串类”中,sprintf方法与反引号命令必须连接输入、状态变化与可复核结果。 :第 2 个正式节点要能回到“每次连接、切片、比较和转换都保留明确的编码与数据来源。”,并说明故障发生前后的第一处差异。
- ↡在“第14章 字符串类”中,字符串的长度与索引必须连接输入、状态变化与可复核结果。 :第 3 个正式节点要能回到“每次连接、切片、比较和转换都保留明确的编码与数据来源。”,并说明故障发生前后的第一处差异。
- ↡在“第14章 字符串类”中,字符串的连接与比较必须连接输入、状态变化与可复核结果。 :第 4 个正式节点要能回到“每次连接、切片、比较和转换都保留明确的编码与数据来源。”,并说明故障发生前后的第一处差异。
- ↡在“第14章 字符串类”中,字符串的分割必须连接输入、状态变化与可复核结果。 :第 5 个正式节点要能回到“每次连接、切片、比较和转换都保留明确的编码与数据来源。”,并说明故障发生前后的第一处差异。
- ↡在“第14章 字符串类”中,换行符的使用方法必须连接输入、状态变化与可复核结果。 :第 6 个正式节点要能回到“每次连接、切片、比较和转换都保留明确的编码与数据来源。”,并说明故障发生前后的第一处差异。
- ↡在“第14章 字符串类”中,字符串的检索与替换必须连接输入、状态变化与可复核结果。 :第 7 个正式节点要能回到“每次连接、切片、比较和转换都保留明确的编码与数据来源。”,并说明故障发生前后的第一处差异。
- ↡在“第14章 字符串类”中,Enumerator对象必须连接输入、状态变化与可复核结果。 :第 8 个正式节点要能回到“每次连接、切片、比较和转换都保留明确的编码与数据来源。”,并说明故障发生前后的第一处差异。
- ↡在“第14章 字符串类”中,连接与reverse必须连接输入、状态变化与可复核结果。 :第 9 个正式节点要能回到“每次连接、切片、比较和转换都保留明确的编码与数据来源。”,并说明故障发生前后的第一处差异。
- ↡在“第14章 字符串类”中,encode方法与nkf库必须连接输入、状态变化与可复核结果。 :第 10 个正式节点要能回到“每次连接、切片、比较和转换都保留明确的编码与数据来源。”,并说明故障发生前后的第一处差异。
对象与状态模型
从输入、接收者到可观察证据
怎样区分字符串内容、字节、编码、可变性与外部命令结果?
输入与接收者
固定%Q、%q与Here Document所需的原始值、Ruby 版本和调用入口。
状态变化
在执行前记录接收者身份,并声明sprintf方法与反引号命令的允许状态。
观察证据
保存第14章 字符串类的初值、参数、编码或资源位置。
正式节点:%Q、%q与Here Document、sprintf方法与反引号命令、字符串的长度与索引、字符串的连接与比较、字符串的分割、换行符的使用方法、字符串的检索与替换、Enumerator对象、连接与reverse、encode方法与nkf库
控制与消息轨迹
在相同初值下定位首个分岔
- 01固定%Q、%q与Here Document的输入和接收者
- 02执行sprintf方法与反引号命令并记录状态
- 03观察字符串的长度与索引的返回或副作用
- 04用encode方法与nkf库核对不变量并复位
运行不变量:每次连接、切片、比较和转换都保留明确的编码与数据来源。
边界故障探针
一次只破坏一个前提
本章回顾:所有文本操作先命名单位与编码
- Literal/%Q/%q/heredoc决定interpolation与escape;format string是shape协议,backticks是process boundary。
- Length/index区分bytes、characters、graphemes,missing/range与版本语义要验证。
- Connection明确new/mutation,comparison区分codepoint与locale,split明确empty/trailing/schema。
- Search返回所需evidence,replacement控制Regexp/backreference/output,Enumerator明确yield unit。
- Force_encoding不转码;外部bytes按已知source验证并encode为内部encoding,output再显式转换。
练习与答案
练习
- 问题 1:建立正常轨迹。 回答“怎样区分字符串内容、字节、编码、可变性与外部命令结果?”,并写出四步执行记录。
- 问题 2:注入单一故障。 只制造“把反引号命令输出当成可信字符串,忽略命令失败和外部编码”,应从哪里开始定位?
- 问题 3:覆盖正式节点。 用一个证据包串联%Q、%q与Here Document、sprintf方法与反引号命令、字符串的长度与索引、字符串的连接与比较、字符串的分割、换行符的使用方法、字符串的检索与替换、Enumerator对象、连接与reverse、encode方法与nkf库,说明为什么结论可由另一位读者独立复核。
名词解释
名词解释
本章出现的专业名词,用大白话再讲一遍。
- %Q、%q与Here Document
“第14章 字符串类”中的正式节点;必须说明它接收什么、改变什么,以及用什么结果复核。
- sprintf方法与反引号命令
“第14章 字符串类”中的正式节点;必须说明它接收什么、改变什么,以及用什么结果复核。
- 字符串的长度与索引
“第14章 字符串类”中的正式节点;必须说明它接收什么、改变什么,以及用什么结果复核。
- 字符串的连接与比较
“第14章 字符串类”中的正式节点;必须说明它接收什么、改变什么,以及用什么结果复核。
- 字符串的分割
“第14章 字符串类”中的正式节点;必须说明它接收什么、改变什么,以及用什么结果复核。
- 换行符的使用方法
“第14章 字符串类”中的正式节点;必须说明它接收什么、改变什么,以及用什么结果复核。
- 字符串的检索与替换
“第14章 字符串类”中的正式节点;必须说明它接收什么、改变什么,以及用什么结果复核。
- Enumerator对象
“第14章 字符串类”中的正式节点;必须说明它接收什么、改变什么,以及用什么结果复核。
- 连接与reverse
“第14章 字符串类”中的正式节点;必须说明它接收什么、改变什么,以及用什么结果复核。
- encode方法与nkf库
“第14章 字符串类”中的正式节点;必须说明它接收什么、改变什么,以及用什么结果复核。