Сказать снова, что эта вещь сверхважна для понимания всего программирования, то это не сказать почти ничего, так как поведение коробок в массивах C++/C, C#, Java просто фантастически поможет вам одновременно и понимать механизмы поведения коробок от частей объектов в наследовании и интерфейсах. Казалось бы, что это совсем разные темы и природа здесь различна, но это вообще не так, ибо в наследовании и в массивах применяются "под капотом" абсолютно такие же принципы механики коробок от объектов.
Конечно, у вас глаза на лоб лезут, потому что в мире вам этого никто еще не говорил, но в данной статье постараюсь вас убедить, что "резиновые" коробки (ссылки и указатели) от массивов "под капотом" в C++, C#, Java полностью аналогичны коробкам от интерфейсов, абстрактных и базовых классов или коробкам разных частей объектов при расширяющем, копирующем и переопределяющем наследовании.
Начнем с природы массивов в C++, C#, Java
Одновременно в данной статье вы поймете природу всех массивов, о которой вам никто в мире традиционно не говорит. Итак, вы должны усвоить на всю жизнь уже мной проверенный факт, что "под капотом" все массивы в упомянутых языках представляют собой объекты одинакового типа, которые расположены в памяти ЭВМ в прямую линию или идут друг за другом. Но здесь есть нюансы, то есть прямая линия одного типа объектов массива может быть организована по-разному.
Открою вам еще одну важную тайну, которую вам в мире не говорят, в языках С++/С все массивы являются просто набором одинакового типа объектов в линию, причем эти объекты расположены в линию рядом, но никак не связаны друг с другом в один объект массива, а эта особенность перетекла в С++ от языка С с 1970-х годов, ведь язык С считается не "объектно-ориентированным". То есть массив в языках С++/С вы должны представить в виде никак не связанных друг с другом одного типа объектов, расположенных в прямую линию в памяти. В языках Java и C# решили придать объектность массивам, но и там "под капотом" все массивы являются идущими друг за другом в прямую линию объектами одного типа, правда линия этих одинаковых объектов в этих языках обнесена "забором" или оберткой объекта. Кроме того, в Java и C# все массивы содержатся в "резиновых" коробках-ссылках, а в С++/С все массивы "содержатся" в "резиновых" коробках-указателях на первый объект в линии массива в памяти.
В языках Java C# все массивы поместили в ссылки и классы, то есть они являются коробочными объектами и коробками, а по моим статьям вы знаете, что в этих языках к коробочным объектам можно получить доступ только по коробке-ссылке. Получается, что массивам здесь сделали обертку объектов и соединили коробками и классами прямую линию объектов в один общий объект массива. Поэтому вы должны понимать разницу "под капотом", где в С++/С массивы являются просто набором одинакового типа объектов в линию, который мы за счет "арифметики указателей" воспринимаем мысленно общим одним объектом массива. В Java и C# также "под капотом" имеется арифметика коробок-ссылок, о которой я вам чуть позже расскажу.
Сейчас мне уже стала ясна вся механика коробок от массивов в этих языках и эта механика очень близка к коробкам от интерфейсов и частей объектов базовых и наследующих классов при наследовании. Вы должны уяснить, что указатели на массивы на самом деле не "резиновые", они всегда содержат адрес первого объекта в прямой линии массива, то есть "под капотом" все коробки от массива содержат всегда первый объект массива, а все доступы к остальным объектам в линии массива в C++,Java, C# происходят по арифметике указателей или ссылок. Это значит, что за счет оператора квадратных скобок коробки (указатели и ссылки) перескакивают по адресам объектов массива в прямой линии, в С++/С указатели скачут по независимым друг от друга объектам в линии, а в Java и C# коробки перемещаются по прямой линии объектов массива, то есть коробка-ссылка всегда содержит подобно наследованию часть объекта массива или один элемент в линии.
Надеюсь, что я вам пояснил основной механизм всех массивов в главных языках? К этой важнейшей теме будем еще возвращаться часто, поэтому не волнуйтесь. Понять механизмы наследования, интерфейсов, ссылок, указателей, массивов, обобщений или женериков и понять типы данных, в том числе мгновенные объекты, методы и операторы "под капотом" - то вы уже будете в программировании асом по современным меркам, ведь этого никто не понимает в мире сейчас.
Разыменование коробок массивов при обращении к элементам оператором квадратных скобок
Об этом вам в мире также не говорят ни один учебник и никто, но "под капотом" у оператора квадратных скобок [ ] для ссылок и указателей массивов происходит операция разыменование коробок или адрес указателей и ссылок преобразуется в сам объект по этому адресу. Причем данная вещь происходит как с указателями С++/С , так и с ссылками-коробками Java и C# , но об этом молчат все официальщики...
int [ ] massiv1 = new int [10]; // Java и C# (1)
int massiv2 [ ] = new int [10]; // C++/C (2)
В данных двух строках (1) и (2) слева создаются две коробки (ссылка и указатель) на массивы в разных языках, ссылка имеет имя massiv1 , а указатель создан под именем massiv2, а справа в данные коробки помещаются пустые массивы с десятью элементами по умолчанию для int целого типа. Теперь вы можете обращаться к элементам данных массивов с помощью этих коробок и оператора квадратных скобок, "под капотом" которого имеется операция разыменования коробок и арифметика указателей и ссылок.
massiv1 [8] = 56; // Java C# (3)
massiv2 [4] = 233; // C++/C (4)
В примерах (3) и (4) идет обращение к элементам массива через коробки с оператором квадратных скобок. Где здесь разыменование? В С++ указатели содержат адреса объектов в виде значений шестнадцатеричного целого формата, то есть переменная указателя massiv1 содержит целый шестнадцатеричный адрес в памяти первого объекта типа int массива или первого элемента прямой линии объектов. То есть коробка в С++ как бы содержит часть объекта массива (хотя массив здесь не в виде объекта), указывая адрес первого элемента в линии, но когда вы обращаетесь квадратными скобками к элементам массива в С++/ С в примере (4) , то вы имеете доступ к самим объектам типа int и можете присваивать значения элементам массива и получать эти целые значения оператором квадратных скобок. То есть вы избавились от шестнадцатеричного адреса объекта в указателе и обратились к значению самого объекта по его номеру в массиве! Здесь и произошло "под капотом" разыменование указателя на один элемент массива, когда вы получили сам объект по его адресу.
Об этом разыменовании массивов почему-то все молчат, как и обо всем другом, что является важным. При обращении к массивам через ссылки-коробки в Java и C# происходит такое же разыменование коробок, но только в данном случае ссылок-коробок, понять которое сложнее разыменования указателей, ибо ссылка содержит сам объект в виде адреса на этот объект, то есть здесь при обращении к самому объекту без ссылки происходит путаница. Но в Java и C# примера (3) разыменование ссылок происходит "под капотом" оператора квадратных скобок для переменных-ссылок на объекты массивов. Это значит, что ссылка-коробка содержит часть объекта массива (в Java и C# массивы уже именно в виде объектов - прямых линий в памяти) в виде адреса его первого в линии элемента, а адреса у коробок-ссылок меряются в самих объектах. Далее в записи massiv1 [8] вы обращаетесь к разыменованному девятому элементу объекта массива, то есть коробка-ссылка по адресу сдвигается на восемь объектов вперед от первого-нулевого элемента, затем оператор квадратных скобок разыменовывает ссылку на девятый элемент и вы обращаетесь к самому объекту целого типа.
На самом деле, здесь все просто, запомните навсегда, что коробки (ссылки и указатели) перемещаются адресами по объекту массива по его элементам в прямой линии, а затем квадратными скобками разыменовывают нужный элемент. Сходство с наследованием и интерфейсами в том, что коробки содержат всегда один элемент из всего объекта прямой линии массива, то есть содержат часть объектов. Напомню разницу природы коробок ссылок и указателей, что ссылки не могут охватывать пространство за пределами объектов, а указатели могут, но оба типа коробок могут содержать любую часть внутри объектов.
Многомерные массивы из равных отрезков в линии
Если вы поняли всю информацию выше, то вы поняли 95 процентов всех массивов, но бывают еще особые виды таких массивов, которые "под капотом" являются той же прямой линией одинаковых элементов в памяти.
Привожу вам пример многомерного обычного массива из основных языков (C# Java C++ C) , который "под капотом" является прямой линией в памяти объектов одинакового типа, но линия эта разделена на равные по длине отрезки, где каждый отрезок является внутренним массивом обычного многомерного массива, который сам является массивом из многих массивов. Для обращения к элементам такого массива из равных отрезков-массивов оператор квадратных скобок немного видоизменили в наших языках. Ниже приведу пример.
int [ , ] massiv4 = new int [10 , 20]; // C#
String [ ] [ ] massiv6 = new String [10] [20]; // Java
string massiv8 [ ] [ ] = new string [10] [20]; // C++/C
Вроде правильно написал. Надо учитывать, что в языке Java какая-то хитрая ссылка на многомерный массив из многих квадратных скобок, вроде бы в одном случае она содержит ступенчатый динамический многомерный массив, о котором я покажу ниже, в другом случае эта же ссылка с несколькими квадратными скобками в Java содержит вроде уже обычный многомерный массив из линии с отрезками. Но перечисленные выше примеры должны объявить ссылки и указатели на обычный многомерный массив в линию отрезков, где в линию идут 10 отрезков или 10 массивов из 20 элементов на каждый, то есть всего в двумерном массиве в линию 200 элементов. В первом случае C# массив содержит целые числа, во втором случае в Java массив из стандартных ссылок на строки, в третьем случае C++ в массиве 200 объектов стандартных строк, то есть 10 массивов по 20 строк в одну линию в памяти.
В итоге, вы должны понять суть, "под капотом" в языках С++/С не существует не только отрезков-массивов внутри прямой линии объектов, но и сами объекты в этой линии никак не связаны друг с другом, но они идут в памяти в прямую линию и оператор многомерных квадратных скобок [ ] [ ] переопределен так, что он может обращаться к простой линии элементов как к многомерному массиву из отрезков-массивов. Как он это делает? Он смещает адреса объектов в линии в соответствии с цифрами в квадратных скобках и разыменовывает коробку от объекта, делая доступным сам объект, а не его адрес у коробок.
В линии объектов массива нет потери памяти!
Если вы не знаете, то в языках С++/С потерей памяти называют случаи, когда на безымянный объект больше не ссылается ни одного указателя и нет ни одной ссылки, то есть если у безымянного объекта больше нет ни одной коробки, то он теряется в памяти и вы не сможете больше никогда обратиться к данному объекту.
Именно поэтому говорят, что в этих языках нет "автоматической сборки мусора", а в Java и C# "сборка мусора" есть. В реале это называется не сборкой мусора, а уничтожением объектов без имени, на адреса которых больше нет ссылок и указателей (коробок). В языках Java и C# этим занимаются автоматические стандартные специальные классы GC (Garbage Collector или "сборщик мусора"), которые работают "под капотом" во взаимосвязи с ссылочными типами или классами (class), по аналогии с "умными указателями" С++ (Smart pointers) можно назвать классы Java и C# "умными ссылками", хотя об этом вам в мире опять никто не говорит.
И вот мы подошли к мысли этой части статьи, которая говорит, что независимые одинаковые безымянные объекты в прямой линии массива в С++/С не вызывают потерю памяти по причине того, что в данном случае мы всегда знаем расположение безымянных объектов в прямой линии массива за счет арифметики указателей.
То есть если мы будем считать массивы в С++/С независимыми, рядом в линию лежащими в памяти объектами (а так и есть), то с точки зрения строгого определения мы получаем потерю памяти всех элементов массива, кроме первого, на который указывает указатель! Но компилятор данных языков разрешает нам такую "потерю памяти", так как по сути потери нет, ведь вы точно знаете место каждого элемента массива и можете к нему всегда обратиться, отсчитав от первого элемента соответствующий сдвиг в линии.
Данная мысль еще раз вам доказывает мои выводы выше, что в С++/С все массивы являются просто идущими в линию в памяти независимыми объектами одного типа, а вот в языках Java и C# массивы уже упакованы в оболочку классов и к ним идет доступ по коробке-ссылке на целый массив, а не по указателю на один элемент массива, как в языках С++/С. В реале "под капотом" в Java и C# происходит то же самое и доступ к элементам массива происходит по арифметике ссылок с помощью таких же сдвигов адресов в линии объектов.
Массивы коробок или динамические массивы
И теперь, наконец, рассмотрим последний тип массивов, механика которых мне известна из всех языков на текущий момент, других типов массивов мне пока не удалось наблюдать, то есть я знаю "под капотом" только массивы в прямую линию, а также массивы коробок от массивов или динамические массивы в С++ (ступенчатые массивы в Java и C#). Я говорю сейчас не о коллекциях, которых огромное количество, а именно о типах всех существующих массивов.
int* massiv [ ] = new int* [10]; // массив из 10 указателей на int в С++
int [ ] [ ] massiv2 = new int [10] [ ]; //ступенчатый массив 10 ссылок C#
Почему эти массивы коробок-ссылок на другие массивы разной длины называют ступенчатыми в C# ? Потому что массивы состоят из ссылок или указателей на адреса других массивов разной длины и на рисунке такие массивы массивов выглядят в виде ступенек, в языках С++/С такие массивы указателей на массивы разной длины называют динамическими массивами , но идиоты называют динамическими и обычные многомерные массивы в Java и C# и возникает путаница, поэтому запомните, что "под капотом" ступенчатые и динамические массивы просто являются обычным массивом указателей или ссылок, которые указывают на адреса других массивов в памяти. То есть динамические массивы представляют собой ту же прямую линию независимых в памяти объектов, которые являются коробками, то есть указателями и ссылками на другие массивы.
Из всего вы должны сделать главные выводы! Все массивы в основных языках являются особым видом объекта в виде прямой линии объектов одинакового типа в памяти, причем в C++/C массивы по сути не являются единым объектом, так как соседние элементы не связаны друг с другом никак в один объект, а вы только подразумеваете эту линию элементов одним объектом массива. В языках Java и C# массивы уже соединены оболочкой классов в один объект, но "под капотом" это такая же прямая линия независимых объектов. Указатели и ссылки в этих языках перемещаются адресами по линейным объектам массива, отсчитывая смещение адреса от первого (нулевого) элемента коллекции и указывая его в операторе квадратных скобок. Оператор квадратных скобок вдобавок разыменовывает указанный в нем адрес объекта в линии массива и коробка (ссылка и указатель) от объекта превращается в сам объект или элемент массива!
Подписывайтесь, ставьте лайки и комментируйте, пока...