Форум программистов, компьютерный форум, киберфорум
C# для начинающих
Войти
Регистрация
Восстановить пароль
Блоги Сообщество Поиск  
 
 
Рейтинг 4.52/25: Рейтинг темы: голосов - 25, средняя оценка - 4.52
 Аватар для SuprSonic
7 / 4 / 3
Регистрация: 07.08.2016
Сообщений: 65
.NET 4.x

Отслеживание изменений массива данных из второго потока

18.01.2019, 19:37. Показов 5937. Ответов 49

Студворк — интернет-сервис помощи студентам
Хочу узнать мнения гуру касаемо подхода в реализации следующего. Есть массив данных и два потока. Один поток производит операции с элементами этого массива (добавление и обновление рандомных элементов). Другой поток должен отслеживать изменения в этом массиве и строго дублировать его. Первый поток должен быть полностью автономным и не зависеть от работы второго. Т.е работа второго никаким образом не должна тормозить работу первого, обратное же допустимо. Т.к. сам массив может быть очень большим(а в теории бесконечным), то проходить циклом во втором потоке весь массив и искать изменения не совсем разумно. Получается первый поток должен сообщить второму о том, какие элементы он изменил (добавил/обновил).

У меня получилось вот это:
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
public class _MultiThread
{
        public ManualResetEventSlim EVENT;
        private bool isRead;
        private int index;
        private int[] data;
 
        public _MultiThread()
        {
            this.data = new int[100];
            this.index = -1;
            this.isRead = false;
            this.EVENT = new ManualResetEventSlim(true);
        }
 
        private void _clear()
        {
            this.EVENT.Reset();
            this.index = -1;
            this.isRead = false;
            this.EVENT.Set();
        }
 
        // Добавляем только уникальные значения
        public unsafe void AddUnique(int _value)
        {
            if (isRead)
            {
                _clear();
                this.data[++this.index] = _value;
                return;
            }
            fixed (int* ptr = &this.data[0])
            {
                for (int i = 0; i <= this.index; i++)
                {
                    if (ptr[i] == _value) return;
                }
                this.data[++this.index] = _value;
            }
        }
 
        public void Reader(Action<int> func)
        {
            this.EVENT.Wait();
            for(int i = 0; i <= this.index; i++)
            {
                func(this.data[i]);
            }
            this.isRead = true;
        }
}
 
public class WORK{
        public _MultiThread CHANGES = new _MultiThread();
        public int[] _ARRAY;
 
        public WORK(){
                this._ARRAY = new int[100];
        }
 
        public void Working(){
                // Какая-то полезная работа с записью результата в случайный элемент массива
                int index = new Random().Next( 0, 99 );  // Выбираем случайный элемент
                this._ARRAY[index] = new Random().Next( 0, 9999999 ); // Генерируем и записываем в него случайное число
                this.CHANGES.AddUnique(index); // Сообщаем какой элемент был изменен
        }
}
 
.....
//В основном цикле
WORK worker = new WORK();
 
new Thread(() =>
{
       // Раз в 1 сек "слушаем" изменения
       while(true){
            Thread.Sleep(1000);
            worker.CHANGES.Reader((index) => {
                 Console.WriteLine ("Был изменен элемент массива под индексом: " + index);
            });
       }
}).Start();
 
new Thread(() =>
{
        while(true){
             Thread.Sleep(25);
             worker.Working();
        }
}).Start();
Для простоты основной массив будет содержат только INT. Для поддержки связи между потоками использую собственный класс _MultiThread. Т.к. первый поток может работать быстрее, то к тому моменту пока второй поток начнет "прослушивать изменения", один и тот же элемент массива может быть обновлен несколько раз, а поскольку второй поток не интересует сколько раз элементы подвергались изменению, то использую функцию AddUnique.
После того как Reader прочитал все изменения, выставляем isRead в true. Первый поток увидев, что изменения были считаны перед добавлением в _MultiThread.data затирает все с помощью _clear().

И вроде все работает как надо. Но, так как я в многопоточности новичок, хотел бы знать как можно иначе реализовать алгоритм? Возможно ли использовать lock и обойтись без ManualResetEventSlim ? И есть ли в моей реализации ошибки?
0
Programming
Эксперт
39485 / 9562 / 3019
Регистрация: 12.04.2006
Сообщений: 41,671
Блог
18.01.2019, 19:37
Ответы с готовыми решениями:

Отслеживание изменений БД
Добрый вечер! Подскажите, есть ли простой способ отслеживания изменений в БД? Необходимо, чтобы при открытии БД юзер выбирал своё...

Отслеживание изменений
Кто-нибудь знает как отслеживать изменения модели $scope.applyingParameters = { checkbox: '', chooseView:...

Отслеживание изменений в БД
Есть БД, к которой подключено несколько клиентов. Каждый из них имеет компоненты &quot;DataSource&quot;, &quot;ADOConnection&quot;...

49
907 / 664 / 318
Регистрация: 23.10.2016
Сообщений: 1,543
20.01.2019, 19:42
Студворк — интернет-сервис помощи студентам
Я опечатался, там return random.Next() должно быть
0
 Аватар для SuprSonic
7 / 4 / 3
Регистрация: 07.08.2016
Сообщений: 65
20.01.2019, 20:24  [ТС]
C#
1
2
3
4
5
6
7
8
        int GetRandom()
        {
            if (random.Next(10) == 0)
            {
                random.Next();
            }
            return random.Next();
        }
Уверены?
0
907 / 664 / 318
Регистрация: 23.10.2016
Сообщений: 1,543
20.01.2019, 21:24
Цитата Сообщение от SuprSonic Посмотреть сообщение
Уверены?
....
C#
1
2
3
4
5
6
7
8
int GetRandom()
{
    if (random.Next(10) == 0)
    {
        return random.Next();
    }
    return random.Next(100);
}
Добавлено через 1 минуту
Вы в первом посте сказали, что массив бесконечный, а значит индексы принимают любые значения. Так же вы сказали, что индексы часто повторяются. Значит для тестов нужно брать набор индексов, принимающих любые значения, но многие из которых повторяются.
0
 Аватар для SuprSonic
7 / 4 / 3
Регистрация: 07.08.2016
Сообщений: 65
20.01.2019, 21:34  [ТС]
Цитата Сообщение от TopLayer Посмотреть сообщение
Вы в первом посте сказали, что массив бесконечный, а значит индексы принимают любые значения.
Ну это в теории. В полевых условиях он ограничен.
0
907 / 664 / 318
Регистрация: 23.10.2016
Сообщений: 1,543
20.01.2019, 21:43
Цитата Сообщение от SuprSonic Посмотреть сообщение
Ну это в теории. В полевых условиях он ограничен.
Задача не сформулирована, поэтому её обсуждение не продуктивно.
0
 Аватар для SuprSonic
7 / 4 / 3
Регистрация: 07.08.2016
Сообщений: 65
20.01.2019, 22:41  [ТС]
Цитата Сообщение от TopLayer Посмотреть сообщение
Задача не сформулирована, поэтому её обсуждение не продуктивно.
Задача сформулирована конкретней некуда. В реальности ничего бесконечного не бывает (я думаю это ни для кого не секрет).

Проверил алгоритмы, можно сказать, что почти в полевых условиях. Так вот, в них HashSet уступает по скорости везде. Самый быстры вариант это simple bit.

Добавлено через 6 минут

Не по теме:

чет поздно уже, спать пора %-)

0
907 / 664 / 318
Регистрация: 23.10.2016
Сообщений: 1,543
21.01.2019, 09:59
Цитата Сообщение от SuprSonic Посмотреть сообщение
Самый быстры вариант это simple bit.
Цитата Сообщение от SuprSonic Посмотреть сообщение
Т.к. сам массив может быть очень большим(а в теории бесконечным), то проходить циклом во втором потоке весь массив и искать изменения не совсем разумно.
Какая разница, проходится по всему исходному массиву или по массиву бит?

Добавлено через 36 минут
И, как я понял, второй поток будет обнулять биты после чтения. То есть, получится, что 2 потока пишут в 1 массив, а значит уже нужна синхронизация, иначе второй поток может затереть значение записанное первым потоком не прочитав его.
0
 Аватар для SuprSonic
