Обучающий пример по работе с файлами в классическом C++. В качестве среды для разработки использовал Visual Studio 2015.
Содержание
Первый случай
У на есть файл input.txt с таким содержимым:
1 2 3 |
Aslapova Nastya 29 5 Nikolaev Dima 12 9 Tokmin Lev 96 75 |
То есть у нас тут хранится таблица с двумя столбцами, где хранятся строковые значения, и два столбца с целыми числами. Элементы разделены знаками табуляции. Количество строчек не известно.
Необходимо считать данные из файла и вывести на экран. При этом текстовые данные должны хранится в переменных типа string, а числа в переменных типа int.
Ниже приведен код программы, которая решает данную задачу. Обратите внимание на то, что тут используется подход по работе с потоками. Потоки может быть связаны не только с файлами или консолью, но также и со строчками.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 |
//Содержимое файла input.txt /* Aslapova Nastya 29 5 Nikolaev Dima 12 9 Tokmin Lev 96 75 */ //#include "stdafx.h"//Visual Studio может попросить #include <iostream> #include <fstream> #include <string> #include <sstream> using namespace std; int main() { setlocale(LC_ALL, "RUSSIAN");//Чтобы русский текст поддерживался //Создаем файловый поток и связываем его с файлом ifstream file("input.txt"); if (file.is_open())//Если открытие файла прошло успешно { cout << "Файл открыт." << endl; string line;//Строчка текста //Будем считывать информацию построчно до тех пор, //пока не закончится файл while (getline(file, line)) { //cout << line << endl;//Можно посмотреть, что в строчке считалось //Теперь в line хранится содержимое строчки из файла. //Будем её разбирать на составные части. //В нашем файле идут две строчки, а потом два числа string name, surname; int age, money; //Создадим поток для считывания данных из строчки istringstream iss(line); //Теперь через стандартный оператор >> считаем данные //Программа сама поймет что в качестве разделителя надо //использовать знак табуляции \t //За раз всё считаем iss >> surname >> name >> age >> money; //Выведем наши данные cout << "Данные из строчки:" << endl; cout << "\tФамилия: " << surname << endl; cout << "\tИмя: " << name << endl; cout << "\tВозраст: " << age << endl; cout << "\tДеньги: " << money << endl; } } else { cout << "Не удалось открыть файл." << endl; } system("pause"); return 0; } |
При старте программы получим такую картину:
Для простого случая такая программа подойдет.
Второй случай
Рассмотрим более сложный случай. Допустим есть еще один столбец с текстовой информацией, но с наличием пробелов.
1 2 3 |
Aslapova Nastya she did not come to class 29 5 Nikolaev Dima he bought a house 12 9 Tokmin Lev he does not know programming 96 75 |
Если мы напишем подобную программу, где только добавим новую string переменную info, то программа не будет работать, как нам нужно.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 |
//Содержимое файла input.txt /* Aslapova Nastya she did not come to class 29 5 Nikolaev Dima he bought a house 12 9 Tokmin Lev he does not know programming 96 75 */ #include "stdafx.h"//Visual Studio может попросить #include <iostream> #include <fstream> #include <string> #include <sstream> using namespace std; int main() { setlocale(LC_ALL, "RUSSIAN");//Чтобы русский текст поддерживался //Создаем файловый поток и связываем его с файлом ifstream file("input.txt"); if (file.is_open())//Если открытие файла прошло успешно { cout << "Файл открыт." << endl; string line;//Строчка текста //Будем считывать информацию построчно до тех пор, //пока не закончится файл while (getline(file, line)) { //cout << line << endl;//Можно посмотреть, что в строчке считалось //Теперь в line хранится содержимое строчки из файла. //Будем её разбирать на составные части. //В нашем файле идут две строчки, а потом два числа string name, surname, info; int age, money; //Создадим поток для считывания данных из строчки istringstream iss(line); //Теперь через стандартный оператор >> считаем данные //Программа сама поймет что в качестве разделителя надо //использовать знак табуляции \t //За раз всё считаем iss >> surname >> name >> info >> age >> money; //Выведем наши данные cout << "Данные из строчки:" << endl; cout << "\tФамилия: " << surname << endl; cout << "\tИмя: " << name << endl; cout << "\tИнформация: " << name << endl; cout << "\tВозраст: " << age << endl; cout << "\tДеньги: " << money << endl; } } else { cout << "Не удалось открыть файл." << endl; } system("pause"); return 0; } |
Почему программа не работает?
1 |
iss >> surname >> name >> info >> age >> money; |
Когда мы в потоке считывали наши данные, то программ не делает разницы между знаком табуляции и знаком пробела в качестве знака разделителя. Поэтому, когда считывается переменная info, то считывается значение до знака пробела, а не до знака табуляции. Отсюда и ошибки.
Как быть? А мы заменим строку
1 |
iss >> surname >> name >> info >> age >> money; |
на цикл, в котором из нашего потока iss будем вытаскивать строчки используя в качестве разделителя то, что там нужно. Будем использовать подобную конструкцию:
1 2 3 4 |
string token; while (getline(iss, token, '\t')) { std::cout << token << '\n'; } |
Нам нужно будет только считанные значения в нужные переменные запихать. Еще не забыть перевести строчки в числа в нужных местах. Итого, полный код будет выглядеть так:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 |
//Содержимое файла input.txt /* Aslapova Nastya she did not come to class 29 5 Nikolaev Dima he bought a house 12 9 Tokmin Lev he does not know programming 96 75 */ #include "stdafx.h"//Visual Studio может попросить #include <iostream> #include <fstream> #include <string> #include <sstream> using namespace std; int main() { setlocale(LC_ALL, "RUSSIAN");//Чтобы русский текст поддерживался //Создаем файловый поток и связываем его с файлом ifstream file("input.txt"); if (file.is_open())//Если открытие файла прошло успешно { cout << "Файл открыт." << endl; string line;//Строчка текста //Будем считывать информацию построчно до тех пор, //пока не закончится файл while (getline(file, line)) { //cout << line << endl;//Можно посмотреть, что в строчке считалось //Теперь в line хранится содержимое строчки из файла. //Будем её разбирать на составные части. //В нашем файле идут две строчки, а потом два числа string name, surname, info; int age, money; //Создадим поток для считывания данных из строчки istringstream iss(line); string token; int i = 0; while (getline(iss, token, '\t')) { if (i == 0) surname = token; if (i == 1) name = token; if (i == 2) info = token; if (i == 3) age = atoi(token.c_str());//перевод string в int if (i == 4) money = atoi(token.c_str());//перевод string в int i++; } //Выведем наши данные cout << "Данные из строчки:" << endl; cout << "\tФамилия: " << surname << endl; cout << "\tИмя: " << name << endl; cout << "\tИнформация: " << info << endl; cout << "\tВозраст: " << age << endl; cout << "\tДеньги: " << money << endl; } } else { cout << "Не удалось открыть файл." << endl; } system("pause"); return 0; } |
При запуске программы получим следующее.
Третий случай
А что будет, если информация из третьего столбца у нас будет написана русскими буквами?
1 2 3 |
Aslapova Nastya не пришла в класс 29 5 Nikolaev Dima купил дом 12 9 Tokmin Lev не знает программирование 96 75 |
Получится не то, что мы хотели.
Тип string не умеет работать с Unicode кодировкой, в которой у нас сохранен текст файла. Как никак C++ появился и развился до внедрения Unicode.
Для этих нужд есть тип wstring. А для него многие вещи в программе придется поменять. Например, вместо cout теперь нужно использовать wcout, вместо istringstream будем использовать wistringstream, перед строчками, которые хотим вывести придется писать L и так далее.
Но и с wstring не всё гладко. Чтобы UTF-8 кодировка заработала, придется пошаманить. Именно по этой причине я не люблю работать с файлами и строками с помощью стандартных средств C++. Лучше использовать те типы переменных, которые есть в той среде, в которой пишется программа. Например, в Qt буду использовать QString и QFile.
Ладно, лирику в сторону. Вот код получившейся программы:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 |
//Содержимое файла input.txt /* Aslapova Nastya не пришла в класс 29 5 Nikolaev Dima купил дом 12 9 Tokmin Lev не знает программирование 96 75 */ //#include "stdafx.h"//Visual Studio может попросить #include <iostream> #include <fstream> #include <string> #include <sstream> #include <codecvt> using namespace std; int main() { setlocale(LC_ALL, "Russian");//Чтобы русский текст поддерживался //Всякое служебное, чтобы корректно работать с UTF-8 кодировкой const locale empty_locale = locale::empty(); typedef codecvt_utf8<wchar_t> converter_type; const converter_type* converter = new converter_type; const locale utf8_locale = locale(empty_locale, converter); //Создаем файловый поток и связываем его с файлом wifstream file(L"input.txt"); if (file.is_open())//Если открытие файла прошло успешно { wcout << L"Файл открыт." << endl; //Извращаемся, чтобы не напортачить с кодировками file.imbue(utf8_locale); wstringstream wss; wss << file.rdbuf(); wstring wval = wss.str(); //Теперь в wval хранится содержимое файла. //Чтобы сильно не отходить от предыдущего кода, то создадим поток //из этой строчки для считывания построчно информации wistringstream content(wval); wstring line;//Строчка текста //Будем считывать информацию построчно до тех пор, //пока не закончится файл while (getline(content, line)) { //wcout << line << endl;//Можно посмотреть, что в строчке считалось //Теперь в line хранится содержимое строчки из файла. //Будем её разбирать на составные части. //В нашем файле идут две строчки, а потом два числа wstring name, surname, info; int age, money; //Создадим поток для считывания данных из строчки wistringstream iss(line); wstring token; int i = 0; while (getline(iss, token, L'\t')) { if (i == 0) surname = token; if (i == 1) name = token; if (i == 2) info = token; if (i == 3) age = stoi(token);//перевод string в int if (i == 4) money = stoi(token);//перевод string в int i++; } //Выведем наши данные wcout << L"Данные из строчки:" << endl; wcout << L"\tФамилия: " << surname << endl; wcout << L"\tИмя: " << name << endl; wcout << L"\tИнформация: " << info << endl; wcout << L"\tВозраст: " << age << endl; wcout << L"\tДеньги: " << money << endl; } } else { wcout << L"Не удалось открыть файл." << endl; } system("pause"); return 0; } |
При запуске программы получим:
Четвертый случай
В последнем примере рассматривался случай, когда тест содержал русский текст и он был в кодировке UTF-8. И да, при этом код нужно сильно переписывать. Однако можно себе упростить жизнь, если файл будет сохранен в кодировке cp1251. Настоятельно не рекомендую её использовать (хотя Windows её до сих пор вставляет во все места, а в стандартном Блокноте Windows кодировка при сохранении называется ANSI). Но с ней код будет выглядеть проще.
Плюс рассмотрим случай, когда нам в коде нужно будет сравнивать фамилию с тем значением, которые введет в консоли пользователь. При этом надо понимать, что кодировка вводимого пользователем текста должна совпадать с кодировкой в текстовом файле (либо производить преобразования кодировок).
Итак, у нас есть такой файл:
1 2 3 |
Аслапова Настя 29 5 Николаев Дима 12 9 Токмин Лев 96 75 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 |
//Содержимое файла input.txt /* Аслапова Настя 29 5 Николаев Дима 12 9 Токмин Лев 96 75 */ Вот окончательный код программы. #include "stdafx.h"//Visual Studio может попросить #include <iostream> #include <fstream> #include <string> #include <sstream> #include <windows.h> using namespace std; int main() { setlocale(LC_ALL, "RUSSIAN");//Чтобы русский текст поддерживался SetConsoleCP(1251);//меняем кодировку консоли принудительно SetConsoleOutputCP(1251);//меняем кодировку консоли принудительно на вывод //Создаем файловый поток и связываем его с файлом ifstream file("input.txt"); if (file.is_open())//Если открытие файла прошло успешно { cout << "Файл открыт." << endl; string line;//Строчка текста //Будем считывать информацию построчно до тех пор, //пока не закончится файл while (getline(file, line)) { //cout << line << endl;//Можно посмотреть, что в строчке считалось //Теперь в line хранится содержимое строчки из файла. //Будем её разбирать на составные части. //В нашем файле идут две строчки, а потом два числа string name, surname; int age, money; //Создадим поток для считывания данных из строчки istringstream iss(line); //Теперь через стандартный оператор >> считаем данные //Программа сама поймет что в качестве разделителя надо //использовать знак табуляции \t //За раз всё считаем iss >> surname >> name >> age >> money; //Выведем наши данные cout << "Данные из строчки:" << endl; cout << "\tФамилия: " << surname << endl; cout << "\tИмя: " << name << endl; cout << "\tВозраст: " << age << endl; cout << "\tДеньги: " << money << endl; string d; cin >> d; if (d == surname) cout << "\t\t Фамилии равны" << endl; else cout << "\t\t Фамилии не равны" << endl; } } else { cout << "Не удалось открыть файл." << endl; } system("pause"); return 0; } |
Что тут добавилось?
Мы подключили заголовочный файл #include <windows.h>.
Мы поменяли кодировку консоли:
1 2 |
SetConsoleCP(1251);//меняем кодировку консоли принудительно SetConsoleOutputCP(1251);//меняем кодировку консоли принудительно на вывод |
И добавили проверку после считывания и анализа каждой строчки. Пользователь вводит фамилию, и мы проверяем равна ли введенная фамилия фамилии из строчку файла.
1 2 3 4 5 6 |
string d; cin >> d; if (d == surname) cout << "\t\t Фамилии равны" << endl; else cout << "\t\t Фамилии не равны" << endl; |