This commit is contained in:
nihonium 2022-09-14 19:05:27 +03:00
parent 46d1c64684
commit ab6732eded
Signed by: nihonium
GPG key ID: 0251623741027CFC
98 changed files with 10319 additions and 0 deletions

View file

@ -0,0 +1,197 @@
/*
Особые методы
В прошлой части мы рассмотрели различные методы классов. Некоторые из этих методов называются особыми.
Они выделяются по сравнению с другими методами тем, что они будут создаваться автоматически даже если вы их не напишите.
К особым методам класса относятся:
1) Конструктор по умолчанию. То есть конструктор, который не принимает ни одного аргумента.
В случае со строкой String это контруктор:
String()
2) Конструктор копирования. То есть конструктор, который создаёт объект из другого объекта такого же типа, что и данный.
В случае со строкой String это контруктор:
String(const String& s)
3) Деструктор. Это метод, который вызывается при удалении объекта. В отличии от конструкторов, деструктор у объекта всегда один.
В случае со строкой String это:
~String()
4) Оператор присваивания. Это метод, который вызывается при присваивании одного метода класса String другому методу класса String.
В случае со строкой String это:
String& operator=(const String& right)
5,6) Есть ещё 2 особых метода, которые мы пройдём позже.
Все остальные методы, включаю другие конструкторы и перегруженные операторы, к особым методам не относятся.
То есть остальные методы не могут создаваться автоматически.
В этой части мы рассмотрим при каких условиях происходит вызов того или иного особого метода.
Для этого будем использовать класс String, в котором была добавлена печать на экран для каждого особого метода и для коструктора из const char*.
Например, конструктор по умолчанию будет печатать Default Constructor и т. д.
*/
#include <iostream>
#include <cstdlib>
using std::cout, std::endl, std::size_t;
class String
{
private:
size_t mSize;
size_t mCapacity;
char* mpData;
public:
String(const char* str)
{
cout << "String Constructor from const char* (" << str << ")" << endl;
size_t i = 0;
while (str[i] != '\0')
i++;
mSize = i;
mCapacity = i + 1;
mpData = (char*)std::malloc(sizeof(char) * mCapacity);
for (size_t i = 0; str[i]; ++i)
mpData[i] = str[i];
mpData[mSize] = '\0';
}
String()
{
cout << "String Default Constructor" << endl;
mSize = 0;
mCapacity = 1;
mpData = (char*)std::malloc(sizeof(char));
mpData[0] = '\0';
}
String(const String& s)
{
cout << "String Copy Constructor (" << s.mpData << ")" << endl;
size_t i = 0;
mSize = s.mSize;
mCapacity = mSize + 1;
mpData = (char*)std::malloc(sizeof(char) * mCapacity);
for (size_t i = 0; i < mSize; ++i)
mpData[i] = s.mpData[i];
mpData[mSize] = '\0';
}
~String()
{
cout << "String Destructor (" << mpData << ")" << endl;
std::free(mpData);
}
String& operator=(const String& right)
{
cout << "String Assignment Operator (" << right.mpData << ")" << endl;
if (this == &right)
return *this;
mSize = right.mSize;
mCapacity = right.mCapacity;
std::free(mpData);
mpData = (char*)malloc(sizeof(char) * mCapacity);
for (size_t i = 0; i <= mSize; ++i)
mpData[i] = right.mpData[i];
return *this;
}
char& operator[](size_t i)
{
return mpData[i];
}
size_t getSize() const {return mSize;}
size_t getCapacity() const {return mCapacity;}
const char* cStr() const {return mpData;}
};
std::ostream& operator<<(std::ostream& left, const String& right)
{
left << right.cStr();
return left;
}
class Book
{
public:
String title;
int pages;
float price;
};
int main()
{
Book a = Book();
cout << a.title << " " << a.pages << " " << a.price << endl;
}
/*
У класса Book есть 3 поля, одно из которых имеет тип String.
Но у класса Book не определён ни один метод. У него нет никаких конструкторов или перегруженных операторов.
Теперь посмотрим на код:
Book a = Book();
Тут мы создаём объект типа Book с помощью конструктора по умолчанию. Но у Book не написан конструктор по умолчанию!
Что же произойдёт в этом случае? Ошибка? Нет, на самом деле в этом случае конструктор будет создан автоматически. Вот такой:
Book() {}
Автоматически-сгенерированный конструктор по умолчанию ничего не делает.
Тем не менее, перед вызовом этого конструктора, компилятор должен вызвать конструктор по умолчанию для всех полей класса,
у которых есть конструктор по умолчанию.
В этом случае, при создании объекта класса Book с помощью конструктора по умолчанию, будет вызван конструктор по умолчанию класса String.
Аналогично, автоматически сгенерируется деструктор по умолчанию, вот такой:
~Book() {}
Он пустой, но нужно помнить, что после вызова деструктора класса, автоматически вызываются деструкторы для все его полей (у которых есть деструкторы)
В этом случае, после вызова деструктора класса Book вызовется деструктор класса String.
Задачи:
1) Скомпилируйте программу, запустите и убедитесь, что в этом примере вызываются
конструктор по умолчанию и деструктор класса String.
*/