7 / 4 / 3
Регистрация: 07.08.2016
Сообщений: 65
21.01.2019, 18:55  [ТС]
Цитата Сообщение от TopLayer Посмотреть сообщение
Какая разница, проходится по всему исходному массиву или по массиву бит?
Это делает второй поток при считывании (а как он это делает не суть важно) главное, чтобы это никак не влияло на скорость работы первого потока. Впрочем, тесты показали, что по массиву бит чтение (до определенного момента) происходит быстрее, чем при использовании foreach у HashSet.

В предыдущем примере, где генерируются числа от 0 до 10240, в реальных условиях эти числа являются индексами элемента массива размером 10240 и неважно какого типа. А об изменении его элементов можно сообщать компактно упаковав в биты представленные массивом byte длиной всего 1280 в котором позиция каждого бита будет соответствовать номеру элемента в основном массиве. Соответсвенно чтобы сообщить об изменении 777-го элемента массива, нужно выставить 777-й бит в 1.

В данном способе запись происходит быстрее, чем его чтение.

Цитата Сообщение от TopLayer Посмотреть сообщение
И, как я понял, второй поток будет обнулять биты после чтения.
В самой первой реализации из первого поста, обнулением занимается первый поток. Второй поток ожидает до тех пор пока первый поток не завершит запись. После чтения второй поток выставляет bool isRead в True сообщая первому, что все прочитано и при последующей записи можно все почистить. Тут 2 потока не пишут в один массив. Однако, при такой подходе, есть вероятность пропуска данных, они не будут прочтены и затерты при следующей записи.

Цитата Сообщение от TopLayer Посмотреть сообщение
значит уже нужна синхронизация
Этого как раз и надо избежать, потому что я не знаю сколько по времени займут манипуляции чтения у второго потока, может быть он вообще отвалится в момент чтения. Тогда первый, как я понимаю, будет сильно страдать.

Добавлено через 1 час 18 минут
Такой вопрос. К чему может привести побитовая запись/чтение массива byte обоими потоками без использования lock?
Сейчас у меня второй поток при считывание бита переключает его в 0.
0
907 / 664 / 318
Регистрация: 23.10.2016
Сообщений: 1,543
21.01.2019, 19:03
Цитата Сообщение от SuprSonic Посмотреть сообщение
Такой вопрос. К чему может привести побитовая запись/чтение массива byte обоими потоками без использования lock?
Сейчас у меня второй поток при считывание бита переключает его в 0.
К тому, что одно из двух записанных значений никогда не будет прочитано.

Добавлено через 5 минут
Сценарий такой. В массиве записано значение X. Первый поток хочет заменить его на Y = f(x). Второй хочет считать и обнулить.
1. Оба потока считывают значение X.
2. Первый поток записывает значение Y.
3. Второй поток записывает значение 0.
В итоге значение Y никогда не будет прочитано вторым потоком.
0
 Аватар для SuprSonic
7 / 4 / 3
Регистрация: 07.08.2016
Сообщений: 65
21.01.2019, 19:20  [ТС]
Цитата Сообщение от TopLayer Посмотреть сообщение
К тому, что одно из двух записанных значений никогда не будет прочитано.
Почему? Ведь второй поток пробегается от начала и до конца по каждому биту и меняет 1 на 0;

И я это представляют так:

Есть 3 случая (допустим всего битов 100):
1. Второй поток уже прочитал 50 битов, первый поток в это же время переключил 20 бит с 0 на 1.
— В этом случае данные будут прочитаны при следующем проходе массива вторым потоком.

2. Второй поток прочитал только 10 битов, первый поток в это же время переключил 20 бит с 0 на 1.
— В этом случае данные будут прочитаны вторым потоком в текущем цикле.

3. Второй поток считывает 88 бит, первый поток в это же время тоже лезет к 88 биту.
— В этом случае ??? кто успел тот и переключил??

Добавлено через 2 минуты
Цитата Сообщение от TopLayer Посмотреть сообщение
Сценарий такой. В массиве записано значение X. Первый поток хочет заменить его на Y = f(x). Второй хочет считать и обнулить.
Почему X и Y то? Там либо 0 либо 1.
0
907 / 664 / 318
Регистрация: 23.10.2016
Сообщений: 1,543
21.01.2019, 20:07
Цитата Сообщение от SuprSonic Посмотреть сообщение
Почему X и Y то? Там либо 0 либо 1.
В вашем случае 0..255
Цитата Сообщение от SuprSonic Посмотреть сообщение
3. Второй поток считывает 88 бит, первый поток в это же время тоже лезет к 88 биту.
— В этом случае ??? кто успел тот и переключил??
Второй поток может записать значение сразу после того, как это сделал первый. То есть значение из первого потока просто затрётся.
0
 Аватар для SuprSonic
