Caesar
Para este problema, você implementará um programa que criptografa mensagens usando a cifra de César, conforme abaixo.
$ ./caesar 13
plaintext: HELLO
ciphertext: URYYB
Problema a Resolver
Em um arquivo chamado caesar.c
em uma pasta chamada
caesar
, escreva um programa que permite criptografar mensagens
usando a cifra de César. No momento em que o usuário executa o programa, ele deve decidir, fornecendo um argumento
de linha de comando, qual deve ser a chave na mensagem secreta que fornecerá em tempo de execução. Não devemos
necessariamente presumir que a chave do usuário será um número; embora você possa assumir que, se for um número,
será um número inteiro positivo.
Começando
Passo a Passo
Eis aqui um passo a passo para a resolução do problema.
Contexto
Supostamente, César (sim, o próprio) costumava "criptografar" (ou seja, ocultar de forma reversível) mensagens confidenciais, deslocando cada letra nelas por um certo número de lugares. Por exemplo, ele poderia escrever A como B, B como C, C como D, ..., e, envolvendo alfabeticamente, Z como A. E assim, para dizer HELLO a alguém, César poderia escrever IFMMP em seu lugar. Ao receber tais mensagens de César, os destinatários teriam que "descriptografá-las" deslocando as letras na direção oposta pelo mesmo número de lugares.
A segurança desse "criptossistema" dependia apenas de César e dos destinatários saberem um segredo, o número de lugares pelos quais César havia deslocado suas letras (por exemplo, 1). Não é particularmente seguro pelos padrões modernos, mas, ei, se você talvez seja o primeiro do mundo a fazer isso, é bastante seguro!
O texto não criptografado é geralmente chamado de texto simples. O texto criptografado é geralmente chamado de texto cifrado. E o segredo usado é chamado de chave.
Para ser claro, aqui está como criptografar HELLO
com
uma chave de 1 produz IFMMP
:
plaintext | H |
E |
L |
L |
O |
---|---|---|---|---|---|
+ key | 1 | 1 | 1 | 1 | 1 |
= ciphertext | I |
F |
M |
M |
P |
Mais formalmente, o algoritmo de Cifra de César cifra mensagens "rotacionando" cada letra por k posições. Mais precisamente, se p é algum texto simples (ou seja, uma mensagem não criptografada), pi é o ith caractere em p, e k é uma chave secreta (ou seja, um número inteiro não negativo), então cada letra, ci, no texto cifrado, c, é calculado como:
ci = (pi + k) % 26onde % 26 aqui significa "resto da divisão por 26". Esta fórmula talvez faça a cifra parecer
mais complicada do que realmente é, mas na verdade é apenas uma forma concisa de expressar o algoritmo com
precisão. De fato, para fins de discussão, pense em A (ou a) como 0, B (ou b) como
1, ..., H (ou h) como 7, I (ou i) como 8, ..., e Z (ou z) como
25. Suponha que César queira dizer confidencialmente "Hi" para alguém usando, desta vez, uma
chave k de 3. E então seu texto simples, p, é Hi
, caso em que o primeiro caractere do seu texto simples,
p0, é H
(também conhecido
como 7), e o segundo caractere do seu texto simples, p1, é i
(também conhecido como 8). O primeiro caractere do seu
texto cifrado, c0, é assim K
, e o segundo caractere do seu texto cifrado,
c1, é assim L
. Faz sentido?
Vamos escrever um programa chamado caesar
que permite
cifrar mensagens usando a cifra de César. No momento em que o usuário executa o programa, eles devem decidir,
fornecendo um argumento de linha de comando, qual será a chave na mensagem secreta que eles fornecerão em tempo de
execução. Não devemos necessariamente assumir que a chave do usuário será um número; embora possamos assumir que,
se for um número, será um inteiro positivo.
Aqui estão alguns exemplos de como o programa pode funcionar. Por exemplo, se o usuário inserir uma chave de
1
e um texto simples de HELLO
:
$ ./caesar 1
plaintext: HELLO
ciphertext: IFMMP
Aqui está como o programa poderia funcionar se o usuário fornecer uma chave de 13
e um texto simples de hello, world
:
$ ./caesar 13
plaintext: hello, world
ciphertext: uryyb, jbeyq
Observe que nem a vírgula nem o espaço foram "deslocados" pela cifra. Apenas os caracteres alfabéticos são rotacionados!
E que tal mais um exemplo? Aqui está como o programa poderia funcionar se o usuário fornecer uma chave de 13
novamente, com um texto simples mais complexo:
$ ./caesar 13
plaintext: be sure to drink your Ovaltine
ciphertext: or fher gb qevax lbhe Binygvar
Por quê?
Observe que o caso da mensagem original foi preservado. As letras minúsculas permanecem minúsculas e as letras maiúsculas permanecem maiúsculas.
E o que acontece se o usuário não cooperar, fornecendo um argumento de linha de comando que não é um número? O programa deve lembrar o usuário de como usar o programa:
$ ./caesar HELLO
Usage: ./caesar key
Ou se o usuário não fornecer nenhum argumento de linha de comando? O programa deve lembrar o usuário como usar o programa:
$ ./caesar
Usage: ./caesar key
Ou se o usuário realmente, realmente não coopera, fornecendo mais de um argumento de linha de comando? O programa deve lembrar o usuário de como usar o programa:
$ ./caesar 1 2 3
Usage: ./caesar key
Demo
Especificação
Projete e implemente um programa, caesar
, que
criptografa mensagens usando a cifra de César.
- Implemente seu programa em um arquivo chamado
caesar.c
em um diretório chamadocaesar
. - Seu programa deve aceitar um único argumento de linha de comando, um número inteiro não negativo. Vamos chamá-lo de k para fins de discussão.
- Se o seu programa for executado sem nenhum argumento de linha de comando ou com mais de um
argumento de linha de comando, o programa deve imprimir uma mensagem de erro de sua escolha (com
printf
) e retornar demain
um valor de1
(que geralmente significa um erro) imediatamente. - Se qualquer um dos caracteres do argumento de linha de comando não for um dígito decimal, seu
programa deve imprimir a mensagem
Usage: ./caesar key
e retornar demain
um valor de1
. - Não assuma que k será menor ou igual a 26. Seu programa deve funcionar para
todos os valores integrais não negativos de k menores que 231 - 26.
Em outras palavras, você não precisa se preocupar se seu programa eventualmente quebrar se o usuário escolher um
valor para k que seja muito grande ou quase grande demais para caber em um
int
. (Lembre-se de que umint
pode transbordar.) Mas, mesmo que k seja maior que 26, os caracteres alfabéticos na entrada do seu programa devem permanecer caracteres alfabéticos na saída do seu programa. Por exemplo, se k for 27,A
não deve se tornar\
mesmo que\
esteja a 27 posições deA
em ASCII, conforme asciitable.com;A
deve se tornarB
, já queB
está a 27 posições deA
, desde que você volte deZ
paraA
. - Seu programa deve exibir
plaintext:
(com dois espaços, mas sem uma nova linha) e, em seguida, solicitar ao usuário umastring
de texto simples (usandoget_string
). - Seu programa deve exibir
ciphertext:
(com um espaço, mas sem uma nova linha), seguido do texto simples correspondente cifrado, com cada caractere alfabético no texto simples "rotacionado" por k posições; caracteres não alfabéticos devem ser exibidos inalterados. - Seu programa deve preservar a caixa das letras: letras maiúsculas, embora rotacionadas, devem permanecer como letras maiúsculas; letras minúsculas, embora rotacionadas, devem permanecer como letras minúsculas.
- Após a saída do texto cifrado, você deve imprimir uma nova linha. Seu programa deve então sair
retornando
0
da funçãomain
.
Aconselhamento
Como começar? Vamos abordar este problema passo a passo.
Pseudocódigo
Primeiro, tente escrever uma função main
em caesar.c
que implemente o programa usando apenas
pseudocódigo, mesmo que ainda não saiba como escrevê-lo em código real.
Dica
Há mais de uma maneira de fazer isso, então aqui está apenas uma!
int main(void)
{
// Make sure program was run with just one command-line argument
// 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
}
Tudo bem editar o seu próprio pseudocódigo depois de ver o nosso aqui, mas não simplesmente copie e cole o nosso no seu!
Contando argumentos da linha de comando
Independentemente do pseudocódigo, vamos primeiro escrever apenas o código C que verifica se o programa foi executado com um único argumento da linha de comando antes de adicionar funcionalidades adicionais.
Especificamente, modifique a função main
em caesar.c
de tal forma que, se o usuário não fornecer nenhum
argumento da linha de comando, ou dois ou mais, a função imprime "Usage: ./caesar key\n"
e, em seguida, retorna 1
, efetivamente encerrando o programa. Se o usuário fornecer
exatamente um argumento da linha de comando, o programa não deve imprimir nada e simplesmente retornar 0
. O programa deve, portanto, comportar-se conforme abaixo.
$ ./caesar
Usage: ./caesar key
$ ./caesar 1 2 3
Usage: ./caesar key
$ ./caesar 1
Dicas
- Lembre-se que você pode imprimir com o
printf
. - Lembre-se que uma função pode retornar um valor com
return
. - Lembre-se que
argc
contém o número de argumentos de linha de comando passados para um programa, além do nome do próprio programa.
Verificando a Chave
Agora que seu programa está (esperamos!) aceitando a entrada conforme prescrito, é hora de mais um passo.
Adicione ao arquivo caesar.c
, abaixo do main
, uma função chamada, por exemplo, only_digits
, que recebe uma string
como argumento e retorna true
se essa string
contiver apenas dígitos, de 0
a 9
, caso contrário, retorna false
. Certifique-se de adicionar o protótipo da função
acima do main
também.
Dicas
- Provavelmente você desejará um protótipo como:
bool only_digits(string s);
E certifique-se de incluir
cs50.h
no topo do seu arquivo, para que o compilador reconheçastring
(ebool
). - Lembre-se de que uma
string
é apenas uma matriz dechar
s. - Lembre-se de que
strlen
, declarado emstring.h
, calcula o comprimento de umastring
. - Você pode achar
isdigit
, declarado emctype.h
, útil, conforme o manual.cs50.io. Mas observe que ele verifica apenas umchar
por vez!
Então modifique main
de forma que ele chame only_digits
em argv[1]
. Se essa função retornar false
, então main
deve imprimir "Usage: ./caesar key\n"
e retornar 1
. Caso contrário, main
deve simplesmente retornar 0
. O programa deve, portanto, se comportar conforme abaixo:
$ ./caesar 42
$ ./caesar banana
Usage: ./caesar key
Usando a Chave
Agora modifique a função main
de tal forma que ela
converta argv[1]
para um int
. Você pode achar útil a função atoi
, declarada em stdlib.h
, conforme o manual.cs50.io. E então use get_string
para solicitar ao usuário algum texto simples com
"plaintext: "
.
Em seguida, implemente uma função chamada, por exemplo, rotate
, que recebe um char
como entrada e também um int
, e rotaciona aquele char
por tantas posições quanto o número de vezes
especificado, caso seja uma letra (ou seja, alfabética), enrolando de Z
para A
(e de z
para a
) conforme necessário. Se o char
não for uma letra, a função deve retornar o mesmo char
inalterado.
Dicas
- É provável que você queira um protótipo como:
char rotate(char c, int n);
Uma chamada de função como
rotate('A', 1)
ou até mesmo
rotate('A', 27)
deverá retornar
'B'
. E uma chamada de função comorotate('!', 13)
deve retornar
'!'
. - Lembre-se de que você pode "converter" explicitamente um caractere
char
em umint
com(char)
, e umint
em umchar
com(int)
. Ou você pode fazer isso implicitamente tratando um como o outro. - Provavelmente você desejará subtrair o valor ASCII de
'A'
de quaisquer letras maiúsculas, para tratar'A'
como0
,'B'
como1
e assim por diante, enquanto realiza operações aritméticas. E então adicioná-lo de volta quando terminar com o mesmo. - Provavelmente você desejará subtrair o valor ASCII de
'a'
de quaisquer letras minúsculas, para tratar'a'
como0
,'b'
como1
e assim por diante, enquanto realiza operações aritméticas. E então adicioná-lo de volta quando terminar com o mesmo. - Você pode achar algumas outras funções declaradas em
ctype.h
úteis, conforme manual.cs50.io. - Provavelmente você encontrará o operador
%
útil quando precisar "dar a volta" aritmeticamente de um valor como25
para0
.
Em seguida, modifique a função main
de forma que ela
imprima "ciphertext: "
e, em seguida, percorra cada
char
do texto simples do usuário, chamando a função
rotate
em cada caractere e imprimindo o valor de
retorno.
Dicas
- Lembre-se de que o
printf
pode imprimir um caractere (char
) usando%c
. - Se você não estiver vendo nenhuma saída quando chamar o
printf
, provavelmente é porque está imprimindo caracteres fora da faixa ASCII válida de 0 a 127. Tente imprimir caracteres temporariamente como números (usando%i
em vez de%c
) para ver quais valores está imprimindo!
Como Testar o Seu Código
Execute o seguinte para avaliar a correção do seu código usando check50
. Mas certifique-se de compilar e testar também por
conta própria!
check50 cs50/problems/2024/x/caesar
Execute o código abaixo para avaliar o estilo do seu código usando style50
.
style50 caesar.c
Como Enviar
No seu terminal, execute abaixo para enviar seu trabalho.
submit50 cs50/problems/2024/x/caesar