Главная Случайная страница


Полезное:

Как сделать разговор полезным и приятным Как сделать объемную звезду своими руками Как сделать то, что делать не хочется? Как сделать погремушку Как сделать так чтобы женщины сами знакомились с вами Как сделать идею коммерческой Как сделать хорошую растяжку ног? Как сделать наш разум здоровым? Как сделать, чтобы люди обманывали меньше Вопрос 4. Как сделать так, чтобы вас уважали и ценили? Как сделать лучше себе и другим людям Как сделать свидание интересным?


Категории:

АрхитектураАстрономияБиологияГеографияГеологияИнформатикаИскусствоИсторияКулинарияКультураМаркетингМатематикаМедицинаМенеджментОхрана трудаПравоПроизводствоПсихологияРелигияСоциологияСпортТехникаФизикаФилософияХимияЭкологияЭкономикаЭлектроника






Функция ввода





Чтение ввода - часто самая запутанная часть программы. Причина в том, что если программа должна общаться с человеком, то она должна справляться с его причудами, условностями и внешне случайными ошибками. Попытки заставить человека вести себя более удобным для машины образом часто (и справедливо) рассматриваются как оскорбительные. Задача низкоуровневой программы ввода состоит в том, чтобы читать символы по одному и составлять из них лексические символы более высокого уровня. Далее эти лексемы служат вводом для программ более высокого уровня. У нас ввод низкого уровня осуществляется get_token(). Обнадеживает то, что написание программ ввода низкого уровня не является ежедневной работой; в хорошей системе для этого будут стандартные функции.

Для калькулятора правила ввода сознательно были выбраны такими, чтобы функциям по работе с потоками было неудобно эти правила обрабатывать; незначительные изменения в определении лексем сделали бы get_token() обманчиво простой.

Первая сложность состоит в том, что символ новой строки '\n' является для калькулятора существенным, а функции работы с потоками считают его символом пропуска. То есть, для этих функций '\n' значим только как ограничитель лексемы. Чтобы преодолеть это, надо проверять пропуски (пробел, символы табуляции и т.п.):

