Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

OOP_C++ / 08

.htm
Скачиваний:
22
Добавлен:
02.02.2015
Размер:
32.72 Кб
Скачать

08 - Использование файловых потоков, векторов, списков и строк стандартной библиотеки С++ Содержание     Предыдущее занятие     Следующее занятие

Занятие 08 Использование файловых потоков, векторов, списков и строк стандартной библиотеки С++ 1 Стандартная библиотека С++ и потоки ввода-вывода Стандартная библиотека С++ представляет собой коллекцию классов и функций для решения сложных и низкоуровневых задач программирования, в частности, классы для потокового ввода/вывода данных. Консольные приложения имеют доступ к стандартным потокам cin и cout - объектам классов istream и ostream соответственно.

Для работы с потоками чаще всего используются перегруженные операции > (для потоков ввода). Для чтения одного символа может использоваться функция get(). Для чтения строки также может использоваться функция-элемент get с параметрами - указателем на символ, целым (зарезервированный размер буфера) и символ-ограничитель. Функция getline() аналогична get в последнем варианте использования, но при этом разделительный символ удаляется из потока. В приведенном примере вводимая строка воспроизводится на экране. Строка может содержать пробелы. В случае использования >> можно вводить только строку без разделителей (пробелов).

#include <iostream> using namespace std; int main(int argc, char* argv[]) { char s[100]; cin.getline(s, 100, '\n'); cout << s; cin.get(); return 0; } Для форматирования потоков используются так называемые манипуляторы, которые вставляются между объектами и изменяют состояние потока. Наиболее часто используемые манипуляторы - endl (вставка символа "конец строки" и очистка буфера), ends (вставка символа "конец строки"), flush (очистка буфера потока), hex (преобразование в шестнадцатиричное представление), fixed (использование нотации с фиксированной точкой), left (выравнивание данных по левому краю), right (выравнивание данных по правому краю).

2 Файловый ввод-вывод Для использования файлового ввода-вывода включается заголовочный файл <fstream>.

Если файл будет использоваться только для вывода, мы определяем объект класса ofstream. Передаваемые конструктору аргументы задают имя открываемого файла и режим открытия. Файл типа ofstream может быть открыт либо - по умолчанию - в режиме вывода (ios_base::out), либо в режиме дозаписи (ios_base::app). Класс ofstream является производным от ostream. Чтобы открыть файл только для чтения, применяется объект класса ifstream, производного от istream. В следующем примере выводится в файл. Файл автоматически создается в текущем каталоге проекта. Как видно из примера, в отличие от консольного вывода, можно свободно использовать символы кириллицы:

#include <fstream> using namespace std; int main(int argc, char* argv[]) { ofstream out("test.txt"); char *s = "Текст выводится в файл!"; out << s; // Можно было вывести текст явно return 0; } Объекты классов ofstream и ifstream разрешено определять и без указания имени файла. Позже к этому объекту можно присоединить файл с помощью функции-члена open(). Чтобы закрыть файл (отключить от программы), вызываем функцию-член close(). Файл можно явно не закрывать, так как он закроется при вызове деструктора потока.

Объект класса fstream (производного от iostream) может открывать файл для ввода или вывода, в том числе одновременно. Например, открытие файл word.out для ввода и дозаписи:

fstream io("word.out", ios_base::in|ios_base::app); Объект класса fstream можно позиционировать с помощью функций-элементов seekg() (для чтения) или seekp() (для записи). Текущая позиция чтения в файле типа fstream возвращается функциями tellg() или tellp().

3 Использование векторов Важнейшей составляющей стандартной библиотеки С++ являются контейнеры. Контейнер представляет собой параметризированный класс, хранящий коллекцию других объектов и включающий базовые функции для поддержки использования общих алгоритмов. Простейший вид контейнеров - последовательность. К последовательностям относятся вектор (vector), список (list) и дек (deque, очередь с двумя концами). Наиболее часто используется вектор.

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

