Форум программистов, компьютерный форум, киберфорум
С++ для начинающих
Войти
Регистрация
Восстановить пароль
Карта форума Темы раздела Блоги Сообщество Поиск Заказать работу  
 
 
Рейтинг 4.81/21: Рейтинг темы: голосов - 21, средняя оценка - 4.81
11 / 11 / 3
Регистрация: 08.03.2014
Сообщений: 70
1

Переопределение операции присваивания

29.07.2014, 04:21. Показов 3866. Ответов 40
Метки нет (Все метки)

Author24 — интернет-сервис помощи студентам
Есть вот такой класс:
C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
#ifndef cd_h
#define cd_h
 
class Cd
{
private:
    char* performance;
    char* label;
    int selections;
    double playtime;
public:
    Cd();
    Cd(const char* s1, const char* s2, int n, double x);
    Cd(const Cd& c);
    virtual Cd& operator=(const Cd& c);
    virtual ~Cd();
    virtual void Report() const;
};
 
class Classic : public Cd
{
private:
    char* main_cmps;
public:
    Classic();
    Classic(const char* mc, const char* s1, const char* s2, int n, double x);
    Classic(char* mc, const Cd& c);
    Classic(const Classic& c);
    virtual Classic& operator=(const Cd& c);
    virtual ~Classic();
    virtual void Report() const;
};
#endif
И после переопределения операции присваивания:
C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
#include "cd.h"
#include <iostream>
 
//class Cd
Cd::Cd()
{
    performance = label = nullptr;
    selections = playtime = 0;
}
 
Cd::Cd(const char* s1, const char* s2, int n, double x)
{
    performance = new char[strlen(s1) + 1];
    strcpy(performance, s1);
    label = new char[strlen(s2) + 1];
    strcpy(label, s2);
    selections = n;
    playtime = x;
}
 
Cd::Cd(const Cd& c)
{
    performance = new char[strlen(c.performance) + 1];
    strcpy(performance, c.performance);
    label = new char[strlen(c.label) + 1];
    strcpy(label, c.label);
    selections = c.selections;
    playtime = c.playtime;
}
 
Cd& Cd::operator=(const Cd& c)
{
    performance = new char[strlen(c.performance) + 1];
    strcpy(performance, c.performance);
    label = new char[strlen(c.label) + 1];
    strcpy(label, c.label);
    selections = c.selections;
    playtime = c.playtime;
    return *this;
}
 
Cd::~Cd()
{
    delete[] performance;
    delete[] label;
}
 
void Cd::Report() const
{
    std::cout << "Performance: " << performance << "\t" << "Label: " << label << std::endl
        << "Selections: " << selections << "\t" << "Playtime: " << playtime << std::endl;
}
 
 
// Class Classic
Classic::Classic() :Cd()
{
    main_cmps = nullptr;
}
 
Classic::Classic(const char* mc, const char* s1, const char* s2, int n, double x) :Cd(s1, s2, n, x)
{
    main_cmps = new char[strlen(mc) + 1];
    strcpy(main_cmps, mc);
}
 
Classic::Classic(char* mc, const Cd& c) : Cd(c)
{
    main_cmps = new char[strlen(mc) + 1];
    strcpy(main_cmps, mc);
}
 
Classic::Classic(const Classic& c):Cd(c)
{
    main_cmps = new char[strlen(c.main_cmps) + 1];
    strcpy(main_cmps, c.main_cmps);
}
 
Classic& Classic::operator=(const Cd& c)
{
    if (this == &c)
        return *this;
    Classic temp = (const Classic&)c;
    Cd::operator=(c);
    main_cmps = new char[strlen(temp.main_cmps) + 1];
    strcpy(main_cmps, temp.main_cmps);
    return *this;
}
 
Classic::~Classic()
{
    delete[] main_cmps;
    
}
 
void Classic::Report() const
{
    Cd::Report();
    std::cout << "Main composition: " << main_cmps << std::endl;
}
Почему то вызвается метод базового класа при присваивании межу собой обьектов производного класса.
Заранее спасибо.

