【程序设计】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 格名的规则看起来简单,但要想出正确的计算逻辑也不容易,还是那个话,程序代码不会让帮我们解决问题,只会帮我们实现解法。