Поможем написать учебную работу
Если у вас возникли сложности с курсовой, контрольной, дипломной, рефератом, отчетом по практике, научно-исследовательской и любой другой работой - мы готовы помочь.
Если у вас возникли сложности с курсовой, контрольной, дипломной, рефератом, отчетом по практике, научно-исследовательской и любой другой работой - мы готовы помочь.
Программирование на языке C++
Лабораторная работа 7
Программа на языке C++. Классы, операторы и потоки.
Цель: Изучить принципы объектно-ориентированного программирования на языке C++. Научится разрабатывать программы, используя классы объектов и работать с потоками ввода/вывода, путем переопределения соответствующих операторов.
DevC++ дает вам возможность объявить базовый класс, который инкапсулирует имена своих свойств, данных, методов и событий. Помимо способности выполнять свою непосредственную задачу объектные методы получают определенные привилегии доступа к значениям свойств и данных класса.
Каждое объявление внутри класса определяет привилегию доступа к именам класса в зависимости от того, в какой секции имя появляется. Каждая секция начинается с одного из ключевых слов: private, protected и public. Пример иллюстрирует обобщенный синтаксис объявления базового класса.
class className
{
private:
<приватные члены данных> <приватные конструкторы> <приватные методы>
protected:
<защищенные члены данных> <защищенные конструкторы> <защищенные методы>
public:
<общедоступные свойства> <общедоступные члены данных> <общедоступные конструкторы> <общедоступный деструктор> <общедоступные методы>
};
Таким образом, объявление базового класса на C++ предоставляет следующие права доступа и соответствующие области видимости:
Следующие правила применяются при образовании различных секций объявления класса:
Следующий пример представляет попытку наполнить объявление базового класса некоторым конкретным содержанием. Отметим характерное для компонентных классов C++Builder объявление свойства Count в защищенной секции, а метода SetCount, реализующего запись в член данных FCount - в приватной секции.
class TPoint {
private:
int FCount; // Приватный член данных void _fastcall SetCount(int Value);
protected:
_property int Count = // Защищенное свойство
{ read= FCount, write=SetCount };
double x; // Защищенный член данных
double у; // Защищенный член данных public:
TPoint(double xVal, double yVal); // Конструктор |
double getX(); |
double getY() ;
Объявления и определения методов хранятся в разных файлах (с расширениями .h и .срр, соответственно). Пример показывает, что когда методы определяются вне класса, их имена следует квалифицировать. Синтаксис такой квалификации метода, определяющей его область видимости, имеет следующий вид:
<имя класса>::<имя метода>
Пример определения конструктора и методов вне класса:
TPoint::TPoint(double xVal, double yVal)
{ // Тело конструктора
void _fastcall TPoint::SetCount( int Value )
{
if ( Value i= FCount ) // Новое значение члена данных? {
FCount = Value; // Запись нового значения Update(); // Вызов метода Update } } double TPoint::getX()
// Тело метода getX, квалифицированного в классе TPoint
}
После того, как вы объявили класс, его имя можно использовать как идентификатор типа при объявлении объекта этого класса (например, TPoint* MyPoint;).
Как следует из названий, конструктор - это метод, который строит в памяти объект данного класса, а деструктор - это метод, который его удаляет. Конструкторы и деструкторы отличаются от других объектных методов следующими особенностями:
Данный пример демонстрирует обобщенный синтаксис объявлений конструкторов и деструктора.
class className
{
public:
// Другие члены данных className(); // Конструктор по умолчанию | className(<список параметров;-);// Конструктор с аргументами | className(const className&); // Конструктор копирования
// Другие конструкторы "className(); // Деструктор
// Другие методы };
Класс может содержать любое число конструкторов, в том числе ни одного. Конструкторы не могут быть объявлены виртуальными. Не помещайте все конструкторы в защищенной секции и старайтесь уменьшить их число, используя значения аргументов по умолчанию. Существует три вида конструкторов:
Класс может объявить только один общедоступный деструктор, имени которого, идентичному имени своего класса, должен предшествовать знак ~ (тильда). Деструктор не имеет параметров и может быть объявлен виртуальным. Если класс не содержит объявления деструктора, компилятор автоматически создаст его.
Обычно деструкторы выполняют операции, обратные тем, что выполняли соответствующие конструкторы. Если вы создали объект класса файл, то в деструкторе этот файл, вероятно, будет закрываться. Если конструктор класса выделяет динамическую память для массива данных (с помощью оператора new), то деструктор, вероятно, освободит выделенную память (с помощью оператора delete) и т.п.
DevC++ дает возможность объявить производный класс, который наследует свойства, данные, методы и события всех своих предшественников в иерархии классов, а также может объявлять новые характеристики и перегружать некоторые из наследуемых функций. Наследуя указанные характеристики базового класса, можно заставить порожденный класс расширить, сузить, изменить, уничтожить или оставить их без изменений.
Наследование позволяет повторно использовать код базового класса в экземплярах производного класса. Концепция повторного использования имеет параллель в живой природе: ДНК можно рассматривать как базовый материал, который каждое порожденное существо повторно использует для воспроизведения своего собственного вида.
Следующий пример иллюстрирует обобщенный синтаксис объявления производного класса. Порядок перечисления секций соответствует расширений привилегий защиты и областей видимости заключенных в них элементов: от наиболее ограниченных к самым доступным.
class className : [<спецификатор доступа>] parentClass {
<0бъявления дружественных классов>
private:
<приватные члены данных>
<приватные конструкторы>
<приватные методы>
protected:
<защищенные члены данных>
<защищенные конструкторы>
<защищенные методы>
public:
<общедоступные свойства>
<общедоступные члены данных>
<общедоступные конструкторы>
<общедоступный деструктор>
<общедоступные методы>
_published:
<общеизвестные свойства>
<общеизвестные члены данных>
<Объявления дружественных функций>
};
Отметим появление новой секции с ключевым словом _published - дополнение, которое C++Builder вводит в стандарт ANSI C++ для объявления общеизвестных элементов компонентных классов. Эта секция отличается от общедоступной только тем, что компилятор генерирует информацию RTTI о свойствах, членах данных и методах объекта и C++Builder организует передачу этой информации Инспектору объектов во время исполнения программы.
Помимо способности выполнять свою непосредственную задачу объектные методы получают определенные привилегии доступа к значениям свойств и данных других классов.
Когда класс порождается от базового, все его имена в производном классе автоматически становятся приватными по умолчанию. Но его легко изменить, указав следующие спецификаторы доступа базового класса:
Большинство операций могут быть перегружены с тем, чтобы они могли получать в качестве операндов объекты класса.
имя_типа operator op(список_параметров)
где op: + - * / % ^ & | ~ ! = < > += -= *= /= %= ^= &= |= << >> <<= >>= == != <= >= && || ++ -- () []
Последние две операции - это вызов функции и индексирование. Функция операция может или быть функцией членом, или получать по меньшей мере один параметр класса.
Часто программы работают с объектами, которые являются конкретными представлениями абстрактных понятий. Например, тип данных int в C++ вместе с операциями +, -, *, / и т.д. предоставляет реализацию (ограниченную) математического понятия целых чисел. Такие понятия обычно включают в себя множество операций, которые кратко, удобно и привычно представляют основные действия над объектами. К сожалению, язык программирования может непосредственно поддерживать лишь очень малое число таких понятий. Например, такие понятия, как комплексная арифметика, матричная алгебра, логические сигналы и строки не получили прямой поддержки в C++. Классы дают средство спецификации в C++ представления неэлементарных объектов вместе с множеством действий, которые могут над этими объектами выполняться. Иногда определение того, как действуют операции на объекты классов, позволяет программисту обеспечить более общепринятую и удобную запись для манипуляции объектами классов, чем та, которую можно достичь используя лишь основную функциональную запись. Например:
class complex {
double re, im;
public:
complex(double r, double i) { re=r; im=i; }
friend complex operator+(complex, complex);
friend complex operator*(complex, complex);
};
определяет простую реализацию понятия комплексного числа, в которой число представляется парой чисел с плавающей точкой двойной точности, работа с которыми осуществляется посредством операций + и * (и только). Программист задает смысл операций + и * с помощью определения функций с именами operator+ и operator*. Если, например, даны b и c типа complex, то b+c означает (по определению) operator+(b,c). Теперь есть возможность приблизить общепринятую интерпретацию комплексных выражений. Например:
void f()
{
complex a = complex(1, 3.1);
complex b = complex(1.2, 2);
complex c = b;
a = b+c;
b = b+c*a;
c = a*b+complex(1,2);
}
Выполняются обычные правила приоритетов, поэтому второй оператор означает b=b+(c*a), а не b=(b+c)*a.
Нестатические функции-элементы воздействуют на объект типа класс, с которым они вызваны. Например, если х - это объект класса Х и f - это функция-элемент Х, то вызов функции х.f() воздействует на х. Аналогично, если xptr - это указатель на объект Х, вызов функции xptr->f() воздействует на *xptr. Но откуда f знает на какой х воздействовать? C++ предоставляет с f указатель на х, называемый this. this передается как скрытый аргумент во всех вызовах нестатических функций элементов.
Ключевое слово this - это локальная переменная, доступная в теле любой нестатической функции-элементе. this не нужно объявлять и она редко явно используется в определении функции. Однако она используется неявно внутри функции при ссылках к элементам. Если вызывается х.f(y), где y - это элемент Х, this устанавливается в &x и y устанавливается в this->y, что эквивалентно x.y.
Операторы new и delete выполняют динамическое распределение и освобождение памяти аналогично стандартным библиотечным функциям malloc и free.
Упрощенный синтаксис
указатель-на-имя = new имя <инициализатор>;
delete указатель-на-имя;
имя может быть любого типа, за исключением "функция, возвращающая ..." (однако указатель на функцию разрешен).
new создает объект типа "имя", распределяя (если возможно) sizeof(имя) байт в свободной памяти (так же называемой куча). Время действия нового объекта начинается с точки создания и заканчивается или по освобождению этой памяти оператором delete, или окончанием программы.
При успешном выполнении new возвращает указатель на новый объект. NULL указатель говорит об ошибке (такой, как недостаток или фрагментированность памяти кучи). Как с malloc Вам требуется проверять указатель на NULL до работы с новым объектом. Однако в отличие от malloc new вычисляет размер "имя" без явного использования оператор sizeof. Более того, указатель, возвращаемый new, имеет правильный тип "указатель на "имя"" и явное приведение типа не требуется.
Пример:
name *nameptr; // name - любой нефункциональный тип
...
if (!(nameptr = new name)) {
errmsg("недостаточно памяти для имени");
exit (1);
}
...
delete nameptr; // разрушает name и освобождает sizeof(name) байт
Примечание. new, являясь ключевым словом, не требует прототипа.
Выходной поток связан с оператором вывода <<. Стандартный оператор сдвига влево << перегружен для операций вывода. Его левый операнд - это объект типа класс ostream. Его правый операнд - это любой тип, для которого был определен выходной поток (то есть встроенный тип или любой тип, для которого была перегрузка).
Пример:
cout<<"Hello!\n";
выводит "Hello!" в cout (стандартный выходной поток, обычно экран).
Оператор << возвращает ссылку на объект ostream, для которого он вызван. Это позволяет использовать несколько каскадных вставок:
void function_display(int i, double d)
{
cout << "i=" << i << ", d=" << d << "\n";
}
на стандартный вывод будет выведено подобное
i = 8, d = 2.34
Прямо поддерживаемые типы для вставки: char (signed и unsigned), short (signed и unsigned), int (signed и unsigned), long (signed и unsigned), char* (интерпретируется как строка), float, double, long double и void*. Целые типы преобразуются в соответствии с правилами по умолчанию для printf (если только Вы не изменили эти правила с помощью флагов ios). Например, для объявлений int i; long l;, операторы
cout<<i<<" "<<l;
printf("%d %ld", i, l);
дают одинаковый результат.
Вставка указателя (void*) так же предопределена:
int i = 1;
cout<<&i; // выводит указатель в 16-ричном виде.
Форматирование для ввода и вывода определяется флагами (манипуляторами) формата, определенными в классе ios. Флаги определены:
public:
enum {
skipws = 0x0001, // пропускает пробелы при вводе
left = 0x0002, // выровненный слева вывод
right = 0x0004, // выровненный справа вывод
internal = 0x0008, // заполнение после указателя знака или базы
dec = 0x0010, // десятичное преобразование
oct = 0x0020, // восьмеричное преобразование
hex = 0x0040, // шестнадцатиричное преобразование
showbase = 0x0080, // показывает указатель базы на вывод
showpoint = 0x0100, // показывает десятичную точку (вывод fp)
uppercase = 0x0200, // вывод в 16-ричном формате прописными буквами
showpos = 0x0400, // показывает '+' с положительными целыми числами
scientific = 0x0800, // использует 1.2345Е2 fp представление и вывод Е
fixed = 0x1000, // использует 123.45 fp представление
unitbuf = 0x2000, // освобождает все потоки после вставки
stdio = 0x4000 // освобождает stdout, stderr после вставки
};
Эти флаги читаются и устанавливаются с помощью элементов-функций rdstate и clear соответственно. Пример применения приведен ниже.
Более простой способ изменить некоторые из переменных формата - в использовании специального оператора, называемого манипулятором. Манипуляторы используют поток как аргумент и возвращают ссылку на тот же поток, следовательно манипуляторы могут быть включены в цепочку вывода (или ввода) для изменения состояния потока в качестве побочного эффекта, без действительного вывода (или ввода). Например
cout<<setw(4)<<i<<setw(6)<<j;
эквивалентно
cout.width(4);
cout<<i;
cout.width(6);
cout<<j;
setw - это параметризованный манипулятор, объявленный в iomanip.h. Другие параметризованные манипуляторы setbase, setfill, setprecision, setiosflags и resetiosflags работают так же. Чтобы использовать их в Вашей программе должен быть включен iomanip.h. Вы можете написать собственные манипуляторы без параметров:
ostream& dingy( ostream& os)
{
return os << "\a\a";
}
...
cout << i << dingy << j;
Непараметризованные манипуляторы dec, hex и oct (объявленные в iostream.h) не имеют параметров и просто изменяют основание преобразования (и оставляют ее измененной):
int i = 36;
cout << dec << i << " "
<< hex << i << "
<< oct << i << endl;
// показывает 36 24 44
Вы можете перегрузить оператор << для вывода Ваших типов данных. Предположим, у Вас есть тип
struct info {
char *name;
double val;
char *units;
};
Вы можете перегрузить <<:
ostream& operator << (ostream& s, info& m) {
s << m.name << " " m.val << " " << m.units;
}
операторы
info x;
...
// инициализация х
...
cout << x;
будут выводить "capacity .25 liters".
Входной поток аналогичен выходному, но использует перегруженный оператор сдвига вправо >>, известный как оператор извлечения (получить из). Левый операнд >> - это объект типа класс и stream. Как и для вывода, правый оператор может быть любого типа, для которого был определен входной поток.
По умолчанию >> пропускает разделители (определенные функцией isspace в ctype.h), затем читает символы, соответствующие типу входного объекта. Пропуск разделителей управляется флагом ios::skipws в перечислении состояния формата. Флаг skipws обычно установлен на пропуск разделителей. Очистка этого флага (например через setf) выключает пропуск разделителей. Заметим так же, что манипулятор ws позволяет Вам удалить разделители.
Рассмотрим следующий пример:
int i;
double d;
cin >> i >> d;
Последняя строка приводит к пропуску разделителей; цифры, читаемые со стандартного ввода (по умолчанию с клавиатуры) затем преобразуются во внутреннюю двоичную форму и сохраняются в переменной i; пропускается больше разделителей, и, наконец, читается число с плавающей точкой, преобразуется и сохраняется в переменной d.
Для типа char (signed или unsigned) действие оператора >> заключается в том, что пропускается разделитель и запоминается следующий символ (не-разделитель). Если Вам нужно читать следующий символ, независимо от того, является ли он разделителем или нет, Вы можете использовать одну из элементов-функций get.
Для символа char* (рассматриваемого как строка), оператор >> пропускает разделитель и запоминает следующие символы (не-разделители) до тех пор, пока не встретится другой символ разделителя. Затем добавляется завершающий нулевой символ. Необходимо избегать "переполнения" строки. Можно изменить длину по умолчанию (0, означающий отсутствие предела) используя setw следующим образом:
char array[SIZE];
...
// инициализация массива
...
cin.width(sizeof(array));
cin >> array;
Для ввода со встроенными типами, если конец ввода происходит до появления любого символа не-разделителя, то в buf не запоминается ничего, а состояние istream устанавливается в "fail". Таким образом, если выход был не инициализирован, то он остается не инициализированным.
Так же, как и для вывода, Вы можете создать функции ввода для Ваших типов данных. Для структуры info, определенной выше, оператор >> может быть перегружен:
istream& operator >> (istream& s, info& m);
{
s >> m.name >> m.val >> m.units;
return s;
}
(В реальной программе Вы, конечно, добавите код для обработки ошибок ввода). Для чтения входной строки, подобной "capacity 1.25 liters", используйте
cin >> m;
Класс ofstream наследует операции вывода из ostream, а ifstream наследует операции ввода из istream. Они предоставляют констракторы и функции-элементы для создания файлов и обработки ввод/вывод. Вы должны включить fstream.h во все программы, использующие эти классы. Рассмотрим пример, который копирует файл FILE.IN в файл FILE.OUT:
#include fstream.h
...
char ch;
ifstream fl("file.in");
if (!f1) errmsg("нельзя открыть файл 'file.in' для ввода"); ofstream f2("file.out");
if (!f2) errmsg ("нельзя открыть файл 'file.out' для вывода"); while (f2 && f1.get(ch) ) f2.put(ch);
Примечание. Заметим, что iostream.h в Borland C++ автоматически включается в fstream.h.
Заметим, что если констракторы для ifstream или ofstream недоступны при открытии заданных файлов, устанавливается соответствующее состояние ошибки для потока.
Констракторы позволяют Вам объявить файл потока без указания имени файла, позже Вы можете связать поток с каким-либо файлом:
ofstream ofile; // создает поток файла вывода
...
ofile.open("payroll"); // ofile связывается с файлом "payroll"
ofile.close(); // закрыть поток ofile
ofile.open("employee"); //ofile можно использовать снова
По умолчанию файл открывается в текстовом режиме. Это означает, что при вводе последовательность возврат каретки/перевод строки преобразуется в символ \n. При выводе производится обратное преобразование. Эти преобразования не производятся в двоичном режиме. Режим файла устанавливается с помощью необязательного третьего параметра функции open, выбранного из следующей таблицы:
Режимы файлов.
Режим |
Действие |
ios::app |
добавление данных - всегда пишет в конец файла |
ios::ate |
переходит на конец файла при открытии |
ios::in |
открывает на ввод (по умолчанию для ifstream) |
ios::out |
открывает на вывод (по умолчанию для оfstream) |
ios::binary |
открывает файл в двоичном режиме |
ios::trunc |
удаляет содержимое, если файл существует (подразумевается для ios::out и, если не заданы ни ios::atе, ни ios::app) |
ios::nocreate |
если файл не существует, open с ошибкой |
ios::noreplace |
если файл существует, open для выходного файла с ошибкой, если не заданы ate или app |
Переписать программу, разработанную в ходе выполнения лабораторной работы №3 с использованием классов и потоков. Для чего создать класс данных для хранения строк, слов или предложений (в зависимости от варианта задания). Разрешается не использовать переменное количество аргументов и вызовы по указателю. Для модификации требуемой по варианту задания разработанная функция должна быть представлена в виде метода данного класса. Для организации ввода/вывода переопределить соответствующие операторы потокового ввода/вывода (>> и <<). Выводы по лабораторной работе должны содержать сравнительный анализ программ (данной и разработанной в ЛР№3).
Задание ЛР№3
Составить программу обработки текста, считываемого из файла. Для чего разработать функцию для обработки текста с переменным числом параметров, в качестве параметров она должна принимать значения текстовых предложений (разделитель - .), строк (разделитель - \n) или слов (разделитель пробел или . , ! ? \n) (по варианту задания) для обработки и возвращать указатель на обработанный текст. В качестве первого параметра имя функции (указатель), используемой для перевода символов из одного формата в другой, которую определить ниже по тексту программы. Данная функция должна вызываться через переданный указатель и принимать обрабатываемый(-ые) символ(ы), возвращая результирующий. Обработанный текст вывести в результирующий файл. В отчете привести исходный и обработанный текст.
Вариант задания рассчитывается по номеру студента в журнале преподавателя.
Вариант |
Функция с переменным числом параметров получает |
Функция обработки символа |
Вариант |
Функция с переменным числом параметров получает |
Функция обработки символа |
1 |
Строки |
Изменение регистра на противоположный (рус) |
Слова |
Изменение регистра на противоположный (англ) |
|
2 |
Слова |
Исправление неверной раскладки (с рус на англ) |
Строки |
Исправление неверной раскладки (с англ на рус) |
|
3 |
Предложения |
Все буквы прописные (рус) |
17 |
Строки |
Все буквы прописные (англ) |
4 |
Слова |
Все буквы строчные (англ) |
18 |
Предложения |
Все буквы строчные (рус) |
5 |
Строки |
Все строки с загл. Буквы |
Строки |
Все строки с мал. Буквы |
|
6 |
Предложения |
Замена всех гласных (рус) на * |
Предложения |
Замена всех согласных (рус) на # |
|
7 |
Строки |
Замена всех загл. (рус) на ~ |
21 |
Строки |
Замена всех загл. (англ) на $ |
8 |
Слова |
Замена всех гласных (англ) на $ |
22 |
Слова |
Замена всех согласных (англ) на $ |
9 |
Предложения |
Замена более двух подряд повторов символов на ^ |
Предложения |
Замена двух и более загл. Символов (рус) на * |
|
10 |
Слова |
Слова с загл. буквы |
24 |
Слова |
Слова с мал. буквы |
11 |
Строки |
Замена двух и более загл. Символов (англ) на $ |
25 |
Строки |
Строки нач. с мал. буквы, все остальные большие (рус) |
12 |
Предложения |
Замена всех цифр на буквы: 0 а, 1 б… |
26 |
Предложения |
Замена всех загл. (рус) на ~ |
13 |
Строки |
Исправление ошибочного нажатия Shift при введении цифр |
Строки |
Строки нач. с мал. буквы, все остальные большие (англ) |
|
14 |
Слова |
Слова нач. с мал. буквы, все остальные большие (укр) |
Слова |
Слова нач. с мал. буквы, все остальные большие (англ) |
Содержание отчета. Содержание отчета совпадает с указанным в указаниям к лабораторной работе 1.
Бьярн Страуструп Введение в язык С++.
Язык программирования С++. Руководство программиста.
Турбо С++. Начальное руководство.
Borland C++ Builder. Руководство программиста.
Марченко А.Л. C++. Бархатный путь.
Microsoft Developer Network.