Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Tekhnologia_programmirovania.pdf
Скачиваний:
182
Добавлен:
08.04.2015
Размер:
1.76 Mб
Скачать

254

Глава 18. Конструктор копирования и оператор присваивания

18.1. Проблемы при копировании

Присваивание и копирование объектов уже рассматривалось в §15.7 на примере класса Date. Для простых классов, не содержащих указатели, проблем не возникает, достаточно встроенного почленного копирования одного объекта в другой объект. Посмотрим, что произойдет при выполнении копирования и инициализации для класса, включающего указатели на выделяемую в конструкторе класса память, на примере многоугольников из программы 49.

Приведем сокращенное объявление класса:

class Plgn

// Класс многоугольников

{

 

int n;

// Число вершин

float *x; float *y;

// Указатели на массивы абсцисс и ординат

static int x0, y0;

// Координаты центра

public:

 

Plgn(int n = 3);

// Конструктор с аргументом по умолчанию

~Plgn() { delete [] x; delete [] y; } // Деструктор освобождает память

static void SetBegCoord(int xn, int yn)

// Установка координат центра

{ x0 = xn; y0 = yn; }

 

};

 

 

# include <stdlib.h>

// Доступ к random

Plgn::Plgn(int nn)

// Конструктор

{…}

// Здесь выделяется память под массивы абсцисс и ординат

Напишем функцию badcopy, которая создает и копирует многоугольники.

void badcopy()

 

 

{

 

 

Plgn P(5);

// (1)

Создание пятиугольника

Plgn T;

// (2)

Создание треугольника

Plgn Q = P;

// (3)

Создание копии Q пятиугольника P

T = P;

// (4)

Попытка превратить треугольник в пятиугольник

}// (5) Удаление Q, T, P

Конструктор копирования и оператор присваивания 255

Рассмотрим манипуляции с памятью, которые выполняет badcopy, рис.77. На шаге (1) создается пятиугольник P со своей переменной n и своими указателями x, y, которые указывают на массивы памяти, выделенные в конструкторе. На шаге (2) создается треугольник T, имеющий свои n, x, y и свои массивы координат. На шаге (3) создается многоугольник Q, у которого n, x, y имеют те же значения, что и у многоугольника P. Указатели Q.x, Q.y указывают на ту же память, что и P.x, P.y. На шаге (4) за счет присваивания T.n, T.x, T.y получают такие же значения, как P.n, P.x, P.y. В результате возникли две проблемы.

Первая проблема состоит в том, что потеряна связь с памятью, выделенной треугольнику T. Память, выделяемая динамически оператором new, должна освобождаться явно оператором delete, в противном случае она все равно будет числится за программой, потерявшей с ней связь. Произойдет «утечка памяти».

Вторая проблема состоит в том, что теперь на одну и ту же область памяти ссылаются по три указателя. При завершении функции badcopy будут вызваны деструкторы объектов P, T, Q, которые три раза освободят одну и ту же память.

 

 

 

 

 

 

Память пятиугольника

Память

 

 

 

 

P

 

 

 

 

 

 

 

 

 

 

 

 

освобождается

 

n

 

x

 

 

 

 

 

 

 

 

 

 

 

трижды

 

 

 

5

 

y

 

(1)

 

 

 

 

 

 

 

 

 

 

Q

n

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

(3)

x

 

 

 

 

 

(4)

 

 

 

 

 

 

 

 

 

5

 

 

n

T

x

 

 

 

 

 

 

 

 

 

 

 

y

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

Память не

 

3

 

y

 

(2)

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

освобождается

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

ни разу

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

3 после (2), 5 после (4) Память треугольника

256 18

Рис.77. Работа с памятью функции badcopy

Можно попробовать выполнить следующую программу, использующую badcopy:

int Plgn::x0, Plgn::y0; // Определение статических членов класса

# include <conio.h> void main()

{

Plgn::SetBegCoord(100, 100); // Установл. значен. статич. членов badcopy();

}

Скорее всего ее выполнение приведет к системной ошибке.

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

Программа 55. Вектора на плоскости

Вданной программе используется графика TC.

Разработаем класс, моделирующий вектора на плоскости. Под вектором будем понимать направленный отрезок, откладываемый от начала координат, рис.Error: Reference source not found.

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

 

 

y

 

 

M`

y

 

 

 

 

 

 

 

δ

``

 

 

 

 

 

 

 

 

 

y1

γ

 

 

β

 

M1

M2

y

 

 

 

2

 

 

 

 

 

 

````

 

 

 

 

 

 

 

 

 

 

`

 

O

α

x

 

x1 x2 x

Рис.44. Вектор на плоскости

Конструктор копирования и оператор присваивания 257

