Appearance
3. 密信(字符串)
知识点:字符串
项目:把悄悄话变成只有你俩懂的"密信"
故事开场:纸条被看到了!
课间,你给好朋友写了一句悄悄话:"放学后一起玩游戏"。
可是同桌眼疾手快,一把抢过纸条念了出来。全班都听见了!太尴尬了!
好朋友说:"我们要发明一种只有我们俩看得懂的写法。" 你俩约定了一个暗号:把每个字倒着写。
"放学后一起玩游戏"倒过来就是:
戏游玩起一后学放看起来像乱码,但你俩知道规则,一眼就能读回来。
今天,我们要写两个程序:
- 加密器:输入一句话,输出倒过来的"密信"
- 解密器:输入"密信",还原出原来的话
笨办法先行:一个字一个字搬
"把字倒过来"……这怎么写?第一个想到的办法:从最后一个字开始,一个个拿出来拼。
在 TypeScript 里,可以用方括号拿到字符串里的某一个字:
ts
const secret = "放学后一起玩游戏"
console.log(secret[0]) // 放
console.log(secret[1]) // 学
console.log(secret[8]) // 戏咦,和数组一样,secret[0] 是第一个字!那倒过来就简单了——从最后往前拿:
ts
const secret = "放学后一起玩游戏"
let reversed = ""
reversed = reversed + secret[8] // 拿最后一个字
reversed = reversed + secret[7]
reversed = reversed + secret[6]
// ……等等,要是句子有 50 个字,我是不是要写 50 行?你发现了:又来了! 写固定次数,只能对付固定长度的句子。
同桌抢的纸条只有 9 个字;要是明天你写个 100 字的"长篇密信",这程序就废了。
而且你注意到没有——secret.length 应该能告诉我们"有几个字"。 我们把"写 50 行"换成"用循环跑 50 次",不就行了?
引出知识点:字符串就是"不能改的字符数组"
动手之前,先认识这位亲戚:
字符串(string)本质上就是一排字符组成的数组。
| 下标 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
|---|---|---|---|---|---|---|---|---|---|
| 字 | 放 | 学 | 后 | 一 | 起 | 玩 | 游 | 戏 | ? |
secret.length是长度——像数组一样secret[i]是第 i 个字——像数组一样- 但:
secret[0] = "x"会失败——字符串不能改!
等一下,为什么字符串不能改?好问题。因为字符串在 TypeScript 里是 不可变(immutable)的:你没办法把"放学后一起玩游戏"里"放"字直接涂掉 改成别的字——你只能重新造一串新的。
你可能会想:"这也太不方便了!"——先别急,看看我们怎么用循环+拼接, "造"出一串新的密信。你会发现,不可变反而让字符串用起来很安全: 别人的程序不会悄悄改掉你的字符串。
动手实现:加密器和解密器
有了循环,写多少字都不怕:
ts
// 加密:把一句话倒过来,变成密信
function encrypt(text: string): string {
let reversed = ""
for (let i = text.length - 1; i >= 0; i--) {
reversed = reversed + text[i] // 从最后一个字开始,往前拼
}
return reversed
}
const secret = "放学后一起玩游戏"
const cipher = encrypt(secret)
console.log("原话:" + secret)
console.log("密信:" + cipher)
console.log("解密:" + encrypt(cipher)) // 再倒一次就回来了!输出:
原话:放学后一起玩游戏
密信:戏游玩起一后学放
解密:放学后一起玩游戏神奇的事情发生了:加密就是倒过来,解密也是倒过来——同一个函数, 用两次就还原了!这就是"倒写"这种加密方式的好处。
跑起来:给密信加个检查——回文
你俩的密信有个新玩法:回文。有些话正着读和倒着读一模一样, 比如:
上海自来水来自海上这本身就是一句"不会泄露秘密"的密信——倒过来还是它自己!
写一个程序,检查一句话是不是回文:
ts
// 检查一句话正着读、倒着读是不是一样(回文)
function isPalindrome(text: string): boolean {
return text === encrypt(text)
}
console.log(isPalindrome("上海自来水来自海上")) // true
console.log(isPalindrome("放学后一起玩游戏")) // false
console.log(isPalindrome("我")) // true
console.log(isPalindrome("")) // true输出:
true
false
true
true咦,为什么 isPalindrome("")(空字符串)是 true? 因为空字符串倒过来还是空的——空空如也,正读反读都一样!
小挑战
- 升级回文:真正的回文判断要忽略标点和空格——"never odd or even" 其实也是回文(把空格去掉再看)。上网查一下
replace的用法, 让isPalindrome忽略空格和标点。 - 统计字数:写一个函数,数出一句话里某个字出现了几次 (提示:循环里用一个计数变量,和"找最长的名字"一样的方法)。
- 信使:把加密器和解密器合起来——输入原话,输出"密信 + 解密还原", 确认还原出来的和原来一字不差。
课后练习
第 1 题(动手题):我的密信
写一句 10 个字以上的话,加密成密信,再解密还原,确认还原成功。
参考答案(点开查看)
ts
const cipher = encrypt("明天下午三点老地方见")
console.log(cipher)
console.log(encrypt(cipher))解密输出应该和"明天下午三点老地方见"一字不差。 如果中间有标点(比如"!"),也会一起倒过来——这正好引出下一个挑战: 要不要把标点也一起加密?由你来决定"暗号规则"。
第 2 题(思考题):谁的密信更长?
"你好"这个字符串,"你好".length 是 2 还是 4? 运行试试,想想为什么。
参考答案(点开查看)
是 2。
length 数的是字符的个数,一个汉字就是一个字符。 (有些字符是"组合"出来的,比如 emoji 表情可能占两个字符——这就是为什么 "😂".length 会是 2。遇到时别慌,记住"length 数的是 UTF-16 编码单元", 以后学编码时会明白。)
第 3 题(思考题):为什么不能改?
下面这段代码会怎么样?运行试试,然后解释为什么。
ts
const word = "放学后一起玩游戏"
word[0] = "上"
console.log(word)参考答案(点开查看)
程序不会崩溃,但"上"也没写成——console.log(word) 输出的还是 "放学后一起玩游戏"。
因为字符串不可变,word[0] = "上" 这一行什么都不做(严格模式下甚至会报错)。 想改字符串,只能重新造一个新的,比如:
ts
const newWord = "上" + word.slice(1) // 取第一个字后面的部分顺便一提,word 是 const,但就算用 let 也改不了字符串本身—— let 只允许你换一个新字符串放进这个变量,不允许你修改旧字符串。
下一课预告:老师让你在黑板上画 20 颗星星。你正准备复制粘贴 20 遍, 老师突然说:"要 1000 颗。"——你的手指还好吗?