Dart 基础 – 内置类型 – 字符串
版权归作者 ©刘龙宾 所有,本文章未经作者允许,禁止私自转载!
1. 定义字符串
1.1 定义单行字符串
在 Dart 语言中,支持单引号和双引号两种方式定义单行字符串,示例代码如下:
void main(List<String> args) {
String s1 = '使用单引号定义字符串';
print(s1);
String s2 = "使用双引号定义字符串";
print(s2);
}
注意:相同内容的字符串,在内存中会被复用,这样能够节省内存空间。例如:
void main(List<String> args) {
String s1 = 'liulongbin';
String s2 = "liulongbin";
// 输出 true
// 证明在内存中只创建了一个 'liulongbin' 的字符串,变量 s1 和 s2 都指向这个内存地址
print(identical(s1, s2));
}
1.2 定义多行字符串
Dart 提供了三个单引号和三个双引号两种定义多行字符串的方式。示例代码如下:
void main(List<String> args) {
// 使用三个单引号定义多行字符串
String s1 = '''
面朝大海,
春暖花开。''';
print(s1);
// 使用三个双引号定义多行字符串
String s2 = """
好好学习,
天天向上。""";
print(s2);
}
注意:在定义多行字符串的时候,内部的文本需要顶头对齐,否则会在对应行的前面,出现额外空白字符的情况。
1.3 字符串是不可变的
字符串一旦在内存中被创建,就无法被重新修改,对字符串的操作总是会在内存中创建一个新的字符串。代码示例如下:
void main(List<String> args) {
// 在内存中开辟第1个字符串 'liulongbin'
// 并把这个字符串的内存地址赋值给变量 s1 和 s2
String s1 = 'liulongbin';
String s2 = 'liulongbin';
print(s1); // 输出 liulongbin
print(s2); // 输出 liulongbin
// 在内存中开辟第2个字符串 '---',
// 再把内存中的第1个字符串 'liulongbin' 和第2个字符串 '---' 拼接起来,
// 拼接的结果是:在内存中开辟第3个字符串 'liulongbin---',
// 最终,把第3个字符串的存储地址,赋值给变量 s2
s2 += '---';
print(s1); // 输出 liulongbin,因为 s1 变量指向内存中的第1个字符串
print(s2); // 输出 liulongbin---,因为 s2 变量指向内存中的第3个字符串
}
2. 拼接与构建字符串
2.1 拼接字符串
Dart 中支持两种拼接字符串的方法,分别是 + 运算符和并列放置多个字符串。示例代码如下:
void main(List<String> args) {
// 使用 + 运算符进行拼接
String s1 = '中华' + "人民" + '''共和国''' + """万岁""";
print(s1); // 输出字符串:中华人民共和国万岁
// 并列放置多个字符串进行拼接
String s2 = '世界' "人民" '''大团结''' """万岁""";
print(s2); // 输出字符串:世界人民大团结万岁
// 即使换行的多个字符串,也能进行拼接
String s3 = '我'
"爱"
'''我的'''
"""祖国""";
print(s3); // 输出字符串:我爱我的祖国
}
2.2 构建字符串
可以使用 StringBuffer
以代码的方式生成字符串。使用 StringBuffer
的优势是在调用 .toString()
之前,StringBuffer
不会在内存中生成新字符串对象。示例代码如下:
void main(List<String> args) {
// 1. 创建 StringBuffer 对象
StringBuffer sb = new StringBuffer();
// 2. 调用 write() 方法,向 sb 中写入单个字符串片段
sb.write('从明天起,');
// 3. 调用 writeln() 方法,向 sb 中写入单个字符串片段,并在末尾写入换行符
sb.writeln('做一个幸福的人');
// 4. 调用 writeAll() 方法,一次性向 sb 中写入多个字符串片段,并使用第二个参数 ',' 进行拼接
// 参数1:要拼接的字符串的数组
// 参数2:拼接时候的分隔符
sb.writeAll(['喂马', '劈柴', '周游世界\n'], ',');
// 5. 调用 StringBuffer 对象的 toString() 方法,生成最终的字符串
print(sb.toString());
// 最终在控制台输出的结果为:
// 从明天起,做一个幸福的人
// 喂马,劈柴,周游世界
}
3. 转义与原始字符串
在字符串中,可以使用 \
来表示转义字符,例如 \n
表示换行符,\t
表示制表符,示例代码如下:
void main(List<String> args) {
String s1 = '面朝大海,\n春暖花开。\t——海子';
print(s1);
// 在控制台输出的结果为:
// 面朝大海,
// 春暖花开。 ——海子
}
字符串前加上 r
作为前缀创建 “raw” 字符串(即不会被做任何处理(比如转义)的字符串):
void main(List<String> args) {
String s2 = r'面朝大海,\n春暖花开。\t——海子';
print(s2);
// 在控制台输出的结果为(原样输出,\n 不会被当作换行符,\t 也不会被当作制表符):
// 面朝大海,\n春暖花开。\t——海子
}
4. 字符串插值
在字符串中,可以使用 ${表达式}
的形式往字符串中插入表达式对应的值。如果表达式是一个标识符,还可以省略掉 {}
。示例代码如下:
void main(List<String> args) {
String name = '小明';
int age = 18;
// 注意:
// ${name} 是完整写法,简写形式为 $name
// ${age} 是完整写法,简写形式为 $age
// ${age + 2} 不能简写
String s1 = '大家好我是${name},今年$age岁了,两年后${age + 2}岁。';
print(s1);
}
5. 字符串的常用方法
5.1 大小写转换
基于字符串的 toUpperCase()
和 toLowerCase()
方法,可以轻松把字符串转为大写和小写形式。示例代码如下:
void main(List<String> args) {
String slogan = 'You guilty,you died';
// 把字符串转为大写
String upperSlogan = slogan.toUpperCase();
print(upperSlogan); // 输出 YOU GUILTY,YOU DIED
// 把字符串转为小写
String lowerSlogan = upperSlogan.toLowerCase();
print(lowerSlogan); // 输出 you guilty,you died
}
5.2 去除字符串两端的空格
调用字符串 trim
相关的方法,可以针对性的去除字符串两端的空格,其中:
trim()
表示去除字符串两端的所有空格trimLeft()
表示仅去除字符串左侧的所有空格trimRight()
表示仅去除字符串右侧的所有空格- 注意:以上3个方法,对字符串中间包含的空格没有任何效果。
示例代码如下:
void main(List<String> args) {
String greeting = ' hello world. ';
// 去除字符串两端的空格
print(greeting.trim());
// 仅去除字符串左侧的空格
print(greeting.trimLeft());
// 仅去除字符串右侧的空格
print(greeting.trimRight());
}
5.3 判断空字符串
- 可以通过字符串的
isEmpty
属性,来判断某一字符串是否为空字符串。- 如果
isEmpty
为 true,则证明这个字符串是空字符串 - 如果
isEmpty
为 false,则证明这个字符串不是空字符串
- 如果
- 相应的,也可以通过字符串的
isNotEmpty
属性,来判断某一字符串是否是不为空的字符串。- 如果
isNotEmpty
为 true,则证明这个字符串是不为空的字符串 - 如果
isNotEmpty
为 false,则证明这个字符串不是不为空的字符串
- 如果
示例代码如下:
void main(List<String> args) {
String s1 = '';
print(s1.isEmpty); // 输出 true,表示 s1 是空字符串
print(s1.isNotEmpty); // 输出 false,表示 s1 不是不为空的字符
String s2 = ' ';
print(s2.isEmpty); // 输出 false,表示 s2 不是空字符串(因为空格也是字符)
print(s2.isNotEmpty); // 输出 true,表示 s2 是不为空的字符串
}
注意:相对于
isEmpty
属性来讲,isNotEmpty
属性的语义比较绕,因此在实际开发中,推荐使用isEmpty
来判断字符串是否为空。
5.4 在字符串中搜索
可以通过字符串的 contains()
方法,判断某一字符串中是否包含特定的字符串,示例代码如下:
void main(List<String> args) {
String slogan = 'You guilty,you died';
// 判断字符串变量 slogan 中是否包含字符串 you
print(slogan.contains('you')); // 输出 true
// 判断字符串变量 slogan 中是否包含字符串 your
print(slogan.contains('your')); // 输出 false
}
可以通过字符串的 startsWith()
方法,判断某一字符串是否以特定的字符串开头。相应的,可以通过字符串的 endsWith()
方法,判断某一字符串是否以特定的字符串结尾。示例代码如下:
void main(List<String> args) {
String slogan = 'You guilty,you died';
print(slogan.startsWith('You')); // 输出 true
print(slogan.startsWith('you')); // 输出 false
print(slogan.endsWith('died')); // 输出 true
print(slogan.endsWith('died.')); // 输出 false
}
可以通过字符串的 indexOf()
方法,在字符串内查找特定的字符串第一次出现时对应的索引的位置(注意:索引从 0 开始)。相应的,还可以通过 lastIndexOf()
方法,在字符串内查找特定的字符串最后一次出现时对应的索引的位置。如果查找的结果是 -1
,则证明要查找的字符串不包含在当前字符串中。示例代码如下:
void main(List<String> args) {
String slogan = 'You guilty,you died';
print(slogan.indexOf('ou')); // 输出 1,证明 ou 第一次出现的位置,是索引为 1 的位置
print(slogan.lastIndexOf('ou')); // 输出 12,证明 ou 最后一次出现的位置,是索引为 12 的位置
print(slogan.indexOf('Punisher')); // 输出 -1,证明字符串 Punisher 不包含在字符串中
print(slogan.lastIndexOf('Punisher')); // 输出 -1,证明字符串 Punisher 不包含在字符串中
}
5.5 分割字符串
字符串的 split()
方法用来分割字符串,分割的结果会得到一个字符串的数组。示例代码如下:
void main(List<String> args) {
String s1 = 'green red blue cyan';
// 按照一个空格分割当前的 s1 字符串
List<String> colors = s1.split(' ');
// 输出 [green, red, blue, cyan]
print(colors);
}
在调用 split()
方法时,如果提供了一个空字符串,则会把字符串分割为单个字符的数组。示例代码如下:
void main(List<String> args) {
String s1 = 'administration';
// 使用“空字符串”分割 s1 字符串
List<String> chars = s1.split('');
// 输出 [a, d, m, i, n, i, s, t, r, a, t, i, o, n]
print(chars);
}
5.6 基于索引访问单个字符
字符串可以基于 [index]
索引的方式,访问对应的单个字符。示例代码如下:
void main(List<String> args) {
String s1 = 'administration';
// 输出索引为3对应的字符串,为 i
// 注意:索引从 0 开始
print(s1[3]);
}
5.7 填充字符串
基于字符串的 padLeft(width, padding)
方法,可以在字符串左侧填充指定的字符,从而保证填充后的字符串达到指定的长度。其中:
- 第一个参数
width
是一个 int 整数,表示填充完毕后字符串的总长度 - 第二个参数
padding
是一个字符串,表示以什么样的字符串进行填充
示例代码如下:
void main(List<String> args) {
String s1 = '7';
// 在字符串 7 的左侧以字符串 0 进行填充,填充完毕后总长度为 3。
// 最后,输出字符串 007
print(s1.padLeft(3, '0'));
}
相应的,还有一个右侧填充字符串的 padRight(width, padding)
方法,用法与 padLeft()
类似,只是 padRight()
会在右侧进行填充。示例代码如下:
void main(List<String> args) {
String s1 = '7';
// 在字符串 7 的右侧以字符串 0 进行填充,填充完毕后总长度为 3。
// 输出字符串700
print(s1.padRight(3, '0'));
}
6. 字符和正则表达式
6.1 创建正则实例
正则表达式可以方便的匹配和提取字符串中的内容。在 Dart 中可以基于 RegExp
类轻松创建正则对象,语法如下:
RegExp reg = new RegExp(r'正则表达式');
其中 new RegExp()
期间,左侧的 r
表示 raw 原始格式的字符串(原样输出的字符串)。因为正则中往往会包含 \d
,\s
等这些元字符(具有特殊意义的专用字符),通过指定 r
从而让传递给正则表达式的字符串,能够被当作普通的字面值对待,不需要把它们当作字符串中的转义字符对待。
6.2 判断正则能否成功匹配字符串
正则对象提供了 hasMatch(string)
方法,用来判断当前的正则能否成功匹配指定的字符串。如果能够匹配成功,则 hasMatch()
的返回值为 true,如果 hasMatch()
的返回值为 false 则证明匹配失败。示例代码如下:
void main(List<String> args) {
// 要匹配的字符串
String s1 = '2022年11月27日 14:13:30';
// 创建正则对象,其中 \d 表示匹配一个数字,+ 表示至少出现1次
RegExp reg = new RegExp(r'\d+');
// 输出 true,表示匹配成功,字符串变量 s1 中存在数字
print(reg.hasMatch(s1));
String s2 = 'abc';
// 输出 false,表示字符串变量 s2 中不存在数字
print(reg.hasMatch(s2));
}
6.3 获取匹配成功的第一个结果
正则对象提供了 firstMatch(string)
方法,用来获取匹配成功后的第一个结果,返回值的类型是 RegExpMatch
对象。如果没有任何匹配的结果,则返回 null
。示例代码如下:
void main(List<String> args) {
String s1 = 'abc';
// 创建正则对象,其中 \d 表示匹配一个数字,+ 表示至少出现1次
RegExp reg = new RegExp(r'\d+');
// 调用 firstMatch 对字符串进行正则匹配,
// 返回值用 match 接收,它的类型是可为 null 的 RegExpMatch 类型
RegExpMatch? match = reg.firstMatch(s1);
// 输出 null,证明没有任何匹配的结果
print(match);
}
如果存在多个匹配结果,则 firstMatch()
只会返回第一个匹配成功的结果,示例代码如下:
void main(List<String> args) {
// 要匹配的字符串
String s1 = '2022年11月27日 14:13:30';
// 创建正则对象,其中 \d 表示匹配一个数字,+ 表示至少出现1次
RegExp reg = new RegExp(r'\d+');
// 调用 firstMatch 对字符串进行正则匹配
RegExpMatch? match = reg.firstMatch(s1);
// match?[0] 和 match?.group(0) 的作用完全等价,用来获取匹配到的完整内容
print(match?[0]); // 输出 2022
print(match?.group(0)); // 输出 2022
}
6.4 在匹配的结果中提取分组
在创建正则对象期间,可以使用()
来提取分组。如果匹配成功,可以使用 .group(index)
方法或者 [index]
的方式,提取分组内容。注意:
.group(index)
和[index]
是完全等价的,都可以提取分组内容.group(0)
或[0]
永远都是匹配成功的完整内容,从.group(1)
或[1]
开始才是提取到的分组内容
示例代码如下:
void main(List<String> args) {
String s1 = '2022年11月27日 14:13:30';
// 创建正则对象期间,使用 () 提取分组
RegExp reg = new RegExp(r'(\d+)年(\d+)月(\d+)日');
// 获取匹配的第一个结果
RegExpMatch? match = reg.firstMatch(s1);
print(match?[0]); // 输出 2022年11月27日
print(match?[1]); // 提取第1个分组的内容,输出 2022
print(match?[2]); // 提取第2个分组的内容,输出 11
print(match?[3]); // 提取第3个分组的内容,输出 27
}
6.5 获取匹配成功的所有结果
正则对象提供了名为 allMatches()
的方法,可以获取到所有的匹配结果,示例代码如下:
void main(List<String> args) {
// 要匹配的字符串
String s1 = '2022年11月27日 14:13:30';
// 创建正则对象,其中 \d 表示匹配一个数字,+ 表示至少出现1次
RegExp reg = new RegExp(r'\d+');
// 1. 调用 allMatches() 对字符串进行正则匹配,获取所有的匹配结果
// 返回值用 matches 来接收,值类型是 Iterable<RegExpMatch>,表示可迭代的 RegExpMatch 集合
// 2. 为了方便起见,Iterable<RegExpMatch> 可以简写为 var,表示类型推断
// var matches 定义的变量,会被 Dart 自动推断为 Iterable<RegExpMatch> 类型的变量
// Iterable<RegExpMatch> matches = reg.allMatches(s1); // 完整写法
var matches = reg.allMatches(s1); // 基于类型推断的简化写法
// 3. 使用 for 循环依次输出所有匹配到的内容
for (var match in matches) {
print(match[0]);
// 共循环了 6 次,分别输出:
// 2022
// 11
// 27
// 14
// 13
// 30
}
}
6.6 使用正则替换字符串
6.6.1 replaceAll
调用字符串的 replaceAll(RegExp, string)
方法,可以把正则匹配到的内容,全部替换为指定的字符串。示例代码如下:
void main(List<String> args) {
// 需求:把字符串中的“所有数字”替换为“保密”
String s1 = '姓名:小红,年龄:18,身高:172,体重:99,爱好:高尔夫';
// 匹配数字的正则
RegExp reg = new RegExp(r'\d+');
// 调用字符串的 replaceAll() 方法,
// 把正则匹配到的内容替换为参数2对应的字符串
String newS1 = s1.replaceAll(reg, '保密');
// 输出的结果如下:
// 姓名:小红,年龄:保密,身高:保密,体重:保密,爱好:高尔夫
print(newS1);
}
6.6.2 replaceAllMapped
字符串的 replaceAll()
方法只能把匹配到的内容替换为固定的文本,可操作性较低。如果想动态提供要替换的内容,建议使用 replaceAllMapped()
方法。示例代码如下:
void main(List<String> args) {
// 需求:把字符串中的“数字”,替换相同位数的"x",例如:
// 数字 9 只有一位数字,所以要替换为 x
// 数字 110 有三位数字,所以要替换为 xxx
// 数字 48 有两位数字,所以要替换为 xx
String s1 = '姓名:小白,年龄:9,身高:110,体重:48,爱好:画画';
// 匹配数字的正则
RegExp reg = new RegExp(r'\d+');
// 调用字符串的 replaceAllMapped() 方法,
// 把正则匹配到的内容,替换为“参数2”这个函数“动态返回的字符串”
String newS1 = s1.replaceAllMapped(
reg, (match) => match[0]!.replaceAll(RegExp(r'.'), 'x'));
// 输出的结果如下:
// 姓名:小白,年龄:x,身高:xxx,体重:xx,爱好:画画
print(newS1);
}
6.7 额外的配置项
在创建正则对象期间,可以指定 caseSensitive
, dotAll
, multiLine
等额外的配置项。接下来分别对他们进行介绍。
6.7.1 caseSensitive
caseSensitive
选项用来指定正则匹配时是否忽略大小写。如果 caseSensitive
的值是 true,表示严格匹配大小写;如果 caseSensitive
的值是 false,表示忽略大小写。如果不提供 caseSensitive
选项的值,则默认值为 true。示例代码如下:
void main(List<String> args) {
String s1 = 'Abc';
// caseSensitive: false 表示正则匹配时忽略大小写
RegExp reg = new RegExp(r'abc', caseSensitive: false);
// 正则对象的 hasMatch() 方法,用来判断正则能否成功匹配对应的字符串,
// true 表示匹配成功;false 表示匹配失败。
// 这里输出的结果是 true,表示匹配成功
print(reg.hasMatch(s1));
}
6.7.2 dotAll
dotAll
选项用来指定正则匹配时 .
元字符能否匹配 \r
回车符与 \n
换行符。
dotAll
选项的默认值是 false,表示元字符 .
只能匹配除了 \r
和 \n
之外的任何单个字符。
把 dotAll
选项设置为 true 之后,元字符 .
可以匹配包括 \r
和 \n
在内的任何单个字符,相当于解除了元字符 .
的封印,使之拥有更强的匹配能力。
示例代码如下:
void main(List<String> args) {
String s1 = 'Abc\r\n';
// 1. dotAll 的默认值为 false,所以此时元字符 . 不能匹配 \r 和 \n
RegExp reg = new RegExp(r'^.+$');
print(reg.hasMatch(s1)); // 输出 false,表示匹配失败
// ----
String s2 = 'Abc\r\n';
// 2. 把 doAll 设置为 true 之后,元字符 . 能够匹配 \r 和 \n 了
RegExp reg2 = new RegExp(r'^.+$', dotAll: true);
print(reg2.hasMatch(s2)); // 输出 true,表示匹配成功
}
6.7.3 multiLine
multiLine
选项用来指定正则能否匹配多行文本。默认值为 false,表示无法匹配多行文本。
把 multiLine
设置为 true 之后,正则会依次匹配多行文本中的每行文本,只要存在能匹配成功的行,则 hasMatch()
方法会返回 true,表示匹配成功。
示例代码如下:
void main(List<String> args) {
String s1 = '''
111
2A2
333
456C''';
// 1. 因为要匹配多行文本,因此要开启 multiLine 选项
RegExp reg = new RegExp(r'^\d+$', multiLine: true);
// 2. 输出 true。因为第1行的 '111' 和第3行的 '333' 能够匹配成功
print(reg.hasMatch(s1));
// 3. 调用正则的 allMatches() 获取到所有匹配的结果
for (RegExpMatch match in reg.allMatches(s1)) {
// 4. 并使用 .group(0) 输出每次匹配到的内容(等同于 match[0])
print(match.group(0));
// 5. 共输出了 2 次,分别是:
// 111
// 333
}
}
版权归作者 ©刘龙宾 所有,本文章未经作者允许,禁止私自转载!
一条评论
石头
点赞。