Препроцессорные средства в C и С++3.1 Основные понятия препроцессорной обработки
Препроцессорнаяобработка (макрообработка) — это преобразование текста путем замены препроцессорныхпеременных их значениями и выполнения препроцессорных операторов (директивпрепроцессора).
В общемслучае препроцессорные средства включают:
— определение препроцессорных переменных и присвоенных им значений;
— средства управления просмотром преобразуемого текста;
— правила подстановки значений макропеременных.
Определениепрепроцессорной переменной часто называют макроопределением или макросом, аподстановку ее значения в обрабатываемый текст — макрорасширением.
Макрообработкасостоит в последовательном просмотре исходного текста и выделения в нем лексем— сканировании текста. Если выделенная лексема является препроцессорнойпеременной, она заменяется на свое значение, т.е. строится макрорасширение.Если встречается препроцессорная директива, то она выполняется. Лексемы, неявляющиеся препроцессорными переменными или директивами, переносятся в выходнойтекст без изменения. Результатом такой обработки является текст, не содержащийпрепроцессорных директив и препроцессорных переменных. Если исходный текст былпрограммой на C или C++, то после макрообработки должен быть полученсинтаксически правильный текст на C или C++.
Какправило, строковые литералы (строки в кавычках) рассматриваются препроцессоромкак отдельные лексемы и переносятся в выходной текст без изменения.
Препроцессоробычно обеспечивает возможность включения в программу исходных текстов издругих файлов, в Си/Си++ это выполняется по директиве
#include имя файла
Есливключаемый файл находится в одном из оглавлений, указываемых в установкахинтегрированной среды в пункте options-directory-include, где можно указатьнесколько путей, разделяя их точкой с запятой, имя файла заключается вуголковые кавычки, например
# include в остальных случаяхуказывается полный путь к включаемому файлу в кавычках:
# include “c:\myinclud\includ1.h”
Привключении файла на место директивы #include вставляется текст из этого файла, апоследующие строки исходного файла сдвигаются вниз, после чего вставленныйтекст сканируется препроцессором.
Отметим,что директивы препроцессора всегда записывается с новой строки и первымсимволом директивы должен быть знак #, которому могут предшествовать толькопробелы и знаки табуляции. Концом текста директивы служит конец строки. Еслидиректива не помещается в одной строке, в конце строки ставится знак \ идиректива продолжается на следующей строке. Количество строк продолжения неограничивается.3.2. Препроцессорные переменные
Препроцессорнаяпеременная (макроимя) объявляется директивой
# define идентификатор значение
Например:
# define L_NAME 6
# define END_FORMULA ‘;’
# define DEBUG
Еслиобъявленное таким способом макроимя встретится в последующем тексте программы,оно будет заменено на соответствующее значение:
char namevar [L_NAME]; // эквивалентно char namevar[6];
if ( c != END_FORMULA ) ...... // if ( c != ‘;’)…
ПеременнаяDEBUG объявлена. но не имеет значения. В последующем тексте можно проверять.объявлено или нет это имя, и в зависимости от результата проверки включать илине включать в программу некоторые операторы.
Объявленноев define макроимя известно препроцессору от точки его объявления до конца файлаили пока не встретится директива
# undefимя
Например,#undef DEBUG
Если впоследующем тексте встретится имя DEBUG, оно будет рассматриваться как обычное,а не препроцессорное имя.
Имеетсяряд предопределенных макроимен, предусмотренных стандартами на языки C и C++, втом числе:
_ _LINE_ _ — номер строки в исходном файле,
_ _FILE_ _ — имя обрабатываемого файла,
_ _DATE_ _ — дата начала обработки препроцессором,
_ _TIME_ _ — время начала обработки,
_ _STDC_ _ — программа должна соответствовать стандарту ANSI.
_ _cplusplus — компилировать программу в соответствии с синтаксисом Си++,
_ _PASCAL_ _ — последующие имена по умолчанию имеют тип “имя языка Pascal”
Предопределенныеимена нельзя объявлять в #define или отменять в #undef.
МакросыFILE, DATE и TIME могут использоваться в сообщениях, выдаваемых в началепрограммы для указания, какая версия программы используется, например,
cout
МакросPASCAL применяется при описании функций, предназначенных для использования впрограммах, написанных на языке Pascal, а также функций, вызываемых операционнойсистемой Windows.
Припрограммировании на Си директивы типа
#define MAX_LEN 80обычно применяютсядля задания именованных констант. Введение описателя const в последние версииСи и в Си++ позволяет определять именованные константы так же, как и обычныепеременные.3.3. Макроопределения (макросы)
Рассмотренныйвыше вариант директивы #define — частный случай. Полный синтаксис этойдирективы имеет вид:
# defineидентификатор(параметры) список_замены
Параметрызадаются их именами, список замены — это текст на C, C++, содержащий именапараметров, например:
# define MAX(a,b) ( (a) > (b) )? (a): (b)
# define PRINT(a) cout
Если вобласти действия этих макроопределений встретится текст
x = MAX( y + p, s);
то онбудет заменен на
x = ( (y + p) > (s))? (y + p): (s);
операторPRINT(x) будет заменен на
cout
Знак #перед именем параметра означает, что значение аргумента рассматривается какстроковый литерал. Если между двумя параметрами в макрорасширении стоят знаки##, то значения аргументов сцепляются по правилу сцепления строк, например,
# define VAR(a,b) ( a##b )
x = d [ VAR(i,j)]; // x = d [ ( ij )];
Использованиемакросов в ряде случаев позволяет сократить исходный текст программы и сделатьего более наглядным. Например, если поместить в файл-заголовок макросы
#if defined(__cplusplus)
# define _PTRDEF(name) typedef name * P##name;
# define _REFDEF(name) typedef name & R##name;
# define _STRUCTDEF(name) struct name; \
_PTRDEF(name) \
_REFDEF(name) \
#endifто мы получимвозможность одной строкой программы
_STRUCTDEF(MyStruct)объявить имяструктурного типа MyStruct, указатель на этот тип PMyStruct и тип ссылки нанего RMyStruct, т.е. получить в выходном тексте строчки
struct MyStruct;
typedef MyStruct *PMyStruct;
typedef MyStruct &RMystruct;3.4. Условная компиляция
Директивыпрепроцессора # if, # else, # endif и # elif позволяют, в зависимости отрезультатов проверки некоторых условий, включать в программу один из несколькихвариантов текста:
# ifпрепроцессорное_условие
текст 1
# else
текст 2
# endif
дальнейшийтекст.
Условие— это константное выражение, которое строится из макроимен, констант и знаковопераций, включая логические связки && и | |. Допускается такжевыражение sizeof (имя_типа) и препроцессорная функция defined( макроимя ),возвращающая 1, если это макроимя определено, и 0, если оно не определено.Вместо директивы
# if defined( DEBUG )можно написать
# ifdef DEBUGа вместо
# if !defined( DEBUG )написать
# ifndef DEBUG
Комбинации#if — #else могут быть вложенными, причем последовательность #else — #ifзаменяется одной директивой #elif с условием:
# ifпрепроцессорное_условие_1
текст 1
# elifпрепроцессорное_условие_2
текст_2
# else
текст_3
# endif
В файлахзаголовков для предотвращения многократного включения одного и того жезаголовка в программу обычно присутствует текст вида:
#if !defined(_ _DEFS_H)
#define _ _DEFS_H
/* Текст объявляемых заголовков */
.......................................
#endif /* Конец _ _DEFS_H */
4. Объектно-ориентированные средства С++4.1 Объектные типы данных
Объектныетипы данных — это агрегатные типы, полностью определяемые программистом,описание объектного типа должно содержать компоненты-данные, определяющиеобласть возможных значений переменных этого типа, и описание операций,допустимых над переменными этого типа и компонентами-данными, составляющимипеременную. Для сохранения совместимости с программами на Си синтаксис описанияобъектного типа в Си++ выбран подобным описанию структурного типа или типаобъединения в Си. В сущности структуры и объединения в Си++ рассматриваются какварианты объектных типов. Имеются три варианта объектных типов: структура(struct), объединение (union) и класс (class), различающиеся возможностямидоступа к компонентам типа. В дальнейшем для краткости все варианты объектныхтипов будем называть классами. Описание объектного типа строится по схеме:
вариант_типа имя_типа : список_базовых_классов
{ компоненты (члены ) класса }
Компонентамикласса могут быть компоненты-данные и компоненты-функции. Компоненты-функциипредназначены для выполнения операций над объектным данным, их часто называютметодами класса.
Длякаждого компонента класса устанавливается уровень доступа либо явно, указаниемуровня доступа одним из ключевых слов public, protected или private сдвоеточием, либо неявно, по умолчанию. Указание уровня доступа относится ковсем последующим компонентам класса, пока не встретится указание другого уровнядоступа. Уровень доступа public разрешает доступ к компонентам класса из любогоместа программы, в котором известна переменная этого класса. Уровень доступаprivate разрешает доступ к компонентам класса только из методов этого класса.Уровень доступа protected имеет смысл только в иерархической системе классов иразрешает доступ к компонентам этого уровня из методов производного класса. Поумолчанию для всех компонент класса типа struct принимается уровень доступаpublic, но можно явно задавать и другие уровни доступа, уровень доступа ккомпонентам класса типа class по умолчанию private, явно можно определять идругие уровни, для класса типа union уровень доступа public и не может бытьизменен.
Например,пусть программист решил в классе TPoint (точка) запретить внешний доступ ккоординатам точки и разрешить внешний доступ к методам перемещения точки наплоскости. Описание класса TPoint можно построить так:
class TPoint
{ private:
int x,y;
public:
void movePoint ( int newx, int newy); // в новую точку
void relmove ( int dx, int dy ); // смещение на dx,dy
int getx ( void ) ( return x; };
int gety ( void ) { return y; };
};
Описаниетела компоненты-функции может быть включено в описание класса, как это сделанов примере для функций getx и gety, или помещено вне описания класса.Компоненты-функции при их вызове неявно получают дополнительный аргумент — указатель на переменную объектного типа, для которой вызвана функция и в телефункции можно обращаться ко всем компонентам класса. В связи с этим приописании тела компоненты-функции вне описания класса нужно использоватьоперацию разрешения контекста, чтобы информировать компилятор о принаждлежностифункции к классу. Методы класса TPoint можно описать так:
void TPoint:: movePoint ( int newx, int newy )
{ x = newx; y = newy ; }
void TPoint:: relmove ( int dx, int dy )
{ x += dx; y += dy ; }
Чтобывыполнить начальную инициализацию компонент-данных при создании переменныхобъектного типа в описание типа включаются специальные методы-конструкторы. Имяконструктора совпадает с именем типа, конструктор не возвращает никакогозначения и для него не указывается тип возвращаемого значения. Длярассмотренного выше класса TPoint можно было обойтись без конструктора ииспользовать для инициализации метод movePoint. Рассмотрим в качестве примеракласс TRect, описывающий прямоугольник со сторонами, параллельными осямкоординат:
enum Boolean {FALSE, TRUE };
class TRect
{ public:
TPoint a,b; // a — левый верхний угол, b — правый нижний угол
void move( int dx, int dy) // перемещение прямоугольника
{ a.relmove ( dx, dy ); b.relmove ( dx, dy );}
void grow( int dx, int dy) // изменение размеров
{ a.x +=dx; a.y += dy; b.x +=dx; b.y += dy; }
void intersect (const TRect& r); // общая часть двух прямоугольников
void Union ( const TRect& r); /* прямоугольник, охватывающий два прямоугольника */
Boolean contains ( const TPoint& p);
/* TRUE, если точка p принадлежит прямоугольнику */
Boolean isEmpty( );
/* TRUE, если ширина или высота прямоугольника равны нулю */
TRect (int ax, int ay, int bx, int by ) // конструктор
{ a,x — ax; a,y = ay; b.x = bx; b.y = by; };
TRect ( TPoint p1, TPoint p2) // конструктор
{ a = p1; b = p2; };
TRect () // конструктор
{ a.x = a.y = b.x = b.y = 0; };
};
/* Методы класса TRect */
void TRect:: intersect (const TRect& r)
{ a.x = max (a.x, r.a.x ); b.x = min ( b.x, r.b.x );
a.y = max (a.y, r.a.y ); b.y = min ( b.y, r.b.y );
};
void TRect:: Union ( const TRect & r )
{ a.x = ( a.x
a.y = ( a.y
b.x = ( b.x >= r.b.x )? b.x: r.b.x ;
b.y = ( b.y >= r.b.y )? b.y: r.b.y ;
};
Boolean TRect:: contains ( const TPoint & p )
{ return Boolean (p.x >= p.x && p.x = a.y && p.y
Boolean TRect:: isEmpty ( )
{ return Boolean ( a.x >= b.x | | a.y >= b.y ); };
Болееполная информация о конструкторах объектных типов приведена в следующемразделе.
Объявлениепеременной объектного типа строится по общим правилам, но за идентификаторомпеременной можно указать в скобках аргументы определенного в классеконструктора, например:
TRect r1(2,4,20,50); // инициализация с использованием первого конструктора
TRect *pr = &r1; // укзатель на TRect
TRect r2, *ptr; // для r2 используется конструктор без параметров
Воперации new для размещения в динамической памяти объектной переменной заименем типа также указываются аргументы конструктора этого типа:
ptr = new TRect( 7,3,18,40);
Дляобращения к компонентам объектного типа имя компоненты должно уточняться именемобъектной переменной или указателем на нее:
r1.grow( 2, -3);
pr->move( 1, 1);
Boolean bb= r1.isEmpty( );4.2. Конструкторы и деструкторы
Описаниекласса обычно содержит специальные методы, вызываемые при создании переменнойэтого класса и удалении переменной из динамической памяти — конструкторы идеструкторы. Конструктор вызывается после выделения памяти для переменной иобеспечивает инициализацию компонент-данных, деструктор вызывается передосвобождением памяти, занимаемой объектной переменной, и предназначен длявыполнения дополнительных действий, связанных с уничтожением объектнойпеременной, например, для освобождения памяти, выделенной для объекта внеучастка, отведенного для компонент-данных.
Как ужеотмечалось, конструктор всегда имеет имя, совпадающее с именем класса, для негоне указывается тип возвращаемого значения и он не возвращает никакого значения.Конструктор должен обеспечивать инициализацию всех компонент-данных. Для классаможет быть объявлено несколько конструкторов, различающихся числом и типамипараметров. В общем случае различают следующие виды конструкторов: конструкторс параметрами, конструктор без параметров и конструктор копирования с однимпараметром — ссылкой на переменную того же объектного типа. Если для объектноготипа не определено ни одного конструктора, компилятор создает для негоконструктор по умолчанию, не использующий параметров. Конструктор копированиянеобходим, если переменная объектного типа передается в какую-нибудь функциюкак аргумент, поскольку все аргументы передаются в функцию по значению.
Деструкторнеобходим, если объектный тип содержит компоненту-данное, являющуюся указателемна динамическое данное, которое должно уничтожаться при уничтожении объектнойпеременной. Деструктор всегда имеет то же имя, что и имя класса, но передименем записывается знак ~ (тильда). Деструктор не имеет параметров и подобноконструктору не возвращает никакого значения.
Вкачестве примера рассмотрим объектный тип TString для представления строковыхданных с более высокой степенью защиты от ошибок, чем это обеспеченостандартными функциями обработки строк из файла-заголовка string.h.
#include
#include
class TString
{ public:
TString(); // конструктор без параметров
TString(int n, char* s=0); // конструктор, создающий пустую строку
/* конструктор, преобразующий массив из char с завершающим нулем
в тип TString */
TString(char* s);
TString(TString& st); // конструктор копирования
~TString(); // деструктор
void print(); // вывод строки на экран
int sz; // длина строки
char* ps; // указатель на память для хранения строки
};
/* Методы класса TString */
TString::TString( ){sz=0; ps=0;}
TString::TString(int n, char* s)
{ sz=n; ps=new char[n+1];
strncpy(ps,s,n); ps[sz]='\0';
}
TString::TString(char* s)
{ sz=strlen(s)+1; ps=new char[sz];
strcpy(ps,s);
}
TString::TString(TString& str)
{ sz = str.sz; ps=new char[sz+1];
strcpy(ps,str.ps);
}
TString::~TString( )
{ if (ps != 0) delete [] ps; }
void TString::print( )
{ if (sz == 0 )
{ cout
cout
}
Нижеприведен пример программы, иллюстрирующей использование данных типа TString.
int main()
{ char rabstr [60] = «yes»;
while (*rabstr !='n')
{ cin >> rabstr;
if (*rabstr == 'n')break;
TString s1();
TString s2(6);
TString s3(6, rabstr);
TString* ps1=new TString(" Это строка по указателю");
cout print();
cout
cout
}
return 0;
}
Описаниеконструктора можно упростить, если компоненты-данные принадлежат к базовымтипам или являются объектными переменными, имеющими конструктор. При описанииконструктора после заголовка функции можно поставить двоеточие и за ним списокинициализаторов вида идентификатор (аргументы ). Например, для класса TPoint изпредыдущего параграфа можно было определить конструктор так:
class TPoint
( .....
public:
TPoint ( int x0, int y0 ) : x (x0), y (y0){ };
}
В этомконструкторе все компоненты получают значения из списка инициализации, а телоконструктора представлено пустым составным оператором.
4.3. Производные классы
Классыобразуют иерархическую структуру, когда выделяется некоторый базовый класс,содержащий общие данные и методы группы сходных классов, и строится несколькопроизводных классов, в которых к данным и методам базового класса добавляютсяданные и методы, необходимые для реализации производного класса. Описаниесистемы классов в этом случае выглядит так:
class TA // базовый класс
{ Переменные и методы TA }
class TAA: public TA // класс, производный от класса TA
{ Переменные и методы TAA }
class TAAB: public TAA // класс, производный от класса TAAB
{ Переменные и методы TAAB }
Доступомк компонентам базового класса управляют ключевые слова public и private. Еслибазовый класс public, то в производном классе public-компоненты базового классаостанутся public, protected-компоненты базового класса останутся protected,private-компоненты базового класса для функций производного класса будутнедоступны.
Еслибазовый класс private, то в производном классе public и protected компонентыбазового класса доступны для функций производного класса, но для следующегопроизводного класса они будут считаться private, т.е. будут недоступны,private-компоненты базового класса недоступны в производных классах.
Конструкторпроизводного класса должен вызывать конструктор своего базового класса:
class TBase
{ public: TBase( int s, int m, int d);
/* Другие компоненты класса TBase */
}
class TVect: public TBase
{ public: TVect ( int k, int s, int m int d): TBase(s, m, d)
{ /* инициализация остальных компонент TVect */};
}4.4. Пример построения системы классов
Известно,что при объявлении массивов в Си/Си++ количество элементов массива задаетсяконстантой и в дальнейшем не может быть изменено. При обращении к элементаммассив отсутствует контроль выхода за пределы индексов массива, что приводит ктрудно обнаруживамым ошибкам в программах. Построим систему классов дляобработки динамических массивов, в которые можно добавлять новые элементы иисключить возможность выхода за пределы текущего размера массива. Общиесвойства массивов с такими сойствами, не зависящие от типа элементов массива,объединим в классе TBase, а для массивов с различными типами элеменов образуемсвои классы. Описания классов объединим в файле заголовков TBASEARR.H, аопределения методов приведем в файле TBASEARR.CPP.
// файл TBASEARR.H
#include
#include
class TBase //базовый класс для массивов всех типов
{int size, //размер элемента
count, //текущее число элементов
maxCount, //размер выделенной памяти в байтах
delta; //приращение памяти в байтах
char *pmem; //указатель на выделенную память
int changeSize(); //перераспределение памяти
protected:
void* getAddr( ){return (void*) pmem;};
void addNewItem(void*); //добавление в конец массива
void error(const char* msg){cout
public:
int getCount() {return count;};
TBase(int s,int m,int d);
TBase();
TBase(TBase&);
~TBase();
};
/* Массив с элементами типа int */
class TIntArray: public TBase
{ public:
int getElem(int index); // Значение элемента по индексу
void putElem(int index,int &pe); // Замена значения элемента по индексу
void addElem(int& el); // Добавление элемента в конец массива
TIntArray& add(TIntArray&); // Сложение двух массивов поэлементно
TIntArray& subtract(TIntArray&); // Вычитание массивов
void printElem(int index); // Вывод значения элемента на экран
void print(); // Вывод на экран всего массива
TIntArray(int m,int d):TBase((int)sizeof(int),m,d){ }; /*Конструктор */
TIntArray(TBase& a):TBase( a ){}; /*Конструктор */
~TIntArray();
};
Определенияметодов приведены в файле TBASEARR.CPP:
#include
#include
#include
#include
/* Методы класса TBase */
TBase::TBase(int s,int m,int d):size(s),maxCount(m),delta(d)
{char* p;
int k;
count = 0; p = pmem = new char [size * maxCount];
for (k=0; k
{ *p = '\0'; p++;}
}
TBase::TBase():size(1),maxCount(10),delta(1)
{char* p;
int k;
count = 0; p = pmem = new char [size *maxCount];
for (k=0; k
{ *p = '\0'; p++;}
}
TBase::TBase(TBase& b):size(b.size),maxCount(b.maxCount),delta(b.delta)
{ int k;
count = b.count; pmem = new char [size * maxCount];
for (k=0; k
{ pmem[k] = b.pmem[k];}
}
TBase::~TBase ()
{ delete [ ] pmem; }
4.5 Виртуальные функции4.5.1. Понятие о “позднем” связывании
Приописании объектных типов функции, имеющие сходное назначение в разных классах,могут иметь одинаковые имена, типы параметров и возвращаемого значения. Приобращении к такой функции с указанием имени объекта компилятору известно, какаяиз одноименных функций требуется. В то же время к объектам производного типаможно обращаться по указателю на базовый тип и тогда на этапе компиляции нельзяустановить, функция какого из производных типов должна быть вызвана. В ходевыполнения программы требуется проверять, на объект какого типа ссылаетсяуказатель и после такой проверки вызывать требуемую функцию. Эти действияназывают “поздним” связыванием, в отличие от “раннего” связывания, при которомуже на этапах компиляции или редактирования связей можно установить адрес точкивхода вызываемой функции. В объектно-ориентированных языках программированиядля решения этой проблемы применяются виртуальные методы.4.5.2. Описание виртуальных функций
Функция-компонентакласса объявляется как виртуальная указанием ключевого слова virtual.Функции-компоненты в производных классах, заменяющие виртуальную функциюбазового класса должны объявляться с тем же именем, тем же списком параметров итипом возвращаемого значения, что и соответствующая функция базового класса. Еслииз производного класса не образуется новых производных классов, ключевое словоvirtual в описании функции можно опустить.
Если впроизводном классе нет объявления функции с тем же именем, что и виртуальнаяфункция базового класса, будет вызываться функция базового класса.
Виртуальнаяфункция может быть объявлена в форме:
virtual void print ( ) = 0;
Такаяфункция называется “чистой” (pure) виртуальной функцией, а объектный тип,содержащий ее объявление, называется абстрактным объектным типом. В программене могут создаваться экземпляры абстрактных типов, такой тип можетиспользоваться только для образования производных типов, причем в производномтипе следует либо снова определить эту виртуальную функцию как чистую, либообявить ее как обычную виртуальную функцию, выполняющую конкретные действия.
Виртуальныефункции особенно полезны, когда к методом класса требуется обращаться черезуказатель на экземпляр класса, а сам этот указатель имеет тип указателя набазовый класс. Пусть, например, в классе TBase объявлена чистая виртуальнаяфункция print:
class TBase //базовый класс для массивов всех типов
{int size, //размер элемента
count, //текущее число элементов
maxCount, //размер выделенной памяти в байтах
delta; //приращение памяти в байтах
char *pmem; //указатель на выделенную память
int changeSize(); //перераспределение памяти
protected:
void* getAddr( ){return (void*) pmem;};
void addNewItem(void*); //добавление в конец массива
void error(const char* msg){cout
public:
int getCount() {return count;};
TBase(int s,int m,int d);
TBase();
TBase(TBase&);
~TBase();
virtual void print ( ) = 0; // Чистая виртуальная функция
};
Тогда впроизводных классах должна быть объявлена замещающая ее функция print,выполняющая реальные действия:
class TIntArray: public TBase
{ /* Другие методы */
virtual void print ( );
}
class TRealArray: public TBase
{ /* Другие методы */
virtual void print ( );
}
Впрограмме, использующей объекты классов TIntArray и TRealArray могут создаватьсяэкземпляры этих классов с возможностью обращения к ним через указатель набазовый класс:
TBase *pb;
TIntArray aint(5,3);
TRealArray areal(4,2);
Тогдадля печати массивов могут применяться операторы
pb = &aint; pb->print(); //Печать массива aint
pb = &areal; pb->print(); // Печать массива areal
Приведемеще один пример использования виртуальных функций. Пусть некоторый любительдомашних животных решил завести каталог своих любимцев и для каждого видаживотных определил свой класс с общим базовым классом Pet. Для краткостиограничимся в описании каждого животного его кличкой и типовым излаваемымживотным звуком с возможностью вывода на экран списка кличек и представленияиздаваемых ими звуков.
Программа:
#include
struct Pet // Базовый класс
{ char *name;
virtual void speak() = 0;
Pet( char *nm){name=nm;}
}
struct Dog: public Pet
{ virtual void speak( ) { cout
Dog(char *nm): Pet(nm) { };
};
struct Cat: public Pet
{ virtual void speak( ) { cout
Cat(char *nm): Pet(nm) { };
}
int main ()
{ Pet *mypets[ ] = { new Dog(“Шарик”),
new Cat(“Мурка”),
new Dog(“Рыжий “)}; // Список животных
const int sz = sizeof( mypets)/ sizeof( mypets [ 0 ]);
for ( int k = 0: k
mypets [ k ]->speak();
return 0;
}4.6. “Дружественные” (friend) функции
Функция,объявленная в производном классе, может иметь доступ только к защищенным(protected) или общим (public) компонентам базового класса.
Функция,объявленная вне класса, может иметь доступ только к общим (public) компонентамкласса и обращаться к ним по имени, уточненному именем объекта или указателя наобъект.
Чтобыполучить доступ к личным компонентам объектов некоторого класса Х в функции, неимеющей к ним доступа, эта функция должна быть объявлена дружественной в классеX:
class X
{ friend void Y:: fprv( int, char*);
/* Другие компоненты класса X */
}
Можнообъявить все функции класса Y дружественными в классе X;
class Y;
class X
{ friend Y;
/* Другие компоненты класса X */
}
class Y
{ void fy1(int, int);
int fy2( char*, int);
/* Другие компоненты класса Y */
}
Дружественнойможет быть и функция, не являющаяся компонентой какого-либо класса, например,
class XX
{ friend int printXX ( );
/* Другие компоненты класса ХХ */
}
Здесьфункция printXX имеет доступ ко всем компонентам класса XX, независимо отзакрепленного за ними уровня доступа.
В теорииобъектно-ориентированного программирования считается, что при хорошоспроектированной системе классов не должно быть необходимости в дружественныхфункциях, однако в ряде случаев их использование упрощает понимание ипоследующие модификации программы.4.7. Статические компоненты класса
Описательstatic в С++ имеет различное назначение в зависимости от контекста, в которомон применен.
Переменныеи функции, объявленные вне класса и вне тела функции с описателем static, имеютобласть действия, ограниченную файлом, в котором они объявлены.
Переменные,объявленные как static внутри функции, видимы только внутри этой функции, носохраняют свои значения после выхода из функции и инициализируются только припервом обращении к функции.
Компонентыкласса также могут объявляться с описателем static, такие компоненты — данныеявляются общими для всех экземпляров объектов этого класса и размещаются впамяти отдельно от данных объектов класса. Доступ к static — компонентам классавозможен по имени, уточненному именем класса (именем типа) или именем объектаэтого класса, причем к static — компонентам класса можно обращаться до созданияэкземпляров объектов этого класса. Статическое данное — член класса должно бытьобязательно инициализировано вне описания класса:
class TBase //базовый класс для массивов всех типов
{ static int nw;
int size, //размер элемента
count, //текущее число элементов
maxCount, //размер выделенной памяти
delta; //приращение памяти
/* Другие компоненты класса TBase */
}
int TBase::nw =1; /* Инициализация статической компоненты класса */
Статическиекомпоненты — функции могут вызываться до создания экземпляров объектов этогокласса и поэтому имеют доступ только к статическим данным класса:
class X
{ static int sx1,sx2;
static void fsx ( int k);
int x1,x2;
/* Другие компоненты класса X */
}
int X::sx1 = 1;
int X::sx2 = 2;
int main ()
{ ..........
X:: fsx( 3 );
..............
}4.8.Переопределение (перегрузка) операций
В языкахпрограммирования определена семантика операций, выполняемых над базовыми(предопределенными) типами данных, например, если x, y и z — переменные типаfloat, то запись x = y + z; предполагает интуитивно очевидные действия,сложение x и y и присваивание переменной z полученной суммы.
Желательнобыло бы и для типов, определяемых в программе, в том числе для классов,определить семантику и алгоритмы операций сложения, вычитания, умножения ит.д., чтобы иметь возможность вместо вызова соответствующих функций записыватьпросто x + y и в случае, когда x и y являются объектами некоторых классов. ВC++ это достигается переопределением имеющихся в языке операций для другихтипов данных.
Переопределеннаяоперация объявляется так:
тип_результата operator знак_операции (формальные параметры)
{ описание_алгоритма_выполнения_операции }
Например:
class TPoint
{ int x,y;
public:
TPoint& operator+=( const TPoint& adder );
TPoint& operator-=( const TPoint& subber );
friend TPoint operator — ( const TPoint& one, const TPoint& two);
friend TPoint operator + ( const TPoint& one, const TPoint& two);
friend int operator == ( const TPoint& one, const TPoint& two);
friend int operator != ( const TPoint& one, const TPoint& two);
};
Полноеопределение этих операций для объектов класса TPoint имеет вид:
inline TPoint& TPoint::operator += ( const TPoint& adder )
{ x += adder.x; y += adder.y; return *this;}
inline TPoint& TPoint::operator -= ( const TPoint& subber )
{ x -= subber.x; y -= subber.y; return *this;}
Остальныеоперации определяются аналогичным образом.
Пусть впрограмме имеются объявления:
TPoint x(12,3), y(21,30), z(18,30);
Тогдаможно записать:
x +=y; y-=z; TPoint r = x + z:
Общиеправила переопределения операций сводятся к следующему:
— Двуместные операции должны иметь два параметра, одноместные — один параметр,причем, если операция объявлена как компонента класса, то неявным первымоперандом является экземпляр объекта (следовательно при определении двуместнойоперации будет задаваться один параметр, одноместная операция объявляется спустым списком параметров). Если операция переопределяется вне класса (сописателем friend ), то для двуместной операции должны быть заданы двапараметра, для одноместной операции — один параметр.
— Припереопределении сохраняется приоритет исходной операции т.е. операция + будетвыполняться раньше операции = и т.д.
— Припереопределении не наследуются свойства коммутативности и ассциативности, т.е.результат выражения х + y — z может отличаться от результата выражения y — z +x и зависит от того, как определены соответствующие операции.
— Недопускается переопределение операций. (точка), .* ( точка -звездочка,обращение к указателю на компоненту класса или структуры), :: (разрешениеконтекста), а также операции # и ##, используемые при препроцессорнойобработке.
— Переопределяемые операции = (присваивание), () (функция), [ ] (индекс), ->(обращение к компоненте класса по указателю) всегда должны быть компонентамикласса и не могут быть static.
— Переопределяемые операции new и delete должны быть static — компонентамикласса.
Востальном к переопределяемым операциям предъявляются те же требования, что и кфункциям.
5. Шаблоны функций и классов5.1. Шаблоны функций
Частовстречаются функции, реализующие одни и те же действия для аргументов различныхтипов. Например, сортировка массива по возрастанию его элементов можетвыполняться одним и тем же методом и для данных типа int и для данных типаdouble. Различие состоит только в типах параметров и некоторых внутреннихпеременных.
В болеепоздние версии С++ включено специальное средство, позволяющее параметризоватьопределение функции, чтобы компилятор мог построить конкретную реализациюфункции для указанного типа параметров функции. Параметризованное определениефункции строится по схеме:
template
Заголовок функции
{ /* Тело функции */ }
Имякласса является параметром и задается идентификатором, локализованным впределах определения функции. Хотя бы один из параметров функции должен иметьтип, соответствующий этому идентификатору.
Параметризованноеопределение функции сортировки массива методом перестановок может быть построеноследующим образом:
template
void sort ( T a[ ], int n )
{ T temp;
int sign;
for ( int k = 0; k > n; k++)
{ sign = 0;
for ( i = 0; i
if ( a [ i ] > a [ i + 1])
{ temp = a [ i ];
a[ i ] = a[ i + 1 ];
a[ i + 1 ] = temp; sign++;
}
if ( sign == 0 ) break;
}
return;
}
Если впрограмме будут объявлены массивы
int aint [10];
double afl [20];и установленызначения элементов этих массивов, то вызов функции
sort ( aint, 10 );обеспечит вызов sortдля упорядочения массива целых, а вызов функции
sort ( afl, 20 )обеспечит вызов sortдля упорядочения массива с элементами типа double.
Еслиэлементами массива являются объекты какого-либо определенного программистомкласса, для которого определена операция отношения >, то функция sort можетбыть вызвана и для такого массива. Разумеется, в объектном коде программы будутприсутствовать все варианты реально вызывамой функции sort. Параметризацияфункции сокращает объем исходного текста программы и повышает его надежность.
Вописателе template можно указывать несколько параметров вида class имя_типа, атакже параметры базовых типов. Например, функция
template
void copy ( T1 a[ ], T2 b[ ], int n)
{ for ( int i = 0; i
a[ i ] = b [ i ] ;
}копирует первые nэлементов массива b типа T2 в первые n элементов массива a типа T1. Разумеется,программист несет ответственность за то, чтобы такое копирование быловозможным. 5.2.Шаблоны классов
Поаналогии с параметризованной функцией можно построить параметризованноеописание класса, позволяющее создавать экземпляры классов для конкретныхзначений параметров. Параметризованный класс описывается следующим образом:
template
class описание класса
Как идля функций, в описателе template может быть задано несколько параметров. Всамом описание класса имена параметров используются как имена типов данных,типов параметров функций и типов значений, возвращаемых функциями.
Вкачестве примера приведем описание класса stack, предназначенного дляпостроения стеков фиксированного максимального размера с элементамипроизволного типа.
enum BOOLEAN ( FALSE, TRUE );
template
class stack
{ private:
enum ( EMPTY = -1 );
Type* s; /* Указатель на массив стека */
int max_len; /* Максимальная длина стека */
int top; /* Индекс элемента в вершине стека */
public:
stack ( ): max_len ( 100 ) /* конструктор без параметров */
{ s = new Type [ 100 ]; top = EMPTY; }
stack ( int size ): max_len( size ) /* Второй конструктор */
{ s = new Type [ size ]; top = EMPTY; }
~stack ( ) { delete [ ] s; } /* Деструктор */
void reset ( ) { top = EMPTY; } /* Очистить стек */
void push ( Type c ) { s [ ++top ] = c; }
Type pop ( ) { return (s [top—] }
Type top_of ( ) { return ( s [top ] }
BOOLEAN empty ( ) { return BOOLEAN ( top == EMPTY ) }
BOOLEAN full ( ) { return BOOLEAN ( top == max_len ) }
};
Следуетотметить, что в этом примере с целью сокращения исходного текста непредусмотрен контроль выхода за пределы стека в методах push и pop.
Чтобысоздать экземпляр параметризованного объектного типа, нужно уточнить имя типазначением параметра в угловых скобках:
stack stack_of_int (50); /* Стек на 50 элементов типа int */
stack stmc (20); /* Стек на 20 элементов типа myClass */
Вприведенном примере все компоненты-функции определены в описании класса. Когдаполное определение функции-члена класса задается вне описания класса, онодолжно уточняться описателем template. Например, если бы метод top_of былопределен вне описания класса, определение имело бы вид:
template
Type top_of () { return s [ top ];}
Отметимнекоторые специфические черты описаний параметризованных классов.
Если впараметризованном классе определены friend-функции, то когда такая функция независит от параметра, будет использоваться единственная friend-функция для всехзначений параметра, а когда friend-функция зависит от параметра, будетиспользоваться своя friend-функция для каждого значения параметра.
Если впараметризованном классе имеются статические (static) компоненты, то длякаждого значения параметра будет использоваться свой экземпляр статическойкомпоненты.
6. Классы для ввода-вывода потоков6.1. Система классов ввода-вывода
Системаввода-вывода С++ основывается на концепции потоков данных, поток представляетсобой, с одной стороны, последовательность данных, с другой стороны потокрассматривается как переменная некоторого объектного типа. Это позволяетвынести общие свойства и операции процессов ввода-вывода в определения базовыхклассов. Ввод-вывод из файлов и консоли, как правило, выполняется сиспользованием буфера и получение данных программой или вывод данных сводится кпересылке данных из одной области памяти в другую. Реальное обращение к внешнимустройствам происходит только при исчерпании данных в буфере (при вводе) илипри заполнении буфера (при выводе).
Системаклассов ввода-вывода С++ использует два базовых класса: класс ios и классstreambuf.
В классеios определены данные, характеризующие состояние потока, и функции, позволяющиеполучить доступ к информации о состоянии потока или изменить его состояние.Состояние потока определяется набором битовых флагов, для обращения к отдельнымфлагам в классе ios описаны перечислимые константы:
— битысостояния (статуса) потока
enum io_state { goodbit = 0x00, // никакие биты не установлены, все хорошо
eofbit = 0x01, // конец файла
failbit = 0x02, // ошибка в последней операции ввода/вывода
badbit = 0x04, // попытка выполнить неверную операцию
hardfail = 0x80 // неисправимая ошибка
};
— битырежима использования потока (режима ввода/вывода)
enum open_mode { in = 0x01, // поток открыт для чтения
out = 0x02, // поток открыт для записи
ate = 0x04, // перейти в конец файла при открытии
app = 0x08, // режим добавления в конец файла
trunc = 0x10, // усечение существующего файла
nocreate = 0x20, // ошибка открытия файла, если он не существует
noreplace = 0x40, // ошибка открытия, если файл существует
binary = 0x80 // двоичный (не текстовый) файл
};
— флагинаправления позиционирования в потоке
enum seek_dir { beg=0, cur=1, end=2 };
— флаги- манипуляторы управления вводом/выводом
enum { skipws = 0x0001, // пропускать пробелы при вводе
left = 0x0002, // выравнивание влево при выводе
right = 0x0004, // выравнивание вправо при выводе
internal = 0x0008, // пробел после знака или основания системы счисления
dec = 0x0010, // преобразование в десятичную систему счисления
oct = 0x0020, // преобразование в восьмеричную систему счисления
hex = 0x0040, // шестнадцатеричное преобразование
showbase = 0x0080, // использовать индикатор системы счисления при выводе
showpoint = 0x0100, // указывать десятичную точку при выводе
//(в числах с плавающей точкой)
uppercase = 0x0200, // прописные буквы при шестнадцатеричном выводе
showpos = 0x0400, // добавлять '+' для положительных целых
scientific= 0x0800, // применять нотацию вида 1.2345E2
fixed = 0x1000, // применять нотацию вида 123.45
unitbuf = 0x2000, // очищать все потоки после вставки в поток
stdio = 0x4000, // очищать stdout, stderr после вставки в поток
boolalpha = 0x8000 // вставлять/извлекать булевы как текст или цифры
};
Посколькуэти перечислимые константы объявлены как компоненты класса ios, для доступа кним требуется уточнение контекста, например, ios::in.
Классstreambuf обеспечивает создание и использование буфера ввода-вывода и содержиткомпоненты-данные для управления буфером и методы доступа к данным в буфере.Объект класса ios содержит указатель на связанный с ним объект streambuf.
Классыistream и ostream являются производными от класса ios, в них определеныфункции, выполняющие ввод (istream) и вывод (ostream) данных базовых типов истрок.
В классеistream определены операции бесформатного ввода (без преобразования вводимыхданных)и операции форматного ввода с преобразованием из внешнего представленияво внутреннее.
Функцииget() читают из потока данные типа char в массив, определяемый первымпараметром, второй параметр задает максимальное число вводимых символов (l),третий параметр устанавливает символ-ограничитель, ограничивающий ввод. Запоследним введенным символом в массив пишется символ ‘\0’.
istream _FAR & _RTLENTRY get( char _FAR *, int l, char = '\n');
istream _FAR & _RTLENTRY get( signed char _FAR *, int l, char = '\n');
istream _FAR & _RTLENTRY get( unsigned char _FAR *, int l, char ='\n');
Функцииread() выполняют чтение из потока и занесение в массив указанного числасимволов
istream _FAR & _RTLENTRY read( char _FAR *, int l);
istream _FAR & _RTLENTRY read( signed char _FAR *, int l);
istream _FAR & _RTLENTRY read(unsigned char _FAR *, int l);
Дляввода строки, заканчивающейся символом-ограничителем, служит функция getline()при этом символ-ограничитель также заносится в массив
istream _FAR & _RTLENTRY getline( char _FAR *, int, char = '\n');
istream _FAR & _RTLENTRY getline( signed char _FAR *, int, char= '\n');
istream _FAR & _RTLENTRY getline(unsigned char _FAR *, int, char= '\n');
Дляизвлечения из потока данных типа char вплоть до символа-ограничителя служитвариант функции get():
istream _FAR & _RTLENTRY get(streambuf _FAR &, char = '\n');
Другиеварианты функции get() обеспечивают извлечение одного символа
istream _FAR & _RTLENTRY get( char _FAR &);
istream _FAR & _RTLENTRY get( signed char _FAR &);
istream _FAR & _RTLENTRY get(unsigned char _FAR &);
int _RTLENTRY get(); // возвращает значение символа
int _RTLENTRY peek(); // возвращает следующий символ без удаления его из потока
int _RTLENTRY gcount(); // число символов, извлеченных из потока
// возвращение указанного символа в поток ввода
istream _FAR & _RTLENTRY putback(char);
Пропусксимволов с остановкой по ограничителю выполняет функция
istream _FAR & _RTLENTRY ignore(int = 1, int = EOF);
Форматныйввод релизуется на основе переопределения операций ввода
istream _FAR & _RTLENTRY operator>> (bool _FAR &);
istream _FAR & _RTLENTRY operator>>
(istream _FAR & (_RTLENTRY *_f)(istream _FAR &));
istream _FAR & _RTLENTRY operator>>
(ios _FAR & (_RTLENTRY *_f)(ios _FAR &) );
istream _FAR & _RTLENTRY operator>> ( char _FAR *);
istream _FAR & _RTLENTRY operator>> ( signed char _FAR *);
istream _FAR & _RTLENTRY operator>> (unsigned char _FAR *);
istream _FAR & _RTLENTRY operator>> ( char _FAR &);
istream _FAR & _RTLENTRY operator>> ( signed char _FAR &);
istream _FAR & _RTLENTRY operator>> (unsigned char _FAR &);
istream _FAR & _RTLENTRY operator>> (short _FAR &);
istream _FAR & _RTLENTRY operator>> (int _FAR &);
istream _FAR & _RTLENTRY operator>> (long _FAR &);
istream _FAR & _RTLENTRY operator>> (unsigned short _FAR &);
istream _FAR & _RTLENTRY operator>> (unsigned int _FAR &);
istream _FAR & _RTLENTRY operator>> (unsigned long _FAR &);
istream _FAR & _RTLENTRY operator>> (float _FAR &);
istream _FAR & _RTLENTRY operator>> (double _FAR &);
istream _FAR & _RTLENTRY operator>> (long double _FAR &);
Извлечениеиз istream и вставка в объект типа streambuf выполняет оператор-функция
istream _FAR & _RTLENTRY operator>> (streambuf _FAR *);
МакросFAR управляет способом представления указателей, макрос RTLENTRY определяетприменение стандартной билиотеки времени выполнения.
Аналогичныеоперации для вывода определены в классе ostream:
// вставить символ в поток
ostream _FAR & _RTLENTRY put( char);
ostream _FAR & _RTLENTRY put(signed char);
ostream _FAR & _RTLENTRY put(unsigned char);
// вставить в поток строку
ostream _FAR & _RTLENTRY write(const char _FAR *, int l);
ostream _FAR & _RTLENTRY write(const signed char _FAR *, int l);
ostream _FAR & _RTLENTRY write(const unsigned char _FAR *, int l);
Операцииформатированного вывола
Вывод«true» или «false» для данных типа bool
ostream _FAR & _RTLENTRY ostream::operator
Выводланных типа char
ostream _FAR & _RTLENTRY operator
ostream _FAR & _RTLENTRY operator
ostream _FAR & _RTLENTRY operator
Выводчисловых данных с преобразованием во внешнее представление
ostream _FAR & _RTLENTRY operator
ostream _FAR & _RTLENTRY operator
ostream _FAR & _RTLENTRY operator
ostream _FAR & _RTLENTRY operator
ostream _FAR & _RTLENTRY operator
ostream _FAR & _RTLENTRY operator
ostream _FAR & _RTLENTRY operator
ostream _FAR & _RTLENTRY operator
ostream _FAR & _RTLENTRY operator
Выводстрок, оканчивающихся нулевым байтом
ostream _FAR & _RTLENTRY operator
ostream _FAR & _RTLENTRY operator
ostream _FAR & _RTLENTRY operator
Выводзначения указателя в символьном формате
ostream _FAR & _RTLENTRY operator
Извлечениеданных их объекта streambuf и вставка в тот же ostream
ostream _FAR & _RTLENTRY operator
Вывод значений манипуляторов
ostream _FAR & _RTLENTRY operator
(ostream _FAR & (_RTLENTRY *_f)(ostream _FAR &));
ostream _FAR & _RTLENTRY operator
(ios _FAR & (_RTLENTRY *_f)(ios _FAR &));
Имеетсятакже класс iostream, производный от класса ios и объединяющий возможностиклассов istream и ostream.
Для рассмотренныхвыше классов отсутствуют конструкторы копирования и операция присваивания,точнее, они объявлены, но не определены. Для тех случаев, когда конструкторкопирования и операция присваивания необходимы, предусмотрены классыistream_withassign, ostream_withassign и iostream_withassign. Как экземплярыобъектов этих классов всегда объявляется объект cin (экземплярistream_withassign), обычно предназначенный для ввода с клавиатуры, и объектыcout, cerr и clog (экземпляры ostream_withassign), обычно предназначенные длявывода на экран.
Ввод-выводдля дисковых файлов обепечивается классами, описания которых содержатся в файлеfstream.h.
Классfilebuf, производный от streambuf, предназначен для добавления в streambufдополнительных средств управления буфером ввода-вывода.
Классfstreambase, производный от класса ios, служит базой для остальных классов,обеспечивающих файловый ввод-вывод, в нем определены методы:
void _RTLENTRY open(const char _FAR *, int, int = filebuf::openprot);
void _RTLENTRY attach(int);
void _RTLENTRY close();
void _RTLENTRY setbuf(char _FAR *, int);
Назначениеэтих методов очевидным образом следует из их названий.
Длянепосредственной работы с файлами служат классы ifstream, ofstream и fstream,базой для них служат классы fstreambase и, соответственно, istream, ostream иiostream. 6.2. Вывод в файл. Ввод из файла
Длявывода данных в дисковый файл в программе должна присутствовать директивапрепроцессора
#include подключающаяописания необходимых классов.
Преждечем выводить данные необходимо создать объект типа ofstream, для которогоимеется несколько конструкторов:
_RTLENTRY ofstream(); // Пустой объект, без привязки к файлу
// С привязкой к файлу, полное имя которого задается первым аргументом:
_RTLENTRY ofstream(const char _FAR *, int = ios::out,
int = filebuf::openprot);
// С привязкой к ранее открытому файлу, заданному своим дескриптором
_RTLENTRY ofstream(int);
// То же, что и предыдущий вариант, но задается новый буфер вывода
_RTLENTRY ofstream(int __f, char _FAR *, int);
Наиболеечасто оказывается полезным второй вариант конструктора, в котором указываетсятолько первый параметр — полное имя файла. Этот конструктор создает объект типаofstream, открывает указанный файл и присоединяет его к потоку вывода.
Собственнооперации вывода реализуются вызовом методов put, write или с использованиемпереопределенных операций
Аналогичнымспособом обеспечивается и ввод из файла: создается объект типа ifstream и дляввода применяются методы get, read или переопределенные операции >>.
Для типаifstream имеется набор аналогичных конструкторов:
_RTLENTRY ifstream();
_RTLENTRY ifstream(const char _FAR *,int = ios::in,
int = filebuf::openprot);
_RTLENTRY ifstream(int);
_RTLENTRY ifstream(int __f, char _FAR *, int);
Вкачестве примера рассмотрим программу, копирующую данные из одного файла вдругой.
#include
#include // Для вызова exit
int main ( int argc, char* argv [ ] )
{ char ch;
if ( argc != 3 ) // Проверка числа аргументов
{ cerr
ifstream source( argv [ 1 ] ); // Входной поток
if (! source )
{ cerr
exit ( 1 ); }
ofstream dest ( argv [2 ] ) ;
if (! dest )
{ cerr
exit ( 1 ); }
while ((ch = source.get ( ) ) != EOF )
dest.put( ch );
close ( source ); close ( dest );
return 0 ;
}6.3. Ввод-вывод данных объектных типов
Организациявывода данных определенных программистом объектных типов, в общем случаезависит от предполагаемого дальнейшего использования этих данных. Могутсохраняться в файле все компоненты данные, или только часть из них, можетприменяться форматированный или бесформатный вывод. Часто возникаетнеобходимость сохранить объектные данные в файле для последующего ихвосстановления в той же или другой программе.
Длявывода объекта в файл в определение класса может быть включенафункция-компонента с параметром ссылкой на объект типа ostream. Часто такойфункции назначают имя print или printon. Более изящным считаетсяпереопределение оператора
Пусть,например, в программе определен класс complex:
class complex
{ double re, im ;
public:
complex (double r =0, double i =0 ): re ( r ), im ( i )
{ } // конструктор
double real ( ) {return ( re ); }
double image ( ) { return ( im ); }
/* другие методы */
}
Тогдадля форматированного вывода комплексного числа в формате ( вещественная часть,мнимая часть ) можно так переопределить операцию
ostream& operator
{ int old_precision = s.precision ( 4 ); // установка числа дробных цифр
s
“, “
s.precision ( old_precision ); // восстановление числа дробных цифр
return ( s );
}
В данномслучае в переопределении
Вводобъектного данного из файла или стандартного потока организуется аналогично:либо в определение класса включается функция-компонента для инициализациикомпонент данных их входного потока, либо переопределяется операция >>для ввода компонент-данных из потока. В некоторых случаях оказывается болееудобным включить в описание класса дополнительный конструктор спараметром-ссылкой на объект типа istream, при этом все базовые классы должныиметь аналогичные конструкторы.
Пусть вописание класса Complex включена переопределенная операция >>:
class complex
{ double re, im ;
public:
complex (double r =0,double i=0 ): re( r ), im( i ) { } // конструктор
double real ( ) {return ( re ); }
double image ( ) { return ( im ); }
friend istream& operator >> ( istream&, Complex& );
/* другие методы */
}
Еслипредположить, что комплексные числа поступают из потока либо в видевещественного числа, возможно заключенного
вскобки, либо в виде пары чисел, разделенных запятой и заключенной в скобки,
топереопределяемую операцию ввода можно описать следующим образом:
istream& operator >> ( istream& s, Complex& c )
{ double re_n = 0, im_n = 0;
char ch = 0;
s >> ch ;
if ( ch == ‘(‘ )
{ s >> re_n >> ch ;
if ( ch == ‘,’ ) s >> im_n >> ch;
if ( ch != ‘)’) s.clear ( ios::failbit ); //Установка признака ошибки
}
else { s.putback ( ch ); s >> re_n; }
if ( s ) { c.re = re_n; c.im = im_n; } // Если не было ошибки
return ( s );
}
Рассмотренныевыше примеры сохранения объектов
в потокеи восстановления их из потока иллюстрируют наиболее простые варианты
этихопераций. Проблема сохранения объектов в потоке существенно усложняется,
когдатребуется сохранять в одном потоке объекты разных типов, когда между
объектамиразных типов имеются ссылки и некоторый объект содержит указатель
наобъект другого типа, причем на один и тот же объект могут ссылаться
несколькодругих объектов. В этой ситуации требуется для каждого сохраняемого
в потокеобъекта заносить в поток идентификатор типа объекта, гарантировать,
чтокаждый объект, на который имеются ссылки из других объектов, будет
помещенв поток только один раз. Средства для разрешения этих проблем имеются
вбиблиотеке классов-контейнеров classlib, содержащей
файлobjstrm.h с определениями необходимых
классови макросов.
Приложение 1. Задачи по программированию на Си
1.Составить функцию для подсчета числа серий положительных, отрицательных чисел инулей длиной не менее К в одномерном массиве целых чисел. Серией называетсяпоследовательность элементов массива, принадлежащих одному классу:
int series ( int n, int *mas, int *kzero, int *kplus, int *kminus, int k);
2.Составить функцию для слияния двух упорядоченных по возрастанию массивов целыхчисел:
int merge (int n, int m, int *mas1, int *mas2, int *res);
3.Составить функцию для построения списка индексов (номеров), строкупорядоченного по возрастанию элементов заданного (k-го) столбца матрицы.Элементыматрицы — целые числа:
void sort (int n, int *mas, int k, int* index);
4.Составить функцию для определения элемента матрицы, являющегося седловойточкой. Седловой точкой называется элемент, удовлетворяющий условиям:
a[k,l] = max min{ a[i,j] } = min max { a[i,j] }
i
Еслиседловой точки нет, установить k=l=-1
5. Составитьфункцию для подсчета количества различных чисел в массиве, содержащем n целыхчисел.
int count(int *a, int n);
6.Составить функцию для разделения текста, заданного строкой литер, на отдельныеслова и подсчета числа слов. Под словом понимается последовательность литер,отличных от пробела, ограниченная слева началом строки или пробелом и справа — пробелом, знаком препинания или концом строки.
int kwords(char* ss, char * sm, int kmax);
ss — исходная строка,
sm — массив строк длиной до 30 литер каждая (для размещения выделенных слов),
kmax — максимальное количество выделенных слов.
Предусмотретьсигнализацию о случаях, когда функция неприменима ( слишком много слов илислишком длинное слово ).
7.Составить функцию для определения
а)наибольшего простого числа, не превосходящего заданное целое n,
б)наименьшего простого числа, превосходящего заданное n.
int simple (int n);
8.Составить функцию для разложения заданного целого числа на простые множители.Результатом функции должен быть массив, содержащий простые множители, и целоечисло — количество множителей.
int simplefactor(int n, int *masfactor);
9.Составить функцию для вычисления числа сочетаний из n злементов по m (n и m — целые):
Cnm = n!/ ((n-m)! * m!)
Результатомфункции должно быть целое число, если Cnm =32767 и булевское значение false.
Boolean binom (int n, int m, int *cnm, double *dcnm);
10.Многочлены представляются в памяти ЭВМ целым числом n — степенью многочлена имассивом коэффициентов a[0],a[1],...,a[n].
а)Составить функцию для вычисления значения y=P(x) многочлена для заданногоаргумента x.
double valpoly( int n, double *a);
б)Составить функцию для вычисления коэффициентов многочлена-произведения двухдругих многочленов, заданных своими степенями и массивами коэффициентов.Функция возвращает степень многочлена — произведения.
int polyprod( int n, double *a, int m, double *b, double *res);
в)Составить функцию для вычисления коэффициентов многочлена-суммы двух других многочленов.Функциявозвращает степень многочлена — суммы.
int polysum ( int n, double *a, int m, double *b, double *res);
11. Внекоторой программе обработки таблиц сведения о динамически образуемых столбцахопределяются глобальными описаниями :
const int L_cln = 20; /*длина столбца таблицы*/
const int L_tab=30; /*максимальное число столбцов*/
struct COLUM
{ char name [ 5 ]; /* имя столбца */
double *adr; /* указатель на столбец */
};
COLUM Tabl [ L_tab } /*массив сведений о столбцах */
Дляобращения к столбцам используются их индексы в массиве Tabl. Если столбец неразмещен в памяти, то значение поля adr равно 0.
а)Составить функцию для выполнения поэлементного сложения двух столбцов собразованием столбца сумм. Входные данные — номера (индексы в Tabl ) столбцовслагаемых и столбца результата. Если столбец-результат не размещен в памяти, торазместить его и отметить зто в Tabl.
int sumcol( int k, int l, int r);
Функциявозвращает 0 при успешном выполнении операции или 1, если один или обастолбца-слагаемых не размещены в памяти.
б)Составить функцию для вычисления столбца частных от деления элементов одногостолбца на элементы другого столбца. Предусмотреть сигнализацию о случае, когдаэлемент столбца-делителя равен нулю. Остальные условия — как в варианте (а).
int subcol ( int k, int l, int r);
в)Составить функцию для вычисления элементов столбца относительных приростов впроцентах di=(ai-bi)/ai*100 для заданного исходного столбца. Остальные условия- как в вариантах (а) и (б).
int relincr ( int k, int r);
Функциявозвращает 1, если встретится делитель, равный 0, в остальных случаяхвозвращает 0.
г)Составить функцию для печати таблицы, содержащей столбцы, перечень которыхзадан списком (массивом ) индексов (номеров) столбцов в массиве Tabl.
int tabprint ( int *list, int n);
12. Внекоторой программе аналитических преобразований выражения, определяющиеформулы, хранятся в виде динамических структур — деревьев с описанием :
enum tpn = { FUNC, OPER, VAR, CNST}; /*типы узлов*/
struct node
{ enum tpn tp_node; /* тип узла */
int cop; /* тип операции или номер функции, константы, переменной */
node *al, *ar; /* указатели на подчиненные узлы */
};
а)Составить функцию для обхода дерева, заданного указателем на его корень, впорядке: левое поддерево, корень, правое поддерево. Выводить на экран (печать)тип узла и значение поля cop в узле.
void lkp ( node *root);
б) Вусловиях пункта (а) выполнить обход дерева в порядке: корень, левое поддерево,правое поддерево.
void klp ( node *root );
в) Вусловиях пункта (а) выполнить обход дерева в порядке: левое поддерево, правоеподдерево, корень.
void lpk ( node *root);
г)Составить функцию для освобождения памяти, занятой некоторым поддеревом.Входной параметр — указатель на корень удаляемого поддерева.
void deltree ( node *root);
13. Внекоторой программе сообщения об ошибках хранятся в файле из записей, каждаязапись состоит из двух строк по 60 литер. Позиция записи в дисковом файлеопределяется кодом ошибки — целым числом.
а)Составитьфункцию для вывода сообщения об ошибке на экран. Входной параметр код (номер)ошибки. Файл с сообщениями об ошибках открывается в главной программе.
б)Составитьпрограмму для создания файла сообщений об ошибках. При использовании этойпрограммы пользователь указывает имя файла, максимальное число сообщений обошибках, а затем задает коды ошибок и соответствующие им сообщения впроизвольном порядке.
14. Составитьфункцию для решения системы линейных уравнений
/> 1методом исключениянеизвестных.
int gauss (int n, double *a, double *b);
Функциявозвращает 0 при успешном выполнении и 1, если метод не работает.
15. Впамяти хранится массив из n
а)Cоставитьфункцию для вычисления оценок математического ожидания и дисперсии случайнойвеличины
void randval( int n, double *a, double *m, double *d);
a — исходный массив, m — вычисляемая оценка математического ожидания, d — оценкадисперсии.
б)Cоставитьфункцию для построения гистограммы (распределения частот) для реализациислучайной величины. Входные данные: n, x -массив значений случайной величиныx[1],...,x[n], k — число интервалов, на которые разбивается диапазон значенийслучайной величины.Результат функции r — массив из k чисел, значения частотпопадания в соответствующий интервал.
16.Выполнить варианты 15 (а),(б) для случая, когда значения реализации случайнойвеличины хранятся в текстовом файле, а остальные данные сообщаютсяпользователем, результаты расчетов выводятся на экран.
17.Значения некоторой функции представлены таблицей с постоянным шагом и в памятихранятся: число точек таблицы n, массив значений функции y[1],...,y[n],начальное значение аргумента x[1] и шаг по аргументу.
а)Составитьфункцию для выборки значения функции из таблицы с линейной интерполяцией.
б)Составитьфункцию для вычисления значения обратной функции для функции, заданной таблицейc постоянным шагом.
18.Составить функцию для выборки значений функции, заданной таблицей с переменнымшагом. Таблица хранится в памяти в форме массива из n строк и двухстолбцов(аргумент и значение функции).
19.Составить функцию для сортировки (упорядочения) массива mas из n элементов повозрастанию значений элементов:
void sort (int n, int *mas);
20.Составить функцию для сортировки (перестановки строк) матрицы из n строк и mстолбцов по возрастанию элементов k-го столбца;
void sortmas ( int n, int m, int *matr, int k);
21.Составить функцию для умножения матрицы matr из n строк и m столбцов на векторvect (из n элементов) с размещением результата в массиве res:
void matrvect( int n, int m, float *matr, float *vect, float *res);
22.Составить функцию для вычисления произведения матриц a из n строк и m с толбцови b из m строк и k столбцов с помещением результата в матрицу c:
void matrprod ( int n, int m, int k, float *a, float *b, float *c);
Приложение 2. Задачи по разработке систем объектов
1.Построить систему классов для описания плоских геометрических фигур: круг,квадрат. прямоугольник. Предусмотреть методы для создания объектов, перемещенияна плоскости, изменения размеров и вращения на заданный угол.
2.Построить описание класса, содержащего информацию о почтовом адресеорганизации. Предусмотреть возможность раздельного изменения составных частейадреса. создания и уничтожения объектов этого класса.
3.Составить описание класса для представления комплексных чисел с возможностьюзадания вещественной и мнимой частей как числами типов double, так и целымичислами. Обеспечить выполнение операций сложения, вычитания и умножениякомплексных чисел.
4.Составить описание класса для работы с цепными списками строк (строкипроизвольной длины) с операциями включения в список. удаление из спискаэлемента с заданным значением данного. удаления всего списка или конца списка.начиная с заданного элемента.
5.Составить описание класса для объектов — векторов, задаваемых координатамиконцов в трехмерном пространстве. Обеспечить операции сложения и вычитаниявекторов с получением нового вектора (суммы или разности), вычисленияскалярного произведения двух векторов, длины вектора, cos угла между векторами.
6.Составить описание класса прямоугольников со сторонами, параллельными осямкоординат. Предусмотреть возможность перемещения прямоугольников на плоскости,изменение размеров, построение наименьшего прямоугольника, содержащего двазаданных прямоугольника, и прямоугольника, являющегося общей частью(пересечением) двух прямоугольников.
7.Составить описание класса для определения одномерных массивов целых чисел(векторов). Предусмотреть возможность обращения к отдельному элементу массива сконтролем выхода за пределы индексов, возможность задания произвольных границиндексов при создании объекта и выполнения операций поэлементного сложения ивычитания массивов с одинаковыми границами индексов, умножения и деления всехэлементов массива на скаляр, печати (вывода на экран) элементов массива поиндексам и всего массива.
8.Составить описание класса для определения одномерных массивов строкфиксированной длины. Предусмотреть возможность обращения к отдельным строкаммассива по индексам, контроль выхода за пределы индексов, выполнения операцийпоэелементного сцепления двух массивов с образованием нового массива, слияниядвух массивов с исключением повторяющихся элементов, печать (вывод на экран)элементов массива и всего массива.
9.Составить описание класса многочленов от одной переменной, задаваемых степеньюмногочлена и массивом коэффициентов. Предусмотреть методы для вычислениязначения многочлена для заданного аргумента, операции сложения, вычитания иумножения многочленов с получением нового объекта — многочлена, печать (выводна экран) описания многочлена.
10.Составить описание класса одномерных массивов строк, каждая строка задается длинойи указателем на выделенную для нее память. Предусмотреть возможность обращенияк отдельным строкам массива по индексам, контроль выхода за пределы индексов,выполнения операций поэелементного сцепления двух массивов с образованиемнового массива, слияния двух массивов с исключением повторяющихся элементов,печать (вывод на экран) элементов массива и всего массива.
11.Составить описание объектного типа TMatr, обеспечивающего размещение матрицыпроизвольного размера с возможностью изменения числа строк и столбцов, выводана экран подматрицы любого размера и всей матрицы.Литература
1.Керниган Б., Ритчи Д. Язык программирования Си. — М., “Радио и связь”, 1989.
2. ПолИрэ. Объектно-ориентированное программирование с использованием С++: Пер. сангл. — Киев: НИПФ “ДиаСофт Лтд, 1995.
3.Цимбал А.А. и др. Turbo C++, язык и его применение. — М.: Джен Ай Лтд, 1993, — 512с.
4.Bjarne Stroustrup The C++ Programming language, Addison Weasley, 1986.