Условие
Написать в файле caesar.c
, программу, шифрующую текст с помощью шифра Цезаря. На вход программы нужно подавать один аргумент командной строки: не негативное целое число. Для простоты назовем его k. Если пользователь выполняет программу без аргументов командной строки или более, чем с одним аргументом, приложение должно возмутиться и вернуть значение 1 (обычно так обозначают ошибки):
return 1;
Во всех остальных случаях программа запрашивает у пользователя текст, который нужно зашифровать, затем выводит на экран текст, зашифрованный ключом k (т.е., смещенный на k позиций вправо по циклу). Если в тексте есть символы, выходящие за пределы английского алфавита, их программа не меняет. После вывода шифрованного текста, приложение завершает работу, main
возвращает 0:
return 0;
Если main не возвращает нуль явно, он возвращается автоматически (на самом деле int
— тип, возвращаемый main
, но об этом в другой раз). Согласно конвенции (правилам хорошего тона в программировании), если вы явно возвращаете 1 чтобы указать на ошибку, то нужно вернуть и 0 в качестве указателя на успешное завершение работы программы.
Хотя в английском алфавите только 26 букв, k может быть и больше 26. По сути, ключ k
= 27 даст тот же результат, что и k
= 1, но нужно позволить пользователю вводить любое неотрицательное число, не превышающее 2^31 – 26 (ограничение связано с тем, что число должно поместиться в int
). Программа также должна учитывать, что строчные буквы шифруются строчными, а прописные — прописными.
С чего начинаем?
Поскольку приложение должно принять значение k непосредственно в строке аргументов, заголовок функции main у нас имеет следующий вид:
int main(int argc, string argv[])
Из шестой лекции вы знаете, что argv — это массив строк. Массив можно представить, как ряд шкафчиков-ячеек в спортзале. В каждом из них спрятано некоторое значение. В нашем случае, внутри каждой ячейки лежит аргумент типа string.
Чтобы открыть первый шкафчик, используем argv[0]
, второй — argv[1]
и так далее. Если у нас есть n
замков, то нам нужно остановиться на argv[n — 1]
, поскольку argv[n]
уже не существует (или существует, но принадлежит кому-то ещё, нам лучше его не трогать).
Таким образом, вы можете получить доступ к аргументу k следующим образом:
string k = argv[1];
Мы полагаем, что там действительно что-то есть! Напомним, argc
— переменная типа int
, равная количеству строк argv
. Значит, лучше проверить значение argc
прежде, чем пытаться открыть ячейку, ведь может статься, что её не существует.
В идеале argc = 2
. Почему так? Внутри argv[0]
обычно находится имя программы. То есть, argc всегда не меньше 1. Но нашей программе нужно, чтобы пользователь предоставил аргумент командной строки k, следовательно, argc = 2
. Естественно, если пользователь в командной строке введет более одного аргумента, argc также подрастает и может быть больше, чем 2.
Если пользователь вводит целое число в строку, это еще не значит, что внесенное значение будет автоматически сохранено в тип int. Точнее, оно НЕ будет. Оно будет string
, даже если выглядит точь-в-точь, как int
! Так что нам нужно конвертировать string в int самостоятельно. К счастью, существует функция atoi
, созданная для этих целей. Её синтаксис:
int k = atoi(argv[1]);
Обратите внимание: k
имеет тип int
, поэтому с ним можно провернуть арифметические действия. С этой функцией не нужно беспокоиться, введёт ли пользователь целое число, или, скажем, foo:
в таком случае atoi
возвратит 0.
Функция atoi
объявлена в библиотеке stdlib.h
, поэтому не забудьте прописать её директивой #include
в начале программы. Код и без этого скомпиллируется, поскольку мы уже включили эту функцию в библиотеку cs50.h
. Тем не менее, лучше доверять нативным библиотекам.
Итак, вы получили k
, сохраненное как int
. Теперь запросим ввод текста. Если вы делали задания первой недели, то уже знакомы с функцией библиотеки CS50, которая называется GetString
. Она-то нам и поможет.
После того, как вы получили k и начальный текст, приступим к шифрованию. Напомним, вы можете пройтись по всем символам строки и напечатать их с помощью следующего цикла:
for (int i = 0, n = strlen(p); i < n; i++)
{
printf("%c", p[i]);
}
Другими словами, точно так же, как argv
— массив строк, string
является массивом символов. Поэтому мы можем использовать квадратные скобки для доступа к отдельным элементам строки точно так же, как получать отдельные строки в argv
. Конечно, нет ничего криптографического в печати каждого из символов. Или, технически, когда k
= 0. Но мы же должны помочь Цезарю зашифровать его текст! Аве, Цезарь!
Чтобы использовать strlen
, нужно подключить ещё одну библиотеку.
Поскольку мы автоматизируем некоторые проверочные тесты, программа должна себя вести себя ровно следующим образом:
username:~/workspace/pset2 $ ./caesar 13
Be sure to drink your Ovaltine!
Or fher gb qevax lbhe Binygvar!
Помимо atoi
, вы можете найти другие классные функции в библиотеках ctype.h
и stdlib.h.
Для этого перейдите по ссылке и поройтесь там немного. Например, isdigit
— явно что-то интересное=).
Когда переходите от Z к A (или от z к a), не забывайте об операторе деления по модулю % в языке Си. Также изучите таблицу, она показывает символы ASCII не только для букв.
Чтобы проверить правильность работы программы с check50
, выполните следующее:
check50 2015.fall.pset2.caesar caesar.c
А если вам интересно поиграть с кодом, сделанным сотрудниками СS50, выполните команду:
~cs50/pset2/caesar
Кстати, uggc://jjj.lbhghor.pbz/jngpu?i=bUt5FWLEUN0.
Разбор задания
Вот что вам нужно сделать:
- Получить ключ
- Получить текст
- Зашифровать
- Вывести на экран зашифрованное сообщение
Для этого:
- Формируем функцию main так, чтобы пользователь вводил ключ в командной строке и проверяем ключ на корректность.
int main(int argc, string argv[])
argc:
- int
- количество аргументов, введенных в командную строку
- если argc = 2, все ок. Если нет, выводим инструкцию и закрываем программу.
- Если argc = 2, проверяем, является ли ключ целочисленным.
- argv — это массив строк, список с введенными в него аргументами.
Как вы уже знаете из лекции, массив — структура данных, содержащая разные данные одного типа в последовательно идущих ячейках.
Например, пользователь ввел строку
blastoff Team Rocket
, тогда:Переводим с помощью функции
atoi()
полученное в целое число. Если это невозможно, функция вернет 0. - Запрос у пользователя текста. Это просто: всё, что вводит пользователь, является строкой.
- Шифрование. Алгоритм прост, но как пояснить компьютеру, какие буквы идут одна за другой? Самое время вспомнить о таблице ASCII!
Однако в строке могут быть не только буквы… Прежде, чем перейти к изменению строк, представьте, что нужно поменять только один символ. Мы хотим сменить буквы из начального текста, а не знаки или цифры. Что мы должны сделать? Для начала нам нужно проверить, есть ли этот символ в алфавите. Это можно сделать с помощью функции isalpha()
.
Если символ входит в алфавит, эта функция возвращает значение true
и false
во всех других случаях. Еще две полезные функции — isupper ()
и islower()
возвращают true
в случае, если буква прописная или строчная соответственно. Таким образом:
Isalpha(‘Z’) -> true
Isalpha(‘;’) -> false
Isupper(‘Z’) ->true
Isupper(‘z’) -> false
Islower(‘Z’) -> false
Islower(‘z’)->true
Если isalpha
возвращает true
, нам нужно поменять этот символ с помощью ключа.
Рассмотрим и разберем в качестве примера программу Замайлы, ассистента CS50.
/*
* asciimath.c
* by Zamyla Chan
*
* Calculates the addition of a char and an integer,
* and displays both the resultant character and its
* ASCII value.
*
* Usage: ./asciimath key [char]
*
*/
#include
#include
#include
int main(int argc, string argv[])
{
if (argc != 2)
{
printf("print the key next time \n");
return 1;
}
// key is the second command line argument
int key = atoi(argv[1]); //преобразование строки в int
int letter = 'A';
printf("\nCalculating '%c' + %d...\n", letter, key);
int result = (letter + key);
printf("The ASCII value of %c is %d.\n\n", result, result);
return 0;
}
Вас может удивить, почему ‘A’ — это целое число, тогда как она явно является буквой. Оказывается символы и целые числа — взаимозаменяемы. Поставив букву A в одиночные кавычки можно получить её ASCII-код в int. Будьте внимательны: вам нужны именно одинарные кавычки, без них компилятор будет искать переменную по имени A, а не символ.
Затем в строке
int result = (letter + key);
мы прибавляем значение ключа к ASCII-коду буквы и сохраняем их в переменной целого типа. Даже если результат имеет тип int, оператор printf использует плейсхолдер %с
для символов. Таким образом, программа печатает символ, связанный с целочисленным результатом. Во втором случае мы выводим на экран число с помощью плейсхолдера %d
.
Вы можете ввести этот код в сs50 IDE и поиграться с ним.
Проверим работу asciimath
для разных ключей. Возьмем значение 25, увидим следующую картинку:

А теперь пусть ключ будет 26:

Мы получили [, а вовсе не букву A. Это просто следующий символ ASCII после Z. Так что простое прибавление ключа работать не будет. Нам нужно использовать формулу шифра, чтобы возвращаться в начало алфавита как только буквы закончатся.
Помните, мы уже писали выше:
ci = (pi + k) % 26
Где ci — буква номер i в шифрованном тексте, pi — буква номер i в незашифрованном тексте, k — ключ, а %26 — остаток от деления на 26 (или «деление по модулю 26»).
Давайте применим эту формулу для буквы Y. Возьмем k = 2. Посчитаем (‘Y’ + 2) %26
ASCII-код буквы ‘Y’= 89. Тогда
(‘Y’ + 2) %26 = (89 + 2)%26 = 91%26 = 13
Но это вовсе не ASCII-значение нужной нам буквы A, которое равно 65.
Теперь давайте придадим каждой букве алфавита значение от 0 до 25 по порядку. В таком случае Y = 24.
(24+2)%26 = 0
Буква А как раз имеет такой индекс. Таким образом, эта формула относится к алфавитному индексу букв, а не их ASCII-значений.
Для печати зашифрованного символа вам нужно будет его ASCII-значение. И разберитесь с тем, как переключаться между ASCII-значением и номером в алфавите.
После того, как мы выяснили формулу для одного символа, нужно применить её для каждой буквы во вводимой с клавиатуры строке. Но только если это буквы!
И помните, для больших и малых букв нужны разные значения. Тут пригодятся функции isupper
и islower
. У вас может быть две формулы, одна для больших букв, другая — для малых, функции помогут выбрать, какую из них применить.
Как применить формулу к каждому отдельному символу в строке? Помним, что строка — это просто массив символов. Определить количество итераций в цикле поможет функция strlen
(длина строки).

ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