Добавлено через 7 минут
Вследствие чего выводистя окно с ошибкой, как я понимаю из за того, что деструктор пытается удалить несуществующий блок памяти.
0
Лучшие ответы (1)
Programming
Эксперт
94731 / 64177 / 26122
Регистрация: 12.04.2006
Сообщений: 116,782
29.07.2014, 04:21
Ответы с готовыми решениями:

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

Переопределение оператора присваивания
Имеется такой простой класс: class TClass { private: float* A; int N; public:...

Для чего нужно переопределение оператора присваивания?
HumansClass&amp; operator=(const HumansClass&amp; right); //переопределение операции присваивания Для...

Операции присваивания
Как можно проиллюстрировать возможности операций присваивания на примере программы?

40
873 / 771 / 173
Регистрация: 11.01.2012
Сообщений: 1,942
29.07.2014, 05:42 2
На каждый оператор New должен быть оператор Delete.
Не освобождаете память. Остальное не смотрел.
0
82 / 82 / 50
Регистрация: 22.09.2012
Сообщений: 495
29.07.2014, 05:44 3
при присваивании одного объекта другому, сначала нужно очистить память, которая уже была выделена, а потом уже выделять новую, равную размеру присваемого объекта. Так-же перед всем этим следует проверить, не является ли аргумент тем-же самым объектом, если да, то и выделять ничего не нужно
C++
1
2
if (this == &c)
   return *this;
Судя по коду, ты делаешь упражнение из учебника Стивена Прата) посмотри предыдущие примеры по этой главе и сам всё поймёшь.
1
11 / 11 / 3
Регистрация: 08.03.2014
Сообщений: 70
29.07.2014, 06:25  [ТС] 4
Цитата Сообщение от MrCold Посмотреть сообщение
На каждый оператор New должен быть оператор Delete.
Не освобождаете память. Остальное не смотрел.
В Деструкторе присутствуют все delete-ы. Память сдесь даже переосвобождается, из за чего и проблема.

Цитата Сообщение от dalay_lama Посмотреть сообщение
при присваивании одного объекта другому, сначала нужно очистить память, которая уже была выделена, а потом уже выделять новую, равную размеру присваемого объекта. Так-же перед всем этим следует проверить, не является ли аргумент тем-же самым объектом, если да, то и выделять ничего не нужно
Да, удаление предыдущего содержимого забыл, но пробелма не решилась т.к. присваивал и так пустому обьекту. Дело в том что Прата не писал о переопределении операции присваения. В примерах у него просто определены разные операции присваения для базового и производных классов. Может это и не может понадобится, но мне стало интересно и захотелось попробовать написать следующий код:
C++
1
2
3
4
5
    Cd obj1;  // базовый класс
    Classic obj2; //производный класс
    Cd* ptr = &obj1; 
    *ptr = obj2;  
    ptr->Report();  // ну и вот здесь хотелось бы что бы использовался метод производного класса
Но выходит так, что после этого, что даже простые обьекты производного класса при присвоении межу собой вызывают операцию присваения, которая определена в базовом класе, а не в производном. Не знаю что не так сделал.
0
873 / 771 / 173
Регистрация: 11.01.2012
Сообщений: 1,942
29.07.2014, 06:34 5
Цитата Сообщение от Gwini Посмотреть сообщение
В Деструкторе присутствуют
деструкторы здесь не помогут.
Но dalay_lama уже сказал что и как
0
82 / 82 / 50
Регистрация: 22.09.2012
Сообщений: 495
29.07.2014, 06:42 6
Цитата Сообщение от Gwini Посмотреть сообщение
Дело в том что Прата не писал о переопределении операции присваения. В примерах у него просто определены разные операции присваения для базового и производных классов.
Вот ты бестыжий врёшь и не краснеешь

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

а пока, перечитай главу ещё раз и повнимательней глядишь и сам найдёшь то, что я тебе выше писал
0
11 / 11 / 3
Регистрация: 08.03.2014
Сообщений: 70
29.07.2014, 06:46  [ТС] 7
Цитата Сообщение от MrCold Посмотреть сообщение
Но dalay_lama уже сказал что и как
не помогло

Добавлено через 3 минуты
Цитата Сообщение от dalay_lama Посмотреть сообщение
когда выделяешь память из кучи в конструкторе
да читал я глубокое копирование и тд. Все работает, если убрать это присвоение триклятое...Его в самом задание то нет, но оно мне спать не дает спокойно
0
5498 / 4893 / 831
Регистрация: 04.06.2011
Сообщений: 13,587
29.07.2014, 06:47 8
Цитата Сообщение от Gwini Посмотреть сообщение
как я понимаю из за того, что деструктор пытается удалить несуществующий блок памяти.
Из-за того, что указатели нулевые, а к ним применяется strlen().
0
82 / 82 / 50
Регистрация: 22.09.2012
Сообщений: 495
29.07.2014, 06:56 9
И кстати, только сейчас обратил внимание. Очень ты не внимательно читаешь) Если б внимательно читал, то знал бы, что операция присваивания не наследуется, соответственно, виртуальной её не нужно объявлять. Если операция присваивания по умолчанию не переопределена, то свойства просто почленно друг друга скопируют, а если класс является производным, тогда сначала вызовется оператор присваивания базового класса, чтобы скушать свои свойства( и соответственно для него, вызовется тот оператор присваивания который определён, т.е. который написал ты или по умолчанию). Соответственно, если ты переопределяешь для производного класса оператор присваивания, тебе нужно вызвать самому оператор присваивания для базового класса
C++
1
BaseClass::operator=(argument);//вызов оператора присваивания для базового класса
0
11 / 11 / 3
Регистрация: 08.03.2014
Сообщений: 70
29.07.2014, 06:57  [ТС] 10
Цитата Сообщение от alsav22 Посмотреть сообщение
Из-за того, что указатели нулевые, а к ним применяется strlen().
Да вроде нормально... strlen применяется только в конструкторах к указателям на строки.
0
82 / 82 / 50
Регистрация: 22.09.2012
Сообщений: 495
29.07.2014, 07:02 11
Выложи, что у тебя в итоге сейчас получается
0
11 / 11 / 3
Регистрация: 08.03.2014
Сообщений: 70
29.07.2014, 07:14  [ТС] 12
Цитата Сообщение от dalay_lama Посмотреть сообщение
И кстати, только сейчас обратил внимание. Очень ты не внимательно читаешь) Если б внимательно читал, то знал бы, что операция присваивания не наследуется, соответственно, виртуальной её не нужно объявлять. Если операция присваивания по умолчанию не переопределена, то свойства просто почленно друг друга скопируют, а если класс является производным, тогда сначала вызовется оператор присваивания базового класса, чтобы скушать свои свойства( и соответственно для него, вызовется тот оператор присваивания который определён, т.е. который написал ты или по умолчанию). Соответственно, если ты переопределяешь для производного класса оператор присваивания, тебе нужно вызвать самому оператор присваивания для базового класса
Есть там такое
C++
1
2
3
4
5
6
7
8
9
10
11
12
Classic& Classic::operator=(const Cd& c)
{
 
    if (this == &c)
        return *this;
    delete[] main_cmps;
    Classic temp = (const Classic&)c;   
Cd::operator=(c);
    main_cmps = new char[strlen(temp.main_cmps) + 1];
    strcpy(main_cmps, temp.main_cmps);
    return *this;
}
И я читал внимательно читал что не наследуется, но вирутальной ее сделать можно. Я понимаю что это может и бесполезно, но мне все же интересно.

Добавлено через 11 минут
Вот к примеру, где могла бы использоватся переопределенная операция присваивания
C++
1
2
3
4
    Cd* ptr = &c1; //указатель на базовый класс, с1 обьект базового класса
    ptr->Report();
    *ptr = c3;    //с3 - обьект производного класса
    ptr->Report();
