【程序设计】7.1 [字与时] 字符串
7.1 字符串
到现在这止,我们在程序中处理过的数据,除了 output 时有个少量文字外,都是数值或数值构成的序列。其实,计算机还能方便地处理文字,在程序语言中,我们把这些文字称为字符串,或简称字串,字符串是不同于整数、浮点数的另一种数据类型。
在 SPL 中,直接在格子写上文字,如果不能被识别其它有意义的数据类型或者语句,就会缺省地认为是字符串常数。在表达式中,也可以用双引号把文字括起来表示字符串常数。
A | B | |
1 | 集算器 | SPL |
2 | ="集算器 " | ="SPL" |
3 | 3+5 | ="3+5" |
4 | =3*4 | '=3*4 |
5 | 2020-1-1 | '2020-1-1 |
6 | for 10 | 'for 10 |
A1 就是字符串常数 "集算器",A2 是个正常的计算格,其计算结果也是 "集算器",和 A1 相同;同理,B1、B2 的格值都是 "SPL";A3 是数字开头,但并不能解释成数值,所以也是字符串,和 B3 一样都是 "3+5"。
A4 是个能计算出数值的计算格,就不是字符串了。希望写一个以 = 开头的字符串常数要在格子前面加写一个单引号’,这个多写的单引号不算字符串的一部分,A4 的格值为 "=3*4"。注意只要有一个单引号,不要在右边再写一个括起来,如果写了,右边的单引号也会被认为是这个字符串的一个字符。
类似地,A5 会被识别的日期常数,也就不是字符串,而希望产生一个这样的字符串,也需要用单引号开头;同样,A6 是一个合法语句,不会被识别成字符串,而希望获得这样的字符串常数,也需要用单引号开头。
字符串也是数据类型,也可以运算,它最常见的运算就是连结,即把两个字符串接起来形成一个更长的字符串。
A | B | |
1 | 集算器 | |
2 | =A1+"2020" | 集算器 2020 |
3 | =A1+2020 | 2020 |
4 | =A1+string(2020) | 集算器 2020 |
5 | =A1/2020 | 集算器 2020 |
6 | =A1/"2020" | 集算器 2020 |
使用 + 号即可把两个字符串相连(A2);但要注意的是,字符串和数值相加却会忽略字符串部分(A3)这是 SPL 的特殊约定,其它大多数程序语言不是这么约定的,而是要么会报错,要么会把数值转换成字符串再连结。把数值用 string 函数转换成字符串就可以和其它字符串正常连结了(A4),如果用斜杠 / 来连结字符串和数值时,则会自动把数值转换成字符串(A5)。而 / 也可以直接拼接两个字符串(A6)。
现在,我们可以利用字符串改造分解质因数的代码,把分解结果显示成完整的表达式。
A | B | C | |
1 | 7215 | =2 | =string(A1)+"=1" |
2 | for A1>1 | if A1%B1==0 | >C1=C1+"*"+string(B1) |
3 | >A1=A1\B1 | ||
4 | next | ||
5 | >B1+=1 |
这段程序执行完之后,C1 格会得到一个字符串:7215=1*3*5*13*37,把分解质因数的结果写出来了。仔细理解程序的执行过程,可以想像出它是如何一步步被拼出来的。
有连接就有拆分,SPL 还提供这样几个函数:
A | B | |
1 | 集算器 2020 | |
2 | =len(A1) | '7 |
3 | =left(A1,2) | '集算 |
4 | =right(A1,3) | '020 |
5 | =mid(A1,3,2) | '器 2 |
6 | =A2.(mid(A1,~,1)) | ["集 "," 算 "," 器 ","2","0","2","0"] |
len 函数返回构成字符串的字符的个数,也称为字符串的长度,注意这个 len 函数和取序列长度的函数名字相同,但写法不一样,这里要把字符串当成参数,而不能写成 A1.len() 的样子。还要注意的是,SPL 采用了 unicode 编码方式,汉字和数字(或英文字母)都只算一个字符,有些早期程序语言中,汉字会算成两个字符。
A3、A4、A5 的 left,right 和 mid 函数将从字符串取出一部分构成一个新字符串返回,函数名称已经反映要取出的部位,再观察运行结果,很容易理解其参数的意义,这里就不细说了。几乎所有的能处理字符串的程序语言都有这几个函数,且命名和参数规则都一样。
A6 用循环函数把一个字符串拆成一个个字符构成的序列,可以再理解 mid 函数。单个字符也是个字符串,就是个长度为 1 的字符串。
分解质因数出来的结果,前面总是会写个 1*,这是因为我们在循环中每次在结果字符串上接续一个 * 和一个因数,如果不写前面的 1,就会出现 7215=*3*5*13*37 的结果,这就不对了。
但无论如何,这个 1* 有点多余,有什么办法呢?
用拆串的办法就可以解决它。
A | B | C | |
1 | 7215 | =2 | =string(A1)+"=" |
2 | for A1>1 | if A1%B1==0 | >C1=C1+if(right(C1,1)=="=","","*")/B1 |
3 | >A1=A1\B1 | ||
4 | next | ||
5 | >B1+=1 |
在拼接因数时判断一下当前串是不是 = 结尾的,如果是说明现在是第一个因子,就不要再拼接 * 号了,否则就拼上。现在就可以得到我们希望的结果 7215=3*5*13*37 了。
也可以事后再做,把多余的 1* 部分去掉:
A | B | C | |
1 | 7215 | =2 | =string(A1)+"=1" |
2 | for A1>1 | if A1%B1==0 | >C1=C1+"*"+string(B1) |
3 | >A1=A1\B1 | ||
4 | next | ||
5 | >B1+=1 | ||
6 | =pos(C1,"=1*") | >C1=left(C1,A6+1)+mid(C1,A6+3) |
我们又碰到一个函数 pos,它将在一个字符串中寻找另一个字符串,后者称为子串,找到子串后将返回位置,也就是原串的第几个字符开始会是这个子串,如果找不到就会返回 null,这和序列的 pos 函数很像,不过它也要把字符串当参数写而不能用对象式的写法。
我们确定地知道现在算出来的结果字符串中一定会有 "=1*" 这个子串(前面加了 = 之后就只会有 1 个了,否则可能有个中间的质因数以 1 结尾时,但 pos 只找第 1 个其实也不会错,但这里严谨一点),找到它的位置后,再用拆串和拼串的办法把其中的 1* 部分去除就可以了。
还可以更简单粗暴地用 replace 函数直接替换掉:=replace(C1,“=1*”,“=”)。同样地,要把 =1* 替换成 =,而不能把 1* 替换成空串。
SPL 提供了很多字符串处理函数,这里不一一列举了,需要时可以查帮助资料。
我们刚才使用了 == 来比较字符串,当然也可以用!=,那么 >、< 这些对字符串有意义吗?
也是有的。
计算机本质上只能处理数值,处理字符时也要把字符用数值表示,这就是编码方式。前面我们说过,SPL 采用的编码方式叫 unicode。
既然是数值,那就可以比大小,字符串比大小时就是用字符的编码来比较。比较规则和序列很像,就是两者的第 1 个字符先对比,如果不同,那大的就大了,如果相同再比第 2 个,…,直到不同或者其中一个不够长了。其实,这才是字典次序这个名词的真正来源。
那么,我们怎么知道两个字符谁大谁小呢?比如 "1" 和 "2" 那个大?"1" 和 "A" 呢?
简单来说,我们写句代码比一下就知道了。但是,全凭死记硬背就太累了,我们来看看这些编码有没什么规律可言,SPL 提供了 asc 函数来返回字符的编码。
A | B | |
1 | =asc("0") | =9.(asc(string(~))) |
2 | =asc("A") | =asc("C") |
3 | =asc("a") | =asc("z") |
4 | =asc("集 ") | =asc("算 ") |
我们不再一个个格子解释了,请读者自行执行这些代码看结果,这里我们直接把编码规律写出来:
1) “0”-"9" 的编码是连续的,从 48 到 57;
2) “A”-"Z" 的编码是连续的,从 65 到 90;
3) “a”-"z" 的编码是连续的,从 97 到 112;
4) 汉字的编码比较复杂,看不出明确的规律。
这套编码标准当初叫 ASCII 码,unicode 在是 ASCII 基础上扩展出来的,所以这个函数也叫 asc。
和 asc 函数相反的,SPL 还有个 char 函数,可以把编码转换成字符。
我们可以利用 char 函数以及刚才发现的规律来写一个函数:给两个整数 r 和 c,计算 Excel 中第 r 行第 c 列的格子叫什么名字。
A | B | C | D | |
1 | >r=2,c=123 | |||
2 | >rc="" | =1 | =26 | |
3 | for c>C2 | >c-=C2 | >C2*=26 | >B2+=1 |
4 | >c-=1 | |||
5 | for B2 | >rc=char(65+c%26)+rc | >c\=26 | |
6 | return rc/r |
计算格名主要麻烦在列,先计算出需要几个字母(B1),再计算出列对应的字母串。
反过来,用 asc 函数可以从格名 rc 反算出行号和列号:
A | B | C | D | |
1 | >rc="PG45" | |||
2 | =len(rc).(upper(mid(rc,~,1))) | |||
3 | >r=c=0 | =-1 | =1 | |
4 | for A2 | if A4>="A" | >c=c*26+asc(A4)-65 | |
5 | >B3+=C3 | >C3*=26 | ||
6 | else | >r=r*10+asc(A4)-48 | ||
7 | return [r,c+B3+1] |
upper 函数用于把字母变成大写。
Excel 格名的规则看起来简单,但要想出正确的计算逻辑也不容易,还是那个话,程序代码不会让帮我们解决问题,只会帮我们实现解法。