Caesar

要解决的问题
据说,凯撒(没错,就是那位凯撒)曾经通过把消息中的每个字母移动若干位来“加密”(也就是以可逆方式隐藏)机密信息。例如,他可能把 一份 写成 B,把 B 写成 C,把 C 写成 D,……,并在字母表末尾回绕,把 Z 写成 A。因此,如果要向某人说 HELLO,凯撒可能会写成 IFMMP。收到这类消息的人必须把字母按相同位数反方向移动,才能“解密”。
这个“密码系统”的保密性依赖于只有凯撒和接收者知道一个秘密:字母被移动了多少位(例如 1 位)。按现代标准看,这并不特别安全;不过,如果你可能是世界上第一个这么做的人,那也算相当安全了。
未加密的文本通常称为明文。加密后的文本通常称为密文。所使用的秘密则称为密钥。
更明确地说,用密钥 1 加密 HELLO 会得到 IFMMP,过程如下:
| 明文 | H |
E |
L |
L |
O |
|---|---|---|---|---|---|
| + 密钥 | 1 | 1 | 1 | 1 | 1 |
| = 密文 | I |
F |
M |
M |
P |
更正式地说,凯撒算法(也就是这种密码)会把每个字母“旋转” k 个位置来加密消息。设 p 为某段明文(即未加密的消息),pi 为 p 中的第 i 个字符,k 为秘密密钥(即非负整数),则密文 c 中的每个字母 ci 可按如下方式计算:
ci = (pi + k) % 26其中 % 26 表示“除以 26 后的余数”。这个公式可能让密码看起来比实际更复杂,但它其实只是用简洁的方式精确表达算法。为了便于讨论,可以把 A(或 a)看作 0,B(或 b)看作 1,……,H(或 h)看作 7,I(或 i)看作 8,……,Z(或 z)看作 25。假设 Caesar 这次只想用密钥 k = 3 私密地向某人发送 Hi。那么他的明文 p 是 Hi,其中第一个字符 p0 是 H(也就是 7),第二个字符 p1 是 i(也就是 8)。因此密文的第一个字符 c0 是 K,第二个字符 c1 是 L。明白了吗?
请在名为 caesar 的文件夹中创建 caesar.c 文件,并编写一个程序,使用凯撒密码加密消息。用户运行程序时,应通过命令行参数指定密钥;运行后再输入需要加密的秘密消息。你不应假设用户提供的密钥一定是数字;不过,如果它确实是数字,你可以假设它是正整数。
演示
规格说明
设计并实现一个名为 caesar 的程序,使用凯撒密码加密消息。
- 在名为
caesar的目录中,用名为caesar.c的文件实现你的程序。 - 你的程序必须接受一个命令行参数:一个非负整数。为了便于讨论,我们把它称为 k。
- 如果程序运行时没有命令行参数,或命令行参数多于一个,程序应立即用
printf输出你自选的错误消息,并让main返回1(通常表示错误)。 - 如果命令行参数中的任意字符不是十进制数字,程序应输出消息
Usage: ./caesar key,并让main返回1。 - 不要假设 k 小于或等于 26。你的程序应适用于所有小于 231 - 26 的非负整数 k。换句话说,如果用户选择的 k 大到或接近大到无法放进
int,你不必担心程序最终会出问题。(记住,int可能溢出。)不过,即使 k 大于 26,输入中的字母字符在输出中仍应保持为字母字符。例如,如果 k 是 27,A不应变成\,尽管根据 asciitable.com,\在 ASCII 中距离A有 27 个位置;在从Z回绕到A的前提下,A应该变成B。 - 你的程序必须输出
plaintext:(两个空格,不带换行),然后使用get_string提示用户输入明文字符串。 - 你的程序必须输出
ciphertext:(一个空格,不带换行),随后输出明文对应的密文:明文中的每个字母字符都要“旋转” k 个位置,非字母字符保持不变。 - 你的程序必须保留大小写:大写字母旋转后仍为大写字母;小写字母旋转后仍为小写字母。
- 输出密文后,应打印一个换行。随后程序应让
main返回0并退出。
建议
该如何开始?让我们一步一步解决这个问题。
伪代码
先尝试在 caesar.c 中写出一个 main 函数,只用伪代码描述程序流程,即使你暂时还不确定如何写成真正的 C 代码也没关系。
提示
实现方式不止一种,下面只是其中一种!
int main(int argc, string argv[])
{
// Make sure program was run with just one 命令行参数
// Make sure every character in argv[1] is a digit
// Convert argv[1] from a `string` to an `int`
// Prompt user for plaintext
// For each character in the plaintext:
// Rotate the character if it's a letter
}
看过这里的伪代码后,可以修改你自己的伪代码,但不要简单地把我们的复制粘贴过去!
统计命令行参数
无论你的伪代码如何,在加入其他功能之前,我们先只写 C 代码来检查程序是否只带了一个命令行参数运行。
具体来说,修改 caesar.c 中的 main:如果用户没有提供命令行参数,或提供了两个及以上参数,函数应打印 "Usage: ./caesar key\n",然后返回 1,也就是退出程序。如果用户恰好提供一个命令行参数,程序应什么也不打印,直接返回 0。因此程序应表现如下。
$ ./caesar
Usage: ./caesar key
$ ./caesar 1 2 3
Usage: ./caesar key
$ ./caesar 1
提示
- 记住,可以用
printf打印内容。 - 记住,函数可以用
return返回一个值。 - 记住,
argc包含传给程序的命令行参数数量,再加上程序自身的名称。
检查密钥
现在你的程序(希望如此!)已经能按要求接受输入了,可以继续下一步。
在 caesar.c 中、main 下方,添加一个函数,例如命名为 only_digits。它接受一个 string 参数;如果该字符串只包含 0 到 9 的数字,则返回 true,否则返回 false。也别忘了在 main 上方添加函数原型。
提示
- 你很可能会需要像这样的函数原型:
bool only_digits(string s);并且要在文件顶部包含
cs50.h,这样编译器才能识别string(以及bool)。 - 记住,
string本质上只是一个char数组。 - 记住,声明在
string.h中的strlen可以计算string的长度。 - 根据 manual.cs50.io,声明在
ctype.h中的isdigit可能会有帮助。但请注意,它一次只能检查一个char!
然后修改 main,让它对 argv[1] 调用 only_digits。如果该函数返回 false,那么 main 应打印 "Usage: ./caesar key\n" 并返回 1。否则,main 应直接返回 0。程序应表现如下:
$ ./caesar 42
$ ./caesar banana
Usage: ./caesar key
使用密钥
现在修改 main,让它把 argv[1] 转换为 int。根据 manual.cs50.io,声明在 stdlib.h 中的 atoi 可能会有帮助。然后使用 get_string,以 "plaintext: " 提示用户输入明文。
接着,实现一个函数,例如命名为 rotate。它接受一个 char 和一个 int 作为输入;如果该 char 是字母,就将它旋转相应数量的位置,并在需要时从 Z 回绕到 A(以及从 z 回绕到 a)。如果该 char 不是字母,函数应原样返回它。
提示
- 你很可能会需要像这样的函数原型:
char rotate(char c, int n);像下面这样的函数调用
rotate('A', 1)甚至
rotate('A', 27)都应返回
'B'。而像下面这样的函数调用rotate('!', 13)应返回
'!'。 - 记住,可以用
(int)显式地把char“转换”为int,也可以用(char)把int转回char。也可以通过把其中一种类型当作另一种使用来隐式完成转换。 - 你很可能需要从任意大写字母中减去
'A'的 ASCII 值,这样在做算术时就能把'A'当作0,'B'当作1,以此类推。计算完成后再把它加回来。 - 你很可能也需要从任意小写字母中减去
'a'的 ASCII 值,这样在做算术时就能把'a'当作0,'b'当作1,以此类推。计算完成后再把它加回来。 - 根据 manual.cs50.io,声明在
ctype.h中的其他函数也可能有帮助。 - 当你需要在算术上从类似
25的值“回绕”到0时,%很可能会很有用。
然后修改 main,让它先打印 "ciphertext: ",再遍历用户明文中的每个 char,对每个字符调用 rotate,并打印其返回值。
提示
- 记住,
printf可以用%c打印char。 - 如果调用
printf时完全看不到任何输出,很可能是因为你正在打印有效 ASCII 范围 0 到 127 之外的字符。可以临时把字符当作数字打印(用%i而不是%c),看看你实际打印的值是什么!
讲解视频
如何测试
正确性
check50 cs50/problems/2026/x/caesar
如何使用 debug50
想运行 debug50 吗?在用 make 成功编译代码后,可以这样做:
debug50 ./caesar KEY
其中 KEY 是你作为命令行参数传给程序的密钥。注意,运行
debug50 ./caesar
理想情况下会让你的程序最后提示用户输入密钥。
代码风格
style50 caesar.c
如何提交
在终端中执行以下命令提交你的作业,并按提示完成操作。
submit50 cs50/problems/2026/x/caesar