Может это вообще не имеет смысла?
0
82 / 82 / 50
Регистрация: 22.09.2012
Сообщений: 495
29.07.2014, 07:22 13
Цитата Сообщение от Gwini Посмотреть сообщение
Может это вообще не имеет смысла?
Имеет.
Выложи весь код. Покажи, что поменял от старта топика

Добавлено через 3 минуты
стоооооп)
0
11 / 11 / 3
Регистрация: 08.03.2014
Сообщений: 70
29.07.2014, 07:24  [ТС] 14
C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
#ifndef cd_h
#define cd_h
 
class Cd
{
private:
    char* performance;
    char* label;
    int selections;
    double playtime;
public:
    Cd();
    Cd(const char* s1, const char* s2, int n, double x);
    Cd(const Cd& c);
    virtual Cd& operator=(const Cd& c);
    virtual ~Cd();
    virtual void Report() const;
};
 
class Classic : public Cd
{
private:
    char* main_cmps;
public:
    Classic();
    Classic(const char* mc, const char* s1, const char* s2, int n, double x);
    Classic(char* mc, const Cd& c);
    Classic(const Classic& c);
    virtual Classic& operator=(const Cd& c);
    virtual ~Classic();
    virtual void Report() const;
};
#endif
C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
#include "cd.h"
#include <iostream>
 
//class Cd
Cd::Cd()
{
    performance = label = nullptr;
    selections = playtime = 0;
}
 
Cd::Cd(const char* s1, const char* s2, int n, double x)
{
    performance = new char[strlen(s1) + 1];
    strcpy(performance, s1);
    label = new char[strlen(s2) + 1];
    strcpy(label, s2);
    selections = n;
    playtime = x;
}
 
Cd::Cd(const Cd& c)
{
    performance = new char[strlen(c.performance) + 1];
    strcpy(performance, c.performance);
    label = new char[strlen(c.label) + 1];
    strcpy(label, c.label);
    selections = c.selections;
    playtime = c.playtime;
}
 
Cd& Cd::operator=(const Cd& c)
{
    std::cout << "\n===base\n";
    delete[] performance;
    delete[] label;
    performance = new char[strlen(c.performance) + 1];
    strcpy(performance, c.performance);
    label = new char[strlen(c.label) + 1];
    strcpy(label, c.label);
    selections = c.selections;
    playtime = c.playtime;
    return *this;
}
 
Cd::~Cd()
{
    delete[] performance;
    delete[] label;
}
 
void Cd::Report() const
{
    std::cout << "\nPerformance: " << performance << "\t" << "Label: " << label << std::endl
        << "Selections: " << selections << "\t" << "Playtime: " << playtime << std::endl;
}
 
 
// Class Classic
Classic::Classic() :Cd()
{
    main_cmps = nullptr;
}
 
Classic::Classic(const char* mc, const char* s1, const char* s2, int n, double x) :Cd(s1, s2, n, x)
{
    main_cmps = new char[strlen(mc) + 1];
    strcpy(main_cmps, mc);
}
 
Classic::Classic(char* mc, const Cd& c) : Cd(c)
{
    main_cmps = new char[strlen(mc) + 1];
    strcpy(main_cmps, mc);
}
 
Classic::Classic(const Classic& c):Cd(c)
{
    main_cmps = new char[strlen(c.main_cmps) + 1];
    strcpy(main_cmps, c.main_cmps);
}
 
Classic& Classic::operator=(const Cd& c)
{
    std::cout << "\n===der\n";
    if (this == &c)
        return *this;
    Cd::operator=(c);
    delete[] main_cmps;
    Classic temp = (const Classic&)c;   
    main_cmps = new char[strlen(temp.main_cmps) + 1];
    strcpy(main_cmps, temp.main_cmps);
    return *this;
}
 
Classic::~Classic()
{
    delete[] main_cmps;
}
 
void Classic::Report() const
{
    Cd::Report();
    std::cout << "Main composition: " << main_cmps << std::endl;
}
Использование класса:
C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
#include <iostream>
#include "cd.h"
using namespace std;
 
