Skip to content

3. 密信(字符串)

知识点:字符串

项目:把悄悄话变成只有你俩懂的"密信"


故事开场:纸条被看到了!

课间,你给好朋友写了一句悄悄话:"放学后一起玩游戏"。

可是同桌眼疾手快,一把抢过纸条念了出来。全班都听见了!太尴尬了!

好朋友说:"我们要发明一种只有我们俩看得懂的写法。" 你俩约定了一个暗号:把每个字倒着写

"放学后一起玩游戏"倒过来就是:

戏游玩起一后学放

看起来像乱码,但你俩知道规则,一眼就能读回来。

今天,我们要写两个程序:

  1. 加密器:输入一句话,输出倒过来的"密信"
  2. 解密器:输入"密信",还原出原来的话

笨办法先行:一个字一个字搬

"把字倒过来"……这怎么写?第一个想到的办法:从最后一个字开始,一个个拿出来拼。

在 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)本质上就是一排字符组成的数组。

下标012345678
  • 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? 因为空字符串倒过来还是空的——空空如也,正读反读都一样!


小挑战

  1. 升级回文:真正的回文判断要忽略标点和空格——"never odd or even" 其实也是回文(把空格去掉再看)。上网查一下 replace 的用法, 让 isPalindrome 忽略空格和标点。
  2. 统计字数:写一个函数,数出一句话里某个字出现了几次 (提示:循环里用一个计数变量,和"找最长的名字"一样的方法)。
  3. 信使:把加密器和解密器合起来——输入原话,输出"密信 + 解密还原", 确认还原出来的和原来一字不差。

课后练习

第 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)   // 取第一个字后面的部分

顺便一提,wordconst,但就算用 let 也改不了字符串本身—— let 只允许你换一个新字符串放进这个变量,不允许你修改旧字符串


下一课预告:老师让你在黑板上画 20 颗星星。你正准备复制粘贴 20 遍, 老师突然说:"要 1000 颗。"——你的手指还好吗?