View file

@ -0,0 +1,191 @@
/*
Особые методы
Проверим, что компилятор автоматически генерирует конструктор копирования и оператор присваивания.
*/
#include <iostream>
#include <cstdlib>
using std::cout, std::endl, std::size_t;
class String
{
private:
size_t mSize;
size_t mCapacity;
char* mpData;
public:
String(const char* str)
{
cout << "String Constructor from const char* (" << str << ")" << endl;
size_t i = 0;
while (str[i] != '\0')
i++;
mSize = i;
mCapacity = i + 1;
mpData = (char*)std::malloc(sizeof(char) * mCapacity);
for (size_t i = 0; str[i]; ++i)
mpData[i] = str[i];
mpData[mSize] = '\0';
}
String()
{
cout << "String Default Constructor" << endl;
mSize = 0;
mCapacity = 1;
mpData = (char*)std::malloc(sizeof(char));
mpData[0] = '\0';
}
String(const String& s)
{
cout << "String Copy Constructor (" << s.mpData << ")" << endl;
size_t i = 0;
mSize = s.mSize;
mCapacity = mSize + 1;
mpData = (char*)std::malloc(sizeof(char) * mCapacity);
for (size_t i = 0; i < mSize; ++i)
mpData[i] = s.mpData[i];
mpData[mSize] = '\0';
}
~String()
{
cout << "String Destructor (" << mpData << ")" << endl;
std::free(mpData);
}
String& operator=(const String& right)
{
cout << "String Assignment Operator (" << right.mpData << ")" << endl;
if (this == &right)
return *this;
mSize = right.mSize;
mCapacity = right.mCapacity;
std::free(mpData);
mpData = (char*)malloc(sizeof(char) * mCapacity);
for (size_t i = 0; i <= mSize; ++i)
mpData[i] = right.mpData[i];
return *this;
}
char& operator[](size_t i)
{
return mpData[i];
}
size_t getSize() const {return mSize;}
size_t getCapacity() const {return mCapacity;}
const char* cStr() const {return mpData;}
};
std::ostream& operator<<(std::ostream& left, const String& right)
{
left << right.cStr();
return left;
}
class Book
{
public:
String title;
int pages;
float price;
};
void print(Book x)
{
cout << x.title << " " << x.pages << " " << x.price << endl;
}
int main()
{
Book a;
a.title = "War and Peace";
a.pages = 1000;
a.price = 1100;
Book b;
b = a;
print(b);
}
/*
Что напечатает данная программа?
Разберём код из функции main подробно:
1) Вызывается автоматически сгенерированный конструктор по умолчанию в строке:
Book a;
При этом перед вызовом этого конструктора вызовется и конструктор класса String поля title.
2) Полям объекта a присваиваются некоторые значения:
a.title = "War and Peace";
a.pages = 1000;
a.price = 1100;
Интересно отметить, что строка a.title = "War and Peace" работает, несмотря на то, что слева от знака
присваивания стоит объект типа String, а справа от знака присваивания стоит объект типа const char[14].
Типы не совпадают, но это работает, так как у класса String есть конструктор от const char*.
Таким образом, сначала вызовется этот конструктор и создастся временный объект типа String, а потом
вызовется оператор присваивания между двумя объектами класса String.
После этого временный объект удалится и, соответственно, вызовется его деструктор.
3) Вызывается автоматически сгенерированный конструктор по умолчанию в строке:
Book b;
4) Вызывается автоматически сгенерированный перегруженный оператор присваивания в строке:
b = a;
Автоматически сгенерированный оператор присваивания применяет оператор присваивания для каждого поля.
5) Передача в функцию print осуществляется по значению. Следовательно в строке
print(b);
объект b должен быть скопирован внутрь функции. Для этого должен быть вызван конструктор копирования.
Конструктор копирования у класса Book не написан, поэтому компилятор автоматически его сгенерирует.
Автоматически сгенерированный конструктор копирования копирует каждое поле в соответствующее поле нового объекта.
Если у поля есть конструктор копирования (как например у класса String), то вызывается конструктор копирования этого поля.
Если у поля нет конструктора копирования (как например у int или float), то поле просто копируется побайтово.
6) Выходим из функции print и вызываем деструктор для объекта x.
7) Выходим из функции main и вызываем деструкторы для объектов a и b.
*/

