Задачи на строки и файлы, C vs C++
Решать такие задачи можно как с помощью классических "сишных" библиотек cstdio
и cstring, так и "новыми" c++-ными
fstream
и string. Что удобнее? Это зависит от задачи. В общем случае, если объёмы данных невелики, а быстродействие критично, char *
может оказаться предпочтительнее, чем класс string
. Если же данных много и мы не хотим возиться с "ручной" обработкой строк, string
делается намного удобнее.
С файлами дело обстоит примерно так же - форматное чтение файлов или просто обработку данных небольшого объёма нередко выгоднее реализовать через FILE *
, а в новых средах с управляемым кодом или при необходимости заставить "длинные" файловые операции выполняться в отдельном потоке приложения, fstream
, конечно же, предпочтительнее.
Приведём несколько примеров, часть из них программируя классическим способом, а часть - "новым".
Пример 1. Текстовый файл содержит произвольные слова на английском языке, по 1 слову в строке, не более 40 слов в файле, длина слова ограничена 80 символами. Переписать слова в другой файл, отсортировав их в алфавитном порядке.
#define _CRT_SECURE_NO_WARNINGS #include <stdio.h> #include <stdlib.h> #include <string.h> int main() { const int SIZE = 40; char s[SIZE][80]; char *p[SIZE], *temp; FILE *fp; int i, j, n = 0; fp = fopen("data.txt", "rt"); if (fp == NULL) { printf("\nCan't open file to read!"); exit(1); } while (1) { fscanf(fp, "%s", &s[n][0]); p[n++] = &s[n][0]; if (feof(fp) || n> SIZE - 1) break; } for (i = 0; i<n - 1; i++) for (j = i + 1; j<n; j++) { if (_stricmp(p[i], p[j])>0) { //сортируем без учёта регистра символов temp = p[i]; p[i] = p[j]; p[j] = temp; } } fclose(fp); fp = fopen("res01.txt", "wt"); if (fp == NULL) { printf("\nCan't open file to write!"); exit(1); } for (i = 0; i<n; i++) fprintf(fp, "%s\n", p[i]); fclose(fp); fflush(stdin); getchar(); return 0; }
Пример 2. Подсчитать количество строк и символов в произвольном файле, а также процент символов, являющихся пробельными (пробел, табуляция, возврат каретки и перевод строки). Строка завершается парой символов «возврат каретки и перевод строки». При подсчете количества символов пара символов "возврат каретки" и "перевод строки" не интерпретируется как один символ.
Последнее - намёк читать файл по байтам с помощью fread
, а не через fgetc
.
#define _CRT_SECURE_NO_WARNINGS #include <stdio.h> #include <stdlib.h> #include <string.h> int main() { FILE *fp; char c; long count = 0, lines = 0, divs = 0; float percent; fp = fopen("data.txt", "rb"); if (fp == NULL) { printf("\nCan't open file"); exit(1); } while (1) { fread(&c, 1, 1, fp); if (feof(fp)) break; count++; if (c == '\n') lines++; if (strchr(" \t\r\n", c)) divs++; } percent = divs * 100 / count; printf("\n Count=%ld", count); printf("\n Lines=%ld", lines + 1); printf("\n Divs =%4.2f%", percent); fclose(fp); fflush(stdin); getchar(); return 0; }
Пример 3. Задана строка s произвольного размера. Реализовать подпрограмму удаления символов из строки, начиная с символа номер n, n≥0 до символа номер k включительно, k≥n. Подпрограмма возвращает указатель на полученную строку или NULL
, если удаление невозможно.
#define _CRT_SECURE_NO_WARNINGS #include <stdio.h> #include <string.h> char *str2(char *s, int n, int k) { char *p = s; int i, len, l; len = strlen(s); if (n<0 || n>len - 1 || k<0 || k>len - 1 || n>k) return NULL; s += n; l = k - n + 1; for (i = n; i<len; i++, s++) *s = *(s + l); *s = '\0'; return p; } int main() { char *s = new char[20]; //не char *s="Hello, world!"; strcpy(s, "Hello, world!"); //- нельзя будет менять строку! char *p = str2(s, 3, 6); if (p == NULL) printf("\n Impossible!"); else printf("\n Result is \"%s\"", p); fflush(stdin); getchar(); return 0; }
Пример 3.1. Реализация этой задачи с помощью класса string
:
#include <iostream> #include <string> using namespace std; string str2(string s, int n, int k) { int len = s.size(); if (n<0 || n>len - 1 || k<0 || k>len - 1 || n>k) return ""; return s.erase(n,k-n+1); } int main() { string s = (string)"Hello, world!"; string p = str2(s, 3, 6); if (p == (string)"") cout << endl << "Impossible"; else cout << endl << "Result is " << p; cin.get(); return 0; }
Пример 4. Прочитать из бинарного файла данных динамический массив, состоящий из целых чисел. Определить, сколько раз в нём встречается максимальное значение.
#define _CRT_SECURE_NO_WARNINGS #include <stdio.h> int main() { char name[80]; printf("\nFile name? "); scanf("%s", name); //fgets добавит \n в конце строки, надо удалять FILE *f = fopen(name, "r+b"); if (f == NULL) { printf("\nCan't open file"); return 1; } fseek(f, 0, SEEK_END); long size = ftell(f); //fgetpos под Windows может создать проблемы int n = (int)size / sizeof(int); fseek(f, 0, SEEK_SET); int *a = new int [n]; if (a == NULL) { printf("\nNo memory"); return 2; } fread(a, sizeof(int), n, f); int max = a[0], kol = 0; for (int i = 0; i<n; i++) { printf("%d ", a[i]); if (a[i]>max) { max = a[i]; kol = 1; } else if (a[i] == max) kol++; } printf("\nMax=%d,kol=%d", max, kol); fflush(stdin); getchar(); getchar(); return 0; }
Исходя из того, что каждое число в бинарном файле займёт sizeof(int)==4
байта (в 32-разрядной системе), файл теста сделаем, например, таким:
0033100000331000
Видно, что здесь всего 4 числа и каждое число повторяется дважды.
Пример 4.1. Реализация этой задачи с помощью поточной библиотеки fstream
:
#include <iostream> #include <fstream> using namespace std; int main() { ifstream file; file.open("data.txt", ios::in | ios::binary); if (!file) { return 1; } file.seekg(0, file.end); long int size = file.tellg(); int n = (int)size / sizeof(int); file.seekg(0, file.beg); int *a = new int[n]; if (!a) { return 2; } file.read((char *)&a[0], sizeof(int)*n); //всегда 1-й параметр явно преобразывать в char * (для Studio) file.close(); //Дальше без изменений int max = a[0], kol = 0; for (int i = 0; i<n; i++) { printf("%d ", a[i]); if (a[i]>max) { max = a[i]; kol = 1; } else if (a[i] == max) kol++; } printf("\nMax=%d,kol=%d", max, kol); fflush(stdin); getchar(); getchar(); return 0; }
Пример 5. Посимвольно считаем абзацы (строки) из текстового файла и запишем в вектор.
#include <iostream> #include <fstream> #include <vector> #include <string> /* необходимо для cout << string */ using namespace std; int main() { const char *filename = "data.txt"; ifstream file(filename); int ch = 0; string s = (string)""; vector <string> v; while ((ch = file.get()) != EOF) { if (char(ch) != '\n') s += char(ch); else { v.push_back(s); s.clear(); } } if (!s.empty()) v.push_back(s); //учесть последнюю строку, если она непуста file.close(); for (unsigned i = 0; i < v.size(); i++) { //здесь обрабатываем как угодно наши элементы вектора v cout << (string)v[i] << '\n'; } cin.get(); return 0; }
В этой заметке мы заставляли ifstream
выполнять работу, которую часто пытаются возложить на функции классического Си.
А можно ли без scanf("%s")
прочитать из потока символы только до нужного разделителя, например, того же
пробела? Проверим на примере стандартного потока ввода cin
.
Пример 6. Прочитать через cin
символы до пробела. В конце кода также показано, как игнорировать весь ввод до нажатия Enter.
#include <iostream> using namespace std; int main() { char c = 0; while (cin.read(&c, 1)) { if (c == ' ') break; cout << endl << c; } if (cin.good()) cout << endl << "space"; else if (cin.eof()) cout << endl << "EOF"; else cout << endl << "Error"; cin.ignore (numeric_limits<streamsize>::max(), '\n'); //Игнорировать всё до нажатия Enter cin.get(); return 0; }
Во многих случаях задачи удобнее решать не просто применяя строки string
и потоки fstream
, но и
готовые алгоритмы из библиотеки <algorithm>
для выполнения типовой обработки данных (вместо "ручной" реализации обработки в цикле).
Приведу пару таких примеров на посимвольную обработку строковых данных.
Пример 7. Дана строка. Подсчитать количество содержащихся в ней цифр.
#include <iostream> #include <algorithm> #include <string> using namespace std; int main() { string str("letters123456abc"); cout << str << endl; cout << "Count digit: " << count_if(str.begin(), str.end(), isdigit) << endl; cin.get(); return 0; }
Пример 8. Дана строка. Вывести все цифры из неё.
#include <iostream> #include <algorithm> #include <string> using namespace std; int main() { string s = "123, don't miss your digits, ou, 456"; for_each(s.begin(), s.end(), [](char c) { if (isdigit(c)) cout << c; }); cin.get(); return 0; }
Платой за лаконичность становится, конечно, меньшая "понятность" кода с точки зрения классического процедурного программирования. Все программы проверены в Visual Studio 2015.
Пример 9. Прочитать лексемы из файла, игнорируя лишние разделители и разделяя слова ровно одним пробелом.
Встроенный переопределённый оператор >>
уже делает это. Вот пример консольной программы, переписывающей входной файл data.txt
из текущей для конфигурации проекта папки в файл result.txt
, где лишних разделителей нет:
#include <iostream> #include <fstream> #include <string> #include <cstdlib> #include <Windows.h> using namespace std; void error(int n) { string msg[] = { "OK", "Can't open file" }; cout << msg[n] << endl; system ("pause"); exit (n); } int main() { ifstream file; ofstream file2; string str; file.open("data.txt", ios::in); file2.open("result.txt", ios::out); if (!file) { error (1); } while (1) { if (file.eof()) break; file >> str; file2 << str << " "; } file2.close(); file.close(); error (0); }
Вот таким был исходный файл:
адын дидива, да, слюшай два тетри !
Вот что из него вышло:
адын дидива, да, слюшай два тетри !
Лишний пробел в конце нетрудно обработать дополнительным условием.
15.12.2016, 17:03 [18498 просмотров]