Вспомогательные сведения Аргументы функции по умолчанию
3. Конструктор
Проблема:
Выход:
Конструктор в классе String
Другие виды конструкторов
Особенности конструктора, как функции:
Класс String:
Недостаток определенного нами класса String
Новые конструкторы
Пример использования конструкторов (3) и (2’)
Замечание
Инициализация значением другой переменной
Разберемся, почему это происходит
Тогда инициализация String r = s; выполнится грамотно.
Все верно!
4. Деструктор
Пример
Особенности деструктора как функции:
char & Index (int)
Возвращаемый тип char
5. Неявный указатель this
Пример
Пример использования функции Plus:
0.99M
Категория: ПрограммированиеПрограммирование

Л2.Ч1. Конструкторы. Деструктор. This

1. Вспомогательные сведения Аргументы функции по умолчанию

В С++ можно не задавать фактические аргументы,
компилятор их подставит автоматически, если в
определении функции задано их некоторое значение.
Например,
void Line(int k, char s =’*’ ) //s - аргумент по умолчанию
{ if (k<0) return;
while ( k-- ) printf(“%c”,s);
}
10 *
50*
20!
Тогда возможны обращения:
Line(10); Line(50); Line(20,’!’);
1

2.

Можно задать несколько аргументов функции
по умолчанию, но в списке аргументов они
должны идти последними.
Например,
void Fun(double x, int k = 30, char s = ’*’) {...}
Возможны обращения:
Fun(15.5); Fun(x, 20); Fun(2*x, 10, ’+’);
Неверно:
Fun(5,,’!’);
2

3. 3. Конструктор

В С++ при определении переменных часто их сразу
инициализируют.
Например, int x = 5;
Предположим, что при определении объекта
String s;
мы хотели бы проинициализировать его, например,
пустой строкой, т.е определить объект s у которого
len = 0; line[0] = ’\0’.
Для структур эта инициализация выполняется так:
String s = {“”, 0};
Для объектов класса такая инициализация запрещена в
силу принципа инкапсуляции.
3

4. Проблема:

1) внутри описания класса инициализировать нельзя по
синтаксису языка,
2) вне класса записать
s.len = 0; s.line[0] = ’\0’;
тоже нельзя, т.к. член-данные из части private
недоступны.
Замечание: если определить член-данные в части
public, то их можно инициализировать как структуру,
т.е.
String s = {“”, 0};
4

5. Выход:

Инициализацию должна выполнять специальная
член-функция класса.
Конструктор!
Определение. Член-функция класса, предназначенная для инициализации член-данных класса при определении объектов класса, называется конструктором.
Конструктор всегда имеет имя класса.
5

6. Конструктор в классе String

Объявление конструктора:
String();
Определение конструктора:
String:: String(){ len = 0; line[0] = ’\0’;}
(1)
Определение объектов:
String s1, s2;
При этом всегда неявно вызывается конструктор и
выполняется инициализация объектов.
Так как заданный конструктор не имеет аргументов, то
он называется конструктором по умолчанию.
6

7. Другие виды конструкторов

В классе можно задать не один конструктор, а
несколько.
Например,
для
класса
String
можно
задать
конструктор с аргументом, аналогичный функции Fill:
String:: String(const char * s)
(2)
{ for( len = 0; s[len]! = '\0'; len++) line[len] = s[len] ;
line[len] = '\0';
}
7

8.

Тогда объекты можно определить таким образом:
работает конструктор по умолчанию (1)
String s1, s2(“Иванов”), s3 = String(“Петров”);
работает конструктор с аргументом (2)
Для объекта s3 конструктор задается явно (но так
конструктор используется редко).
Заметим, что в классе всегда должен быть один
конструктор по умолчанию и один (или
несколько) с аргументами.
8

9. Особенности конструктора, как функции:

1. Главная: конструктор не имеет возвращаемого
значения (даже void), так как его единственное
назначение – инициализировать собственные членданные объекта;
2. Конструктор имеет имя класса;
3. Конструктор
всегда
работает
определении объектов класса.
неявно
при
9

10. Класс String:

const int MS = 255;
class String
{ char line[MS];
int len;
public:
String();
String(const char * s);
void Fill(const char *);
int Len() { return len;}
void Print() { cout << line; }
char & Index(int i);
};
10

11. Недостаток определенного нами класса String

Он берет для каждого объекта 259 байтов памяти, хотя
фактически использует меньше.
Изменим определение класса String следующим
образом
class String{ char *line; int len;
public:
....
};
Тогда конструкторы надо определить иначе, т.к. кроме
инициализации значений член-данных, они должны брать
память в динамической области для поля line.
11

12. Новые конструкторы

В новом классе объявим 2 конструктора
String(int L = 80);
//с аргументом по умолчанию
String (const char *); //с аргументом строкой
и определим их вне класса:
String:: String(int L)
// L=80 – не повторять!
{ line = new char [L]; len=0; line[0]=’\0’; }
(3)
String::String(const char * s)
(2’)
{line = new char [strlen(s)+1];
// +1 - для нуль-кода
for (len=0; s[len] != ’\0’; line[len] = s[len], len++);
line[len] = ‘\0’;
}
12