View file

@ -0,0 +1,148 @@
/*
Замечание по поводу автоматической генерации конструктора по умолчанию
*/
#include <iostream>
#include <cstdlib>
using std::cout, std::endl, std::size_t;
class String
{
private:
size_t mSize;
size_t mCapacity;
char* mpData;
public:
String(const char* str)
{
cout << "String Constructor from const char* (" << str << ")" << endl;
size_t i = 0;
while (str[i] != '\0')
i++;
mSize = i;
mCapacity = i + 1;
mpData = (char*)std::malloc(sizeof(char) * mCapacity);
for (size_t i = 0; str[i]; ++i)
mpData[i] = str[i];
mpData[mSize] = '\0';
}
String()
{
cout << "String Default Constructor" << endl;
mSize = 0;
mCapacity = 1;
mpData = (char*)std::malloc(sizeof(char));
mpData[0] = '\0';
}
String(const String& s)
{
cout << "String Copy Constructor (" << s.mpData << ")" << endl;
size_t i = 0;
mSize = s.mSize;
mCapacity = mSize + 1;
mpData = (char*)std::malloc(sizeof(char) * mCapacity);
for (size_t i = 0; i < mSize; ++i)
mpData[i] = s.mpData[i];
mpData[mSize] = '\0';
}
~String()
{
cout << "String Destructor (" << mpData << ")" << endl;
std::free(mpData);
}
String& operator=(const String& right)
{
cout << "String Assignment Operator (" << right.mpData << ")" << endl;
if (this == &right)
return *this;
mSize = right.mSize;
mCapacity = right.mCapacity;
std::free(mpData);
mpData = (char*)malloc(sizeof(char) * mCapacity);
for (size_t i = 0; i <= mSize; ++i)
mpData[i] = right.mpData[i];
return *this;
}
char& operator[](size_t i)
{
return mpData[i];
}
size_t getSize() const {return mSize;}
size_t getCapacity() const {return mCapacity;}
const char* cStr() const {return mpData;}
};
std::ostream& operator<<(std::ostream& left, const String& right)
{
left << right.cStr();
return left;
}
class Book
{
public:
String title;
int pages;
float price;
Book(const String& aTitle, int aPages, float aPrice)
{
title = aTitle;
pages = aPages;
price = aPrice;
}
};
int main()
{
Book a;
}
/*
Конструктор по умолчанию не генерируется автоматически если у класса написан хотя бы один конструктор (любой).
Например, в этом пример у класса Book написан один конструктор:
Book(const String& aTitle, int aPages, float aPrice)
Поэтому в данном случае конструктор по умочанию автоматически генерироваться не будет.
В строке Book a; должен быть вызван конструктор по умолчанию.
Но у класса Book такого конструктора нет и автоматически он не был создан. Поэтому эта строка приведёт к ошибке.
Но конструктор копирования, оператор присваивания и деструктор автоматически генерироваться будут.
Задача:
Исправьте ошибку, написав конструктор по умолчанию самостоятельно.
*/

View file

