Swift 中的格式化字符串

Swift 中的格式化字符串

在 Swift 中,因为 String 实现了 ExpressibleByStringLiteral 这个协议,所以正常情况下直接使用 let str = "This is a string." 这样的字面量表达配合字符串插值即可创建我们想要的实例。

但是如果涉及数字的输出,往往还是需要使用格式化字符串的方式来初始化字符串,格式化字符串也就是类似于 Java 和 C 语言那样的构造方式,调用 init(format: String, _ arguments: CVarArg...) 这个方法就可以使用格式化字符串。举个例子,使用下面的代码就可以实现以四舍五入规则输出两位小数。

1
2
3
var double = 0.12345
print(String(format: "%.2f", double))
// 0.12

以上这些知识都是已经滚瓜烂熟的了,不用再强调。主要是我在使用 iOS 13中新推出的 CryptoKit 对字符串进行 SHA256 加密,标准的写法是这样的:

1
2
3
4
5
6
extension String {
func sha256(using encoding: String.Encoding = .utf8) -> String {
let data = self.data(using: encoding)!
return SHA256.hash(data: data).compactMap { String(format: "%0.2x", $0)}.joined()
}
}

对字符串构成的 Data 对象进行哈希,得到32个8位数据,每个数据取值范围为0~255,要得到 SHA256 字符串,需要将每个数据转换为一个两位的十六进制数,然后将十六进制的数据拼接到一起,得到的64位字符串就是我们需要的 SHA256 加密字符串。

我对第4行的 “%0.2x” 比较好奇,所以专门去查了一下关于格式化字符串的资料。

格式化字符串,format string,是一些编程语言中用于指定输出格式与输出位置的字符串参数,其中格式化占位符用于对应参数列表并按指定的格式对参数进行转换。对于上面的代码段,“%0.2x” 就是格式化字符串,其中 %0.2x 是格式化占位符,$0 是参数列表。这里面最重要的就是格式化占位符。

格式化占位符的格式如下:

%[parameter][flags][field width][.precision][length]type

对于每一种类型的参数,都有不同的可选项,下面挨个介绍一下。

parameter

parameter 是可选的,默认缺省,parameter 的作用是指定参数列表的输出顺序,默认没有值的情况下,参数列表中的值按顺序填入占位符所在的位置。可以使用形如 n$ 的格式填入 parameter,说明这个占位符使用参数列表中的第 n 个参数进行填充。

1
2
print(String(format: "This is %2$d, and that is %1$d", 1, 2))
// This is 2, and that is 1

flags

flags 标示一些输出格式的设置,有以下几个可选配置:

  • +:默认缺省 ,如果为 + 表示总是显示正数的符号
1
2
print(String(format: "%+d", 1))
// +1
  • 空格:默认缺省,有符号数在忽略正号或者输出0个字符的时候,前缀一个空格
1
2
print(String(format: "% d", 1))
// 1(注意1左边有一个空格)
  • -:默认缺省,缺省时字符串右对齐,否则左对齐
1
2
3
4
print(String(format: "%5d", 1))
// 1(注意1左边有4个空格)
print(String(format: "%-5d", 1))
// 1 (注意1右边有4个空格)
  • #:默认缺省,对于’g‘与’G’,不删除尾部0以表示精度。对于’f’, ‘F’, ‘e’, ‘E’, ‘g’, ‘G’, 总是输出小数点。对于’o’, ‘x’, ‘X’, 在非0数值前分别输出前缀0, 0x, and 0X表示数制。
1
2
print(String(format: "%#x", 255))
// 0xff

field width

指定字符串最小宽度,可选,宽度不足的话按照之前设置的左右对齐方式对齐,并在另一端补空格,宽度超过的话不做截断。如果宽度值的前缀为0,代表用0补齐宽度不足的字符串。

1
2
print(String(format: "%03d", 1))
// 001

.precision

指定精度,可选,对于不同类型的数值有不同的作用。对于整型(d,i,u,x,o),位数不足在左侧用0补齐,精度超出的不做处理。对于浮点(a,e,f),指小数点右侧的位数,不足的话在右侧用0补齐,超出的部分四舍五入进行截断。对于浮点(g),指有效数字最大位数。

1
2
3
4
print(String(format: "%.3d", 1))
// 001
print(String(format: "%.3f", 1.23456))
// 1.235

length

指定浮点或整型的长度,可选。

修饰符 描述
h 可用于将 d, o, u, x, X 转换为 shortunsigned short
hh 可用于将 d, o, u, x, X 转换为 charunsigned char
l 可用于将 d, o, u, x, X 转换为 longunsigned long
ll, q 可用于将 d, o, u, x, X 转换为 long longunsigned long long
L 可用于将 a, A, e, E, f, F, g, G 转换为 long double
z 可用于将 d, o, u, x, X 转换为 size_t.
t 可用于将 d, o, u, x, X 转换为 ptrdiff_t.
j 可用于将 d, o, u, x, X 转换为 intmax_tuintmax_t
1
2
3
var short = Int(Int16.max) + 1 // 32768
print(String(format: "%hd", short))
// -32768

type

这个是最重要的,也是必选的,用于指定我们的参数类型。

我从苹果开发者官方文档中找到了这个表格,这个表格基于 Objective-C,但是大部分内容对 Swift 依然适用。

修饰符 描述
%@ 对象描述,输出对象方法 descriptionWithLocale:description 返回的字符串。
%% '%' 输出%字符。
%d, %D 有符号32位整型 (int)。
%u, %U 无符号32位整型 (unsigned int)。
%x 无符号32位整型 (unsigned int),以小写十六进制输出。
%X 无符号32位整型 (unsigned int),以大写十六进制输出。
%o, %O 无符号32位整型 (unsigned int),以八进制输出。
%f 64位浮点数 (double).
%e 64位浮点数 (double),以小写科学计数法输出。
%E 64位浮点数 (double),以大写科学计数法输出。
%g 64位浮点数 (double),当指数部分在闭区间 [-4,5] 内,输出为定点形式;否则输出为指数浮点形式。
%G 64位浮点数 (double),当指数部分在闭区间 [-4,5] 内,输出为定点形式;否则输出为指数浮点形式。
%c 8位无符号字符 (unsigned char).
%C 16位UTF-16字符 (unichar).
%s 8位无符号字符数组。
%S 16位UTF-16字符数组。
%p 指针。
%a 64位浮点数 (double),使用小写科学计数法与十六进制进行输出。
%A 64位浮点数 (double),使用大写科学计数法与十六进制进行输出。
%F 64位浮点数,以十进制输出。

至此我们就介绍完了,举一个综合性例子:

1
print(String(format: "%-#.8X", 54321))

结合上面说的参数,这个格式化字符串的意思就是,以左对齐(-),表示数制(#),最少8位(.8)的格式输出一个大写十六进制数(X),最终输出“0X0000D431”。

此时再回到开头我看到的 SHA256 算法中的那个格式化字符串”%0.2x“,意思也就明白了,输出一个0补齐宽度不足的(0),最少2位(.2)的小写十六进制数(x),也可以省略为“%.2x"。

参考资料

[1] String Format Specifiers[E].https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/Strings/Articles/formatSpecifiers.html

[2] 格式化字符串[E].https://zh.wikipedia.org/wiki/格式化字符串


Swift 中的格式化字符串
https://wenchanyuan.com/swift_format_string/
作者
蟾圆
发布于
2020年9月29日
许可协议