7 / 4 / 3
Регистрация: 07.08.2016
Сообщений: 65
21.01.2019, 20:09  [ТС]
Цитата Сообщение от TopLayer Посмотреть сообщение
В вашем случае 0..255
Вы ошибаетесь. В моем случае я оперирую 1 битом. Бит, а не байт. Бит либо 0 либо 1.
0
907 / 664 / 318
Регистрация: 23.10.2016
Сообщений: 1,543
21.01.2019, 20:15
Цитата Сообщение от SuprSonic Посмотреть сообщение
Вы ошибаетесь. В моем случае я оперирую 1 битом. Бит, а не байт. Бит либо 0 либо 1.
Ну, вот в этом коде вы читаете и пишете в массив именно байты.
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
            using (new Diagnostics("simple bit[]"))
            {
                int read_sum = 0, length = 0;
                byte[] array = new byte[1280];
 
                for (int i = 0; i < count; i++)
                {
                    int value = GetRandom();
                    var value = GetRandom();
                    int bit = (int)(value % 8);
                    if (((array[value /= 8] >> bit) & 1) == 0){
                        array[value] |= (byte)(0x0000001 << bit);
                        length++;
                    }
                }
 
                // Считываем каждое значение по отдельности и высчитываем общую сумму
                for (int i = 0; i < length/8 + (length % 8 > 0 ? 1 : 0); i++)
                {
                    read_sum += ((array[i] >> 0) & 1) > 0 ? (i * 8 + 0) : 0;
                    read_sum += ((array[i] >> 1) & 1) > 0 ? (i * 8 + 1) : 0;
                    read_sum += ((array[i] >> 2) & 1) > 0 ? (i * 8 + 2) : 0;
                    read_sum += ((array[i] >> 3) & 1) > 0 ? (i * 8 + 3) : 0;
                    read_sum += ((array[i] >> 4) & 1) > 0 ? (i * 8 + 4) : 0;
                    read_sum += ((array[i] >> 5) & 1) > 0 ? (i * 8 + 5) : 0;
                    read_sum += ((array[i] >> 6) & 1) > 0 ? (i * 8 + 6) : 0;
                    read_sum += ((array[i] >> 7) & 1) > 0 ? (i * 8 + 7) : 0;
                }
 
                Console.Write("Read_sum: " + read_sum + " | Length:" + length + " || ");
            }
Добавлено через 1 минуту
C#
1
2
3
4
// считать байт из памяти
// применить к нему поразрядную операцию
// записать байт в память
array[value] |= (byte)(0x0000001 << bit);
0
 Аватар для SuprSonic
7 / 4 / 3
Регистрация: 07.08.2016
Сообщений: 65
21.01.2019, 20:31  [ТС]
Цитата Сообщение от TopLayer Посмотреть сообщение
// считать байт из памяти
// применить к нему поразрядную операцию
// записать байт в память
array[value] |= (byte)(0x0000001 << bit);
В данном случае биту под номер bit присваивается 1. Сам же бит находится в байте массива array под индексом value.
0
907 / 664 / 318
Регистрация: 23.10.2016
Сообщений: 1,543
21.01.2019, 20:33
Цитата Сообщение от SuprSonic Посмотреть сообщение
В данном случае биту под номер bit присваивается 1. Сам же бит находится в байте массива array под индексом value.
Верно. А потом байт, в котором находился этот бит, пишется обратно в массив целиком.
0
 Аватар для SuprSonic