#include <vector> using std::vector; При создании переменной типа вектора следует указать параметр - тип данных, которые содержатся в этом векторе. При инициализации вектора можно задавать его размерность:

vector<int> v(n); Начальное значение числа элементов n может быть как константой, так и переменной, а также любым выражением, результат которого можно привести к целому типу. С помощью функции-элемента resize() изменяется текущая длина вектора.

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

vector <double> vd(5); vd[6] = 10; // Нет никаких сообщений об ошибке vd.at(7) = 11.5; // Генерация исключения При выполнении программы размер вектора, как и любого другого последовательного контейнера, можно узнать с помощью функции-элемента size(). В циклах рекомендуется использовать параметры типа unsigned int вместо int, так как функция size() возвращает unsigned int и в противном случае компилятор будет генерировать предупреждение:

for (unsigned int i = 0; i < vd.size(); i++) vd[i] = i / 2.; Элементы можно вставлять с конца последовательности с помощью функции-элемента push_back(), которая принимает аргумент того же типа, что и элементы вектора:

vector<int> v; int elem; for (int i=0; i < n; i++) cin >> elem; v.push_back(elem); С помощью функции pop_back() можно удалить последний элемент.

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

vector <vector <int> > a(5); // 5 строк for (unsigned int i = 0; i < a.size(); i++) a.resize(6); // 6 столбцов // Обход элементов вектора: for (unsigned int i = 0; i < a.size(); i++) for (unsigned int j = 0; j < a[i].size(); j++) a[i][j] = i + j; Проход (итерация) по любому контейнеру может осуществляться путем определения класса итератора, подходящего для данного вида контейнера. Каждый контейнерный класс в стандартной библиотеке С++ способен сгенерировать итератор соответствующий конкретному контейнеру. Итератор обобщает понятие указателя на элемент последовательности. Итератор может быть использован для указания на определенное значение. Пара итераторов может быть использована для определения диапазона или последовательности значений, содержащихся в контейнере.

Контейнер vector может предоставлять следующие итераторы:

vector::iterator - итератор, реализующий прямой проход по контейнеру;

vector::reverse_iterator - итератор, реализующий обратный проход по контейнеру;

vector::const_iterator - итератор, через который нельзя менять элементы контейнера;

vector::const_reverse_iterator - константный итератор, реализующий обратный проход по контейнеру.

Итераторы вектора являются итераторами произвольного доступа. Для них реализованы операции сложения с целыми и вычитания целых.

Непосредственно как объект итератор внутри контейнера не содержится, там описан только его тип. Переменную-итератор нужно определять отдельно:

vector::iterator vi; // v.begin() и v.end() возвращают итератор, // указывающий на начало и за конец вектора // Изменяем значение через итератор: for (vi = v.begin(); vi != v.end(); vi++) *vi = 200; Приведенный выше пример может быть также применен к списку и итератору списка.

Итератор const_iterator работает аналогично обычному, за исключением того, что через него нельзя менять элементы массива.

Функция-элемент

insert(iterator pos, const T& x); вставляет х перед позицией pos. Функция-элемент

erase(iterator first, iterator last); удаляет подпоследовательность из вектора.

4 Контейнеры list и deque Список (list) - это последовательность, оптимизированная для вставки и удаления элементов. Однако следующие операции, характерные для списков, доступны и для других последовательностей:

insert(p, x) - добавление х перед p;

insert(p, n, x) - добавление n копий х перед p;

insert(p, first, last) - добавление перед p элементов последовательности, заданной итераторами first и last;

erase(p) - удаление элемента в позиции p;

erase(first, last) - удаление последовательности, заданной итераторами;

clear() - удаление всех элементов.

Контейнер deque - это очередь с двумя концами. Эта последовательность оптимизирована таким образом, что операции с обоими концами эффективны, как в списке, а обращение по индексу приближается по своей эффективности к вектору.