новое имя для результирующего вектора, например, имя для суммы векторов a и b должно быть a+b.

На рис.Error: Reference source not found показана математическая плоскость, вектор OM и стрелка на его конце. Для рисования лепестков стрелки нужно соединить точку M с точками M1 и M2. Если известны координаты x, y конца вектора, то координаты точек M1 и M2 можно вычислить по формулам:

x1 = x l cosδ , x2 = x l cosγ ,

y1 = y l sinδ , y2 = y l sinγ .

Здесь l – длина лепестка стрелки, δ = α-β, γ = α+β, α – угол между вектором и осью x, β – угол отклонения лепестков стрелки от вектора.

При изображении вектора нужно принять некоторую точку экрана за начало отсчета. Сделаем координаты этой точки статическими членами класса. Кроме этого, статическими членами сделаем длину лепестка стрелки l и угол отклонения лепестка β.

Объявление класса поместим в заголовочном файле Bivect.h:

// Файл Bivect.h

 

#ifndef BIVECTH

 

#define BIVECTH

 

class Bivect

 

{

 

double x, y;

// Координаты вектора

char *name;

// Имя вектора

static int xc, yc;

// Координаты центра экрана

static int l;

// Длина лепестков стрелки

static double beta;

// Угол отклонения в радианах

public:

 

Bivect ()

// Конструктор по умолчанию

{ x = 0; y = 0; name = 0;}

// создает нулевой вектор без названия

Bivect (float x, float y, char *s);

// Конструктор

Bivect(Bivect&);

// Конструктор копирования

~Bivect()

// Деструктор освобождает

{ delete[] name; }

// память, занимавшуюся именем

//SetStaticParam: установка статических членов класса.

//Угол bt в градусах

static void SetStaticParam(int xci, int yci, int len, double bt);

Bivect& operator=(Bivect &a);

// Оператор присваивания

258

18

 

Bivect operator+(Bivect &a);

// Сумма

Bivect operator-(Bivect &a);

// Разность

Bivect operator*(double);

// Умножение вектора на число

friend Bivect operator*(double, Bivect&); // Умножение числа на вектор

Bivect operator-();

// Противоположный вектор

void Show(int color = 10);

// Рисование вектора на экране

private:

 

void Arrow();

// Изображает стрелку на конце вектора

};

 

# endif

 

Функция Arrow(), которая делает необходимые вычисления и изображает лепестки стрелки, сделана закрытой, так как ее будет вызывать только метод класса Show().

Функция умножения числового коэффициента на вектор объявлена с ключевым словом friend (друг), чтобы она имела доступ к закрытым членам класса. Данная функция не может быть членом класса, так как ее первый аргумент не член класса.

Реализацию методов класса поместим в файл Bivect.cpp:

// Файл Bivect.cpp

#include "Bivect.h"

#include <graphics.h>

#include <string.h>

#include <math.h> #include <stdlib.h>

Bivect::Bivect(float x0, float y0, char *nm)

// Конструктор

{

// x0, y0 - координаты, nm - имя вектора

x = x0; y = y0;

name = new char[strlen(nm) + 1]; strcpy(name, nm);

}

Bivect::Bivect(Bivect& b)

{

x = b.x; y = b.y;

name = new char[strlen(b.name) + 1]; strcpy(name, b.name);

}

//Выделение памяти под имя

//Копирование имени

//Конструктор копирования

//Копируем координаты

//Выделение памяти под имя

//Копирование имени

//SetStaticParam: установка статических переменных класса.

//Угол bt задается в градусах

void Bivect::SetStaticParam(int xci, int yci, int len, double bt)

{

Конструктор копирования и оператор присваивания 259

xc = xci; yc = yci; l = len; beta = bt * M_PI / 180;

}

 

 

Bivect& Bivect::operator=(Bivect &b)

// Оператор присваивания

{

 

 

if (this != &b){

 

// Если не присваивание самому себе

delete name;

 

// Освобождение старой памяти

name = new char [strlen(b.name)+1]; // Выделение памяти под имя

if (name)

 

// Если удалолсь выделить память

strcpy(name, b.name);

 

// Копирование имени

x = b.x;

 

// Присваивание координат

y = b.y;

 

 

}

 

 

return *this;

// Возвращение обновленного вектора

}

 

 

Bivect Bivect::operator+(Bivect &b)

// Суммирование векторов

{

 

 

// Временный массив для имени суммы

char *tmp = new char[strlen(name) + 2 + strlen(b.name)];

strcpy(tmp, name);

// Формирование имени нового

strcat(tmp, "+");

// вектора из двух имен,

strcat(tmp, b.name);

// соединенных знаком '+'

Bivect sum(x + b.x, y + b.y, tmp);

// Создание нового вектора

delete[] tmp;

// Удаление временной строки

return sum;

// Возвращение созданного вектора

}

 

