Проверка данных при вводе. Нужна помощь!
От: maksqwe  
Дата: 24.12.06 13:42
Оценка:
Я только начинающий программер... Потому не надо писать типа "... вот ламо... rtfm... " Я лишь прошу консультации.

Прога — консолька, пишу на Visual Studio 2005.
Собственно столкнулся с такой неприятной неприятностью. Прога у меня считывает вводимую инфу, предположим какое-либо число (int), как сделать проверку введенных символов, на факт присутствия в них символов не относящихся к цифрам? Просто если ввести буквы, а прога считывает в интовскю переменную, то она вылетает...

Прошу помочь!! Буду премного благодарен!
Re: Проверка данных при вводе. Нужна помощь!
От: dotidot Россия  
Дата: 24.12.06 14:45
Оценка:
Здравствуйте, maksqwe, Вы писали:
Вот вам костыль из моей далекой молодости
Вообще — он ужасен, но работает(сделан специально под виндовый cmd)
см. коментарии.
#ifndef _1251_to_866_H
#define _1251_to_866_H

#include <iostream>
#include <string>
#include <stdexcept>

// cp1251(в ней обычно MSVC исходник сохраняет)->cp866(виндовая консолька)
std::string
_r(const char* str)
{
    int i = 0;
    int y = 0;
    char z;
    std::string tmp;
    
    while (*(str + i) != '\0')
    {
        y = z = *(str + i);
        if ((y <= -17) && (y >= -64))
            z = 192 + y;
        if ((y <= -1) && (y >= -16))
            z = 240 + y;
        tmp+=z;
        i++;
    }
    return tmp;
}

struct __resurrect_cin
{
    __resurrect_cin(){}
    static void 
    resurrect()
    {
        if(!std::cin)
        {
            std::cin.clear();
            std::cin.ignore(4096,'\n');
            throw(std::runtime_error(_r("Ошибка ввода. Повторите.\n")));
        }
    }
};

const __resurrect_cin resurrect_cin;

std::istream& 
operator>>(std::istream& in, const __resurrect_cin&)
{
    __resurrect_cin::resurrect();
    return in;
}

#endif

пример использования кривого костыля:
#include "1251_to_866.h"
#include <iostream>
#include <сmath>

using namespace std;

double 
zad51(double x,double y)
{
    if(y<=0.0)
        if((-1<=x)&&(1<=x))
            return exp(x+3)/(1+fabs(x));
    return x*x*y;
}

int 
main()
{
    double d1,d2;
    while(true)
    {
        cout<<_r("Задание 51.\n Введите значения переменных:\n");        
        try
        {
            cin>>d1>>d2>>resurrect_cin;
            cout<<_r("\nРезультат: ")<<zad51(d1,d2)<<"\n";
            system("Pause");
            return 0;
        }
        catch(runtime_error& a)
        {
            cout<<a.what();
        }        
    }
    return 0;
}
Re: Проверка данных при вводе. Нужна помощь!
От: LaptevVV Россия  
Дата: 24.12.06 14:46
Оценка:
Здравствуйте, maksqwe, Вы писали:

M>Я только начинающий программер... Потому не надо писать типа "... вот ламо... rtfm... " Я лишь прошу консультации.


M>Прога — консолька, пишу на Visual Studio 2005.

M>Собственно столкнулся с такой неприятной неприятностью. Прога у меня считывает вводимую инфу, предположим какое-либо число (int), как сделать проверку введенных символов, на факт присутствия в них символов не относящихся к цифрам? Просто если ввести буквы, а прога считывает в интовскю переменную, то она вылетает...

M>Прошу помочь!! Буду премного благодарен!

Для этого нужно написать собственную функцию посимвольного ввода с проверкой на легальность символов.
Хочешь быть счастливым — будь им!
Без булдырабыз!!!
Re: Проверка данных при вводе. Нужна помощь!
От: igna Россия  
Дата: 24.12.06 15:55
Оценка: 1 (1)
Здравствуйте, maksqwe, Вы писали:

M>... Прога у меня считывает вводимую инфу, предположим какое-либо число (int), как сделать проверку введенных символов, на факт присутствия в них символов не относящихся к цифрам? Просто если ввести буквы, а прога считывает в интовскю переменную, то она вылетает...


В некоторых случаях можно так:

#include <iostream>

using namespace std;

int main()
{
    int i;
    if (cin >> i)
        cout << "i = " << i << '\n';
    else
        cout << "error\n";
}
Re: Проверка данных при вводе. Нужна помощь!
От: Roman Odaisky Украина  
Дата: 24.12.06 19:54
Оценка:
Здравствуйте, maksqwe, Вы писали:

M>Прога — консолька, пишу на Visual Studio 2005.

M>Собственно столкнулся с такой неприятной неприятностью. Прога у меня считывает вводимую инфу, предположим какое-либо число (int), как сделать проверку введенных символов, на факт присутствия в них символов не относящихся к цифрам? Просто если ввести буквы, а прога считывает в интовскю переменную, то она вылетает...

Лучше всего проверять не посимвольно, а положиться на стандартные языковые средства работы со строками и распознавания в них чисел. Наверное, самый «правильный» способ такой:
std::string s;
. . .
// как-то получили строку в s. Например, std::cin >> s.
. . .
int value = boost::lexical_cast<int>(s); // can throw boost::bad_lexical_cast
. . .
// если вводим из потока (stream >> s), то можно и сразу проверять - см. совет г-на Лаптева выше.

Здесь в случае «лишних» символов в строке будут вылетать исключения. Следовательно, программа унаследует все преимущества и недостатки последних. Если, как я понимаю, строки должны являться числами (а иначе вылетаем с ошибками), то исключения — то, что надо.

Boost: www.boost.org

2 All: я понимаю, что Boost — то еще развлечение для начинающего, но надо начинать с лучшего, верно?

P. S. Это всё из предположения, что язык — C++.
До последнего не верил в пирамиду Лебедева.
Re[2]: Проверка данных при вводе. Нужна помощь!
От: Roman Odaisky Украина  
Дата: 24.12.06 19:56
Оценка:
RO>// если вводим из потока (stream >> s), то можно и сразу проверять — см. совет г-на Лаптева выше.

Прошу прощения, имел в виду http://rsdn.ru/Forum/Message.aspx?mid=2278529&amp;only=1
Автор: igna
Дата: 24.12.06
До последнего не верил в пирамиду Лебедева.
Re[2]: Проверка данных при вводе. Нужна помощь!
От: maksqwe  
Дата: 24.12.06 22:46
Оценка:
I>В некоторых случаях можно так:

I>
I>#include <iostream>

I>using namespace std;

I>int main()
I>{
I>    int i;
I>    if (cin >> i)
I>        cout << "i = " << i << '\n';
I>    else
I>        cout << "error\n";
I>}
I>


После чего бесконечно повторяется следующая функция... Как сделать что бы этого не происходило???
Re[3]: Проверка данных при вводе. Нужна помощь!
От: LaptevVV Россия  
Дата: 25.12.06 07:03
Оценка:
Здравствуйте, maksqwe, Вы писали:

I>>В некоторых случаях можно так:


I>>
I>>#include <iostream>
I>>using namespace std;
I>>int main()
I>>{
I>>    int i;
I>>    if (cin >> i)
I>>        cout << "i = " << i << '\n';
I>>    else
I>>        cout << "error\n";
I>>}
I>>


M>После чего бесконечно повторяется следующая функция... Как сделать что бы этого не происходило???

вот так — читай сюда:

Состояния потока
Каждый поток (и стандартные в том числе) в каждый момент времени находится в некотором состоянии. Эти состояния имеют названия good, bad, fail и eof (end-of-file). Сами состояния определены в классе ios_base как целые статические константы. Эти константы называют флагами состояния потока [1-27.4.2.1.3].

typedef int iostate;
goodbit     = 0x00   
badbit      = 0x01 
eofbit      = 0x02
failbit     = 0x04

Значения флагов зависят от реализации (показанные значения флагам присвоены в системе C++ Builder 6), однако имена определены в стандарте, поэтому именно так флаги состояния называются во всех без исключения системах. В программе имена флагов состояния нужно записывать с префиксом, например
std::ios_base::eofbit

С таким же успехом можно использовать класс, производный от ios_base. В частности, в текстах программ стандартной библиотеки часто встречается более короткий префикс
std::ios::eofbit

В базовом классе ios_base определено поле
iostate _M_iostate;            // библиотека STLport

ПРИМЕЧАНИЕ
Такое имя поле имеет в системе Borland C++ Builder 6. В другой реализации имя поля может быть другим.
В этом поле сохраняются флаги состояния во время работы программы. Это поле мы можем прочитать методом
iostate rdstate();

Установить любой флаг можно методом
void setstate(iostate flag);

Для установки нескольких флагов, естественно, нужно воспользоваться битовыми операциями, например
setstate(std::ios::eofbit | std::ios::failbit);

Система ввода/вывода С++ предоставляет несколько методов, с помощью которых мы всегда можем узнать состояние потока:
bool good() const;    // следующая операция может выполняться
bool eof() const;    // виден конец ввода
bool fail() const;    // следующая операция не выполняется
bool bad() const;    // поток испорчен

Если после выполнения некоторой операции поток находится в состоянии good, то это хорошая ситуация: во время предыдущей операции не произошло никаких непредвиденных событий и может быть выполнена следующая операции ввода/вывода. В остальных случаях следующая операция выполнена не будет.
Состояние fail и состояние bad являются состояниями ошибки потока. Если поток находится в одном из этих состояний, то операции обмена не выполняются. Состояние bad включает в себя состояние fail: когда поток находится в состоянии bad, он находится и в состоянии fail; обратное неверно.
Если поток находится в состоянии fail, то операция ввода/вывода завершилась неудачно, однако поток не испорчен и никакие символы не потеряны. Обычно это состояние устанавливается при ошибках форматирования в процессе чтения — например, программа пытается прочитать целое число, а первый же символ является недопустимым (буква или знак операции). Поток из состояния fail мы можем вернуть в нормальное состояние с помощью метода clear(), например
if (stream.fail()) stream.clear();

После этого можно выполнять операции обмена — опять до очередной ошибки. Метод clear() перегружен и позволяет не только сбросить, но и затем установить нужные флаги состояния. Прототип этого метода
void clear(iostate flag);

Состояние bad – это более тяжелое состояние. Флаг badbit указывает на неработоспособность потока данных или потерю данных. Когда поток в состоянии bad, ни в чем нельзя быть уверенным, поэтому в таких случаях лучше завершать выполнение программы.
Состояние eof может возникнуть только при операции чтения. Для стандартного потока ввода это состояние возникает при вводе комбинации клавиш <Ctrl> + <z>. Для файлов это состояние устанавливается при первой попытке чтения после последнего байта файла. При этом поток тоже переводится в состояние fail.
Состояние потока и логические условия
Состояние потока можно проверять непосредственно в условиях if и while. Например, ввод массива целых значений можно выполнять в цикле таким образом
int aa[10] = {0};
i = 0;
while(cin >> aa[i++]);

Цикл завершается по одной из двух причин:
1. Создалась ситуация «end-of-file»; для входного потока это эквивалентно нажатию комбинации клавиш <Ctrl> + <z>;
2. В потоке встретился символ, не соответствующий типу вводимой переменной; в этом случае поток переводится в состояние fail;
Например, мы набрали на клавиатуре следующую последовательность символов
1<пробел>2<пробел>3<пробел>4<пробел>5e6<enter>
Тогда элементы массива а будут иметь следующие значения:
aa[0] = 1;
aa[1] = 2;
aa[2] = 3;
aa[3] = 4;
aa[4] = 5;
aa[5] = 0;
aa[6] = 0;
aa[7] = 0;
aa[8] = 0;
aa[9] = 0;

Символ ‘e’после цифры 5 не является допустимым для целого числа, поэтому ограничивает ввод.
В условиях можно задавать и явный вызов методов. Например, мы хотим посчитать количество символов ‘\n’, которым оканчиваются вводимые строки. Это можно сделать, используя метод get():
int nl = 0;
while(cin.get(ch))             // читать все символы, в том числе пробельные
{ if(ch=='\n') nl++; 
  cout.put(ch); 
}

Так как «неправильных» символов не существует, цикл можно завершить только вводом комбинации <Ctrl> + <z>. Можно использовать и другую форму метода get():
int nl = 0;
while(ch = cin.get() && ch != EOF)             // читать все символы
{ if(ch=='\n') nl++; 
  cout.put(ch); 
}

Константа EOF помещается в ch при нажатии комбинации <Ctrl> + <z>.
В библиотеке реализована логическая операция operator!, с помощью которой можно проверить аварийное состояние потока, например
if (!(cin >> x))
{    // ввод завершился неудачей
    . . .
}

Обратите внимание, выражение ввода заключено в скобки. Это делать необходимо, так как в соответствии с приоритетом операций выражение без скобок интерпретируется как
(!cin) >> x

Естественно, ввод и проверку состояния потока можно разделить, например
cin >> x;
if(!cin)
{    // ввод завершился неудачей
    . . .
}

Во всех приведенных примерах, на месте cin может стоять любой поток.
При разделении ввода и проверки можно использовать явный вызов соответствующего метода. Например, таким способом мы можем проверять наступление ситуации «end-of-file» для потока, связанного с файлом.
while (!from.eof())         // явная проверка на конец файла
{ getline(from, s);         // чтение строки из потока from
  cout << s << endl; 
}

Однако тут возможны сюрпризы. По умолчанию ограничителем строки является символ ‘\n’, который записан в конце каждой строки. В данном случае при считывании последней строки ситуации конца файла не возникает — она возникает только при попытке ввода за концом файла. Поэтому цикл выполнится лишний раз и на экране дважды появится последняя строка.
Правильная последовательность операторов может быть такой
getline(from, s);             // чтение строки из потока from
while (!from.eof())         // явная проверка на конец файла
{ cout << s << endl; 
  getline(from, s);         // чтение строки из потока from
}

Другой вариант — бесконечный цикл с проверкой «end-of-file» внутри цикла
while (true)         
{   getline(from, s);         // чтение строки из потока from
    if(from.eof()) break;     // явная проверка на конец файла
    cout << s << endl; 
}

Хочешь быть счастливым — будь им!
Без булдырабыз!!!
Re[2]: Проверка данных при вводе. Нужна помощь!
От: LaptevVV Россия  
Дата: 25.12.06 07:11
Оценка:
Здравствуйте, Roman Odaisky, Вы писали:

RO>Лучше всего проверять не посимвольно, а положиться на стандартные языковые средства работы со строками и распознавания в них чисел. Наверное, самый «правильный» способ такой:

RO>
RO>std::string s;
RO>. . .
RO>// как-то получили строку в s. Например, std::cin >> s.
RO>. . .
RO>int value = boost::lexical_cast<int>(s); // can throw boost::bad_lexical_cast
RO>

RO>Здесь в случае «лишних» символов в строке будут вылетать исключения. Следовательно, программа унаследует все преимущества и недостатки последних. Если, как я понимаю, строки должны являться числами (а иначе вылетаем с ошибками), то исключения — то, что надо.
RO>Boost: www.boost.org
RO>2 All: я понимаю, что Boost — то еще развлечение для начинающего, но надо начинать с лучшего, верно?
Тогда уж лучше использовать стандартные строковые потоки:

Строковые потоки
В отличие от строковых потоков, реализованных в библиотеке <cstdio>, которые могут быть только входными или только выходными, строковые потоки объектно-ориентированной библиотеки [1-27.7] могут быть еще и двунаправленными. Кроме того, нам нет необходимости резервировать память для строки при работе с ними. И наконец, со строковыми потоками можно использовать все средства форматирования или собственные написанные манипуляторы.
Чтобы использовать строковые потоки, мы должны прописать в программе оператор
#include <sstream>
После этого в программе можно объявлять объекты-строковые потоки трех видов:
 входной istringstream [1-27.7.2];
 выходной ostringstream [1-27.7.3];
 двунаправленный stringstream [1-27.7.4];
Чтобы продемонстрировать возможности строковых потоков, перепишем примеры 10.4 и 10.6. Первый пример (листинг 10.32) — аналог примера 10.4 — демонстрирует возможности выходных строковых потоков.

