Алгоритмизация. Введение в язык программирования C++

Работа с файловыми потоками. Пользовательские функции

Показывать лекцию целиком

Занятие 1. Работа с файловыми потоками

Программа С++ может взаимодействовать не только с экраном DOS, но и с файлами. Мы будем рассматривать работу с текстовыми файлами (*.txt) и таблицами MS Excel (*.xls).

К примеру, требуется составить программу для решения дифференциального уравнения. Программа выдаст в качестве ответа набор чисел. Количество чисел может быть любым числом – и 10, и 200. Поэтому разумно в этом случае печатать эти данные не столько на экран DOS, сколько в файл *.xls, а затем в MS Excel строить график. При этом говорят, что программа выводит в файл данные.

Приведем другой пример. Имеется файл, который содержит какие-то данные. Скажем, нужно их рассчитать по какой-то формуле. Тогда программа должна считать из файла данные.

Чтобы программа могла взаимодействовать с файлом, неоходимо использовать переменную типа файловый поток. Такая переменная задается ключевым словом fstream.

Для работы с файловым потоком необходимо:

  • подключить библиотеку fstream:
    #include <fstream>
  • объявить переменную типа файловый поток:
    fstream f;
  • открыть файл:
  • для записи в файл:
    f.open("1.txt", ios::out);
  • для чтения из файла:
    f.open("1.txt", ios::in);
  • произвести запись в файл или чтение из файла:
  • для записи в файл:
    f<<"x="<<x;
  • для чтения из файла:
    f>>x;
  • закрыть файл: f.close();
  • Примечание. При открытии файла на запись файл создается в папке с проектом. Если файл уже существует, то все содержимое стирается. Если требуется печатать в конец уже существующего файла, то при открытии файла надо использовать строку:

    f.open("1.txt", ios::app);

    Пример 1. Считать из файла число и показать его на экране.

    Решение. Сначала рассмотрим, как выполнить эту задачу в Visual Studio, затем – в Borland C++.

    Создадим проект в Visual Studio, назовем его "8.1". При этом генерируется папка с нашим проектом. Открываем папку 8.1\8.1 и создаем текстовый документ, называем его 1.txt, как показано на рис. 7.1.

    (рис 7.1) Создание текстового файла в папке с проектом

    Открываем файл 1.txt и записываем одно число, например, -3.27, как показано на рис. 7.2.

    (рис 7.2) Исходный текстовый файл для примера 1

    Сохраняем и закрываем файл. Теперь переходим в приложение Visual Studio и в нашем проекте прописываем код программы.

    Код программы для примера 1:

    // 8.1.cpp: определяет точку входа для консольного приложения.
    //
    #include "stdafx.h"
    #include <iostream>
    #include <fstream>
    using namespace std;
    int main()
    { 
    	double x;
    	fstream f;
    	f.open("1.txt", ios::in);
    	f>>x;
    	f.close();
    	cout<<"x="<<x<<endl;
    	return 0;
    }

    Результат выполнения программы:

    Чтобы работать с файлами в Borland C++, создадим папку F:/BC/FILES. Эта папка предназначена для хранения файлов, с которыми будет взаимодействовать наша программа. Создаем файл 1.txt в папке F:/BC/FILES, записываем в него число -3.27.

    При открытии файла необходимо записать имя файла как FILES/1.txt, чтобы наша программа искала текстовый файл именно в этой папке.

    Код программы:

    #include <iostream.h>
    #include <fstream.h>
    int main()
    { 
    	double x;
    	fstream f;
    	f.open("FILES/1.txt", ios::in);
    	f>>x;
    	f.close();
    	cout<<"x="<<x<<endl;
    	return 0;
    }

    Результат выполнения программы:

    Пример 2. Составить программу, которая производит запись двух чисел в файлы *.txt и *.xls.

    Решение. Обозначим числа как $$a$$ и $$b$$, зададим $$а=7,2; b=-10.89$$. Файлы не будем предварительно создавать, т.к. они возникнут автоматически при запуске нашей программы. Создаем новый проект 8.2 в Visual Studio.

    Объявим два файловых потока ftxt – для записи в текстовый файл 1.txt, fxls – для записи в табличный файл 2.xls.

    Сделаем так, чтобы в файле 1.txt появилась надпись:

    a=7.2 b=-10.89

    В файле 2.xls выведем каждый кусочек сообщения в разные ячейки. Для этого следует использовать символ табуляции "\t".

    Код программы для примера 2:

    // 8.2.cpp: определяет точку входа для консольного приложения.
    //
    #include "stdafx.h"
    #include <iostream>
    #include <fstream>
    using namespace std;
    int main()
    {
    	double a, b;
    	fstream ftxt, fxls;
    	a=7.2;
    	b=-10.89;
    	cout<<"a="<<a<<" b="<<b<<endl;
    	ftxt.open("1.txt", ios::out);
    	fxls.open("2.xls", ios::out);
    	ftxt<<"a="<<a<<" b="<<b;
    	fxls<<"a=\t"<<a<<"\tb=\t"<<b;
    	ftxt.close();
    	fxls.close();
    	return 0;
    }

    Результат выполнения программы:

    экран DOS:

    текстовый файл 1.txt:

    табличный файл 2.xls:

    Примечание. Файлы 1.txt и 2.xls создаются в папке с проектом 8.2\8.2, как показано на рис. 7.3.

    (рис 7.6) Созданные программой файлы в папке с проектом

    Решим данную задачу в Borland C++.

    Код программы:

    #include <iostream.h>
    #include <fstream.h>
    int main()
    {
    	double a, b;
    	fstream ftxt, fxls;
    	a=7.2;
    	b=-10.89;
    	cout<<"a="<<a<<" b="<<b<<endl;
    	ftxt.open("FILES/1.txt", ios::out);
    	fxls.open("FILES/2.xls", ios::out);
    	ftxt<<"a="<<a<<" b="<<b;
    	fxls<<"a=\t"<<a<<"\tb=\t"<<b;
    	ftxt.close();
    	fxls.close();
    	return 0;
    }

    Результат выполнения программы:

    экран DOS:

    текстовый файл 1.txt:

    табличный файл 2.xls:

    Примечание. Т.к. в примере 2 мы задали имя текстового файла 1.txt так же, как в примере 1, то после выполнения программы примера 2 в папке FILES будет находиться два файла. Файл 1.txt запишется заново. При этом потеряется число -3.27, которое мы печатали для примера 1.

    В следующих примерах будем приводить коды программ в среде Visual Studio.

    Пример 3. Построить таблицу значений функции $$y=\sin x$$ при $$0\le x\le 2$$ с шагом 0,1.

    Решение. Данный пример решается с помощью циклического алгоритма. Будем использовать цикл for. Построим таблицу значений функции на экране, а также в файле 1.xls и построим график в MS Excel.

    Блок-схема:

    Код программы:

    // 8.3.cpp: определяет точку входа для консольного приложения.
    //
    #include "stdafx.h"
    #include <iostream>
    #include <math.h>
    #include <iomanip>
    #include <fstream>
    using namespace std;
    int main()
    {
    	double x, y;
    	fstream f;
    	f.open("1.xls", ios::out);
    	cout<<setw(10)<<"x"<<setw(10)<<"y"<<endl;
    	f<<"x"<<"\t"<<"y"<<endl;
    	for(x=0; x<=2; x=x+0.1){
    		y=sin(x);
    		cout<<setw(10)<<x<<setw(10)<<y<<endl;
    		f<<x<<"\t"<<y<<endl;
    	}
    	f.close();
    	return 0;
    }

    Результат выполнения программы:

    экран DOS:

    табличный файл 1.xls:

    Файл 1.xls с построенным графиком:

    Пример 4. В матрице $$а(5, 5)$$ вычислить сумму положительных элементов, произведение элементов на главной диагонали, количество элементов, больших 2.

    Решение. Матрица а является двумерным массивом, состоящим из пяти строк и пяти столбцов. Проинициализируем массив по формуле $$a[i][j]=7\sin(2,3ij)$$. Массив и все результаты вычислений будем выводить в файл 1.xls.

    Блок-схема:

    Код программы:

    // 8.4.cpp: определяет точку входа для консольного приложения.
    //
    #include "stdafx.h"
    #include <iostream>
    #include <math.h>
    #include <iomanip>
    #include <fstream>
    using namespace std;
    int main()
    {
    	double a[5][5], s, p;
    	int i, j, k;
    	fstream f;
    	f.open("1.xls", ios::out);
    	cout<<"matrix:"<<endl;
    	f<<"матрица:"<<endl;
    	for(i=0; i<5; i=i+1){
    		for(j=0; j<5; j=j+1){
    			a[i][j]=7.0*sin(2.3*i*j);
    			cout<<setw(10)<<a[i][j];
    			f<<'\t'<<a[i][j];
    		}
    		cout<<endl;
    		f<<endl;	
    	}
    	s=0;
    	for(i=0; i<5; i=i+1){
    		for(j=0; j<5; j=j+1){
    			if(a[i][j]>0){
    				s=s+a[i][j];
    			}
    		}
    	}
    	cout<<"s="<<s<<endl;
    	f<<"Сумма положительных=\t"<<s<<endl;
    	p=1;
    	for(i=0; i<5; i=i+1){
    		for(j=0; j<5; j=j+1){
    			if(i==j){
    				p=p*a[i][j];
    			}
    		}
    	}
    	cout<<"p="<<p<<endl;
    	f<<"Произв. на гл. диаг.=\t"<<p<<endl;
    	k=0;
    	for(i=0; i<5; i=i+1){
    		for(j=0; j<5; j=j+1){
    			if(a[i][j]>2){
    				k=k+1;
    			}
    		}
    	}
    	cout<<"k="<<k<<endl;
    	f<<"Количество элем.>2 =\t"<<k<<endl;
    	f.close();
    	return 0;
    }

    Результат выполнения программы:

    экран DOS:

    табличный файл 1.xls:

    Примечание. Если данный код запустить в программе Borland C++, то русский текст следует заменить латиницей.

    Занятие 2. Пользовательские функции

    Ранее в программах мы использовали функции из разных библиотек, например, $$\sin(x)$$ из библиотеки <math.h> или setw(10) из библиотеки <iomanip>. Что общего во всех функциях? Все функции представляют собой некий шаблон, по которому вычисляется значение какой-то функции или выполняется набор каких-то действий. В языке С++ есть возможность создавать свои функции, не относящиеся к библиотекам. Такие функции называются пользовательскими функциями.

    Для того, чтобы создать и использовать свою пользовательскую функцию в программе, необходимо:

  • объявить пользовательскую функцию:
    тип_данных имя_функции (список параметров);
    здесь тип_данных – это возвращаемый тип данных функции, который определяется ответом функции (выходные данные), имя функции – любое имя (по аналогии с именем переменной), список параметров – это то, с чем наша функция будет работать (входные данные);
  • описать пользовательскую функцию;
  • вызвать пользовательскую функцию.
  • Объявление пользовательской фукнции должно быть обязательно до функции main(), описание может быть вместе с объявлением, а может быть после функции main(). Вызов пользовательской функции может быть внутри функции main() или внутри другой пользовательской функции.

    Первый вариант описания пользовательской функции – до функции main() представлен на рис. 7.4.

    (рис 7.4) Описание пользовательской функции до функции main()

    Второй вариант описания пользовательской функции – после функции main() представлен на рис. 7.5.

    (рис 7.5) Описание пользовательской функции после функции main()

    Конечно, приведенные выше варианты не заработают, т.к. они показывают общую структуру записи программ. В наших примерах всегда будем использовать первый вариант.

    Вообще говоря, любую функцию следует воспринимать как некий воображаемый аппарат для выполнения какой-либо работы. При этом аппарату нужно работать над каким-то материалом, т.е. входными данными, известными заранее. Этими данными являются параметры функции. Когда аппарат получил материал, то начинает свою работу. Какую именно работу он будет выполнять с входным материалом, зависит от шаблона, т.е. от описания функции. После того, как вся работа будет выполнена, аппарат выдаст нам результат своей работы, некий ответ. Это то, ради чего мы и создавали этот аппарат. Схемотично данный процесс представлен на рис. 7.6.

    (рис 7.6) Принцип работы пользовательской функции

    Например, для известной нам функции $$\sin(x)$$ из библиотеки <math.h>, входными данными является один параметр – вещественное число $$х$$, сам аппарат представляет собой процесс вычисления синуса от $$х$$, результатом является полученное значение синуса. Результат является вещественным числом, которое задается ключевым словом double. Поэтому можно было бы представить объявление и описание функции $$\sin(x)$$ следующим образом:

    double sin(double x){
    double res;
    res=...; // вычисление синуса по сложной математической формуле
    return res;  // ответ, результат
    }

    Здесь внутри функции объявлена локальная переменная double res, в которую будет насчитываться ответ (res – result – результат). Данная переменная будет видна только внутри пользовательской функции. Переменная res – это, своего рода, временная переменная для расчета синуса. Строка return res; выдает в ответ вычисленное значение синуса.

    Вызов функции $$\sin(x)$$ внутри функции main() выглядит следующим образом:

    z=sin(1.5);

    Примечание. Хотелось бы обратить внимание на разницу между списком параметров при объявлении функции и список аргументов при вызове функции. Объявление, описание и список формальных параметров составляют весь шаблон. Здесь параметры представляют собой некие абстрактные параметры, которые пока нельзя "пощупать". А вот когда идет вызов функции, абстрактные параметры приобретают вполне реальные формы и становятся аргументами функции, конкретными числами. В программе это отражается следущим образом. Объявление и описание функции делается один раз:

    double function(double a, double b){...}

    Вызовов функции может быть сколько угодно:

    int main(){
    double x, y, z1, z2, z3;
    x=10;
    y=-0.3;
    z1=function(x,y); // первый вариант вызова
    z2=function(1.8, y); // второй вариант вызова
    z3=function(1.8, 0); // третий вариант вызова
    return 0;
    }

    Рассмотрим несколько примеров по созданию пользовательских функций.

    Пример 1. Требуется создать пользовательскую функцию для вычисления квадрата вещественного числа.

    Решение. Составим схему работы нашей пользовательской функции в общем виде на рис. 7.7.

    (рис 7.7) Схема в общем виде для примера 1

    Т.к. параметр х должен быть вещественным, то ему соответствует тип double. Пользовательская функция должна считать квадрат числа, поэтому назовем ее kv. В ответ будет выдаваться вещественное число, поэтому возвращаемый тип у функции будет double.

    Объявление и описание функции kv:

    double kv(double x){
    double res;
    res=x*x;
    return res;
    }

    Код программы и результат выполнения программы:

    Пример 2. Вычислить таблицу "$$x, Z$$" значений функции $$Z=\begin{cases}x+3,x<1\\4x,x\ge 1\end{cases}$$ при $$-1\le х\le 2$$ с шагом 1.

    Решение. Сделаем пользовательскую функцию для вычисления значения функции $$Z(x)$$. Возвращаемый тип – double, т.к. ответом является вещественное число. Параметр один, double x. В описании пользовательской функции будет вычисление функции по разветвляющемуся алгоритму. В функции main() реализуем циклический алгоритм для вывода таблицы значений функции.

    Код программы:

    // test.cpp: определяет точку входа для консольного приложения.
    //
    #include "stdafx.h"
    #include <iostream>
    #include <iomanip>
    using namespace std;
    double fz(double x){
    	double res;
    	if(x<1){
    		res=x+3.0;
    	}
    	else {
    		res=4.0*x;
    	}
    	return res;
    }
    int main(){
    	double x, z;
    	cout<<setw(10)<<"x"<<setw(10)<<"z"<<endl;
    	for(x=-1; x<=2; x=x+1){
    		z=fz(x);
    		cout<<setw(10)<<x<<setw(10)<<z<<endl;
    	}
    return 0;
    }

    Результат выполнения программы:

    Пример 3. Вычислить таблицу "$$x, y, Z$$" значений функции $$Z(x,y)=\begin{cases}x+y,xy<1\\x-y,xy\ge 1\end{cases}$$ при $$-1\le х\le 0,5$$ с шагом 1, $$5\le y\le 15$$ с шагом 5 .

    Решение. Сделаем пользовательскую функцию для вычисления значения функции $$Z(x)$$. Возвращаемый тип – double, т.к. ответом является вещественное число. Параметров два, double x, double y. В описании пользовательской функции будет вычисление функции по разветвляющемуся алгоритму. В функции main() реализуем вложенные циклы для вывода таблицы значений функции.

    Код программы:

    // test.cpp: определяет точку входа для консольного приложения.
    //
    #include "stdafx.h"
    #include <iostream>
    #include <iomanip>
    using namespace std;
    double fz(double x, double y){
    	double res;
    	if(x*y<1){
    		res=x+y;
    	}
    	else {
    		res=x-y;
    	}
    	return res;
    }
    int main(){
    	double x, y, z;
    	cout<<setw(10)<<"x"<<setw(10)<<"y"<<setw(10)<<"z"<<endl;
    	for(x=-1; x<=0.5; x=x+1){
    		for(y=5; y<=15; y=y+5){
    			z=fz(x, y);
    			cout<<setw(10)<<x<<setw(10)<<y<<setw(10)<<z<<endl;
    		}
    	}
    	return 0;
    }

    Результат выполнения программы:

    Пример 4. Вычислить таблицу значений функции $$Z(n)=\sum^n_{i=1}\frac{1}{i}$$ при $$1\le n\le 10$$ с шагом 1.

    Решение. Сделаем пользовательскую функцию для вычисления значения функции $$Z(n)$$. Возвращаемый тип – double, т.к. ответом является вещественное число. Параметр один, int n. В описании пользовательской функции будет вычисление функции по циклическому алгоритму. В функции main() реализуем цикл по $$n$$ для вывода таблицы значений функции.

    Код программы:

    // test.cpp: определяет точку входа для консольного приложения.
    //
    #include "stdafx.h"
    #include <iostream>
    #include <iomanip>
    using namespace std;
    double fz(int n){
    	double res;
    	int i;
    	res=0;
    	for(i=1; i<=n; i=i+1){
    		res=res+1.0/i;
    	}
    	return res;
    }
    int main(){
    	int n;
    	double z;
    	cout<<setw(10)<<"n"<<setw(10)<<"z"<<endl;
    	for(n=1; n<=10; n=n+1){
    		z=fz(n);
    		cout<<setw(10)<<n<<setw(10)<<z<<endl;
    	}
    	return 0;
    }

    Результат выполнения программы:

    Пример 5. Создать пользовательскую функцию для вывода массива из 5 строк и 5 столбцов на экран.

    Решение. Пользовательскую функцию назовем print_mass. В качестве параметра будет сам массив, например, double х[5][5]. Результатом пользовательской функции является вывод массива на экран, поэтому никакого числа в ответе не будет. Следовательно, возвращаемый тип на этот раз будет void, что означает "пустой". В описании пользовательской функции организуем вывод двумерного массива во вложенном цикле. В функции main() инициализируем два массива $$A[5][5], B[5][5]$$ и вызовем функцию print_mass два раза.

    Код программы:

    // test.cpp: определяет точку входа для консольного приложения.
    //
    #include "stdafx.h"
    #include <iostream>
    #include <iomanip>
    using namespace std;
    void print_mass(double x[5][5]){
    	int i, j;
    	for(i=0; i<5; i=i+1){
    		for(j=0; j<5; j=j+1){
    			cout<<setw(10)<<x[i][j];
    		}
    	cout<<endl;
    	}
    }
    int main(){
    	double A[5][5], B[5][5];
    	int i,j;
    	for(i=0; i<5; i=i+1){
    		for(j=0; j<5; j=j+1){
    			A[i][j]=2.3*i+j;
    			B[i][j]=i-1.5*j;
    		}
    	}
    	cout<<"massiv A:"<<endl;
    	print_mass(A);
    	cout<<"massiv B:"<<endl;
    	print_mass(B);
    	return 0;
    }

    Результат выполнения программы:

    Пример 6. Массив $$А[5][5]$$ задан по формуле $$А[i][j]=0,3i-j$$, массив $$B[5][5]$$ по формуле $$B[i][j]=i-1.5j$$. Вычислить сумму положительных элементов массивов.

    Решение. Создадим пользовательскую функцию для вычисления суммы положительных элементов массива, назовем ее sum_pol. В качестве параметра будет формальный массив $$x[5][5]$$. В функции main() проинициализируем массивы $$A$$ и $$B$$, затем два раза вызовем функцию sum_pol.

    Код программы:

    // test.cpp: определяет точку входа для консольного приложения.
    //
    #include "stdafx.h"
    #include <iostream>
    #include <iomanip>
    using namespace std;
    double sum_pol(double x[5][5]){
    	int i, j;
    	double s;
    	s=0;
    	for(i=0; i<5; i=i+1){
    		for(j=0; j<5; j=j+1){
    			if(x[i][j]>0){ s=s+x[i][j];}
    		}
    	}
    	return s;
    }
    int main(){
    	double A[5][5], B[5][5], sa, sb;
    	int i,j;
    	for(i=0; i<5; i=i+1){
    		for(j=0; j<5; j=j+1){
    			A[i][j]=0.3*i+j;
    			B[i][j]=i-1.5*j;
    		}
    	}
    	sa=sum_pol(A);
    	cout<<"s A>0 = "<<sa<<endl;
    	sb=sum_pol(B);
    	cout<<"s B>0 = "<<sb<<endl;
    	return 0;
    }

    Результат выполнения программы:

    Краткие итоги

    Чтобы работать с файлами, необходимо использовать переменную типа fstream. Рассмотрены случаи вывода в файл и чтения из файла.

    Вопросы

  • Что такое файловый поток?
  • Что необходимо сделать, чтобы использовать в программе файловый поток?
  • Как сделать разделение по ячейкам при записи в файл *.xls?
  • Что такое пользовательская функция?
  • Что такое объявление, описание, вызов пользовательской функции?
  • Упражнения

  • Составьте программу, которая создаст следующие файлы:
  • Составьте блок-схему и программу для построения таблицы значений функции $$Z(x, y)=5,5x-2,8y$$ при $$0\le x\le 1$$ с шагом $$0,2; -2\le y\le$$ 0 с шагом 0,8. Таблицы вывести на экран, в файлы *.txt, *.xls.
  • Для массива $$X(15)$$ найти минимальный элемент, максимальный элемент; найденные минимальный и максимальный элементы поменять местами. Исходный массив $$X(15)$$ считать из файла. Исходный массив, результаты вычислений и измененный массив вывести на экран и в файл *.xls.
  • Составить блок-схему и программу для вычисления суммы 20 чисел из файла. Указание: использовать циклический алгоритм, не использовать массивы.
  • В программе для задачи 3 создать пользовательские функции для вычисления минимального и максимального элементов.
  • Массив $$А(10)$$ считать из файла. Вывести массив $$А$$ на экран. Поменять местами первый и последний элементы. Измененный массив вывести на экран. Указание: для вывода массива на экран создать пользовательскую функцию.
  • Вернуться к учебному плану