Перегрузка операций.
Тест Задание №1
Предположим, что Square — это класс, а square — это объект этого класса. Какой способ перегрузки лучше использовать для следующих операторов?
Ответ:
бинарный оператор + лучше всего реализовать как обычную/дружественную функцию;
унарный оператор - лучше всего реализовать как функцию-член;
оператор << должен быть реализован как обычная/дружественная функция;
оператор = должен быть реализован как функция-член.
Задание №2
Напишите класс Average, который будет вычислять среднее значение всех передаваемых ему целых чисел. Используйте два члена: первый должен быть типа int32_t и использоваться для вычисления суммы всех передаваемых чисел, второй должен быть типа int8_t и использоваться для вычисления количества передаваемых чисел. Чтобы найти среднее значение, нужно разделить сумму на количество.
a) Текст программы:
#include <iostream> using namespace std;
class Average { private: int_least32_t m_total; // прописываем типы необходимых данных int_least8_t m_numbers;
public: Average() { }
friend std::ostream& operator<<(std::ostream& out, const Average& average) //объявляем дружественный поток для того, чтоб прописать оператор вывода //для того, чтоб вывести члены класса { out << static_cast<double>(average.m_total) / average.m_numbers; //так как здесь нужно деление с плавающей запятой, а не целочисленное //мы используем системное преобразование типов для более точного результата
return out; }
Average& operator+=(int num) // Поскольку operator+= изменяет свой левый операнд, мы запишем его как член { m_total += num; ++m_numbers; return *this; } }; int main() { Average avg; avg += 5; std::cout << avg << '\n'; // 5 / 1 = 5 avg += 9; std::cout << avg << '\n'; // (5 + 9) / 2 = 7 avg += 19; std::cout << avg << '\n'; // (5 + 9 + 19) / 3 = 11 avg += -9; std::cout << avg << '\n'; // (5 + 9 + 19 - 9) / 4 = 6 (avg += 7) += 11; // выполнение цепочки операций std::cout << avg << '\n'; // (5 + 9 + 19 - 9 + 7 + 11) / 6 = 7 Average copy = avg; std::cout << copy << '\n'; return 0; }
|
Результат работы программы:
b) Требуется ли этому классу явный конструктор копирования или операция присваивания?
Ответ: здесь можно обойтись без явного конструктора копирования или операции присваивания.
Задание №3
Напишите свой собственный класс-массив целых чисел IntArray (не используйте std::array или std::vector). Пользователи должны передавать размер массива при создании объекта этого класса, а сам массив (переменная-член) должен выделяться динамически. Используйте макрос assert для проверки передаваемых значений, а также свой конструктор копирования и перегрузку операции присваивания, если это необходимо.
Текст программы:
#include <iostream> #include <cassert> // для assert
class IntArray { private: int m_length = 0; int* m_array = nullptr;
public: IntArray(int length) : m_length{ length } { assert(length > 0 && "IntArray length should be a positive integer");
m_array = new int[m_length] {}; }
// Конструктор копирования, использующий глубокое копирование IntArray(const IntArray& array) : m_length{ array.m_length } { // размещаем новый массив m_array = new int[m_length];
// копируем элементы из исходного массива в новый for (int count = 0; count < array.m_length; ++count) m_array[count] = array.m_array[count]; }
~IntArray() { delete[] m_array; }
friend std::ostream& operator<<(std::ostream& out, const IntArray& array) { for (int count = 0; count < array.m_length; ++count) { out << array.m_array[count] << ' '; } return out; }
int& operator[] (const int index) { assert(index >= 0); assert(index < m_length); return m_array[index]; }
// Оператор присваивания, использующий глубокое копирование IntArray& operator= (const IntArray& array) { // защита от самоприсваивания if (this == &array) return *this;
// если этот массив уже существует, удалить его, // чтобы не было утечек памяти delete[] m_array;
m_length = array.m_length;
// размещаем новый массив m_array = new int[m_length];
// копируем элементы из исходного массива в новый for (int count = 0; count < array.m_length; ++count) m_array[count] = array.m_array[count]; return *this; }
};
IntArray fillArray() { IntArray a(6); a[0] = 6; a[1] = 7; a[2] = 3; a[3] = 4; a[4] = 5; a[5] = 8; return a; }
int main() { IntArray a = fillArray(); std::cout << a << '\n'; IntArray b(1); a = a; b = a; std::cout << b << '\n'; return 0;
} |
Результат работы программы:
Пояснение: глубокое копирование выделяет память для копии, а затем копирует фактическое значение, поэтому копия живет в отдельной памяти от источника. Таким образом, копия и источник различаются и никоим образом не влияют друг на друга. Выполнение глубокого копирования требует, чтобы мы написали наши собственные конструкторы копирования и перегруженные операторы присваивания.
Во-первых, мы даже должны проверить, есть ли в источнике строка. Если это так, то мы выделяем достаточно памяти для хранения копии этой строки. И в конце нам нужно вручную скопировать строку.
Обратите внимание, что наш оператор присваивания очень похож на наш конструктор копирования, но есть три основных отличия:
-мы добавили проверку на самоприсваивание;
-мы возвращаем *this, чтобы можно было добавить оператор присваивания в цепочку;
-нам нужно явно освободить любое значение, которое уже содержится в строке (чтобы не было утечки памяти, когда данные заново размещаются позже).
Наследование в языке C++.
Тест
Задание №1
Для каждой из следующих программ определите результат выполнения. Дайте его объяснение. Если программа не скомпилируется, то объясните почему. Запускать код не нужно, вы должны определить результат/ошибки программ без помощи компилятора.
a) #include <iostream>
class Parent {
public:
Parent () {
std::cout << "Parent()\n";
}
~Parent () {
std::cout << "~Parent()\n";
}
};
class Child: public Parent{
public:
Child () {
std::cout << "Child()\n";
}
~Child () {
std::cout << "~Child()\n";
}
};
int main () {
Child ch;
}
Ответ:
Parent()
Child()
~Child()
~Parent()
Потому что сначала создается объект в родительском классе, потом создается объект в классе ребенка. После чего в обратном порядке созданные объекты уничтожатся.
b)
#include <iostream>
class Parent{
public:
Parent (){
std::cout << "Parent()\n";
}
~Parent () {
std::cout << "~Parent()\n";
}
};
class Child: public Parent {
public:
Child () {
std::cout << "Child()\n";
}
~Child () {
std::cout << "~Child()\n";
}
};
int main () {
Child ch;
Parent p;
}
Ответ:
Parent()
Child()
Parent()
~Parent()
~Child()
~Parent()
Так как сначала вызывает объект класса ребенка, то создается объект родительского класса, после чего объект класса ребенка. Затем мы просто вызываем родительский класс. После чего в обратном порядке созданные объекты будут уничтожены.
c) #include <iostream>
class Parent {
private:
int m_x;
public:
Parent (int x): m_x(x) {
std::cout << "Parent()\n";
}
~Parent () {
std::cout << "~Parent()\n";
}
void print () {std::cout << "Parent: " << m_x << '\n'; }
};
class Child: public Parent{
public:
Child (int y): Parent (y) {
std::cout << "Child()\n";
}
~Child () {
std::cout << "~Child()\n";
}
void print () { std::cout << "Child: " << m_x << '\n'; }
};
int main () {
Child ch (7);
ch.print ();
}
Ответ:
Программа не сработает, так как переменная m_x находится в приватной области, и класс ребенок не имеет доступа к этой закрытой области родительского класса.
d)
#include <iostream>
class Parent {
protected:
int m_x;
public:
Parent (int x): m_x(x) {
std::cout << "Parent()\n";
}
~Parent () {
std::cout << "~Parent()\n";
}
void print() { std::cout << "Parent: " << m_x << '\n'; }
};
class Child: public Parent
{
public:
Child (int y): Parent(y) {
std::cout << "Child()\n";
}
~Child () {
std::cout << "~Child()\n";
}
void print () { std::cout << "Child: " << m_x << '\n'; }
};
int main () {
Child ch(7);
ch.print();
}
Ответ:
Parent()
Child()
Child: 7
~Child()
~Parent()
Класс ребенок будет иметь доступ к переменной m_x, так как открытые и защищенные члены базового класса под ключевым словом protected являются защищенными членами производных классов. После чего в обратном порядке созданные объекты будут уничтожены.
e)
#include <iostream>
class Parent {
protected:
int m_x;
public:
Parent (int x): m_x(x) {
std::cout << "Parent()\n";
}
~Parent () {
std::cout << "~Parent()\n";
}
void print () {std::cout << "Parent: " << m_x << '\n'; }
};
class Child: public Parent {
public:
Child (int y): Parent(y)
{
std::cout << "Child()\n";
}
~Child () {
std::cout << "~Child()\n";
}
void print () { std::cout << "Child: " << m_x << '\n'; }
};
class D2: public Child {
public:
D2(int z): Child(z) {
std::cout << "D2()\n";
}
~D2() {
std::cout << "~D2()\n";
}
// Обратите внимание, здесь нет метода print()
};
int main (){
D2 d (7);
d.print ();
}
Ответ:
Parent()
Child()
D2()
Child: 7
~D2()
~Child()
~Parent()
Сначала мы создаем объект в классе D2. Для этого нужно сначала нужно создать объекты в классах Child и Parent. Так как в классе D2 нет своего метода вывода, используется метод родительского класса Child. После чего в обратном порядке созданные объекты будут уничтожены.
Задание №2
Создайте классы Apple и Banana, которые наследуют класс Fruit. У класса Fruit есть две переменные-члены: name и color.
Добавьте новый класс GrannySmith, который наследует класс Apple.
Текст программы:
#include <iostream> #include <string> using namespace std;
class Fruit { public: string name; string color; Fruit() {}; Fruit(string c) { name = "None"; color = c; } }; class Apple :public Fruit { public: Apple(string nn) : Fruit(nn) { name = "apple"; } string getName() { return name; } string getColor() { return color; } }; class Banana : public Fruit { public: Banana() { name = "banana"; color = "yellow"; } string getName() { return name; } string getColor() { return color; } }; int main() { Apple a("red"); Banana b; std::cout << "My " << a.getName() << " is " << a.getColor() << ".\n"; std::cout << "My " << b.getName() << " is " << b.getColor() << ".\n"; |
Результат работы программы:
#include <iostream> #include <string> using namespace std;
class Fruit { public: string name; string color; Fruit() {}; Fruit(string c) { name = "None"; color = c; } }; class Apple :public Fruit { public: Apple() : Fruit() { name = "Granny Smith apple"; color = "green"; } Apple(string nn) : Fruit(nn) { name = "apple"; } string getName() { return name; } string getColor() { return color; } }; class Banana : public Fruit { public: Banana() { name = "banana"; color = "yellow"; } string getName() { return name; } string getColor() { return color; } }; class GrannySmith : Apple { public: GrannySmith() : Apple() {} string getName() { return name; } string getColor() { return color; } }; int main() { Apple a("red"); Banana b; GrannySmith c; std::cout << "My " << a.getName() << " is " << a.getColor() << ".\n"; std::cout << "My " << b.getName() << " is " << b.getColor() << ".\n"; std::cout << "My " << c.getName() << " is " << c.getColor() << ".\n"; |
Результат работы программы:
