-2

Код:

#include <iostream>
#include <fstream>
#include <cstdlib>
using namespace std;

#define O_F_NAME "output.txt"

int main()
{
                                                /* Variables */
    char need, it;
    char f_name[80];
    ios::pos_type pos;
    int min_num = 0, max_num, i = 1;

                                                /* Introduction */

    cout << "Enter the file: ";
    cin.get(f_name, 80);

    ifstream in(f_name, ios::in);
    if (!in) {
        cout << "Error in opening file.\n\n";
        exit(1);
    }

    cout << "\nEnter symbol for find: ";
    cin >> need;

    ofstream out(O_F_NAME, ios::out); 
    if (!out) {
        cout << "Error in creating output file.\n\n";
        exit(2);
    }
                                                /* Make head of output file */

    out << "Result of searching for \"" << need << "\":\n\n";

                                                /* Find max number of symbol */

    in.seekg(0, ios::end);
    pos = in.tellg();
    max_num = (int)pos - 1; // pos - number of end  
    in.seekg(0, ios::beg);

                                                /* Find 'it', write out to the file */

    while (!in.eof()) {
        it = (char)in.peek(); // ios::cur++
        if (it != need) {
            it = (char)in.get();
            continue;
        }
        out << i++ << ") '";
        pos = in.tellg(); // cur number
        if ((int)pos > min_num) {
            in.seekg(-1, ios::cur);
            out << (char)in.get();
        }
        out << (char)in.get();
        if (in.eof()) continue;
        if ((int)pos < max_num) {
            out << (char)in.get();
        }
        out << "'\n";
    }

    cout << "\nSuccess. Results of searching placed in \"" << O_F_NAME << "\"\n\n";

    out.close();
    in.close();
    system("pause");
    return 0;
}

Как справедливо заметили, я не указал, что хочу видеть на выходе:) Должно быть так: [x][need][y] - где x - символ слева от искомого, y - справа(если need первый или последний символ в файле, то "обрамление" опускается). Если need = -(ищим тире) вывод программы(применил на книгу: book.txt) примерно такой:

1) 'tor'
2) 'f-t'
3) 'f-t'
4) 'h-d'
5) 'h-d'
6) 'n-k'
7) '-te'
8) 'f-c'
9) 's.
'
...

(Откуда, например, взялась строка 1) 'tor' если искали -?)

Мне казалось алгоритм исключает обрабатывание всех символов кроме нужного здесь:

        it = (char)in.peek(); // ios::cur++
        if (it != need) {
            it = (char)in.get();
            continue;
        }

Программа должна искать символ, который задаёт пользователь, в файле, который он вводит. Она работает, но странно(выводит неожиданные "три символа"). На маленьких(созданных вручную) файлах всё нормально, но на книге даёт такой вывод(смотрите выше). Не могу понять, чем это может быть обусловлено?

  • 3
    Отладчик и внятное описание что же программа должна делать, вероятно, вам помогут. – Владимир Мартьянов Sep 08 '15 at 16:43
  • 1
    Вы не могли бы (1) оставить только минимальный для понимания вопроса код, (2) объяснить, что именно вы хотите, чтобы этот код делал? – VladD Sep 08 '15 at 16:49
  • @VlaD , программа должна искать символ, который задаёт пользователь, в файле, который вводит пользователь. Программа работает, но странно. На маленьких(созданных вручную) файлах всё нормально, но на книге даёт такой вот вывод(смотрите выше). Я не могу понять, чем это может быть обусловлено. Я думал, 70 строчек не так много, тем более, половина этого - алгоритм поиска, а вторая - простой(на сколько я понимаю) книжный код открытия потоков – Lurking Elk Sep 08 '15 at 16:54
  • @DimTeam задумайтесь о том, что описание ожидаемого поведения было бы неплохо внести в сам вопрос. – Владимир Мартьянов Sep 08 '15 at 16:56
  • @ВладимирМартьянов внёс, посмотреть можете? – Lurking Elk Sep 08 '15 at 17:05
  • @DimTeam начните сами с использования отладчика. – Владимир Мартьянов Sep 08 '15 at 17:08
  • Ну, книжный код наверное не нужен. Просто если при взгляде на простыню текста приходится ещё и думать, что из него относится к вопросу, а что нет, то не у всех хватает энтузиазма сесть и разобраться. Вот немного нытья по этому поводу. – VladD Sep 08 '15 at 17:13
  • Вот это видели: http://ru.stackoverflow.com/a/421084/10105? Цикл while (!eof()) неверен. – VladD Sep 08 '15 at 17:18
  • А ещё вы не контролируете ошибки, это неправильно, судя по всему. – VladD Sep 08 '15 at 17:27
  • @VladD спасибо за ответ, какие ошибки? – Lurking Elk Sep 08 '15 at 17:29
  • @VladD почитал вашу ссылку, я знаю, что eof() проверяет достижение конца файла после последней операции ввода/вывода, но я это учёл и проверяю посреди цикла while: if (in.eof()) continue; – Lurking Elk Sep 08 '15 at 17:40
  • @DimTeam: Прогнал ваш код на английской «Алисе в стране чудес», проблем не обнаружил. Выложите текст, на котором сбоит. – VladD Sep 08 '15 at 22:02
  • 1
    @DimTeam: Кстати, я бы всё же переписал ваш рабочий цикл так: http://pastebin.com/kRWF83Gx. Вы почему-то экономите на переменных, а экономить надо операции ввода-вывода, они на два порядка «тяжелее». – VladD Sep 08 '15 at 22:03
  • Во-первых, так и не смог понять, какой именно вывод Вы ожидаете получить от программы? Я прогнал ее на таком файле:

    asdkjnajn2-scbhjabschb-asdyyqwtd1- aschhjuasd-sadfhwqed-

    И получил: Result of searching for "-":

    1. '-sc'
    2. '-as'
    3. '-

    a' 4) 'd-s' 5) 'd-'

    Должно быть выведено обрамление искомого символа что ли?

    – boeing777 Sep 09 '15 at 06:34
  • @boeing777: Странно. У меня на том же тексте выдало 1) '2-s' 2) 'b-a' 3) '1- ' 4) 'd-s' 5) 'd-'. – VladD Sep 09 '15 at 08:34
  • @DimTeam: Вы должны проверять, выполнились ли файловые операции успешно. Для этого есть проверки типа if (!in) или if (!in.good()). – VladD Sep 09 '15 at 08:40
  • @boeing777 именно, должно быть "обрамление" искомого символа – Lurking Elk Sep 09 '15 at 16:11
  • @VladD добавил текст, на котором тестирую, посмотрите пожалуйста – Lurking Elk Sep 09 '15 at 16:14
  • @VladD внедрил ваш переписанный цикл в свой код - всё работает отлично и как я хотел(с обрамлением). Спасибо большое, дело было в seekg()? Она в некоторых случаях неправильно перемещала указатель на -1 символ? – Lurking Elk Sep 09 '15 at 16:54
  • @DimTeam: Если вы погуглите «problem with seekg», многие замечают, что если какая-то из операций не срабатывает (или если достигнут конец файла), с этого момента seekg не работает, пока не сбросить ошибку при помощи in.clear(). Может быть, в этом была ваша проблема. Оформлю код в ответ. – VladD Sep 09 '15 at 18:20
  • @VladD не спешите, он не совсем корректен) Я сейчас думаю, как его исправить. Попробуйте искать - в такой строке вашим циклом: -qwer- И спасибо за ответ) – Lurking Elk Sep 09 '15 at 18:24

3 Answers3

2

Позволил себе переписать ваш код. На моём тесте сработало. Проверьте на вашей книге.

#include <iostream>
#include <fstream>
#include <cstdlib>
using namespace std;

#define O_F_NAME "output.txt"

int output(const char* txt, ofstream& out)
{
  static int count = 0;
  out << ++count <<  ") '" << txt << "'" << endl;
  return count;
}

int main()
{
                                                /* Variables */
  int start_pos = 0, end_pos = 0;
  char* str = 0;

  int need = 0, cur = 0;
  int count = 0;

  char f_name[80];
                                                /* Introduction */

    cout << "Enter the file: ";
    cin.get(f_name, 80);

    ifstream in(f_name, ios::in);
    if (!in) {
        cout << "Error in opening file.\n\n";
        exit(1);
    }

    char tmp;
    cout << "\nEnter symbol for find: ";
    cin >> tmp;
    need = (int)tmp;
//    cout << ">>> need " << (char)need << " is " << tmp << endl;

    ofstream out(O_F_NAME, ios::out); 
    if (!out) {
        cout << "Error in creating output file.\n\n";
        exit(2);
    }
                                                /* Make head of output file */

    out << "Result of searching for \"" << need << "\":\n\n";

    if (!in.eof()) {
      cur = in.get();
//      cout << ">>>> cur " << (char)cur << endl;
      while (!in.eof()) {
        if (cur == need || in.peek() == need) {
          start_pos = in.tellg();
          start_pos -= 1;
//          cout << ">>> start_pos " << start_pos;
          cur = in.get();
          while (cur == need) {
            cur = in.get();
          }
          end_pos = in.tellg();
//          cout << " end_pos " << end_pos << endl;

          int length = end_pos - start_pos;
//          cout << "length " << length << endl;
          str = new char[length];
          in.seekg(start_pos);
          in.read(str, length);
//          cout << "str '" << str << "'" << endl;
          count = output(str, out);

          in.seekg(end_pos);

          delete[] str;
          str = 0;
        }
        else {
          cur = in.get();
        }
      }
    }

                                                /* Find max number of symbol */

    cout << "\nSuccess. Results of searching placed in \"" << O_F_NAME << "\" count = " << count << "\n\n";

    out.close();
    in.close();
//    system("pause");
    return 0;
}
aleks.andr
  • 2,489
  • На моём не сработало: `Result of searching for "45":
    1. 'torээээ««««««««юоюоюоюою'
    2. 'f-tээээ««««««««юоюоюоюою' и так далее, добавил в пост книгу, на которой пробую
    – Lurking Elk Sep 09 '15 at 16:05
  • Даже там, где у моей версии всё хорошо выводится, ваша выдаёт кучу <<<<<<<<<<<юююююююю и так далее – Lurking Elk Sep 09 '15 at 16:40
  • @Dim Team , давайте разбираться по порядку. Я проверил код на вашей книге, вот результат ссылка. Книга предварительно переконвертирована в кодировку UTF-8. Код собирался на Debian Jessie, компилятор gcc 4.9.2. Проблема вероятнее всего кроется в несоответствии кодировок и, как вам выше уже написали, в окончании строк (\r - unix против \r\n - windows). – aleks.andr Sep 10 '15 at 06:08
  • Кстати, вот ваша книга в UTF-8, на которой я тестировал. – aleks.andr Sep 10 '15 at 06:32
2

Я не могу воспроизвести проблему у себя (Visual Studio 2013), но она может заключаться, например, в том, что seekg не работает, если поток находится в ошибочном состоянии или достиг конца файла (!). Для этого случая вы должны бы перед seekg вызвать clear.

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

int eofvalue = istream::traits_type::eof();
int curr = eofvalue, prev = eofvalue;
bool needExit = false;
while (!needExit)
{
    int prevprev = prev;
    prev = curr;
    curr = in.get();
    needExit = (curr == eofvalue);
    // не выходим сразу, прогоним ещё одну итерацию
    // для отлова искомого символа в финальной позиции

    if (prev != need)
        continue;

    cout << i++ << ") '";
    if (prevprev != eofvalue)
        cout << (char)prevprev;
    cout << (char)prev;
    if (curr != eofvalue)
        cout << (char)curr;
    cout << "'\n";
}

Ещё по вашему коду:

  1. Цикл обработки while (!in.eof()) в общем случае неверен. Даже если сейчас код правильно детектирует конец файла из-за особенностей обработки кода в цикле, небольшое изменение может сделать его неправильным.
  2. Код (char)in.peek() и (char)in.get() также не очень хорош. Дело в том, что функция get() не зря возвращает не char, а больший тип: если достигнут конец файла, возвращается значение istream::traits_type::eof(), которое не попадает в тип char. При приведении типов вы теряете информацию о конце файла. Это особенно опасно в сочетании с предыдущей проблемой.

Обновление: Окей, новый код работает, а в чём же проблема исходного кода?

Если коротко: вы смешали произвольный доступ и построчную обработку, это не очень хорошо. Если вы хотите управлять позицией чтения через tellg/seekg, открывайте файл в бинарном режиме, это избавит вас от «сюриризов».

Подробное объяснение: Здесь замешано много вещей. Для начала, ваш файл имеет юниксовские концы строк (\n). Вы, судя по всему, работаете на Windows, где нативные концы строк имеют вид \r\n. Поскольку вы открываете файл в текстовом режиме (без ios::binary), то при построчном чтении происходит автоматическая трансляция строк в нативные строки: \n заменяется на \r\n. Это значит, что после того, как in.get() возвращает вам \n, рантайм начинает считать, что текущая позиция в файле увеличилась не на 1, а на 2, из-за неявно пропущенного \r.

Затем, что происходит при выполнении seekg? Позиция в файле устанавливается в новое значение. При этом для того, чтобы корректно пересчитать отличие номера байта в файле от его позиции в модифицированном потоке (в который неявно вставлены \r перед каждым \n), рантайму пришлось бы прочитать весь файл от той точки, где вы находитесь, до той точки, куда вы перемещаетесь. Это могло бы быть очень долго, поскольку вы имеете право сделать очень большой прыжок. Но seekg должно выполняться быстро, поэтому рантайм не заморачивается и сбрасывает подсчитанное отличие в 0.

Это приводит к тому, что seekg «промахивается» на столько байт, сколько раз встретился символ перевода строки в файле. Дальше цикл повторяется, с тем же результатом.

Если бы вы открыли файл в бинарном режиме (а раз вы его обрабатываете посимвольно, так надо и делать), проблема бы не возникла, так как в этом режиме трансляция строк не производится.

VladD
  • 206,799
  • надо проверять failbit или включить эксепшены – Sergey S Sep 09 '15 at 18:36
  • Спасибо за ответ, на 90% он верен. НО, если искать - в файле с таким содержимым: -qwer-, ваш код с треском проваливается, так как: -1 - символ 'я'(cp1251) и заканчивается файл искомым символом(следующий - eof) - программа его не выводит. В результате её вывод будет таким: 1) 'я-q' P.S. MVS 2013/Win7(русская) – Lurking Elk Sep 09 '15 at 18:49
  • Такой цикл меня устраивает, он верен? http://pastebin.com/NXu4RtFf – Lurking Elk Sep 09 '15 at 19:00
  • 1
    @DimTeam: подправил рабочий код, теперь работает. Сейчас гляну, что у вас там. // Посмотрел, так по идее тоже можно. – VladD Sep 09 '15 at 19:15
  • 1
    @DimTeam: Написал в ответе, в чём была причина неправильного поведения. – VladD Sep 09 '15 at 19:37
  • @VladD большое спасибо за столь развёрнутый ответ. Я немного не понял здесь: if (curr != -1) cout << (char)curr;, почему именно -1? – Lurking Elk Sep 09 '15 at 19:41
  • @DimTeam: Это ошибка :-) Должно быть, конечно, istream::traits_type::eof(). Сейчас исправлю. – VladD Sep 09 '15 at 19:42
  • @VladD :-) не могли бы вы оставить какой-нибудь адрес(e-mail etc.)? Хотел бы развёрнуто вас отблагодарить, кое-что спросить, но это будет уже флуд – Lurking Elk Sep 09 '15 at 19:45
  • @DimTeam: Лучше всего пишите в C/C++-чат (http://chat.stackexchange.com/rooms/26298/c-c), я там сижу. – VladD Sep 09 '15 at 19:46
  • Кстати, хотелось бы отметить, что переменная need должна быть объявлена именно как char - иначе cin >> need записывает символ не только в младший(вроде бы) из 4 байт – Lurking Elk Sep 10 '15 at 14:57
  • 1
    @DimTeam: Угу, именно так. Если need имело бы тип int, а пользователь ввёл бы 1, то в результате присвоилось бы значение 1, а не 0х31 == '1'. А а вообще бы не прочиталось (и увело бы cin в ошибочное состояние до явного сброса ошибки). – VladD Sep 10 '15 at 15:04
  • int prevprev = prev почему вы объявили prevprev именно в теле цикла? – Lurking Elk Sep 10 '15 at 15:07
  • 1
    @DimTeam: Потому что его можно унести внутрь цикла, в отличие от остальных. Правило: старайтесь объявлять переменные в как можно более внутреннем блоке. И как можно ближе к месту первого использования. – VladD Sep 10 '15 at 15:09
  • Ясно, это вопрос удобства и стиля? И означает ли это, что на каждой итерации происходит "переобъявление" переменной prevprev? – Lurking Elk Sep 10 '15 at 15:11
  • 1
    @DimTeam: Угу, вопрос стиля. Это можно видеть как «переобъявление», но оптимизатор, конечно, выделит память заранее и один раз. – VladD Sep 10 '15 at 15:33
  • То бишь, по-сути, оптимизатор вынесет это объявление из цикла, но мне "визуально" удобнее так записывать? – Lurking Elk Sep 10 '15 at 15:39
  • И ещё, никак не могу понять, почему сравнение int и char: if (prev != need) работает при разном размере переменных? != - побитовый оператор, так с чем он сравнивает "остаток" переменной prev(int)? С нулями? – Lurking Elk Sep 10 '15 at 16:46
  • 1
    @DimTeam: Смотрите. Когда вы пишете код, вы пишете его как понятнее. Оптимизатор в любом случае гораздо лучше соптимизирует, чем и я и вы вручную, уж поверьте мне. Так что на оптимизацию, которая принесёт один несчастный такт процессорного времени, не заморачивайтесь. – VladD Sep 10 '15 at 18:49
  • 1
    Сравнение int с char работает так: определяется «наибольший общий тип» (int), оба сравниваемых элемента приводятся к этому типу (то есть, char расширяется до int'а), и сравниваются два int'а. – VladD Sep 10 '15 at 18:51
2

Всем спасибо за помощь, отдельное @VladD. Теперь программа работает как надо. Возможно, кому-то пригодится её законченная версия:

#include <iostream>
#include <cstdlib>
#include <fstream>
using namespace std;

#define O_F_NAME "output.txt"

int main()
{
    char f_name[80], need;

    cout << "Enter the file: ";
    cin.get(f_name, 80);

    ifstream in(f_name, ios::in);
    if (!in) {
        cout << "Error in opening file.\n\n";
        system("pause");
        exit(1);
    }

    cout << "\nEnter symbol for find: ";
    cin >> need;

    ofstream out(O_F_NAME, ios::out); 
    if (!out) {
        cout << "Error in creating output file.\n\n";
        system("pause");
        exit(2);
    }

    out << "Result of searching for \"" << need << "\":\n\n";

// main cycle

    int i = 0, eofv = iostream::traits_type::eof();
    int prev = eofv, cur = eofv;
    bool needExit = false;

    while (!needExit) {
        int prevprev = prev;
        prev = cur;
        cur = in.get();

        needExit = (cur == eofv);

        if (prev != need) continue;

        out << ++i << ") '";
        if (prevprev != eofv) out << (char)prevprev;
        out << (char)prev;
        if (cur != eofv) out << (char)cur;
        out << "'\n";
    }

    cout << "\nSuccess. Found " << i << " matches.\n\n"
        << "Results of searching \"" << need << "\" in \"" << f_name
        << "\" placed in \"" << O_F_NAME << "\".\n\n";

    out.close();
    in.close();
    system("pause");
    return 0;
}