Для контейнеров list и deque имеются операции push_front() - добавление нового первого элемента и pop_front() - удаление первого элемента.

5 Строки стандартной библиотеки Строки стандартной библиотеки аналогичны векторам, специализированным для работы с последовательностями символов. Для работы со строками необходимо подключить заголовочный файл <string>. Строку можно проинициализировать по умолчанию (пустой строкой), цепочкой символов, другой строкой типа std::string:

string s1; string s2("a string"); string s3 = "initial value"; string s4(s3); Можно также, установив размер строки, заполнить ее заданным символом, либо проинициализировать строку другой последовательностью, используя пару итераторов:

string s7(10, '\n'); string s8(aList.begin(), aList.end()); Как и векторы, строки характеризуются логической и физической длиной. Эти величины можно получить с помощью функций-элементов size() и capacity(). Функция-элемент reserve() меняет физическую длину строки. Функция-элемент max_size() возвращает максимальный размер строки, которая может быть размещена в памяти. Функция-элемент length() - синоним функции size().

С помощью функции-элемента resize() изменяется текущая длина строки. Вторым параметром функции можно указать символ, вставляемый в новые позиции.

s7.resize(15, '\t'); // вставка символов табуляции Функция-элемент empty() возвращает true, если строка не содержит символов.

Доступ к отдельным символам в строке аналогичен доступу к элементам массива:

s = "string"; s[1] = 'p'; // s == "spring" Строке может быть присвоено значение другой строки, массива символов, или отдельного символа:

s1 = s2; s2 = "a new value"; s3 = 'x'; Оператор += может быть использован для всех трех форм присваивания.

С помощью функций-элементов assign() и append() можно выделить часть присваиваемой последовательности символов:

s4.assign(s2, 0, 3);// присваивание первых трех символов s4.append(s5, 2, 3);// добавление символов 2, 3 и 4 Оператор + осуществляет сшивание двух строк. Функция-элемент swap() обменивает содержимое двух строк.

Доступ к символам может быть осуществлен с помощью операции [] или функции at(). Функция at() генерирует исключение out_of_range, если осуществлена попытка получения символа за пределами строки.

Для строк не реализовано их приведение к типу char* по умолчанию. Функция-элемент c_str() возвращает указатель на оканчивающийся нулем массив символов.

Для вставки, удаления и замены символов используются функции-элементы insert(), erase() и replace():

s3.insert(3, "abc"); // вставка abc после позиции 3 s3.erase(4, 2); // удаление позиций 4 и 5 s3.replace(4, 2, "pqr");// замена позиций 4 и 5 на pqr Функция-элемент copy() копирует в строку символы из указанного диапазона:

// Присваивает s4 позиции от 2 до конца s3: s3.copy(s4, 2); // Присваивает s4 позиции от 2 до 4 строки s5 : s5.copy(s4, 2, 3); Функция-элемент compare() используется для лексикографического сравнения строк. Необязательные аргументы могут задавать различные исходные позиции для сравнения. Функция возвращает отрицательное значение, если получатель меньше аргумента, ноль, если они равны, и положительное в противном случае.

Чаще используются операции сравнения , которые также могут использоваться для сравнения с цепочкой символов.

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

cout << s4.substr(3); // вывод от позиции 3 до конца cout << s4.substr(3, 2);// вывод позиций 3 и 4 Функция-элемент find() определяет первое включение аргумента в текущую строку. Необязательный целый аргумент определяет стартовую позицию для поиска Функция возвращает найденную позицию или значение за пределами индекса. Функция rfind() ищет с конца в обратную сторону.

s1 = "mississippi"; cout << s1.find("ss"); // возвращает 2 cout << s1.find("ss", 3); // возвращает 5 cout << s1.rfind("ss"); // возвращает 5 cout << s1.rfind("ss", 4); // возвращает 2 Если подстрока не найдена, возвращается значение, большее длины строки.

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

y = a02 + a12 / x + a22 / x2 + a32 / x3 +...+ an2 / xn

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