void Bravo(const Cd& d);
int main()
{
    Cd c1("Beatles", "Capitol", 14, 35.5);
    Classic c2 = Classic("Piano Sonata in B flat, Fantasia in C", "Alfred Brendel", "Philips", 2, 57.17);
    Cd* pcd = &c1;
    cout << "\nUsing type cd* pointer to object:\n";
    pcd->Report();
    pcd = &c2;  
    pcd->Report();
    cout << "\nCalling a function with Cd reference argument:\n";
    Bravo(c1);
    Bravo(c2);
    cout << "\nTesting assignment: ";
    Classic copy;
    copy = c2;     // здесь вместо присваения производного класа, вызывается присваение базового
    copy.Report();
    Classic c3("la-la-la-la", c1);
    c3.Report();
    Classic c4 = c3;
    c4.Report();
 
    Cd* ptr = &c1;   //указатель на базовый класс, с1 обьект базового класса
    ptr->Report();   //вызвается метод базового класса
    *ptr = c3;      //с3 — обьект производного класса
    ptr->Report();   //тут надо бы, что б вызывался метод производного класса
 
 
    return 0;
}
 
void Bravo(const Cd& disk)
{
    disk.Report();
}
0
82 / 82 / 50
Регистрация: 22.09.2012
Сообщений: 495
29.07.2014, 07:31 15
стоооооп) теперь я понял)
смотри.
C++
1
2
3
4
Cd* ptr = &c1; //указатель на базовый класс, с1 обьект базового класса - ОК
ptr->Report();//вывоз метода базового класса
*ptr = c3;//с3 у нас является производным классом. ты выполняешь операцию присваивания для БАЗОВОГО класса
ptr->Report();
К чему я это, ты же не меняешь адрес, на который указывает ptr. Ты ему сказал, получи адрес c1 - он его получил. Потом ты ему говоришь, чтобы он принял значения от c3. По сути, всё равно что вот так вот написать:
C++
1
2
Cd c1;
c1 = c3;
Вызовется операция присваивания БАЗОВОГО класса, т.к. мы помним, что ссылки на базовый класс, могу принимать и на производные классы. Соответственно, базовый класс получит базовые значения из производного.
отсюда и получается, что ptr->Report(); вызывает у тебя метод базового класса, потому-что он и хранит адрес объекта базового класса!
1
11 / 11 / 3
Регистрация: 08.03.2014
Сообщений: 70
29.07.2014, 07:37  [ТС] 16
Цитата Сообщение от dalay_lama Посмотреть сообщение
стоооооп) теперь я понял)
смотри.
Код C++
1
2
3
4
Cd* ptr = &c1; //указатель на базовый класс, с1 обьект базового класса - ОК
ptr->Report();//вывоз метода базового класса
*ptr = c3;//с3 у нас является производным классом. ты выполняешь операцию присваивания для БАЗОВОГО класса
ptr->Report();
К чему я это, ты же не меняешь адрес, на который указывает ptr. Ты ему сказал, получи адрес c1 - он его получил. Потом ты ему говоришь, чтобы он принял значения от c3. По сути, всё равно что вот так вот написать:
Код C++
1
2
Cd c1;
c1 = c3;
Вызовется операция присваивания БАЗОВОГО класса, т.к. мы помним, что ссылки на базовый класс, могу принимать и на производные классы. Соответственно, базовый класс получит базовые значения из производного.
отсюда и получается, что ptr->Report(); вызывает у тебя метод базового класса, потому-что он и хранит адрес объекта базового класса!
Понятно, значит это смысла не имеет и впринципе переопределять операцию присваения не нужно. Но мне все же интересно это:
C++
1
2
3
Classic copy;
    copy = c2;     // здесь вместо присваения производного класа, вызывается присваение базового
    copy.Report();