Листинг 10.32. Выходной строковый поток
#include <iostream>
#include <sstream>
using namespace std;
int main()
{  char s[] = "computer", c = 'L';
   int   i = 35;
   float fp = 1.7320534f;
   ostringstream os;
   /* Форматирование и вывод данных */
   os << "String: "    << s << "; " 
      << "Character: " << c << "; "
      << "Integer: "   << i << "; "
      << "Real: "      << fp<< "; " 
      << endl;
   cout << "Output:" << '\n' << os.str() << endl;
   cout << "character count = " << os.str().length() << endl;
   int ch = getchar();                    // чтобы видеть результат
   return EXIT_SUCCESS;
}

Эта программа делает абсолютно то же самое, что и программа в примере 10.4. Но посмотрите, насколько она стала проще и надежней! Во-первых, программисту нет необходимости резервировать память для формируемой строки — строковый поток все делает во внутреннем буфере. Во-вторых, нет необходимости следить за позицией внутри буфера (как это делается в примере 10.4 — переменная j) — все делает сам поток (вернее, сам буфер, поскольку это объект типа string). Внутренний буфер всегда можно получить, вызвав для потока метод str().
Выходной поток может быть создан в режиме дозаписи (как и файл). Тогда он должен быть проинициализирован существующей строкой, например,
string s = “Шестнадцатеричное число = “;
ostringstream os(s, ios::out|ios::app);
os << hex << 123;
cout << os.str() << endl;

В результате работы этого фрагмента на экран будет выдано
Шестнадцатеричное число = 7B
Сама строка s остается без изменений.
Вместо двух потоков используем один двунаправленный поток (листинг 10.33).
Листинг 10.33. Аналог примера 10.6
{ string names[4] = {"Peter", "Mike", "Shea", "Jerry"};
  string  temp[4];
  string  name;
  int   age;
  long  salary;
  stringstream strm;            // двунаправленный поток
srand( (unsigned)time(NULL));    // инициализация датчика случайных чисел
/* создание данных name, age и salary */
for (int loop=0; loop < 4; ++loop)
 strm<<names[loop]<<' '<<rand()%10+20<<' '<<rand()%5000+27500L<< endl;
/* вывод шапки */
   cout << setw(4) << "#" << " | "
        << left  << setw(20) << "Name" << " | "
        << right << setw(5)  << "Age" << " | "
        << right << setw(15) << "Salary" << endl;
   cout << "   --------------------------------------------------\n";
   strm.seekg(0);        // перевод головки чтения/записи в начало потока 
   for (loop=0; loop < 4; ++loop)
   {  strm >> name >> age >> salary;    // ввод name, age и salary 
      cout << right << setw(4) << (loop + 1) << " | " 
           << left  << setw(20) << name << " | "
           << right << setw(5) << age << " | "
                    << setw(15) << salary << endl;
   }
}