Вначале создается новое консольное приложение. Его главный модуль (с функцией main()) можно назвать UnitMain.cpp. Далее в директории проекта целесообразно создать новый текстовый файл, например файл с именем input.txt и таким содержимым:

2 1 2 1 Первая строка - это величина n, а вторая - коэффициенты a0, a1 и a2 соответственно. Текстовый файл можно создать как с помощью программы "Блокнот", так и в среде MS Visual Studio .NET.

Далее создается новый модуль с именем UnitFunction. Заголовочный файл нового модуля будет содержать следующий код:

#ifndef UnitFunction_h #define UnitFunction_h bool getA(char *fileName); double y(double x); bool writeTable(double left, double right, double step, char *fileName); void deleteA(); #endif Функция getA() будет осуществлять чтение из заданного файла исходных данных. Если чтение пройдет успешно, функция вернет true. Следующая функция должна вычислять y(x). Функция writeTable() должна выводить в заданный файл таблицу значений функции при изменении аргумента на интервале от left до right с шагом step. Функция deleteA() нужна для освобождения памяти, занимаемой массивом.

В файл UnitFunction.cpp помещаем следующий код:

#include <fstream> #include "UnitFunction.h" namespace { int n; double* a = 0; } bool getA(char *fileName) { std::ifstream in(fileName); if (!(in >> n)) return false; if (a) delete [] a; a = new double [n + 1]; for (int i = 0; i <= n; i++) { if (!(in >> a[i])) return false; } return true; } double y(double x) { double result = 0; double z = 1; for (int i = 0; i <= n; i++) { result += a[i] * a[i] / z; z *= x; } return result; } bool writeTable(double left, double right, double step, char *fileName) { std::ofstream out(fileName); if (!out) return false; for (double x = left; x < right + step; x += step) out << x << "\t" << y(x) << "\n"; return true; } void deleteA() { if (a) delete [] a; } Переменные внутреннего использования (a и n) целесообразно поместить в безымянное пространство имен. Как видно из примера, объекты-потоки можно сравнивать с целыми (булевыми) значениями. Если входной поток дает 0 после чтения, данные из файла не прочитаны, что означает ошибку.

Основной модуль программы будет выглядеть так:

#include <iostream> #include "UnitFunction.h" using namespace std; int main(int argc, char* argv[]) { if (getA("input.txt") && writeTable(2, 3, 0.1, "results.txt")) cout << "OK"; else cout << "Error!"; deleteA(); return 0; } В случае успешного завершения программы в директории проекта появится файл results.txt.

Пример 2 Разработанная ранее программа, в которой вводится интервал значений и шаг аргумента и вычисляются значения следующей функции

y = a02 + a12 / x + a22 / x2 + a32 / x3 +...+ an2 / xn,

может быть модифицирована с использованием вектора вместо массива. В этом случае величина n может не вводиться из файла, так как вводимые значения элементов можно добавлять в вектор с помощью push_back(). Таким образом, необходимый текстовый файл будет иметь следующее содержимое:

1 2 1 Строка - это коэффициенты a0, a1 и a2 соответственно.

В заголовочном файле модуля UnitFunction перечислены те же функции, что и в ранее созданной программе, за исключением deleteA():

#ifndef UnitFunction_h #define UnitFunction_h bool getA(char *fileName); double y(double x); bool writeTable(double left, double right, double step, char *fileName); #endif Наибольшие изменения должны коснуться файла UnitFunction.cpp:

#include <fstream> #include <vector> #include "UnitFunction.h" namespace { std::vector <double> a; } bool getA(char *fileName) { std::ifstream in(fileName); if (!in) return false; double x; while (in >> x) a.push_back(x); return true; } double y(double x) { double result = 0; double z = 1; for (unsigned int i = 0; i < a.size(); i++) { result += a[i] * a[i] / z; z *= x; } return result; } // Без изменений: bool writeTable(double left, double right, double step, char *fileName) { std::ofstream out(fileName); if (!out) return false; for (double x = left; x < right + step; x += step) out << x << "\t" << y(x) << "\n"; return true; } Переменные внутреннего использования (a и n) целесообразно поместить в безымянное пространство имен. Как видно из примера, объекты-потоки можно сравнивать с целыми (булевыми) значениями. Если входной поток дает 0 после чтения, данные из файла не прочитаны, что означает ошибку.