Кстати ошибка вылитает именно здесь, а именно из за того что деструктор производного класа пытается удалить член char* main_cmps, память под которого не была выделена т.к. вызывается присваение базового а не производного класса.
0
5498 / 4893 / 831
Регистрация: 04.06.2011
Сообщений: 13,587
29.07.2014, 07:52 17
Цитата Сообщение от Gwini Посмотреть сообщение
Да вроде нормально... strlen применяется только в конструкторах к указателям на строки.
Это конструктор?
Цитата Сообщение от Gwini Посмотреть сообщение
Cd& Cd::operator=(const Cd& c)
{
* * performance = new char[strlen(c.performance) + 1];
* * strcpy(performance, c.performance);
* * label = new char[strlen(c.label) + 1];
* * strcpy(label, c.label);
* * selections = c.selections;
* * playtime = c.playtime;
* * return *this;
}
0
82 / 82 / 50
Регистрация: 22.09.2012
Сообщений: 495
29.07.2014, 07:54 18
Цитата Сообщение от Gwini Посмотреть сообщение
Понятно, значит это смысла не имеет и впринципе переопределять операцию присваения не нужно.
В базовом классе, для производного класса определять операцию присвоения нет необходимости. Только может, если ты хочешь знать, что принял именно производный класс и в соответствии с этим сделать, что-то ещё, но, к сожалению, никакого примера придумать не могу

Цитата Сообщение от Gwini Посмотреть сообщение
Кстати ошибка вылитает именно здесь, а именно из за того что деструктор производного класа пытается удалить член char* main_cmps, память под которого не была выделена т.к. вызывается присваение базового а не производного класса
я рад, что ты разобрался
1
11 / 11 / 3
Регистрация: 08.03.2014
Сообщений: 70
29.07.2014, 08:01  [ТС] 19
Цитата Сообщение от alsav22 Посмотреть сообщение
Это конструктор?
Нет, но strlen все равно применяется к строке в уже существующему объекту.

Добавлено через 6 минут
Цитата Сообщение от dalay_lama Посмотреть сообщение
я рад, что ты разобрался
Спасибо знать бы еще почему вызвается присвоение базового класса вместо производного Ну да ладно, если перопределние присвоения бесполезно то может и незачем знать. Просто определю не виртуальное присвоение в производном класе и все. Еще раз спасибо.
0
82 / 82 / 50
Регистрация: 22.09.2012
Сообщений: 495
29.07.2014, 08:12 20
Я немного не понял, почему ты не понял=)
давай ещё раз:
C++
1
2
3
4
5
6
Cd ob1;//хей, я объект, базового класса
Classic ob2;//хей, я объект производного класса
 
ob1 = ob2;/*присваиваем объекту базового класса, значения производного. Почему так можно? потому-что прототип operator(Cd &cd) - аргумент ссылочка на базовый, да? а объекты производного класса, можно передавать по ссылке объектам базового*/
ob2 = ob1;/*а такое уже не прокатит, обратное нельзя, ссылки на производный, не могут принимать ссылки на базовый(только если не делать приведение типа), соответственно, для такого вида нужно написать своё присвоение с блэк джеком и плюшками.
прототип такой Classic::operator=(Cd &cd);*/
что именно не понятно?
0
29.07.2014, 08:12
IT_Exp
Эксперт
87844 / 49110 / 22898
Регистрация: 17.06.2006
Сообщений: 92,604
29.07.2014, 08:12
Помогаю со студенческими работами здесь

Перегрузка операции присваивания и сложения
Необходимо составить описание класса для определения одномерных массивов целых чисел. Границы...

Переопределение операции []
В классе строка переопределить операцию , которая будет возвращать символ по индексу, который...

Переопределение операции инкремента
Добрый вечер! Задачка простая и кода с примерами в сети куча, но меня волнует вопрос почему...

Переопределение операции ввода
Всем доброго времени суток. Сейчас пытаюсь переопределить операцию ввода для вектора. Написал...


Искать еще темы с ответами

Или воспользуйтесь поиском по форуму:
20
Ответ Создать тему
КиберФорум - форум программистов, компьютерный форум, программирование
Powered by vBulletin
Copyright ©2000 - 2024, CyberForum.ru