7 / 4 / 3
Регистрация: 07.08.2016
Сообщений: 65
21.01.2019, 20:41  [ТС]
Цитата Сообщение от TopLayer Посмотреть сообщение
Верно. А потом байт, в котором находился этот бит, пишется обратно в массив целиком.
И ? Оперирую то я битом. Позиция бита == индексу элемента в массиве. Вместо byte можно и uint/int и т.д. юзать.
0
907 / 664 / 318
Регистрация: 23.10.2016
Сообщений: 1,543
21.01.2019, 20:51
Цитата Сообщение от SuprSonic Посмотреть сообщение
И ? Оперирую то я битом. Позиция бита == индексу элемента в массиве. Вместо byte можно и uint/int и т.д. юзать.
Вы читаете из памяти один байт. Как-то его меняете, а потом записываете обратно. И пофигу, чем вы там оперируете. Компьютер не умеет бит в память записывать, только байт целиком, как минимум.

Добавлено через 4 минуты
Другими словами, проц берет целый байт из массива, записывает его в свой регистр. Там, в регистре, меняет ему один из битов. Потом значение из регистра пишет обратно в память.

Добавлено через 1 минуту
То, что это записывается в одну строчку, не значит, что оно содержит только одну операцию.
0
 Аватар для SuprSonic
7 / 4 / 3
Регистрация: 07.08.2016
Сообщений: 65
21.01.2019, 21:18  [ТС]
Цитата Сообщение от TopLayer Посмотреть сообщение
Вы читаете из памяти один байт. Как-то его меняете, а потом записываете обратно. И пофигу, чем вы там оперируете. Компьютер не умеет бит в память записывать, только байт целиком, как минимум.
Это для меня не секрет. Не пойму какую мысль вы хотите донести?
Я вижу что по результатам теста hashset уступает по скорости. Пытаюсь найти более быстрый и более оптимальный по производительности/затратам вариант. Есть решение по-лучше?

Самый наглядный тест, который все покажет.
Рандом с минимумом 100_000 и максимум 1_000_000 (т.е. это если предположить, что исходный массив имеет длину в 1 млн.). HashSet здесь вообще нервно курит. Побитовый сценарий справляется в 4 раза быстрее, с учетом что чтение производится с последующим переключением прочитанного бита в 0.

C#
1
2
3
4
5
6
7
8
 
                       .........
                        if ((byte)((array[i] >> 0) & 1) > 0)
                        {
                            array[i] ^= 1 << 0;
                            read_sum += ((i * 8 + 0));   // где 0 позиция бита
                        }
                        ........
PS. Если тестить будете, не забудьте, что массив byte должен равняться 128000, чтобы хватило битов.

Добавлено через 10 минут
Цитата Сообщение от TopLayer Посмотреть сообщение
Другими словами, проц берет целый байт из массива, записывает его в свой регистр. Там, в регистре, меняет ему один из битов. Потом значение из регистра пишет обратно в память.
Насколько знаю на уровне проца, кэша и регистра все быстро отрабатывает. А как там HashSet реализован вообще не известно.

Цитата Сообщение от TopLayer Посмотреть сообщение
То, что это записывается в одну строчку, не значит, что оно содержит только одну операцию.
Я не настолько нуб, конечно, но спасибо))
0
907 / 664 / 318
Регистрация: 23.10.2016
Сообщений: 1,543
21.01.2019, 21:24
Цитата Сообщение от SuprSonic Посмотреть сообщение
Это для меня не секрет. Не пойму какую мысль вы хотите донести?
Ну вы же не поняли, почему речь идёт о значениях от 0 до 255.
Цитата Сообщение от SuprSonic Посмотреть сообщение
Самый наглядный тест, который все покажет.
Рандом с минимумом 100_000 и максимум 1_000_000 (т.е. это если предположить, что исходный массив имеет длину в 1 млн.). HashSet здесь вообще нервно курит. Побитовый сценарий справляется в 4 раза быстрее, с учетом что чтение производится с последующим переключением прочитанного бита в 0.
Приведите код тестируемого сценария, время выполнения которого необходимо минимизировать.

Добавлено через 1 минуту
Цитата Сообщение от SuprSonic Посмотреть сообщение
Насколько знаю на уровне проца, кэша и регистра все быстро отрабатывает. А как там HashSet реализован вообще не известно.
Речь шла о корректности кода, а не о его производительности. Или вам быстрый багованный код нужен?
0
 Аватар для SuprSonic
