БлогNot. Задачи на строки и файлы, C vs C++

Задачи на строки и файлы, 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 [18112 просмотров]


теги: учебное textprocessing c++ алгоритм studio

показать комментарии (3)