Лекции по C++
Функция сравнивает первые num символов строк strl и str2, не делая
различия в регистре символов. Возвращает в качестве результата сравнения
целую величину:
< 0 когда strl меньше, чем str2;
= 0 когда strl равна str2;
> 0 когда strl больше, чем str2.
Пример
char string1[] = "Borland C++";
char string2[] = "BORLAND Pascal";
i = strnicmp(string1, string2, 7);
В последнем операторе переменной i присваивается значение 0, так как
подстрока "Borland" отличается в этих строках только регистром.
Рассмотрим пример программы, в которой применяются функции сравнения строк.
Программа из листинга 5 объявляет массив строк и присваивает им значения.
Затем программа выводит исходный массив, сортирует его и выводит значения
строк отсортированного массива. |
(см. List7_5.cpp - Исходный текст программы STRING2.CPP)
Преобразование строк
Функция strlwr
Прототип функции strlwr:
char* strlwr (char *source)
Функция преобразует символы верхнего регистра в символы нижнего регистра в
строке source. Другие символы не затрагиваются. Функция возвращает
указатель на строку source.
Пример
char str[] = "HELLO THERE";
strlwr(str);
Переменная str теперь содержит строку "hello there".
Функция strupr
Прототип функции strupr:
char* strupr(char *source)
Функция преобразует символы нижнего регистра в символы верхнего регистра в
строке source. Другие символы не затрагиваются. Функция возвращает
указатель на строку source.
Пример
char str[] = "Borland C++";
strupr(str);
Переменная str теперь содержит строку "BORLAND С ++".
Обращение строк
Библиотека STRING.H предлагает функцию strrev для записи символов в строке
в обратном порядке.
Функция strrev
Прототип функции strrev:
char* strrev(char *str)
Функция обращает порядок символов в строке str и возвращает указатель на
строку str. char str[] = "Hello";
strrev(str) ;
cout « str;
Будет выведено "olleH".
Рассмотрим программу, которая манипулирует символами в строке.
List7_6.cpp показывает исходный текст программы STRING3.CPP. Программа
выполняет следующие задачи:
. Запрашивает у вас ввод строки
. Отображает ваш ввод
. Выводит вашу строку в нижнем регистре
. Выводит вашу строку в верхнем регистре
. Отображает символы, которые вы ввели, в обратном порядке
. Выводит сообщение, что ваш ввод не содержит символов верхнего регистра,
если это так
. Выводит сообщение, что ваш ввод не содержит символов в нижнем регистре,
если это так
. Выводит сообщение, что ваша строка симметрична, если это так
Поиск символов
Библиотека STRING.H предлагает ряд функций для поиска символов в строках.
Это функции strchr, strrchr, strspn, strcspn и strpbrk. Они осуществляют
поиск в строках символов и простых символьных шаблонов.
Функция strchr
Функция strchr определяет первое вхождение символа в строку.
Прототип функции strchr:
char* strchr(const char *target, int c)
Функция находит первое вхождение символа с в строку target. Функция
возвращает указатель на символ в строке target, который соответствует
заданному образцу с. Если символ с в строке не обнаруживается, функция
возвращает 0.
Пример
char str[81] = "Borland C++";
char *strPtr;
strPtr = strchr(str, '+');
Указатель strPtr теперь содержит адрес подстроки "++" в строке str.
Функция strrchr
Функция strrchr определяет последнее вхождение символа в строке.
Прототип функции strrchr:
char* strrchr(const char *target, int c)
Функция находит последнее вхождение символа с в строку target. Функция
возвращает указатель на символ в строке target, который соответствует
заданному образцу с. Если символ с в строке не обнаруживается, функция
возвращает 0.
Пример
char str[81] = "Borland C++ is here";
char* strPtr;
strPtr = strrchr(str, '+');
Указатель strPtr теперь указывает на подстроку "+ is here " в строке str.
Функция Strspn
Функция strspn возвращает число символов с начала строки, совпадающих с
любым символом из шаблона.
Прототип для функции strspn:
size_t strspn(const char *target, const char *pattern)
Функция strspn возвращает число символов от начала строки target,
совпадающих с любым символом из шаблона pattern.
Пример
char str[] = "Borland C++ 5";
char substr[] = "narlBod";
int index;
index = strspn(str, substr);
Этот оператор присваивает 8 переменной index, потому что первые восемь
символов из str содержатся в подстроке substr.
Функция strcspn
Функция strcspn просматривает строку и выдает число первых символов в
строке, которые не содержатся в шаблоне.
Прототип функции strcspn:
size_t strcspn(const char* str1, const char* str2)
Функция strcspn просматривает строку str1 и выдает длину подстроки,
отсчитываемой с начала строки, символы которой полностью отсутствуют в
строке str2.
Пример
char strng[] = "The rain in Spain";
int i = strcspn(strng, " in");
Этот пример возвращает 3 (расположение первого пробела в строке strng)
переменной i.
Функция strpbrk
Функция strpbrk просматривает строку и определяет первое вхождение любого
символа из образца.
Прототип функции strpbrk:
char* strpbrk(const char* target, const char* pattern)
Функция strpbrk ищет в строке target первое вхождение любого символа из
образца pattern. Если символы из образца не содержатся в строке, функция
возвращает 0.
Пример
char *str = "Hello there how are you";
char *substr = "hr";
char *ptr;
ptr = strpbrk(str, substr);
cout « ptr « endl;
Вы увидите на экране строку "here how are you", потому что 'h' встречается
в строке str раньше, чем 'r'.
Поиск строк
Библиотека функций STRING.H предлагает для поиска подстроки в строке
функцию strstr.
Функция strstr
Прототип функции strstr:
char* strstr(const char *str, const char *substr);
Функция ищет в строке str первое вхождение подстроки substr. Функция
возвращает указатель на первый символ найденной в строке str подстроки
substr. Если строка substr не обнаружена в строке str, функция возвращает
0.
Пример
char str[] = "Hello there! how are you";
char substr[] = "how";
char *ptr;
ptr = strstr (str, substr);
cout « ptr « endl ;
Это приведет к выводу строки "how are you", поскольку в строке str ,
была обнаружена подстрока "how". Указатель ptr содержит адрес остатка
первоначальной строки, начинающегося с подстроки "how".
Функция strtok
Библиотека функций для работы со строками имеет функцию strtok, которая
дает вам возможность разбить строку на подстроки на основании заданного
набора символов-ограничителей.
Подстроки иногда называются лексемами.
Прототип функции strtok:
char* strtok(char *target, const char * delimiters);
Функция разбивает строку на лексемы, согласно символам-ограничителям,
заданным в параметре delimeters. В следующем примере показано, как работать
с этой функцией и как получать лексемы, на которые была разбита строка.
Функция strtok вводит символ '\0' после каждой лексемы. (Опять же не
забудьте сохранить копию вашей строки в другой строковой переменной.)
Пример
#include // см. файл Ex02.cpp
#include
int main()
{
char *str = "(Base_Cost + Profit) * Margin";
char *tkn = "+*()";
char *ptr = str;
printf("%s\n", str);
// Первый вызов функции
ptr = strtok(str, tkn);
printf("Лексемы этой строки: %s", ptr);
while (ptr)
{
// Первый аргумент должен быть равен нулю
if ((ptr = strtok(0, tkn)) != 0)
printf (",%s", ptr);
}
printf("\n");
return 0;
}
В результате выполнения этой программы на экран выводятся следующие строки:
(Base_Cost + Profit) * Margin
Лексемы этой строки: Base_Cost, Profit, Margin
Рассмотрим пример программы поиска символов и строк. Листинг 7
(List7_7.cpp) содержит исходный текст программы STRING4.CPP. Программа
выполняет следующие задачи:
. Запрашивает у вас ввод основной строки
. Запрашивает строку поиска
. Предлагает вам ввести символ поиска
. Выводит линейку цифр и основную строку
. Выводит номер символа в основной строке, с которого начинается строка
поиска *
. Выводит номер символа в основной строке, совпавшего с символом поиска.
Основы объектно-ориентированного программирования СИНТАКСИС ОСНОВНЫХ
КОНСТРУКЦИЙ
Объявление базовых классов
В С++ мы имеем возможность объявлять классы, которые инкапсулируют
элементы-данные и функции-элементы. Эти функции изменяют и позволяют
обращаться к значениям данных-элементов и выполняют другие задачи.
Базовый класс
Базовый класс определяется следующим образом (синтаксис):
class className
{
private:
protected:
public:
};
Пример 1:
class point
{
protected:
double х;
double у;
public:
point(double xVal, double yVal);
double getX();
double getY();
void assign(double xVal, double yVal);
point& assign(point &pt);
};
Разделы класса
Классы С++ имеют три различных уровня доступа к своим элементам - как к
данным, так и к функциям:
- Закрытые (частные) элементы
- Защищенные элементы
- Открытые элементы
К данным в закрытом разделе имеют доступ только функции-элементы
класса.
Классам-потомкам запрещен доступ к закрытым данным своих 6азовых
классов.
К данным в защищенной секции имеют доступ функции-элементы класса и
классов-потомков. Данные из открытой секции находятся в области видимости
функций-элементов класса, функций-элементов классов-потомков, и вообще
доступны кому угодно.
Существуют следующие правила для разделов класса:
1. Разделы могут появляться в любом порядке.
2. Один и тот же раздел можно определять несколько раз.
3. Если не определен ни один раздел, компилятор (по умолчанию) объявляет
все элементы закрытыми.
4. Помещать данные-элементы в открытый раздел следует только в том случае,
если в этом есть необходимость, например, если это упрощает вашу задачу.
Обычно элементы-данные помещаются в защищенный раздел, чтобы к ним
имели доступ функции-элементы классов-потомков.
5. Используйте для изменения значений данных и доступа к ним функции-
элементы. При использовании функции вы можете осуществлять проверку
данных и, если нужно, изменять другие данные.
6. Класс может иметь несколько конструкторов.
7. Класс может иметь только один деструктор, который должен объявляться в
открытом разделе класса.
8. Функции-элементы (в том числе конструкторы и деструкторы), состоящие из
нескольких операторов, должны определяться вне объявления класса.
Определение функции может содержаться в том же файле, в котором
определяется класс. Это напоминает порядок работы с обычными функциями:
задание прототипа и определение функции.
Конструкторы являются специфическим типом функций-элементов, тип
возвращаемого значения для которых не указывается, а имя должно совпадать с
именем класса-хозяина. Вызываются они при создании нового представителя
класса. Деструктор вызывается для разрушения представителя класса.
При определении функции-элемента вы должны указать ее имя и имя ее
класса. Сначала вы должны Сначала необходимо указать имя класса (т.н.
квалификатор), а затем, через два двоеточия (::), имя функции. В качестве
примера рассмотрим такой класс:
class point
{
protected:
double x;
double y;
public:
point(double xVal, double yVal);
double getX();
// другие функции-элементы
};
Определения конструктора и функций-элементов должны выглядеть так
point::point (double xVal, double yVal)
{
// операторы
}
double point::getX()
{
// операторы
}
После того, как вы объявили класс, вы можете использовать имя класса в
качестве спецификатора типа данных при объявлении представителей класса.
Синтаксис объявления тот же, что и при объявлении переменной.
В листинге 8.1 приведен исходный текст программы RECT.CPP. Программа
предлагает вам ввести длину и ширину прямоугольника (в данном примере
прямоугольник является объектом). Затем программа выводит значения длины,
ширины и площади определенного вами прямоугольника.
Конструкторы
Конструкторы и деструкторы в С++ вызываются автоматически, что
гарантирует правильное создание и разрушение объектов класса.
Общий вид (синтаксис) объявления конструктора:
class className
{
public:
className(); // конструктор по умолчанию
className(const className &c); // конструктор копии
className(); // остальные конструкторы
};
Пример 2:
// Замечание: Здесь только объявление класса без описания объявленных
// функций-параметров
class point
{
protected:
double x;
double y;
public:
point();
point(double xVal, double yVal);
point(const point &pt);
double getX();
double getY();
void assign(double xVal, double yVal);
point& assign(point &pt);
};
int main()
{
point p1;
point p2(10, 20);
point p3(p2);
p1.assign(p2);
cout ] parentClass
{
private:
protected:
public:
};
Пример 4 объявления класса Rectangle и класса-потомка Box:
class Rectangle
{
protected:
double length;
double width;
public:
Rectangle(double len, double wide);
double getLength() const;
double getWidth() const;
double assign(double len, double wide);
double calcArea();
};
class Вох : public Rectangle
{
protected:
double height;
public:
Box(double len, double wide, double height);
double getHeight () const;
assign(double len, double wide, double height);
double calcVolume();
};
(см. LIST8-3.CPP)
Виртуальные функции
Мы уже упоминали о полиморфизме - важной особенности объектно-
ориентированного программирования. Рассмотрим следующий пример (6):
#include
class X
{
public:
double A(double x) { return x * x; }
double B(double x) { return A(x) / 2; }
};
class Y : public X
{
public:
double A(double x) { return x * x * x; }
};
int main ()
{
Y y;
cout );
};
class className2 : public className1
{
// функции-элементы
virtual returnType functionName();
};
Пример 7, показывающий, как при помощи виртуальных функций можно
реализовать полиморфное поведение классов X и Y:
#include
class X
{
public:
virtual double A(double x) { return x * x; }
double B (double x) { return A(x) / 2; }
};
class Y : public X
{
public:
virtual double A(double x) { return x * x * x; }
};
main()
{
Y y;
cout
class A
{
public:
A() {}
virtual void foo(char c)
{ cout );
};
Пример 9:
class String
{
protected:
char *str;
int len;
public:
String();
~String();
// другие функции-элементы
friend String& append(String &str1, String &str2);
friend String& append(const char* str1, String &str2);
friend String& append(String &str1, const char* str2);
};
Дружественные функции могут решать задачи, которые при помощи
функций-элементов решаются с трудом, неуклюже или не могут быть решены
вообще.
Рассмотрим простой пример использования дружественных функций. Текст
программы FRIEND.CPP представлен в листинге 8.5. Программа следит за
памятью, отведенной для хранения массива символов. Эта программа - первый
шаг к созданию класса string.
Операции и дружественные операции
Последняя программа использовала функции-элементы и дружественную
функцию, которые реализовали действия, выполняемые в стандартных типах с
помощью операций вроде = и +. Подход типичен для языков C и Pascal, потому
что эти языки не поддерживают определяемые пользователем операции. В
отличии от них C++ позволяет вам объявлять операции и дружественные
операции. Эти операции включают в себя: +, -, *, /, %, ==, !=, =,
>, +=, -=, *=, /=, %=, [],
(), >. Обратитесь к описанию языка C++, где обсуждаются детали
определения этих операций. С++ трактует операции и дружественные операции
как специальный тип функций-элементов и дружественных функций.
Общий синтаксис для объявления операций и дружественных операций:
class className
{
public:
// конструкторы и деструктор
// функции-элементы
// унарная операция
returnType operator operatorSymbol();
// бинарная операция
returnType operator operatorSymbol(operand);
// унарная дружественная операция
friend returnType operator operatorSymbol(operand);
// бинарная дружественная операция
friend returnType operator operatorSymbol(firstOperand, secondOperand);
};
Пример 10:
class String
{
protected:
char *str;
int num;
public:
String();
~String();
// другие функции-элементы
// операция присваивания
String& operator =(String& s);
String& operator +=(String& s);
// операции конкатенации
friend String& operator +(String& s1, String& s2);
friend String& operator +(const char* s1, String& s2);
friend String& operator +(String& s1, const char* s2);
// операции отношения
friend int operator >(String& s1, String& s2);
friend int operator =>(String& s1, String& s2);
friend int operator
class X
{
public:
double A(double x) { return x * x; }
double B(double x) { return A(x) / 2; }
};
class Y : public X
{
public:
double A(double x) { return x * x * x; }
};
int main ()
{
Y y;
cout );
};
class className2 : public className1
{
// функции-элементы
virtual returnType functionName();
};
Пример 7, показывающий, как при помощи виртуальных функций можно
реализовать полиморфное поведение классов X и Y:
#include
class X
{
public:
virtual double A(double x) { return x * x; }
double B (double x) { return A(x) / 2; }
};
class Y : public X
{
public:
virtual double A(double x) { return x * x * x; }
};
main()
{
Y y;
cout
class A
{
public:
A() {}
virtual void foo(char c)
{ cout );
};
Пример 9:
class String
{
protected:
char *str;
int len;
public:
String();
~String();
// другие функции-элементы
friend String& append(String &str1, String &str2);
friend String& append(const char* str1, String &str2);
friend String& append(String &str1, const char* str2);
};
Дружественные функции могут решать задачи, которые при помощи
функций-элементов решаются с трудом, неуклюже или не могут быть решены
вообще.
Рассмотрим простой пример использования дружественных функций.
Текст программы FRIEND.CPP представлен в листинге 8.5. Программа следит за
памятью, отведенной для хранения массива символов. Эта программа - первый
шаг к созданию класса string.
Операции и дружественные операции
Последняя программа использовала функции-элементы и дружественную
функцию, которые реализовали действия, выполняемые в стандартных типах с
помощью операций вроде = и +. Подход типичен для языков C и Pascal, потому
что эти языки не поддерживают определяемые пользователем операции. В
отличии от них C++ позволяет вам объявлять операции и дружественные
операции. Эти операции включают в себя: +, -, *, /, %, ==, !=, =,
>, +=, -=, *=, /=, %=, [], (), >. Обратитесь к описанию языка C++,
где обсуждаются детали определения этих операций. С++ трактует операции и
дружественные операции как специальный тип функций-элементов и
дружественных функций.
Общий синтаксис для объявления операций и дружественных операций:
class className
{
public:
// конструкторы и деструктор
// функции-элементы
// унарная операция
returnType operator operatorSymbol();
// бинарная операция
returnType operator operatorSymbol(operand);
// унарная дружественная операция
friend returnType operator operatorSymbol(operand);
// бинарная дружественная операция
friend returnType operator operatorSymbol(firstOperand, secondOperand);
};
Пример 10:
class String
{
protected:
char *str;
int num;
public:
String();
~String();
// другие функции-элементы
// операция присваивания
String& operator =(String& s);
String& operator +=(String& s);
// операции конкатенации
friend String& operator +(String& s1, String& s2);
friend String& operator +(const char* s1, String& s2);
friend String& operator +(String& s1, const char* s2);
// операции отношения
friend int operator >(String& s1, String& s2);
friend int operator =>(String& s1, String& s2);
friend int operator , если вы ссылаетесь
на них при помощи указателей.
Complex *pC;
pC = new Complex;
// операции с объектом, к которому обращаются по указателю pC
delete pC;
или
Complex *pC = new Complex;
// операции с объектом, к которому обращаются по указателю pC
delete pC;
Контрольные вопросы
1. Найдите ошибку в следующем объявлении класса:
class String {
char *str;
unsigned len;
String ();
String(const String& s);
String(unsigned size, char = ' ');
String(unsigned size);
String& assign(String& s);
~String();
unsigned getLen() const;
char* getString();
// другие функции-элементы
};
2. Найдите ошибку в следующем объявлении класса:
class String {
protected:
char *str;
unsigned len;
public:
String();
String(const char* s);
String(const String& s);
String(unsigned size, char = ' ');
String(unsigned size);
~String();
// другие функции-элементы
3. Верно или нет? Следующий оператор, который создает объект s класса
String, объявленного ранее, является правильным:
s = String("Hello Borland C++");
4. Если в программе OPERATOR.CPP вы следующим образом измените объявления
объектов, будет ли программа компилироваться без ошибок?
String s1 = String("Kevin");
String s2 = String(" Нау");
String s3 = s1;
ФАЙЛОВЫЕ ОПЕРАЦИИ ВВОДА/ВЫВОДА
Сегодняшний урок посвящен файловым операциям ввода/вывода с
использованием библиотеки управления потоками C++. У вас есть две
возможности: либо использовать функции файлового ввода/вывода, описанные в
заголовочном файле STDIO.H, либо функции stream-библиотеки C++. Каждая из
этих библиотек имеет множество мощных и удобных функций. Сегодня будут
представлены основные операторы, которые позволят вам читать и записывать
данные в файл. Вы изучите следующие темы:
Стандартные функции потоков ввода/вывода
- Последовательный ввод/вывод потока с текстовой информацией
- Последовательный ввод/вывод двоичных данных
- Прямой доступ к потоку двоичных данных
Stream-библиотека C++
Stream-библиотека (известная также как библиотека iostream) выполнена
в виде иерархии классов, которые описаны в нескольких заголовочных файлах.
Файл IOSTREAM.H, используемый до сих пор, - это только один из них. Другой,
который будет интересен в этой главе, - FSTREAM.H. Файл IOSTREAM.H
поддерживает основные классы для ввода/вывода потока. Файл FSTREAM.H
содержит определения для основных классов файлового ввода/вывода.
Существуют дополнительные файлы библиотеки ввода/вывода, в которых
имеются более специализированные функции ввода/вывода.
ОБЩИЕ ФУНКЦИИ ПОТОКОВОГО ВВОДА/ВЫВОДА
В этом разделе представлены функции-элементы ввода/вывода, являющиеся
общими как для последовательного, так и для прямого доступа. Эти функции
включают open, close, good и fail в дополнение к операции !. Функция open
открывает файловый поток для ввода, вывода, добавления, а также для ввода и
вывода. Эта функция позволяет указывать тип данных, с которыми вы
собираетесь работать: двоичные или текстовые.
При работе с файловым вводом/выводом очень важно знать различие между
текстовым и двоичным режимами. Текстовый режим предназначен для текстовых
файлов, в которых имеются строки обычного текста. Двоичный режим
используется для любых других и особенно для файлов, которые сохраняются в
форматах, неудобных для чтения человеком.
Существуют некоторые особые тонкости, связанные с файлами текстового
режима, на которые следует обратить особое внимание и запомнить. Первая из
них - символ EOF (26 в коде ASCII или Ctrl+Z) - представляет собой метку
(символ) конца файла. В текстовом режиме, где встречается символ EOF,
система C++ низкого уровня автоматически продвигается к концу файла; вы
ничего не можете прочитать после специального символа. Это может вызвать
проблемы, если такой специальный символ окажется в середине файла.
Другая особенность текстового режима заключается в том, как
интерпретируются строки текстового файла. Каждая строка заканчивается
последовательностью конца строки (EOL). На компьютерах PC и совместимых с
ними EOL-последовательность представлена двумя символами кода ASCII: CR (13
в коде ASCII или Ctrl+M) и LF (10 в коде ASCII или Ctrl+J). Эта CRLF-
последовательность используется функциями чтения и записи текстовой строки,
которые автоматически, вставляют ее в файл или удаляют из него. Заметьте,
что на большинстве других, систем (UNIX и Macintosh) EOF просто является
символом LF.
Функция-компонент open
Прототип функции open
void open (const char* filename, int mode, int m = filebuf::openprot);
Параметр filename задает имя открываемого файла. Параметр mode
указывает режим ввода/вывода. Далее следует список аргументов для mode,
описанных в заголовочном файле FSTREAM.H:
- in открыть поток для ввода,
- out открыть поток для вывода,
- ate установить указатель потока на конец файла,
- app открыть поток для добавления,
- trunk удалить содержимое файла, если он уже существует (bc++5),
- nocreate инициировать ошибку, если уже не существует,
- noreplace инициировать ошибку, если файл уже существует,
- binary открыть в двоичном режиме.
Пример 1.
// открыть поток для ввода
fstream f;
f.open("simple.txt", ios::in);
// открыть поток для вывода fstream f;
fstream f;
f.open ("simple.txt", ios::out);
// открыть поток ввода/вывода для двоичных данных fstream f;
fstream f;
f.open("simple.txt", ios::in | ios::out | ios::binary);
Внимание: Классы файловых потоков предусматривают конструкторы,
которые выполняют действия (и имеют такие же параметры) функции-компонента
open.
Функция close закрывает поток и освобождает использовавшиеся ресурсы.
Эти ресурсы включают буфер памяти для операции потокового ввода/вывода.
Функция-компонент close
Прототип для функции close:
void close();
Пример 2.
fstream f;
// открыть поток
f.open ( "simple.txt", ios:: in);
// работа с файлом
// закрыть поток
f.close ();
Stream-библиотека C++ включает в себя набор основных функций, которые
контролируют состояние ошибки потоковой операции. Эти функции включают
следующие:
1. Функция good() возвращает ненулевое значение, если при выполнении
потоковой операции не возникает ошибки. Объявление функции good: int
good();
2. Функция fail() возвращает ненулевое значение, если при выполнении
потоковой операции возникает ошибка. Объявление функции fail: int fail();
3. Перегруженная операция ! применяется к экземпляру потока для определения
состояния ошибки.
Stream-библиотека C++ предоставляет дополнительные функции для
установки и опроса других аспектов и типов ошибок потока.
ПОСЛЕДОВАТЕЛЬНЫЙ ТЕКСТОВЫЙ ПОТОК ВВОДА/ВЫВОДА
Функции и операции последовательного текстового ввода/вывода являются
довольно простыми. Вы уже имели дело со многими из них в предыдущих уроках.
Эти функции и операции включают:
- Операция извлечения из потока > читает символы потока.
- Функция getline читает строку из потока.
Функция-элемент getline
Прототипы функции-элемента getline:
istream& getline (char* buffer, int size, char delimiter = '\n');
istream& getline (signed char* buffer, int size, char delimiter = '\n');
istream& getline (unsigned char* buffer, int size, char delimiter = '\n');
Параметр buffer - это указатель на строку, принимающую символы из
потока. Параметр size задает максимальное число символов для чтения.
Параметр delimiter указывает разделяющий символ, который вызывает
прекращение ввода строки до того, как будет введено количество символов,
указанное в параметре size. По умолчанию параметру delimiter присваивается
значение '\n'.
Пример 3.
fstream f;
char textLine[MAX];
f.open("sample.txt", ios::in);
while (!f.eof()) {
f.getline(textLine, MAX);
cout пробелы.
- Записывает эти строки в выходной файл и также в стандартное окно
вывода.
Листинг 10.1. Исходный код программы TRIM.CPP
// C++ программа демонстрации последовательного файлового
// ввода/вывода
Программа в листинге 10.1 не объявляет никаких классов, вместо этого
она фокусируется на использовании файловых потоков для ввода и вывода
текста. Эта программа описывает функции trimStr, getInputFilename,
getOutputFilename, processLines и обязательную функцию main.
Функция trimStr вычищает пробелы в строках, передаваемых
через параметр s. Эта функция объявляет переменную i и присваивает ей
индекс символа, находящегося сразу за завершающим нулем. Функция использует
цикл while, начинающийся в строке 14, чтобы выполнить обратное сканирование
символов в строке s до первого символа, не являющегося пробелом. Оператор в
строке 16 присваивает завершающий нуль символу, стоящему справа от
последнего символа, не являющегося пробелом, в строке s.
Функция getInputFilename получает имя входного файла и открывает
соответствующий файловый поток. Параметр inFile передает это имя вызывающей
функции. Ссылочный параметр f передает открытый входной поток вызывающей
функции. Функция getInputFilename объявляет локальный флажок ok и
использует цикл do-while (строки с 23 по 34), чтобы открыть входной файл.
Строка 25 содержит первый оператор тела цикла, в котором флажок ok
инициализируется значением true. Оператор вывода в строке 26 запрашивает
ввод имени входного файла; в строке 27 с помощью вызова функции getline это
имя принимается и сохраняется в переменной inFile. Оператор в строке 28
пытается открыть входной файл, используя параметр потока f. Оператор open
использует значение ios::in для указания на то, что входной текстовый файл
был открыт. Если вызов возвращает ошибку, оператор if (строка 29) определит
это, сообщит об ошибке открытия файла пользователю и присвоит переменной ok
значение false. При значении ok, равном true, цикл do-while будет
выполняться и сохранять ответы пользователя до тех, пока не произойдет
успешное открытие файла.
Функция getOutputFilename подобна функции getInputFilename в попытках
получить имя файла от пользователя и открыть файл. Однако в этом случае
файл открывается на запись и его имя сравнивается с именем входного файла.
В строке 47 проверяется совпадение имен входного и выходного файлов, и,
если пользователь ввел одно и то же имя, ему предлагается снова ввести имя
выходного файла.
Функция processLines читает строки их входного файлового потока,
приводя их в порядок и записывая в выходной файловый поток. Параметры fin и
font передают файловые указатели входного и выходного потоков,
соответственно. Эта функция объявляет локальную строковую переменную line и
использует (строки с 69 по 74) цикл while для обработки текстовых строк.
Предложение while содержит вызов функции getline, которая читает следующую
строку входного потока fin и присваивает переменной line содержимое этой
строки. В теле этого цикла просто вызывается функция trimStr, а затем line
передается в потоки fout и cout. Заметьте, что команды для получения строки
текста из файла и передачи ее в файл в точности совпадают с теми, которые
могли бы использоваться для получения текста с экрана и передачи его на
экран. Это происходит потому, что используемые вами cout и cin - на самом
деле только потоки, которые открываются автоматически и работают
непосредственно с
экраном.
Функция main, как обычно со всеми уже описанными функциями, довольно
простая. Она только объявляет переменные файловых потоков fin, fout и
inFile, outFile для сохранения имен этих потоков. Далее входной и выходной
файлы открываются в функциях getInputFilename и getOutputFilename. Наконец,
функция processLine приводит в порядок и копирует содержимое файла, а
функция-компонент close вызывается для каждого из потоков.
ПОСЛЕДОВАТЕЛЬНЫЙ ДВОИЧНЫЙ ФАЙЛОВЫЙ ВВОД/ВЫВОД
Stream-библиотека C++ имеет перегруженные потоковые функции-элементы
write и read для последовательного двоичного файлового ввода/вывода.
Функция write посылает ряд байт в выходной поток. Эта функция может
записывать любую переменную или экземпляр в поток.
Функция-элемент write
Прототип перегруженной функции-элемента:
ostream& write(const char* buff, int num);
ostream& write(const signed char* buff, int num);
ostream& write(const unsigned char* buff, int num);
Параметр buff - это указатель на буфер, содержащий данные, которые
будут посылаться в выходной поток. Параметр num указывает число байт в
буфере, которые передаются в этот поток.
Пример 4.
const MAX = 80;
char buff[MAX+1] = "Hello World!";
int len = strlen (buff) + 1;
fstream f;
f.open("CALC.DAT", ios::out | ios::binary);
f.write((const unsigned char*) &len, sizeof(len));
f.write((const unsigned char*) buff, len);
f.close();
В этом примере открывается файл CALC.DAT, записывается целое,
содержащее число байт в строке и записывается сама строка перед тем, как
файл закрывается.
Функция read считывает некоторое количество байт из входного потока.
Эта функция может считывать любую переменную или экземпляр из потока.
Функция-элемент read
Прототип перегруженной функции-элемента read:
ostream& read(char* buff, int num);
ostream& read(signed char* buff, int num);
ostream& read(unsigned char* buff, int num);
Параметр buff - это указатель на буфер, который принимает данные из
входного потока. Параметр num указывает число считываемых из потока байт.
Пример 5.
const MAX = 80;
char buff [MAX+1];
int len;
fstream f;
f.open("CALC.DAT", ios::in | ios::binary);
f.read((unsigned char*) &len, sizeof(len));
f.read((unsigned char*) buff, len);
f.close();
В этом примере считывается информация, записанная в предыдущем
примере.
Рассмотрим пример, выполняющий последовательный двоичный потоковый
ввод/вывод. В листинге 10.2 представлен исходный код программы ARRAY.CPP.
Эта программа объявляет класс, который моделирует численный динамический
массив. Операции ввода/вывода позволяют программе читать и писать как
отдельные элементы массива, так и целый массив в двоичный файл. Эта
программа создает массивы arr1, arr2 и аrrЗ, а затем выполняет следующие
задачи:
- Присваивает значения элементам массива arr1. (Этот массив имеет 10
элементов).
- Присваивает значения элементам массива аrrЗ. (Этот массив имеет 20
элементов).
- Отображает значения массива arr1.
- Записывает элементы массива arr1 в файл ARRAY1.DAT (по одному за
операцию).
- Читает элементы массива arr1 из этого файла в массив arr2 (по
одному за операцию). (Массив arr2 имеет 10 элементов, то есть он
одного размера с массивом arr1).
- Отображает элементы массива arr2.
- Отображает элементы массива аrrЗ.
- Записывает элементы массива аrrЗ в файл ARRAY3.DAT, все сразу.
- Читает (все сразу) данные из файла ARRAY3.DAT и сохраняет их в
массиве arr1.
- Отображает значения массива arr1. (Выход показывает, что массив
arr1 имеет тот же размер, что и массив arr3).
Листинг 10.2. Исходный код программы ARRAY.CPP
// C++ демонстрация последовательного двоичного
// ввода/вывода
Программа листинга 10.2 объявляет версию класса Array, который похож
на приводимый в главе 8 в листинге 8.2. Основное отличие в том, что здесь
мы использовали operator[ ] для замены и функции store, и recall. Эта
операция проверяет правильность указания индекса и возвращает значение в
badIndex, если аргумент выходит за диапазон массива. В дополнение к
operator[ ] мы добавили функции-элементы writeElem, readElem, writeArray и
readArray для выполнения последовательного двоичного файлового
ввода/вывода. Мы также добавили функции resize и getPrt как вспомогательные
функции соответственно для изменения размера массива и для возвращения
указателя на соответствующий элемент массива.
Обратите внимание также на копирующий конструктор в строке 29, и
особенно на то, что он действительно ничего не определяет. Это - прием,
который может быть использован для защиты других функций или классов от их
автоматического копирования, когда вы знаете, что это плохо (в этом случае
потому, что используются динамические данные, содержимое которых необходимо
копировать).
Функция writeElem, определенная в строках с 43 по 49, записывает
одиночные элементы массива в выходной поток. Параметр os представляет
выходной поток. Параметр index определяет элемент массива для записи.
Функция writeElem возвращает true, если индекс правильный и если операция
по выводу осуществляется без ошибок. После того, как writeElem записывает
элемент массива, внутренний указатель потока продвигается в следующее
положение.
Функция readElem, определяемая в строках с 51 по 57, считывает
одиночный элемент массива из входного потока. Параметр Is представляет
входной поток. Параметр index определяет индекс элемента массива для
чтения. Эта функция возвращает true, если индекс массива правильный и если
операция по вводу осуществляется без ошибок. После того, как readElem
считывает элемент массива, внутренний указатель потока продвигается в
следующее положение.
Функции writeElem и readElem позволяют экземпляру класса
соответственно писать и читать элементы данных из различных потоков.
Функция writeArray, определенная в строках с 59 по 69, записывает все
элементы массива в двоичный файл. Параметр filename определяет имя
выходного файла. Функция открывает выходной поток и записывает значение
элемента класса size, а затем и элементы динамического массива. Функция
writeArray возвращает true, если массив в поток записан успешно. Иначе она
возвращает false. Эта функция открывает локальный выходной поток, используя
потоковую функцию open и передавая ей имя файла и режим ввода/вывода. Режим
ввода/вывода представляет собой выражение ios::out|ios::binary, которое
определяет, что поток открывается только для вывода двоичных записей. Эта
функция дважды вызывает потоковую функцию write - первый раз для записи
компонента класса size, второй - для записи элементов динамического
массива.
Функция readArray, определенная в строках с 71 по 83, читает все
элементы массива из двоичного файла. Параметр filename определяет имя
входного файла. Функция открывает входной поток и считывает значение
компонента класса size, а затем считывает элементы динамического массива.
Функция readArray возвращает true, если она успешно считывает массив из
потока. В противном случае, возвращается false. Функция открывает локальный
входной поток, используя потоковую функцию open и передавая ей имя файла и
аргументы режима ввода/вывода. Аргумент режима ввода/вывода - это выражение
ios::in | ios::binary, которое определяет, что поток открыт только для
двоичного ввода. Функция делает два вызова потоковой функции read, первый -
для чтения элемента класса size, и второй - для чтения элементов
динамического массива. Другим свойством функции readArray является то, что
она изменяет размер экземпляра класса Array для настройки его в
соответствии с данными двоичного файла, вызывая функцию-элемент resize. Это
означает, что динамический массив, который доступен посредством экземпляра
класса, может либо уменьшаться, либо расширяться в зависимости от размера
массива, сохраняемого в файле.
Функция-элемент resize, которая начинается в строке 65, на самом деле
очень простая. Она проверяет, является ли требуемый размер тем же, что и
установленный ранее. Если нет, то память, зарезервированная функцией
dataPtr, освобождается, а затем создается новая область памяти,
соответствующая новому размеру. Этот новый размер присваивается компоненту
класса size.
Функция dispArray чаще всего является функцией-элементом, но я решил
сделать ее здесь обычной функцией, чтобы лучше показать, как использование
функции operator[ ] позволяет тем, кто работает с классом Array, обращаться
к нему таким же способом, какой они применяют к элементам стандартного
массива. В этом случае есть простой цикл for, который выполняется для
каждого элемента arr и отображает его содержимое.
Наконец, мы подходим к функции main (строка 104). Обычно она в
основном
только запускает функции, которые уже были созданы, для выполнения
следующих задач:
- Объявляет (строка 108) три экземпляра класса Array с именами arr1,
arr2 и аrr3. (Первые два экземпляра имеют тот же самый размер
динамического массива, заданный константой SIZE1, в то время как
аrr3 имеет больший размер, определенный константой SIZE2).
- Объявляет (строка 111) файловый поток f и открывает его (используя
конструктор потока) для доступа к файлу ARRAY1.DAT в двоичном
режиме.
- Использует циклы for (строки с 114 по 116), чтобы произвольно
присвоить значения экземплярам arr1 и аrr3.
- Отображает элементы экземпляра arr1 (строка 119).
- Записывает элементы массива arr1 в выходной файловый поток f,
используя цикл for (строка 122) для вызова функции-компонента
writeElem с выходным файловым потоком f и переменной цикла i.
- Закрывает файловый поток f, вызывая функцию-элемент close этого
потока.
- Открывает (строка 127) файловый поток f для доступа к файлу
ARRAY1.DAT. (На это раз сообщение open определяет режим двоичного
ввода)
- Считывает элементы в arr2 (которому до сих пор не присваивались
никакие значения) из входного файлового потока f, используя цикл
for (строка 128).
- Закрывает входной поток (строка 130). D Отображает элементы
экземпляров arr2 и аrr3 (строки 132 и 133).
- Записывает все содержимое аrr3, вызывая функцию-компонент
writeArray. (Функция writeArray имеет аргумент имени файла
ARRAY3.DAT).
- Считывает массив файла ARRAY3.DAT в экземпляр arr1, вызывая функцию-
компонент readArray и передавая ей в качестве аргумента имени файла
ARRAY3.DAT.
- Отображает новые элементы экземпляра arr1.
Файловый ввод/вывод с прямым доступом
Файловые операции ввода/вывода прямого доступа также используют
потоковые функции-элементы read и write, представленные в предыдущем
разделе. Stream-библиотека имеет ряд функций, позволяющих вам передвигать
указатель потока в любое необходимое положение. Функция-элемент seekg -
одна из таких функций.
Функция-элемент seekg
Прототип для перегруженной функции-компонента seekg:
istream& seekg(long pos);
istream& seekg(long offset, seek_dir dir);
Параметр pos в первой версии определяет абсолютное положение байта в
потоке. Во второй версии параметр offset определяет относительное смещение,
в зависимости от аргумента dir. Аргументы для последнего параметра:
ios::beg С начала файла
ios::cur С текущей позиции файла
ios::end С конца файла
Пример
const BLOCK SIZE = 80
char buff[BLOCK_SIZE] = "Hello World!";
f.open("CALC.DAT", ios::in | ios::out | ios::binary);
f.seekg(3 * BLOCK_SIZE); // продвинутся к блоку 4
f.read((const unsigned char*)buff, BLOCK_SIZE);
cout < buff < endl;
fclose ();
Виртуальный массив - это базирующийся на диске массив, который
сохраняет строки фиксированного размера на диске. Вы можете рассматривать
его как обычный массив, который вместо сохранения своих элементов в памяти
записывает их в дисковый файл.
Рассмотрим пример файлового ввода/вывода прямого доступа. В листинге
10.3 приведен исходный код программы VIRTUAL.CPP и реализует виртуальный
массив. Программа выполняет следующие задачи:
- Использует внутренний список имен для создания объекта виртуального
массива.
- Отображает элементы неупорядоченного объекта виртуального массива.
- Сортирует элементы объекта виртуального массива.
- Отображает элементы сортированного объекта виртуального массива.
-
Листинг 10.3. Исходный код прогшраммы VIRTUAL.CPP
// C++ демонстрация файлового ввода/вывода прямого доступа
Программа листинга 10.3 объявляет класс VmArray. Этот класс моделирует
динамический базирующийся на диске массив, который сохраняет все его
элементы в двоичном файле прямого доступа. Заметьте, что в этом классе
объявлен экземпляр класса fstream и что не существует указателя на
динамический массив. Класс объявляет конструктор, деструктор и ряд функций-
компонентов.
Конструктор класса имеет два параметра: Size и filename. Параметр Size
задает размер виртуального массива. Параметр filename именует двоичный
файл, который сохраняет элементы экземпляров класса. Конструктор открывает
поток f, используя потоковую функцию open и передавая ей в качестве
аргументов filename и выражение для режима работы с файлом ios::in |
ios::out | ios::binary.
Это выражение указывает, что поток открывается для двоичного
ввода/вывода (то есть режима прямого доступа). Если конструктор успешно
открывает файловый поток, он заполняет файл пустыми строками. Деструктор
класса выполняет простую задачу закрытия файлового потока f.
Функции setElem и getElem поддерживают прямой доступ к элементам
массива. Эти функции используют потоковую функцию seekg, чтобы
устанавливать указатель потока на соответствующий элемент массива. Затем
функция setElem вызывает потоковую функцию write для сохранения элемента
массива (передаваемый параметром str). Напротив, функция getElem называет
потоковую функцию read, чтобы получить элемент массива (возвращаемый через
аргумент str). Обе функции возвращают результат типа bad, который указывает
на успешность операции ввода/вывода.
Класс VmArray также объявляет функцию BubbleSort для сортировки
элементов виртуального массива. Эта функция использует функции-элементы
getElem и setElem для доступа и свопинга элементов массива. Затем, наконец,
запускается последняя функция-элемент display для элементов виртуального
массива, которая посылает их на экран. Функция main выполняет следующие
задачи:
- Объявляет экземпляр arr класса VmArray. (Этот экземпляр сохраняет
10 строк в двоичном файле ARR.DAT)
- Присваивает случайное значение элементам экземпляра аот, используя
цикл for (строки 97 и 98).
- Отображает несортированные элементы экземпляра arr, вызывая функцию-
элемент display.
- Сортирует массив, вызывая функцию BubbleSort.
- Отображает сортированные элементы экземпляра arr.
Заключение
Сегодняшний урок представил краткое введение в библиотеку ввода/вывода
C++ и вынес на обсуждение следующие вопросы:
- Общие функции ввода/вывода, включая open, close, good, fail и
оператор !.
- Функция open открывает файловый поток ввода/вывода и поддерживает
попеременный и множественный режимы ввода/вывода. Функция close
закрывает файловый поток. Функции good и fail индицируют успешную
или ошибочную, соответственно, потоковую операцию ввода/вывода.
- C++ позволяет выполнять последовательный потоковый ввод/вывод для
текста с использованием операций < и >, так же как и при помощи
потоковой функции getline. Операция < позволяет записать символы и
строки (а также и другие предопределенные типы данных). Операция >
применяется для
- получения символов. Функция getline позволяет вашему приложению
считывать строки с клавиатуры или из текстового файла.
- Последовательный потоковый ввод/вывод двоичных данных использует
потоковые функции write или read для записи или считывания данных
из переменных любого типа.
- Потоковый ввод/вывод прямого доступа для двоичных данных использует
функцию seekg в объединении с функциями read и write. Функция seekg
позволяет вам передвигать потоковый указатель либо в абсолютное,
либо в относительное положение в потоке.
Вопросы и ответы
Как можно эмулировать прямой доступ к строкам в текстовом файле?
Сначала считывайте строки из файла как текст, получайте длину строк
(плюс два символа для конца каждой строки) и сохраняйте накапливаемую длину
в специальном массиве. (Назовите его, например, lineIndex) Этот массив
сохраняет позицию байта, где начинается каждая строка. Последний элемент
массива будет содержать размер файла. Для доступа к строке номер i,
используйте функцию seek или seekg, чтобы найти смещение для lineIndex[i].
Размер строки номер i равен lineIndex[i+1] - lineIndex[i+1].
Как написать процедуру общего назначения для копирования между входным
ивыходным файловым потоком?
Вам необходимо использовать потоковую функцию gcount() для получения
ряда байт фактически читаемых в последнем неформатированном потоковом
вводе. Вот функция copyStream:
void copyStream(fstreamit fin, fstreamil fout,
unsigned char* buffer, int buffSize)
{
int n;
while (fin. read (buffer, buffaize))
{
n = fin.gcount();
fout.write (buffer, n);
}
}
Практикум
Контрольные вопросы
1. Верно или нет? Потоковые функции ввода/вывода read и write способны
правильно считывать и записывать данные любого типа.
2. Верно или нет? Потоковые функции ввода/вывода read и write способны
правильно считывать и записывать данные любого типа, не имеющих указателей.
3. Верно или нет? Функции seek и seekg расширяют файл, когда вы передаете
индекс, который на один или более байт превышает текущий конец файла.
4. Верно или нет? Аргументы функций seek и seekg не требуют проверки
диапазона.
Упражнение
Создайте программу VSEARCH.CPP, модифицируя программу VIRTUAL.CPP.
Класс VmArray в VSEARCH.CPP должен иметь функцию binSearch, которая
проводит двоичный поиск в элементах сортированного массива. Добавьте цикл в
конец функции main для поиска в массиве arr, используя неупорядоченные
данные инициализирующего списка. (Элементы этого списка доступны при
использовании данных-указателей.)
Страницы: 1, 2, 3, 4, 5
|