В основном модуле удаляется вызов deleteA():

#include <iostream> #include "UnitFunction.h" using namespace std; int main(int argc, char* argv[]) { if (getA("input.txt") && writeTable(2, 3, 0.1, "results.txt")) cout << "OK"; else cout << "Error!"; return 0; }

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

y = (f0(x) - y0)(f1(x) - y1) ... (fn - 1(x) - yn - 1)

fi(x) = (x - x0)(x - x1) ... (x - xi - 1)(x - xi + 1) ... (x - xn - 1)

Значения элементов xi и yi вводятся из заранее подготовленного файла. Файл с именем points.txt может содержать следующие пары xi и yi:

0 1 1 2 3 3 Создаем новое консольное приложение. Добавляем в него модуль с именем, например, UnitMath. Заголовочный файл будет аналогичен файлу из предыдущего примера:

#ifndef UnitMath_h #define UnitMath_h bool getXY(char *fileName); double y(double x); bool writeTable(double left, double right, double step, char *fileName); #endif В файл UnitMath.cpp помещаем следующий код:

#include <fstream> #include <vector> #include "UnitMath.h" namespace { std::vector <double> vx; std::vector <double> vy; } bool getXY(char *fileName) { std::ifstream in(fileName); if (!in) return false; double x, y; while (in >> x >> y) { vx.push_back(x); vy.push_back(y); } return true; } double y(double x) { double result = 1; for (unsigned int i = 0; i < vy.size(); i++) { double f = 1; for (unsigned int j = 0; j < vx.size(); j++) if (i != j) f *= x - vx[j]; result *= f - vy[i]; } return result; } bool writeTable(double left, double right, double step, char *fileName) { std::ofstream out(fileName); if (!out) return false; for (double x = left; x < right + step; x += step) out << x << "\t" << y(x) << "\n"; return true; } Основной модуль программы будет выглядеть так:

#include <iostream> #include "UnitMath.h" using namespace std; int main(int argc, char* argv[]) { if (getXY("points.txt") && writeTable(0, 2, 0.5, "results.txt")) cout << "OK"; else cout << "Error!"; return 0; } Пример 4 Допустим, в строке необходимо удалить все лишние пробелы (оставить ровно по одному пробелу между словами). Это действие можно реализовать одним циклом:

#include <fstream> #include <string> using namespace std; int main(int argc, char* argv[]) { string s = "abc de f gh i"; unsigned int pos; while((pos = s.find(" ")) < s.length()) s.erase(pos, 1); cout << s; return 0; } 7 Задания на самостоятельную работу Задание 1 Реализовать программу, в которой вводится интервал значений и шаг аргумента и вычисляются значения полинома:

y = a0 + a1x + a2x2 + a3x3 + ... + anxn

Величина n и значения элементов вводятся из заранее подготовленного файла. Результирующие значения записываются в другой файл. Функции ввода данных, вычисления и вывода необходимо разместить в отдельном модуле.

Задание 2* Реализовать предыдущее задание, изменив формулу следующим образом:

y = -a0 + a1x - a2x2 + a3x3 - ... ± anxn

Задание 3 Изменить программу, в которой вводится интервал значений и шаг аргумента и вычисляются значения полинома так, чтобы она использовала вектор.

Задание 4 Реализовать программу построения таблицы функции, интерполированной с помощью полинома Лагранжа.

 

Содержание     Предыдущее занятие     Следующее занятие

 

© 2001 - 2006 Иванов Л.В.

Соседние файлы в папке OOP_C++