char ch do { // пропускает пропуски за исключением '\n' if(!cin.get(ch)) return curr_tok = END; } while (ch!='\n' && isspace(ch));

Вызов cin.get(ch) считывает один символ из стандартного потока ввода в ch. Проверка if(!cin.get(ch)) не проходит в случае, если из cin нельзя считать ни одного символа; в этом случае возвращается END, чтобы завершить сеанс работы калькулятора. Используется операция! (НЕ), поскольку get() возвращает в случае успеха ненулевое значение.

Функция (inline) isspace() из обеспечивает стандартную проверку на то, является ли символ пропуском (#8.4.1); isspace(c) возвращает ненулевое значение, если c является символом пропуска, и ноль в противном случае. Проверка реализуется в виде поиска в таблице, поэтому использование isspace() намного быстрее, чем проверка на отдельные символы пропуска; это же относится и к функциям isalpha(), isdigit() и isalnum(), которые используются в get_token().

После того, как пустое место пропущено, следующий символ используется для определения того, какого вида какого вида лексема приходит. Давайте сначала рассмотрим некоторые случаи отдельно, прежде чем приводить всю функцию. Ограничители лексем '\n' и ';' обрабатываются так:

switch (ch) { case ';': case '\n': cin >> WS; // пропустить пропуск return curr_tok=PRINT;

Пропуск пустого места делать необязательно, но он позволяет избежать повторных обращений к get_token(). WS - это стандартный пропусковый объект, описанный в; он используется только для сброса пропуска. Ошибка во вводе или конец ввода не будут обнаружены до следующего обращения к get_token(). Обратите внимание на то, как можно использовать несколько меток case (случаев) для одной и той же последовательности операторов, обрабатывающих эти случаи. В обоих случаях возвращается лексема PRINT и помещается в curr_tok.

Числа обрабатываются так:

case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': case '.': cin.putback(ch); cin >> number_value; return curr_tok=NUMBER;

Располагать метки случаев case горизонтально, а не вертикально, не очень хорошая мысль, поскольку читать это гораздо труднее, но отводить по одной строке на каждую цифру нудно.

Поскольку операция >> определена также и для чтения констант с плавающей точкой в double, программирование этого не составляет труда: сперва начальный символ (цифра или точка) помещается обратно в cin, а затем можно считывать константу в number_value. Имя, то есть лексема NAME, определяется как буква, за которой возможно следует несколько букв или цифр:

if (isalpha(ch)) { char* p = name_string; *p++ = ch; while (cin.get(ch) && isalnum(ch)) *p++ = ch; cin.putback(ch); *p = 0; return curr_tok=NAME; }

Эта часть строит в name_string строку, заканчивающуюся нулем. Функции isalpha() и isalnum() заданы в; isalnum(c) не ноль, если c буква или цифра, ноль в противном случае.

Вот, наконец, функция ввода полностью:

token_value get_token() { char ch; do { // пропускает пропуски за исключением '\n' if(!cin.get(ch)) return curr_tok = END; } while (ch!='\n' && isspace(ch)); switch (ch) { case ';': case '\n': cin >> WS; // пропустить пропуск return curr_tok=PRINT; case '*': case '/': case '+': case '-': case '(': case ')': case '=': return curr_tok=ch; case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': case '.': cin.putback(ch); cin >> number_value; return curr_tok=NUMBER; default: // NAME, NAME= или ошибка if (isalpha(ch)) { char* p = name_string; *p++ = ch; while (cin.get(ch) && isalnum(ch)) *p++ = ch; cin.putback(ch); *p = 0; return curr_tok=NAME; } error("плохая лексема"); return curr_tok=PRINT; } }

Поскольку token_value (значение лексемы) операции было определено как целое значение этой операции *4, обработка всех операций тривиальна.

Таблица имен

К таблице имен доступ осуществляется с помощью одной функции

name* look(char* p, int ins =0);

Ее второй параметр указывает, нужно ли сначала поместить строку символов в таблицу. Инициализатор =0 задает параметр, который надлежит использовать по умолчанию, когда look() вызывается с одним параметром. Это дает удобство записи, когда look("sqrt2") означает look("sqrt2",0), то есть просмотр, без помещения в таблицу. Чтобы получить такое же удобство записи для помещения в таблицу, определяется вторая функция:

inline name* insert(char* s) { return look(s,1);}

Как уже отмечалось раньше, элементы этой таблицы имеют тип:

srtuct name { char* string; char* next; double value; }

Член next используется только для сцепления вместе имен в таблице. Сама таблица - это просто вектор указателей на объекты типа name:

const TBLSZ = 23; name* table[TBLSZ];

Поскольку все статические объекты инициализируются нулем, это тривиальное описание таблицы table гарантирует также надлежащую инициализацию.

Для нахождения элемента в таблице в look() принимается простой алгоритм хэширования (имена с одним и тем же хэш-кодом зацепляются вместе):

int ii = 0; // хэширование char* pp = p; while (*pp) ii = ii<< 0) ii = -ii; ii %= TBLSZ;

То есть, с помощью исключающего ИЛИ каждый символ во входной строке "добавляется" к ii ("сумме" предыдущих символов). Бит в x^y устанавливается единичным тогда и только тогда, когда соответствующие биты в x и y различны. Перед применением в символе исключающего ИЛИ, ii сдвигается на один бит влево, чтобы не использовать в слове только один байт. Это можно было написать и так:

ii <<= 1; ii ^= *pp++;

Кстати, применение ^ лучше и быстрее, чем +. Сдвиг важен для получения приемлемого хэш-кода в обоих случаях. Операторы

if (ii < 0) ii = -ii; ii %= TBLSZ;

обеспечивают, что ii будет лежать в диапазоне 0...TBLSZ-1; % - это операция взятия по модулю (еще называемая получением остатка).

Вот функция полностью:

extern int strlen(const char*); extern int strcmp(const char*, const char*); extern int strcpy(const char*, const char*); name* look(char* p, int ins =0) { int ii = 0; // хэширование char* pp = p; while (*pp) ii = ii<< 0) ii = -ii; ii %= TBLSZ; for (name* n=table[ii]; n; n=n->next) // поиск if (strcmp(p,n->string) == 0) return n; if (ins == 0) error("имя не найдено"); name* nn = new name; // вставка nn->string = new char[strlen(p)+1]; strcpy(nn->string,p); nn->value = 1; nn->next = table[ii]; table[ii] = nn; return nn; }

После вычисления хэш-кода ii имя находится простым просмотром через поля next. Проверка каждого name осуществляется с помощью стандартной функции strcmp(). Если строка найдена, возвращается ее name, иначе добавляется новое name.

Добавление нового name включает в себя создание нового объекта в свободной памяти с помощью операции new (см. #3.2.6), его инициализацию, и добавление его к списку имен. Последнее осуществляется просто путем помещения нового имени в голову списка, поскольку это можно делать даже не проверяя, имеется список, или нет. Символьную строку для имени тоже нужно сохранить в свободной памяти. Функция strlen() используется для определения того, сколько памяти нужно, new - для выделения этой памяти, и strcpy() - для копирования строки в память.

Date: 2015-09-25; view: 303; Нарушение авторских прав; Помощь в написании работы --> СЮДА...



mydocx.ru - 2015-2024 year. (0.005 sec.) Все материалы представленные на сайте исключительно с целью ознакомления читателями и не преследуют коммерческих целей или нарушение авторских прав - Пожаловаться на публикацию