- •Конструктор копирования (KK) может вызываться явно, а также при определении объекта в сочетании
- •Переопределение операций внутри класса
- •если формальный параметр или результат переда-ются
- •приведение типа - особенность операции – отсутствие
- •Переопределение операции () позволяет использовать
- •Переопределение операций распределения памяти class memory{
- •void free(void *q0) {
- •Классы стандартных потоков ввода-вывода
- •Перегрузка функций
- •Функции void Func(int); int Func(int);
- •Для того чтобы объекты, в состав которых входят
- •выделить в памяти место под объект создать «пустой»
- •Основная сущность полиморфизма - в возможности
- •При переходе от объекта производного класса к внут-
- •class A {
- •Внешний полиморфизм – ср-во объединения классов
- •Внутренний полиморфизм как элемент «отложенного»
- •примеры «отложенного» программирования:
- •Простые алгоритмы – взять составные части и
Конструктор копирования (KK) может вызываться явно, а также при определении объекта в сочетании с инициализатором:
poly a3=a1; // Синтаксис КК - определение с инициализатором poly a4(a2); // Явный вызов КК
!!! основная его задача – обеспечивать корректное копирование объектов при их передаче и возвращении в функцию (метод) по значению. Если KK def, то он автоматически вызывается компилятором еще в 2х случаях:
• при использовании формального параметра, передаваемого по значению (формальный параметр - объект, передаваемый по значению, создается в стеке в момент вызова функции и инициализируется копией фактического параметра (вызов КК));
• функция возвращает результат в виде объекта, передаваемого по значе- нию (в вызывающем контексте компилятор формирует безымянный вре- менный объект, в момент выполнения оператора return он инициализирует- ся копией объекта в выражении, записанном в операторе return (вызов КК).
!!!!!!!! деструкторы вызываются для всех объектов, в том числе и для формальных параметров – значений, и для временных объектов – результатов. Поэтому конструктор копирования «уравнивает»
количество конструкторов и деструкторов и обязателен в перечисленных случаях, если объекты содержат динамические структуры данных или связанные ресурсы.
int compare(poly &T){ // Сравнение полиномов int k=0;
poly R(*this); // Копия текущего for (int i=R.n;i>=0;i--)
R.pd[i]=-R.pd[i]; // Инвертирование коэффициентов R.add(T); // Вычитание из второго - текущего R.normalize();
if (R.n==0 && R.pd[0]==0)
return 0; // Результат - нулевой полином = равны if (R.pd[R.n]<0) return 1;
return -1;} // Иначе - знак старшего коэффициента
Переопределение операций внутри класса
Если операция работает с операндами, принад- лежащими к некоторому классу, то ее желательно внести в этот класс (чтобы не было проблем с доступом к закрытой части объекта).
переопределение операций в классе - возможно, если I операнд операции является объектом этого класса - вводится специальный метод:
метод определяется в классе первого операнда;имя метода – operator<знак операции>;первый операнд – текущий объект класса;
II операнд – формальный параметр, кот может быть передан по значению или по ссылке;тип формального параметра должен совпадать с типом второго операнда;
результат операции может быть произвольного типа, он может возвращаться как указатель, ссылка или значение;на действия, выполняемые в теле метода,
ограничений не накладывается (содержательная интерпретация операции может быть любой);
если формальный параметр или результат переда-ются
по значению, а объект содержит динамические данные, то в классе необходим конструктор, который автоматически вызывается при передаче такого операнда и возвращении результата по return.
Тип результата операции и способ его формиро-вания может быть , интерпретация – тоже. Следить нужно только за соблюдением закрытости данных объекта и за корректностью работы с динамическими данными.
poly operator+(poly T){// Переопред сложения |
|||
|
T.add(*this); // II операнд по значению (копия) |
||
|
return T; } |
// Добавление I к копии II |
|
// Переопределение сравнений |
|
||
int operator<(poly &T) { |
return compare(T)<0; } |
||
int operator>(poly &T) { |
return compare(T)>0; } |
||
int operator==(poly &T){ return compare(T)==0;} |
|||
К экзамену – операторы !=, <=, >= и т.д. |
|||
Особенности переопределения операций |
|||
Присваивание: |
|
|
|
· |
разрушение содержимого текущего объекта – |
||
левого операнда (аналогично деструктору); |
|||
· копирование содержимого объекта-параметра |
|||
(правого операнда) в текущий объект (аналогично |
|||
конструктору копирования); |
|
||
· |
возвращение ссылки на текущий объект. |
||
poly &operator=(poly &R){ |
// Присваивание |
if (&R==this) return *this;// Присваив "сам в себя" delete []pd; // Разрушить левую часть (текущий) load(R.n,R.pd);//Копия правой части (аналог констр) return *this; } // Возвращает ссылку на левый
приведение типа - особенность операции – отсутствие
формальных параметров и спецификации типа ре- зультата, поскольку он и так определяется def типом.
// переопределение приведения к int - возвращает // размерность полинома
operator int(){ return n; }
«Преобразовать» объект можно . . . к указателю. Если указатель интерпретировать как динамичес-кий массив, то можно выгрузить в него внутрен-ние данные объекта (например, к-ты полинома).
// Переопределение приведения к double* - // возвращение динамического массива operator double*(){
double *q=new double[n+2];
q[0]=n; // в нулевой ячейке – размерн полинома for (int i=0;i<=n;i++) // начиная с первой – к-ты q[i+1]=pd[i];
return q;}
Такое преобразование будет срабатывать и при присваивании объекта-полинома к указателю
(выражение вида double *q=a1;)
Переопределение операции () позволяет использовать |
||||
синтаксис вызова ф-ии применительно к объекту класса |
||||
(имя объекта с круглыми скобками). Кол-во операндов в |
||||
скобках может быть любым. |
|
|
|
|
Переопределение операции [ ] позволяет использо-вать |
||||
синтаксис эл-та массива (имя объекта с квадр скоб). |
|
|||
В классе полиномов выражение вида a[i] позволяет |
||||
получить ссылку на i-ый к-т полинома, а выр-е вида a(i,v) |
||||
записать значение v в качестве i-го к-та |
|
|||
Операции создания и уничтожения объектов в ди- |
||||
намической памяти могут быть переопределены: |
||||
static void *operator new(size_t size); |
|
|
||
static void operator delete (void *); |
|
|
||
void * - указатель на область памяти, выделяемую под |
||||
объект, size |
- размер объекта в |
байтах, size_t - тип |
||
размерности области памяти, int или long. Переопре- |
||||
деление этих операций позволяет написать собст-венное |
||||
распределение |
памяти |
для |
объектов |
класса. |
Переопределенные операции будут вызываться при
создании динамических объектов (но не их массивов).
Переопределение операций распределения памяти class memory{
char *pa; // Обл распределяемой памяти – «кучи» int sz0;
public:При выделении памяти имеется несколько стратегий pa=newпоиска свободного блока. Простейшая - ищется своб
блок, строго подходящий по размеру. Если нашли, то он отмечается как занятый. В противном случае необходимый блок «отрезается» от конца самого первого. При таком подходе самый первый блок будет
void всегда свободен, и от него будут отрезаться
«недостающие» куски. !!Такая стратегия хороша, char когда преобладают запросы на выделение памяти
int lnt;блоками одного и того же размера.
while(p<pa+sz0){//
lnt=*(int*)p; // Извлечь из-под указателя длину блока if (lnt<0){// Занятый блок - пропустить
lnt=-lnt; // Инвертировать длину printf("busy:"); }
else printf("free:"); // Вывести 16ричный адрес и длину printf(" addr=%8x sz=%d\n",p,lnt); p+=lnt+sizeof(int); // Сдвинуть указатель на длину
// блока + длина счетчика
void *malloc(int sz) { char *p=pa;
int lnt;
while(p<pa+sz0){// Пока не достиг адреса конца области lnt=*(int*)p; // Извлечь из-под указателя длину блока if (lnt<0) // Занятый блок - пропустить
p+= Ф-я выделения памяти возвращает указатель на else {ifпамять «вслед за» счетчиком длины выделенной
области. Т. е, запоминает, сколько памяти было
выделено, и при выполнении ф-и освобождения
return
первым делом смещает указатель назад к счетчику
}длины. !! данная версия в состоянии проверить корректность возвращаемого ф-ей main адреса
} } |
занятого блока (хотя и не обязана это делать). |
lnt=*(int*)pa; |
|
if (sz+sizeof(int)>lnt) return NULL; // Остаток мал – |
|
|
// нет памяти |
lnt -=sz+sizeof(int); // Уменьшить размер I блока |
|
*(int*)pa=lnt; // и записать полученный остаток |
|
p=pa+lnt+sizeof(int); // Указатель на новый блок |
|
*(int*)p=-sz; // Записать в него размерность (занят – <0) |
|
return p+sizeof(int); // Возвратить указат на память |
|
} |
// «вслед за» счетчиком |
void free(void *q0) { |
|
char *q=(char*)q0-sizeof(int); // Сместить указатель |
|
|
// на счетчик длины |
int lnt=*(int*)q; // Извлечь счетчик длины |
|
lnt=-lnt; |
|
char *pr=NULL,*p=pa; // pr – указат на предыдущ блок |
|
int |
Кроме формального объявления блока свободным |
(путем инвертирования счетчика) необходимо еще |
|
if |
«склеить» с текущим блоком предыдущ и последующ, |
pr=p;если они также свободны (дефрагментация). Для |
|
|
поиска предыдущего блока - сделать лишний цикл |
} // |
просмотра содержимого распределяемой памяти с |
запоминанием указателя на предыдущий блок. |
|
*(int*)q=lnt; |
|
if |
«Cклеивание» - увеличение длины предыдущего блока |
|
на размер текущего (+ счетчик длины текущего). |
} p=pr;Аналогичное действие надо и для следующего блока
lnt=*(int*)p;
q=p+lnt+sizeof(int);
if (q<pa+sz0 && *(int*)q>0) // Есть следующ и он своб *(int*)p+=*(int*)q+sizeof(int); // Склеить со следующ };
}