@ -0,0 +1,164 @@
/*
Удалённые методы
*/
#include <iostream>
#include <cstdlib>
using std::cout, std::endl, std::size_t;
class String
{
private:
size_t mSize;
size_t mCapacity;
char* mpData;
public:
String(const char* str)
{
cout << "String Constructor from const char* (" << str << ")" << endl;
size_t i = 0;
while (str[i] != '\0')
i++;
mSize = i;
mCapacity = i + 1;
mpData = (char*)std::malloc(sizeof(char) * mCapacity);
for (size_t i = 0; str[i]; ++i)
mpData[i] = str[i];
mpData[mSize] = '\0';
}
String()
{
cout << "String Default Constructor" << endl;
mSize = 0;
mCapacity = 1;
mpData = (char*)std::malloc(sizeof(char));
mpData[0] = '\0';
}
String(const String& s)
{
cout << "String Copy Constructor (" << s.mpData << ")" << endl;
size_t i = 0;
mSize = s.mSize;
mCapacity = mSize + 1;
mpData = (char*)std::malloc(sizeof(char) * mCapacity);
for (size_t i = 0; i < mSize; ++i)
mpData[i] = s.mpData[i];
mpData[mSize] = '\0';
}
~String()
{
cout << "String Destructor (" << mpData << ")" << endl;
std::free(mpData);
}
String& operator=(const String& right)
{
cout << "String Assignment Operator (" << right.mpData << ")" << endl;
if (this == &right)
return *this;
mSize = right.mSize;
mCapacity = right.mCapacity;
std::free(mpData);
mpData = (char*)malloc(sizeof(char) * mCapacity);
for (size_t i = 0; i <= mSize; ++i)
mpData[i] = right.mpData[i];
return *this;
}
char& operator[](size_t i)
{
return mpData[i];
}
size_t getSize() const {return mSize;}
size_t getCapacity() const {return mCapacity;}
const char* cStr() const {return mpData;}
};
std::ostream& operator<<(std::ostream& left, const String& right)
{
left << right.cStr();
return left;
}
class Book
{
public:
String title;
int pages;
float price;
Book() {};
Book(const String& aTitle, int aPages, float aPrice)
{
title = aTitle;
pages = aPages;
price = aPrice;
}
Book(const Book& b) = delete;
};
int main()
{
Book a;
Book b = a;
}
/*
Если же вы не хотите создавать какой-либо метод и не хотите чтобы он создавался автоматически,
то его можно удалить с помощью ключевого слова delete.
Например, в этом примере у класса Book удалён конструктор копирования вот так:
Book(const Book& b) = delete;
Это означает, что конструктор копирования не создастся автоматически.
Поэтому в строке:
Book b = a;
произойдёт ошибка компиляции.
Задача:
1) Можно ли передать объект класса с удалённым конструктором копирования в функцию по значению?
Например, если есть функция:
void print(Book b)
{
cout << b.title << " "<< b.pages << " " << b.price << endl;
}
Можно ли туда передать что-нибудь?
*/

View file

@ -0,0 +1,188 @@
/*
Неявное приведение типа с помощью конструктора от одного параметра.
*/
#include <iostream>
#include <cstdlib>
using std::cout, std::endl, std::size_t;
class String
{
private:
size_t mSize;
size_t mCapacity;
char* mpData;
public:
String(const char* s)
{
cout << "String Constructor from const char* (" << s << ")" << endl;
size_t i = 0;
while (s[i] != '\0')
i++;
mSize = i;
mCapacity = i + 1;
mpData = (char*)std::malloc(sizeof(char) * mCapacity);
for (size_t i = 0; s[i]; ++i)
mpData[i] = s[i];
mpData[mSize] = '\0';
}
String()
{
cout << "String Default Constructor" << endl;
mSize = 0;
mCapacity = 1;
mpData = (char*)std::malloc(sizeof(char));
mpData[0] = '\0';
}
String(const String& s)
{
cout << "String Copy Constructor (" << s.mpData << ")" << endl;
size_t i = 0;
mSize = s.mSize;
mCapacity = mSize + 1;
mpData = (char*)std::malloc(sizeof(char) * mCapacity);
for (size_t i = 0; i < mSize; ++i)
mpData[i] = s.mpData[i];
mpData[mSize] = '\0';
}
~String()
{
cout << "String Destructor (" << mpData << ")" << endl;
std::free(mpData);
}
String& operator=(const String& right)
{
cout << "String Assignment Operator (" << right.mpData << ")" << endl;
if (this == &right)
return *this;
mSize = right.mSize;
mCapacity = right.mCapacity;
std::free(mpData);
mpData = (char*)malloc(sizeof(char) * mCapacity);
for (size_t i = 0; i <= mSize; ++i)
mpData[i] = right.mpData[i];
return *this;
}
char& operator[](size_t i)
{
return mpData[i];
}
size_t getSize() const {return mSize;}
size_t getCapacity() const {return mCapacity;}
const char* cStr() const {return mpData;}
};
std::ostream& operator<<(std::ostream& left, const String& right)
{
left << right.cStr();
return left;
}
void print(String s)
{
cout << s << endl;
}
int main()
{
String a;
a = "Dog";
print("Mouse");
}
/*
Одна из скрытых вещей, где используются конструкторы, это для неявного приведения типов.
Рассмотрим следующие строки:
String a;
a = "Dog";
Во второй строке должен вызваться оператор присваивания.
Слева от оператора присваивания стоит объект типа String, а справа от оператора присваивания стоит объект типа const char[4].
Поэтому должен вызваться метод operator=(const char* s) класса String. (при передаче в функцию массив автоматически конвертируется в указатель)
Но такого метода в классе String нет. Что же тогда будет сделано?
В этом случае будет сделано следующее:
1) Будет создан временный объект типа String с использованием конструтора String(const char* s).
2) Будет вызван оператор присваивания operator=(const String& s). Объекту a присвоится временный объект.
3) Временный объект будет уничтожен, при этом вызовется деструктор класса String.
Рассмотрим строку:
print("Mouse");
Функция print должна принимать объект типа String, но на вход ей приходит объект типа const char[6].
Что будет сделано в этом случае?
И в этом случае всё сработает, так как объект s функции print будет создан с помощью конструктора класса String от const char*.
Таким образом конструктор от одного аргумента автоматически используется для неявного приведения одного типа в другой.
Задачи:
1) Что если мы напишем конструктор класса String от числа типа int.
Этот конструктор будет принимать число n и создавать строку, состоящую из n символов 'a'
String(int n)
{
cout << "String Constructor from int (" << n << ")" << endl;
mSize = n;
mCapacity = mSize + 1;
mpData = (char*)std::malloc(sizeof(char) * mCapacity);
for (size_t i = 0; i < mSize; ++i)
mpData[i] = 'a';
mpData[mSize] = '\0';
}
Будет ли этот конструктор использоваться для неявного приведения чисел типа int в строки типа String?
Например, будет ли работать следующий код:
String b;
b = 5;
(b будет строкой состоящей из 5 символов 'a' ?)
print(10);
(напечатает строку, состоящую из 10 символов 'a')
*/