13. Пример использования конструкторов (3) и (2’)

String s1(10), s2, s3(“без слов”);
конструктор (2’)
конструктор (3) конструктор (3)
аргумент задан L=10 аргумент по умолчанию L=80
line
s1:
line
s2:
line
s3:
len
0
len
0
len
8
L=8+1=9
10 байт
\0
80 байт
\0
б е з
...
9 байт
с л о в \0
13

14. Замечание

В классе должен быть или конструктор по умолчанию
без аргументов вида (1), или конструктор с аргументом
по умолчанию вида (3).
В противном случае, следующее определение
String ss;
вызовет сообщение о двусмысленности:
‘Ambiguity between ‘String::String()’ and ‘String::String(int)’
- ‘Двусмысленность между String() и String( int )’
14

15. Инициализация значением другой переменной

В С++ кроме инициализации константным значением
int x = 5;
используется и инициализация одного данного
значением другого:
int y = x;
В классе String подобная инициализация
привести привести к ошибкам.
Рассмотри почему может возникнуть ошибка.
может
15

16.

Пусть заданы следующие определения
String s(“паровоз”);
...
String r = s;
// определение объекта r и
// инициализация его значением
// объекта s
r.Index(4) = ‘х’ ;
r.Index(6) = ‘д’;
// изменим на пароход
Теперь выведем объекты s и r:
s.Print();
r.Print();
Увидим, что выведется пароход в обоих случаях.
Это недопустимо.
16

17. Разберемся, почему это происходит

char * s)
(2’)
При определении объекта String::String(const
{line = new char [strlen(s)+1];
String s(“паровоз”);
for(len=0; s[len] !=’\0’; line[len]=s[len], len++);
line[len] != ’\0’;
работает конструктор,
}
который возьмет память в динамической области 8 байтов
len
line
s:
7
п а р
о
з \0
о в
и адрес первого запишет в поле s.line.
куча!
Затем цикл for занесет в поле *line слово паровоз и
одновременно определит len = 7.
17

18.

При определении объекта r
String r = s; // или String r(s);
компилятор просто выполняет копирование полей
r.line = s.line и r.len = s.len
len
line
х
д
s:
7
п а р
о
з
о в
r:
7
\0
А значит поле r.line будет
показывать на ту же
динамическую область!
И при выполнении операторов
r.Index(4) = ‘х’ ; r.Index(6) = ‘д’;
изменятся оба объекта.
18

19.

Что неграмотно и
недопустимо !
19

20.

Поэтому для инициализации одного объекта другим
надо
определить
специальный
конструктор
копирования, заголовок которого в классе общем
случае имеет вид
X( const X& );
// где X - имя класса
В классе String его можно задать следующим образом:
String::String(const String &s)
{ line = new char[ s.len+1 ];
for (len = 0; len<=s.len; line[ len ] = s.line[ len ], len++);
len--;
}
объявив его в классе
String(const String &);
20

21. Тогда инициализация String r = s; выполнится грамотно.

Тогда инициализация String::String(const String &s)
{line = new char [strlen(s)+1];
String r = s;
for (len=0; len<=s.len; line[len]=s.line[len], len++);
выполнится грамотно. } len--;
s:
len
7
Конструктор копирования
возьмет для объекта r
line
п
а
р
о
о
в
х
п
а
р
о
в
з
\0
куча
line
д
о
з
\0
len
5
1
7
4
3
2
6
новую динамическую память
длиной s.len + 1 байтов.
И цикл for затем запишет из объекта s в поле r.line слово
паровоз, в поле r.len длину 7.
При выполнении операторов r.Index(4)=‘х’ ; r.Index(6)=‘д’;
r:
значение s.line теперь не изменится !
21

22. Все верно!

Оператор
s.Print(); ‒ выведет ‘паровоз’
r.Print(); ‒ выведет ‘пароход’
Замечание
Конструктор
копирования
кроме
рассмотренной
инициализации работает также
• при передаче значений фактических аргументовобъектов в функцию
• при возврате результата-объекта из функции.
22

23. 4. Деструктор

В языке С++ одним из важных моментов является
освобождение памяти, занятой переменными, при
выходе из функции.
Рассмотрим пример. Определена функция
void F()
{ int k;
String s1(20), s2(“ФПМК”), *s3;
s3= new String (“ТГУ”);
}
23

24.

При выходе из функции освобождается память для
локальных объектов, т.е. k, s1,s2, s3.
Но рассмотрим внимательнее, как это будет реализовано.
При выходе
куча
k:
line
s1:
\0
0
line
s2:
Пустая строка из 20 байтов
len
...
5 байт
len
Ф
4
new
П
line
М
К
\0
len
s3:
3
Не экономно!
Т
конструктор 2’
Эта память
будет
“брошена!”
Г
У
\0
24

25.

Для того, чтобы при выходе из функций динамическая
память, которая берется конструкторами объектов,
освобождалась
автоматически,
надо
задать
специальную член-функцию
деструктор.
Определение. Деструктор - это член-функция
класса,
предназначенная
для
освобождения
динамической памяти, занимаемой член-данными
класса, при выходе из функций.
Деструктор имеет формат
~ имя_класса(){…}
25

26. Пример

Для класса String деструктор можно определить таким
образом
~ String(){delete [ ] line;}
В этом случае при выходе из области видимости
функции F() память для объектов s1, s2, которую брал
конструктор для поля line, будет освобождена.
Заданный деструктор это будет делать по умолчанию.
26

27.

Память по операции
Работает
стандартное
освобождение
3) Стандартно
от ячейки
s3 delete s3;
памяти
от локальных
данных
при выходе
из функции
будет освобождена в 3 этапа:
при выходе из функции
куча
k:
line
s1:
0
line
s2:
Пустая строка из 20 байтов
len
...
5 байт
len
Ф
4
new
s3:
\0
П
line
М
К
Работает
деструктор
\0
len
3
Т
Г
Динамическую память, занятую
объектом, заданным через указатель s3,
надо освобождать явно операцией
delete s3;
У
\0
1) деструктором
27
2) операцией delete

