Условие

Написать в файле 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.

Разбор задания

Вот что вам нужно сделать:

  • Получить ключ
  • Получить текст
  • Зашифровать
  • Вывести на экран зашифрованное сообщение

Для этого:

  1. Формируем функцию main так, чтобы пользователь вводил ключ в командной строке и проверяем ключ на корректность.
    int main(int argc, string argv[])

    argc:

    • int
    • количество аргументов, введенных в командную строку
    • если argc = 2, все ок. Если нет, выводим инструкцию и закрываем программу.
    • Если argc = 2, проверяем, является ли ключ целочисленным.
    • argv — это массив строк, список с введенными в него аргументами.

    Как вы уже знаете из лекции, массив — структура данных, содержащая разные данные одного типа в последовательно идущих ячейках.

    Задание 1. Написать шифр Цезаря - 1

    Например, пользователь ввел строку blastoff Team Rocket, тогда:

    Задание 1. Написать шифр Цезаря - 2

    Переводим с помощью функции atoi() полученное в целое число. Если это невозможно, функция вернет 0. 

    Задание 1. Написать шифр Цезаря - 3
  2. Запрос у пользователя текста. Это просто: всё, что вводит пользователь, является строкой. 
  3. Шифрование. Алгоритм прост, но как пояснить компьютеру, какие буквы идут одна за другой? Самое время вспомнить о таблице ASCII!
    cs50 ascii chart

Однако в строке могут быть не только буквы… Прежде, чем перейти к изменению строк, представьте, что нужно поменять только один символ. Мы хотим сменить буквы из начального текста, а не знаки или цифры. Что мы должны сделать? Для начала нам нужно проверить, есть ли этот символ в алфавите. Это можно сделать с помощью функции 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, увидим следующую картинку: 

Задание 1. Написать шифр Цезаря - 4

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

Задание 1. Написать шифр Цезаря - 5

Мы получили [, а вовсе не букву 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 (длина строки). 

Задание 1. Написать шифр Цезаря - 6