Bivect Bivect::operator-(Bivect &b)

// Вычитание векторов

{

 

// Временный массив для имени разности

char *tmp = new char[strlen(name) + 2 + strlen(b.name)];

strcpy(tmp, name);

// Формирование имени нового

strcat(tmp, "-");

// вектора из двух имен,

strcat(tmp, b.name);

// соединенных знаком '-'

Bivect diff(x - b.x, y - b.y, tmp);

// Создание нового вектора

delete[] tmp;

// Удаление временной строки

return diff;

// Возвращение созданного вектора

}

 

Bivect Bivect::operator*(float k)

// Умножение вектора на число

{

 

char factor[10];

// Место для числового множителя

gcvt(k, 3, factor);

// Преобразование числа в строку

// Память под имя произведения

char *tmp = new char[strlen(factor) + 2 + strlen(name)]; strcpy(tmp, factor); // Копируем сомножитель

260 18

strcat(tmp, " ");

// Добавляем пробел

strcat(tmp, name);

// Добавляем название вектора

Bivect prnmb(k * x, k * y, tmp);

// Создание вектора-произведения

delete[] tmp;

 

return prnmb;

 

}

 

Bivect operator*(float k, Bivect& a)

// Умножение числа на вектор

{

 

return a * k; // Используем оператор умножения вектора на число

}

 

 

Bivect Bivect::operator-()

// Противоположный вектор

{

 

 

Bivect b;

// Нулевой вектор без названия

return b - *this;

// Из нулевого вектора вычитаем данный

}

 

 

void Bivect::Show(int color)

// Функция рисования вектора на экране

{

 

 

int c = getcolor();

// Запоминаем текущий цвет рисования

setcolor(color);

// Установить новый цвет рисования

line(xc, yc, xc + x, yc - y);

 

// Рисуем отрезок

Arrow();

 

// со стрелкой

int xt = 2, yt = 2;

 

// Нахождение отступа

if(x <= 0) xt = -6;

 

// от конца вектора

if(y >= 0) yt = -10;

 

// для вывода названия

outtextxy(xc + x + xt, yc - y + yt, name);

// Вывод названия

setcolor(c);

// Восстановление прежнего цвета рисования

}

 

 

// Round: округляет x до ближайшего целого int Round(double x)

{

return x - int(x) > 0.5 ? int(x)+1: int(x);

}

 

 

void Bivect::Arrow()

// Стрелка для вектора

{

 

 

double x1, y1, x2, y2;

 

double alpha = atan2(y, x);

 

double delta = alpha - beta;

 

double gamma = alpha + beta;

 

x1

= x - l * cos(delta);

// Координаты конца

y1

= y - l * sin(delta);

// первого лепестка

// Первая часть стрелки

 

line(Round(xc + x), Round(yc - y), Round(xc + x1), Round(yc - y1));

x2

= x - l * cos(gamma);

// Координаты конца

Конструктор копирования и оператор присваивания 261

y2 = y - l * sin(gamma); // второго лепестка // Вторая часть стрелки

line(Round(xc + x), Round(yc - y), Round(xc + x2), Round(yc - y2));

}

Напишем небольшую программу для испытания разработанного класса, поместим ее в файле MnBivect.cpp:

// Файл MnBivect.cpp

#include "Bivect.h"

#include <conio.h>

// Определение статических членов класса Bivect int Bivect::xc, Bivect::yc, Bivect::l;

double Bivect::beta;

void main()

{

int gd = DETECT, gm;

//Определение объектов класса Bivect Bivect a(-80, 60, "a"), c, b(-100, -100, "b");

initgraph(&gd, &gm, "d:\\Programs\\BorlandC\\BGI");

//Установка значений статических переменных

Bivect::SetStaticParam(getmaxx() / 2, getmaxy() / 2, 12, 15);

line (10, getmaxy() / 2, getmaxx(), getmaxy() / 2);

// Ось OX

line (getmaxx() / 2, 10, getmaxx() / 2, getmaxy());

// Ось OY

a.Show(GREEN);

// Рисуем зелёный вектор a

getch();

 

 

b.Show(BLUE);

// Рисуем синий вектор b

getch();

 

 

c = a + b;

// Сумма векторов

c.Show(YELLOW);

// Рисуем сумму a и b

getch();

 

 

c = a - b;

// Разность векторов

c.Show(RED);

// Рисуем разность a и b

getch();

 

 

c = -b;

// Противоположный вектор

c.Show(WHITE);

// Вектор, противоположный вектору b

getch();

 

 

a = -1.5 * a;

// Изменение вектора a

 

a.Show();

 

 

getch();

 

 

closegraph();

// Переход в текстовый режим