7 / 4 / 3
Регистрация: 07.08.2016
Сообщений: 65
21.01.2019, 21:36  [ТС]
Цитата Сообщение от TopLayer Посмотреть сообщение
Ну вы же не поняли, почему речь идёт о значениях от 0 до 255.
Прочтите пожалуйста еще раз мои посты повнимательнее. Потому как попахивает каким-то стебом уже.

Чего там понимать? В байте 8 бит. 2^8 = 256 в byte же можно запихнуть значение от 0 до 255. Что тут сверхнового?

Добавлено через 10 минут
Цитата Сообщение от TopLayer Посмотреть сообщение
Речь шла о корректности кода, а не о его производительности. Или вам быстрый багованный код нужен?
А в чем код то не корректен и какие именно баги? Конкретизируйте, пожалуйста.
Использовать HashSet ради сокращения длины кода жертвуя производительностью?! Я не настолько перфекционист.
0
Надоела реклама? Зарегистрируйтесь и она исчезнет полностью.
inter-admin
Эксперт
29715 / 6470 / 2152
Регистрация: 06.03.2009
Сообщений: 28,500
Блог
21.01.2019, 21:36

Отслеживание изменений
Здравствуйте, учу C#. Сделал таблицу. Когда нажимаю кнопку &quot;Поместить&quot; без введённых данных, то выдаст ошибку, меня бросает на изменение...

отслеживание изменений
И вновь день добрый. как отследить, изменилось ли определенное поле в jsp, непосредственно через Controller ?

Отслеживание изменений
Добрый день. Уважаемые форумчане, подскажите где подсмотреть. Пытаюсь написать пинговалку. private bool ConnectedStatus(string _ip) ...

отслеживание изменений
вот у каждого разработчика получается есть своя копия репозитория(которую он получает спомощью git clone) так? И если я у себя создам...

Отслеживание изменений
Здравствуйте, подскажите пожалуйста, как в Access можно отследить изменения и пользователя который эти изменения сделал. В Excel есть такая...


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

Или воспользуйтесь поиском по форуму:
40
Ответ Создать тему
Новые блоги и статьи
Был там один разговор по поводу свободы в материальном мире.
kumehtar 19.08.2026
Суть: рассматривается живое существо, оказавшееся внутри довольно странной системы (этого мира) и пытающееся обустроить в ней свой кусок пространства. Жизнь действительно предъявляет каждому. . .
Когда логика программы не спасает от человеческих ошибок
Maks 18.08.2026
В последнее время всё чаще и чаще сталкиваюсь с таким явлением, как абсолютная невнимательность (или глупость) пользователей. Проявляется это чаще всего на работе в коллективе. Допустим, человек с. . .
Лето уходит
kumehtar 17.08.2026
Мысли в слух
kumehtar 17.08.2026
Забавно, насколько сейчас стала доступна информация. Например о магии, духовном развитии, медитациях, и других подобных направлениях, ранее зачастую тайных, передаваемых от учителя к ученику. Хотя. . .
Перемещение строк из ТЧ в другой документ с учетом текущего пробега
Maks 17.08.2026
Реализация из решения ниже выполнена на примере нетипового документа "Автозапчасти", с ТЧ "Шины". За основу взят алгоритм отсюда: https:/ / www. cyberforum. ru/ blogs/ 359708/ 10838. html Задача: . . .
Саморегулирующийся социальный контракт для сервера cross-section.
Hrethgir 14.08.2026
С кодом конечно таких глубоких размышлений пока не было, впрочем я уже привык к алгоритмизации. Суть предмета записи: снова в диалоге с нейросетью (я взял пока себе ник для учётки админа - Rector). . . .
Часы электронные
Uhbif79 12.08.2026
Выкладываю программу часов. Программа позволяет: 1. Использовать системное время и дату, 2. Есть возможность вводить время и дату вручную. 3. Реализованы 2 будильника: начало и конец рабочего дня. . . .
Часы с будильником на основе класса QLCDNumber
Uhbif79 12.08.2026
Всем добрый день, выкладываю программу часов с будильником на основе класса QLCDNumber. Здесь я пробовал самостоятельно создавал классы, впервые столкнулся с видимостью переменной одного класса из. . .
КиберФорум - форум программистов, компьютерный форум, программирование
Powered by vBulletin
Copyright ©2000 - 2026, CyberForum.ru