28. Особенности деструктора как функции:

• он не имеет аргументов;
• он не возвращает значения;
• работает неявно для всех объектов при выходе из
функций.
Замечание. Работу деструктора можно “увидеть”, если
в деструкторе задать какой-либо вывод.
Например,
~String() {printf(“\nРаботает деструктор класса String”);
delete [ ] line;
}
28

29.

class String
{ char *line; int len;
public:
String(int L=80);
// конструктор с аргументом по умолчанию
String(const char *); // конструктор с аргументом-строкой
String(const String &);
// конструктор копирования
~String(){ delete [] line;}
// деструктор
void Print() { printf(“%s”, line);}
int Len() { return len;};
char & Index( int );
void Fill( const char* );
};
29

30. char & Index (int)

char & Index (int)
Определим (поправим!) функцию Index за классом:
char & String::Index( int i)
{ if (i<0 || i>=len)
{ printf(”\n Индекс за пределами “); exit(0);}
return line[i];
}
Тип возвращаемого значения char & - ссылка
возвращает не просто значение символа, а ссылку на
ячейку, где он находится.
Это и позволяет выполнить присвоение вида
r.Index (4) = ’х’;
30

31. Возвращаемый тип char

Если определить тип возвращаемого значения просто
char, то присвоение вида
r.Index (4) = ’х’;
(*)
было бы ошибочным,
так как функция вернет значение символа и компилятор
будет трактовать оператор (*), как присвоение одного
кода символа другому коду, как в данном примере
‘в’=’х’;
что невозможно.
В других операциях в этом случае символ использоваться
может, кроме присвоения ему нового значения.
31

32. 5. Неявный указатель this

Каждый объект класса имеет свою копию член-данных.
Но на все объекты имеется один экземпляр каждой
член-функции.
Вопрос: как же член-функция “понимает”, с членданными какого объекта она работает?
Ответ:
с теми, которые
вызвавшему эту функцию.
НАПРИМЕР,
s2.Print();
принадлежат
объекту,
// с член-данными объекта s2
32

33.

Когда объект вызывает функцию, то в эту функцию
передается неявный указатель на этот объект.
Его можно задать и явно с помощью ключевого слова
this
НАПРИМЕР,
void Print() { cout << this->line; }
Однако в данном случае - это излишне.
Но бывают ситуации (и довольно частые в ООП), когда
приходится задавать этот указатель явно.
33

34. Пример

В классе String определим функцию, которая будет к
первой строке приписывать вторую и результатом
возвращать первую
(конкатенация строк).
Объявим ее в классе:
String & Plus(String &);
34

35.

Определяем ее вне класса:
String & String :: Plus(String &s2)
{char *t = new char[ len+1];
strcpy_s(t, len+1, line);
delete []line; len = len+s2.len;
// новая длина
line = new char[ len+1];
strcpy_s(line, len+1, t);
strcat_s(line, len+1, s2.line);
delete [] t;
return *this; }
//возвращаем «этот» объект
Вернуть надо 2 поля line и len.
Но: return line, len; ‒ нельзя!
35

36.

Другой вариант определения функции Plus
String & String :: Plus(String &s2)
{ len += s2.len
char *t = new char [len+1];
//берем память под результат
strcpy_s(t, len+1, line);
// копируем в нее старую строку
strcat_s(t, len+1, s2.line);
//дописываем строку s2
delete []line;
line = t;
return *this;
}
36

37. Пример использования функции Plus:

String s1(“Объект “), s2(“класса String.”);
String *s3 = new String(s1.Plus(s2));
// работает функция Plus,
// а затем конструктор копирования
// при создании динамического объекта s3
s3->Print();
// вывод *s3 = ”Объект класса String.”;
37
English     Русский Правила