View file

@ -0,0 +1,147 @@
/*
Ключевое слово explicit.
*/
#include <iostream>
#include <cstdlib>
using std::cout, std::endl, std::size_t;
class String
{
private:
size_t mSize;
size_t mCapacity;
char* mpData;
public:
explicit String(const char* s)
{
cout << "String Constructor from const char* (" << s << ")" << endl;
size_t i = 0;
while (s[i] != '\0')
i++;
mSize = i;
mCapacity = i + 1;
mpData = (char*)std::malloc(sizeof(char) * mCapacity);
for (size_t i = 0; s[i]; ++i)
mpData[i] = s[i];
mpData[mSize] = '\0';
}
explicit String(int n)
{
cout << "String Constructor from int (" << n << ")" << endl;
mSize = n;
mCapacity = mSize + 1;
mpData = (char*)std::malloc(sizeof(char) * mCapacity);
for (size_t i = 0; i < mSize; ++i)
mpData[i] = 'a';
mpData[mSize] = '\0';
}
String()
{
cout << "String Default Constructor" << endl;
mSize = 0;
mCapacity = 1;
mpData = (char*)std::malloc(sizeof(char));
mpData[0] = '\0';
}
String(const String& s)
{
cout << "String Copy Constructor (" << s.mpData << ")" << endl;
size_t i = 0;
mSize = s.mSize;
mCapacity = mSize + 1;
mpData = (char*)std::malloc(sizeof(char) * mCapacity);
for (size_t i = 0; i < mSize; ++i)
mpData[i] = s.mpData[i];
mpData[mSize] = '\0';
}
~String()
{
cout << "String Destructor (" << mpData << ")" << endl;
std::free(mpData);
}
String& operator=(const String& right)
{
cout << "String Assignment Operator (" << right.mpData << ")" << endl;
if (this == &right)
return *this;
mSize = right.mSize;
mCapacity = right.mCapacity;
std::free(mpData);
mpData = (char*)malloc(sizeof(char) * mCapacity);
for (size_t i = 0; i <= mSize; ++i)
mpData[i] = right.mpData[i];
return *this;
}
char& operator[](size_t i)
{
return mpData[i];
}
size_t getSize() const {return mSize;}
size_t getCapacity() const {return mCapacity;}
const char* cStr() const {return mpData;}
};
std::ostream& operator<<(std::ostream& left, const String& right)
{
left << right.cStr();
return left;
}
void print(String s)
{
cout << s << endl;
}
int main()
{
String a;
a = "Dog";
print("Mouse");
print(10);
}
/*
Иногда всё-таки не хочется, чтобы конструкторы использовались для неявного приведения типов.
Ведь такое приведение типов может произойти там, где мы этого не хотим.
Чтобы конструктор не использовался для неявного приведения, его нужно пометить с помощью ключевого слова explicit.
В этом примере, конструкторы String(const char* s) и String(int n) помечены как explicit.
Поэтому эти конструкторы не будут использоваться для неявного приведения типов и код в функции main выдаст ошибку.
Но эти конструкторы всё равно можно вызывать явно, вот так:
String a;
a = String("Dog");
print(String("Mouse"));
print(String(10));
*/