Эта программа сначала пишет в поток strm. Потом из того же потока выполняется считывание и вывод на экран.
Перед считыванием устанавливается позиция чтения методом seekg() [1-27.6.1.3]. В отличие от библиотеки <cstdio> в объектно-ориентированной библиотеке позиционирование можно выполнять и для строковых, и для файловых потоков. Это нельзя делать только для стандартных потоков. Набор методов, связанных с позиционированием, значительно расширен и намного удобнее, чем функции библиотеки <cstdio>. Мы рассмотрим методы позиционирования далее.
Входной строковый поток можно использовать, чтоб реализовать считывание чисел из поля фиксированной длины. Не будем писать манипулятор с аргументами, а напишем простой шаблон функции (листинг 10.34).
Листинг 10.34. Ввод из поля фиксированной длины
template <class T>
void fixedread(istream &in, T &t)
{    if (in.width() > 0)                    // ширина поля установлена
    { string field;
      in >> field;                        // ввод по ширине или до пробела
      istringstream strm(field);            // входной строковый поток
      if (!(strm >> t)                    // ввод их строкового потока
        in.setstate(ios::failbit);        // ошибки ввода
    }
    else                                 // ширина поля по умолчанию
      in >> t;                            // ввод в переменную
}

Если ширина поля ввода установлена (то есть, не равна нулю), то объявляется строка для ввода и ввод выполняется в нее. Затем этой строкой инициализируется строковый поток. Обратите внимание на то, что ввод из строкового потока проверяется на корректность и в случае ошибки устанавливается флаг failbit для входного потока-параметра.
Использовать эту функцию можно так:
int k = 0; cin.width(3);
fixedread(cin, k);

Если мы наберем на клавиатуре 1234, то в переменную k попадет число 123. При наборе строки «56 4567» в переменную k попадает число 56.

И кстати об исключениях:

Потоки и исключения
По умолчанию при ошибках ввода/вывода исключения не генерируются. Однако можно указать, при установке каких флагов состояния должно генерироваться исключение. В состав класса basic_ios [1-27.4.4] входит метод exceptions(), который и позволяет это сделать. Прототип метода следующий

void exceptions(iostate flags);

Задать генерацию исключения при установке флага ios::badbit можно так:
stream.exceptions(ios::badbit);

Флаги, как обычно, можно комбинировать, например
stream.exceptions(ios::badbit|ios::failbit);

Исключения будут генерироваться не только во время операций ввода/вывода, но и при установке флагов методами clear() и setstate().
Если аргумент равен 0 или ios::goodbit, исключения генерироваться не будут.
Вызов без аргумента возвращает текущие флаги, при установке которых генерируется исключение, например
ios_base::iostate flags = stream.exceptions();

Если возвращается goodbit, исключения не генерируются.
Генерируемые исключения являются объектами класса ios_base::failure [1 27.4.2.1.1], который является наследником класса exception (см. главу 4). Класс ios_base::failure имеет следующую структуру:
class ios_base::failure : public exception 
{ public:
    explicit failure(const string& msg);
    virtual ˜failure();
    virtual const char* what() const throw();
};

Как видите, сложного ничего нет.

Хочешь быть счастливым — будь им!
Без булдырабыз!!!
Re: Проверка данных при вводе. Нужна помощь!
От: Megabyte Россия  
Дата: 25.12.06 15:26
Оценка: +1
On Sun, 24 Dec 2006 16:42:35 +0500, maksqwe <62114@users.rsdn.ru> wrote:

> Прога — консолька, пишу на Visual Studio 2005.

> Собственно столкнулся с такой неприятной неприятностью. Прога у меня
> считывает вводимую инфу, предположим какое-либо число (int), как
> сделать проверку введенных символов, на факт присутствия в них символов
> не относящихся к цифрам? Просто если ввести буквы, а прога считывает в
> интовскю переменную, то она вылетает...

string s;
cin >> s;
int i;
int err = sscanf(s, "%d", &i);

--
Using Opera's revolutionary e-mail client: http://www.opera.com/mail/
Posted via RSDN NNTP Server 2.0
Re: Проверка данных при вводе. Нужна помощь!
От: greenya Украина  
Дата: 25.12.06 15:40
Оценка:
Здравствуйте, maksqwe, Вы писали:

M>Я только начинающий программер... Потому не надо писать типа "... вот ламо... rtfm... " Я лишь прошу консультации.


M>Прога — консолька, пишу на Visual Studio 2005.

M>Собственно столкнулся с такой неприятной неприятностью. Прога у меня считывает вводимую инфу, предположим какое-либо число (int), как сделать проверку введенных символов, на факт присутствия в них символов не относящихся к цифрам? Просто если ввести буквы, а прога считывает в интовскю переменную, то она вылетает...

M>Прошу помочь!! Буду премного благодарен!


для этого нужно просто считывать не в числовую переменную а в строковую. после -- конвертировать ее в числовое значение -- ошибки не будет. если человек введёт "вася", то оно просто конвертирует его безошибочно в значение 0.

более сложный вариант: -- читать посимвольно и пропускать только символы от "0" до "9".
Re[2]: Проверка данных при вводе. Нужна помощь!
От: igna Россия  
Дата: 25.12.06 16:44
Оценка:
Здравствуйте, Megabyte, Вы писали:

M>sscanf(s, "%d", &i);


Или istringstream(s) >> i.
Re: Проверка данных при вводе. Нужна помощь!
От: Edge  
Дата: 26.12.06 09:26
Оценка:
Здравствуйте, maksqwe, Вы писали:

M>Прога — консолька, пишу на Visual Studio 2005.

M>Собственно столкнулся с такой неприятной неприятностью. Прога у меня считывает вводимую инфу, предположим какое-либо число (int), как сделать проверку введенных символов, на факт присутствия в них символов не относящихся к цифрам?

isalpha и ей подобные
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.