Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
методичка по программированию СУА.doc
Скачиваний:
15
Добавлен:
11.11.2019
Размер:
1.3 Mб
Скачать

Копирующий конструктор

Остановимся чуть подробнее на одном из видов конструктора с аргументом, в котором в качестве аргумента выступает объект того же самого класса. Такой конструктор часто называют копирующим, поскольку предполагается, что при его выполнении создается объект-копия другого объекта. Для класса String он может выглядеть следующим образом:

class String

{

public:

String(const String& s);

};

String::String(const String& s)

{

length = s.length;

str = new char[length + 1];

strcpy(str, s.str);

}

Очевидно, что новый объект будет копией своего аргумента. При этом новый объект независим от первоначального в том смысле, что изменение значения одного не изменяет значения другого.

// первый объект с начальным значением

// "Astring"

String a("Astring");

// новый объект – копия первого,

// т.е. со значением "Astring"

String b(a);

// изменение значения b на "AstringAstring",

// значение объекта a не изменяется

b.Concat(a);

Столь логичное поведение объектов класса String на самом деле обусловлено наличием копирующего конструктора. Если бы его не было, компилятор создал бы его по умолчанию, и такой конструктор просто копировал бы все атрибуты класса, т.е. был бы эквивалентен:

String::String(const String& s)

{

length = s.length;

str = s.str;

}

При вызове метода Concat для объекта b произошло бы следующее: объект b перераспределил бы память под строку str, выделив новый участок памяти и удалив предыдущий (см. определение метода выше). Однако указатель str объекта a по-прежнему указывает на первоначальный участок памяти, только что освобожденный объектом b. Соответственно, значение объекта a испорчено.

Для класса Complex, который мы рассматривали ранее, кроме стандартного конструктора можно задать конструктор, строящий комплексное число из целых чисел:

class Complex

{

public:

Complex();

Complex(int rl, int im = 0);

Complex(const Complex& c);

// прибавить комплексное число

Complex operator+(const Complex x) const;

private:

int real; // вещественная часть

int imaginary; // мнимая часть

};

//

// Стандартный конструктор создает число (0,0)

//

Complex::Complex() : real(0), imaginary(0)

{}

//

// Создать комплексное число из действительной

// и мнимой частей. У второго аргумента есть

// значение по умолчанию — мнимая часть равна

// нулю

Complex::Complex(int rl, int im=0) :

real(rl), imaginary(im)

{}

//

// Скопировать значение комплексного числа

//

Complex::Complex(const Complex& c) :

real(c.real), imaginary(c.imaginary)

{}

Теперь при создании комплексных чисел происходит их инициализация:

Complex x1; // начальное значение – ноль

Complex x2(3);

// мнимая часть по умолчанию равна 0

// создается действительное число 3

Complex x3(0, 1); // мнимая единица

Complex y(x3); // мнимая единица

Конструкторы, особенно копирующие, довольно часто выполняются неявно. Предположим, мы бы описали метод Concat несколько иначе:

Concat(String s);

вместо

Concat(const String& s);

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

b.Concat(a)

компилятор создал бы временную переменную типа String – копию объекта a, и передал бы ее в качестве аргумента. При выходе из метода String эта переменная была бы уничтожена. Представляете, насколько снизилось бы быстродействие метода!

Второй пример вызова конструктора – неявное преобразование типа. Допустима запись вида:

b.Concat("LITERAL");

хотя сам метод определен только для аргумента – объекта типа String. Поскольку в классе String есть конструктор с аргументом – указателем на байт (а литерал – как раз константа такого типа), компилятор произведет автоматическое преобразование. Будет создана автоматическая переменная типа String с начальным значением "LITERAL", ссылка на нее будет передана в качестве аргумента метода String, а по завершении Concat временная переменная будет уничтожена.

Чтобы избежать подобного неэффективного преобразования, можно определить отдельный метод для работы с указателями:

class String

{

public:

void Concat(const String& s);

void Concat(const char* s);

};

void

String::Concat(const char* s)

{

length += strlen(s);

char* tmp = new char[length + 1];

if (tmp == 0) {

// обработка ошибки

}

strcpy(tmp, str);

strcat(tmp, s);

delete [] str;

str = tmp;

}