Популярные услуги

Все письменные КМ под ключ за 3 суток! (КМ-6 + КМ-7 + КМ-8 + КМ-9 + КМ-10)
КМ-6. Динамические массивы. Семинар - выполню любой вариант!
Любая задача на C/C++
Одно любое задание в mYsql
Любой тест по базам данных максимально быстро на хорошую оценку - или верну деньги!
Любой реферат по объектно-ориентированному программированию (ООП)
Повышение уникальности твоей работе
КМ-2. Разработка простейших консольных программ с использованием ООП + КМ-4. Более сложные элементы ООП - под ключ!
Оба семинара по программированию под ключ! КМ-2. Разработка циклических алгоритмов + КМ-3. Функции и многофайловые программы в Си
Любой реферат по информатике

Лекция 6

2021-03-09СтудИзба

Лекция 6

На прошлой лекции мы начали рассматривать тип данных указатели/ссылки. На этой лекции мы продолжим рассмотрение этого типа данных и рассмотрим функциональный тип данных.

  Две основные проблемы, которые связаны с типом данных указатели, связаны с тем,  что обычно указатель используется для ссылок на динамические структуры данных, которые расположены в динамической памяти. По крайней мере, в строгих языках программирования, к которым с этой точки зрения относятся стандарт Паскаль, Ада 83, Оберон, с одной стороны, есть понятие указателя, а с другой стороны это понятие указателя, особенно в Аде 83, ограничено таким образом, что указатели ссылаются только на объекты из динамической памяти. Для таких указателей мы отметили две главные проблемы:

1) Мусор

2) Висячие ссылки.

  В языках, где есть динамическая сборка мусора, а из этих языков она есть только у языка Оберон, динамическая сборка мусора полностью решает проблему мусора и проблему висячих ссылок за счет проблем с эффективностью. Причем, самое неприятное обстоятельство, связанное с динамической сборкой мусора это то, что память освобождается из-под объекта, не по команде программиста, а в произвольный момент времени, который выбирает сам динамический менеджер памяти. Есть несколько версий решения этой проблемы: первая - это стремительно нарастающее быстродействие компьютера, вторая - улучшающийся алгоритм сборки мусора. Существуют, также, другие версии решения этой проблемы. Введение в методы реализации динамической сборки мусора есть в книге Себесты.

  Что касается языков без динамической сборки мусора, то для них эти проблемы естественно остаются, и они очень остры.

Рекомендуемые материалы

  Кроме этого здесь есть еще один аспект. Многие версии Паскаля, прежде всего Турбо Паскаль, Delphi, как наследник Турбо Паскаля и, конечно же, такие языки как С и С++ - это языки нестрогие в том плане, что там понятие указатель расширено, а именно: указатель – это не только ссылка на объект динамической памяти, в этих языках есть понятие адресной операции, которая выглядит в С - &, в Delphi - @. Т. е. можно взять адрес произвольного объекта и присвоить его указателю. В результате никакой видимой разницы между двумя типами указателей нет. И в таких языках, наряду с этими двумя проблемами появляются проблемы, связанные с тем, что мы можем пользоваться указателем, например на автоматический, то есть квазистатический объект, размещаемый в стеке, мы уже покинули соответствующий блок, а указатель продолжает существовать. Эта проблема очень похожа на проблему висячей ссылки, т. е. указатель у нас определен, куда-то указывает, но про содержимое этой области памяти мы ничего сказать уже не можем потому, что она освобождена. Т. е. это проблема, связанная с висячей ссылкой, но которая получается за счет того, что мы можем брать любой объект памяти, в том числе и статический. Кроме этого появляется проблема ошибочного освобождения памяти, т. е. мы освобождаем указатель, который на самом деле указывает не на динамический объект.

  Эти ошибки очень опасны, потому что они проявляются совершенно не там, где их ждешь, и поэтому их устранение очень накладно. И поэтому во многих языках, прежде всего в этих строгих, отказались от идеи того, что указатель указывает на произвольный объект. Например в Аде 83, как уже говорилось, единственная возможность проинициализировать указатель – с помощью операции new T, ну и, разумеется, операции присваивания :=. Если реализация Ады позволяла динамическую сборку мусора, то тогда соответствующее решение было весьма и весьма надежным. Но с одной стороны, как  уже говорилось, реализация Ады допускает и нединамическую сборку мусора, а с другой стороны ситуация в 90-ых годах несколько изменилась. И интересно, что в Аде 95 создатели языка были вынуждены расширить язык таким образом, чтобы допустить указатели и на нединамические объекты. Спрашивается: зачем, ведь, как мы выяснили, это еще более добавляет ненадежности в указатель, а указатель – это самый ненадежный простой базисный тип данных в ЯП. Что изменилось с 1983 года? Подходы к надежности – нет, Ада 95 с точки зрения надежности исповедывала те же критерии, что и предыдущий стандарт языка – Ада 83. Изменились внешние условия: если создатели Ада 83 были уверены, что они проектируют единый универсальный ЯП, который должен заменить все существовавшие к тому времени в соответствующей проблемной области языки программирования. Ситуация в 95-м году понятно была какой – было ясно, что новый стандарт Ада не может претендовать на роль всеобъемлющего ЯП. В результате связь программ на языке Ада с программами на других языках из чисто опционального (какого-то дополнительного) средства, как  это было в момент создания первого стандарта Ада, превратился в насущную необходимость. Для чего нужны указатели на нединамические объекты? Прежде всего, чтобы можно было вызывать программы на других ЯП и соблюдать соглашения о вызовах, соглашения о типах параметров, которые приняты для тех ЯП. Чаще всего вызываются программы, написанные на С, на Фортране. Для вычислительных программ – подпрограммы на Фортране, для системных программ - это интерфейсы на С или С++, а в этих языках все системные интерфейсы разработаны так, что без указателей там не обойтись. Cпрашивается: можно ли попытаться сохранить достаточно надежным язык? Создатели Ада 95, за счет, естественно, некоторого усложнения языка наши достаточно приемлемое решение  для сохранения надежности. Новые проблемы возникли с тем, что, скажем, в том же самом С++, внешне никак нельзя отличить указатель, размещенный с помощью процедуры new от указателя, который мы получили с помощью адресной операции. Вот от этого свойства указателей и надо отказаться. В языке Ада есть обычный указательный тип а-ля Ада 83

Type PT is access T;

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

X1:PT;

то в этом случае мы можем размещать X1 с помощью оператора new, который вызывает менеджер динамической памяти.

X1:=new T;

Эти указатели в языке Ада 95 остались. Но появился еще новый указательный тип, а именно

Type PTT is access all T;

Это означает, что указатели PTT могут ссылаться как на динамические объекты, так и на нединамические. Если мы опишем переменную

X2:PTT;

то мы можем, во-первых,  использовать и старый способ размещения динамической памяти и, как следствие, способ присваивания указателя

X2:=new T;

Но кроме этого можно еще адресовать и объекты из нединамической памяти, но не любые объекты, а только те, которые специально для этого предназначены. Пусть у нас есть какая-то переменная

Y1:T;

и переменная Y2, которая описана с помощью ключевого слова aliased

Y2:aliased T;

И то и то относится к одному и томуже типу данных, однако ключевое слово aliased говорит о том, что дополнительно к числу операций, которые можно проводить над типом T добавляется еще операция вычисления адреса, а именно допустима конструкция

X2:=Y2'access;

Мы получили в Х2 адрес переменной Y2. При этом соответствующий указатель должен быть объявлен с атрибутом all.

X1:=Y2'access;

ошибка потому, что слева соответствующий указатель не может воспринимать адресную операцию.

X2:=Y1'access;

ошибка, связанная с тем, что указатель Х2 относится к типу PTT, но, однако, к Y1 не применима операция access. В результате проблемы, о которых говорилось ранее, здесь снимаются. И в результате можно передавать и адреса нединамических объектов, скажем функций, написанных на языке С. Это скорее всего будут системные вызовы из соответствующей библиотеки, или из интерфейса прикладного программиста API. Поэтому эта проблема, разумеется, снялась. И это, конечно, достаточно неплохое решение.

  Еще в языках основанных на С, на С++ есть понятие адресной арифметики, которая, во общем-то приводит  к тому, что надежность соответствующих языков вызывает очень и очень большие сомнения.

  В более строгих языках, чем С и С++ операции над указателями фактически ограничены следующим образом: во-первых операция присваивания := , кроме этого операция размещения в динамической памяти new, ну и еще в языке Ада для некоторого вида указателей адресная операция access, но адресная операция, которая защищена с помощью соответствующих конструкций. И если в языке реализована динамическая сборка мусора, то есть у нас отсутствует оператор типа delete, то решение с указателем получается вполне надежным. То есть никаких проблем с указателем, о которых мы говорили, у нас нет. И поэтому если мы определим указатели таким образом, что крайне ограничим им набор операций: только присваивание и только выделение динамической памяти и больше никаких других операций (ну и естественно передача как параметр, что эквивалентно присваиванию) то получившаяся конструкция указателя становится вполне надежной. Но только ее уже не надо называть указателем, потому что слово указатель в определенного рода кругах вызывает недоверие. И, следовательно, тогда такой ограниченный тип данных с точки зрения множества значений все равно остается адресом, и  имеет смысл переименовать его в такой тип данных, как ссылка. И действительно, в современных языках программирования происходит интересный процесс, а именно: у нас появляется новый ссылочный тип данных. Это языки Delphi, Java и C#. В Java, например, все типы данных делятся на:

а) простые типы данных, которые мы уже рассматривали. Это, прежде всего, арифметические, логические, символьные и т. д..

б) ссылочные типы данных, к которым относятся массивы, классы и интерфейсы. И все эти данные описываются с помощью ссылок.

Та же самая ситуация и в языке Delphi. Там есть понятие класса – новое понятие. Так вот объекты классов в языке Delphi это исключительно ссылки и то же самое относится и к С#. Но по сравнению с Java, Delphi и C# несколько перенавороченны, а именно: в Delphi есть еще старое понятие записи, унаследованное из Турбо Паскаля, а в C# есть еще понятие структуры, которое похоже на урезанный класс. Когда мы будем говорить о составных типах данных, мы поговорим о структурах языка C#. Но в целом, именно основные типы данных, выражаемых классом, в этих языках выражаются с помощью ссылок.

  Рассмотрим размещение переменной. Пусть у нас есть класс Х. Мы объявляем объект х класса Х

Х х;

В принципе это объявление синтаксически верно и на языке Java, и на языке C++, и на языке C#. Но на языке С++ это значит совершенно другое, а именно то, что в С++ объект соответствующего типа данных размещается в памяти, в какой - это зависит от того где находится это объявление: либо в квазистатической памяти, если оно находится, например, внутри блока, либо в статической памяти, если оно объявлено она верхнем уровне. Если же речь идет об объявлении в С# или Java, то это на самом деле размещается только ссылка на объект класса Х. То есть, везде, где речь идет об именах объектов, нужно понимать, что это ссылки, причем то же самое относится и к массивам. Например, в Java

char a[];

Причем в Java, как и в C# запрещено даже указывать здесь длину массива. Здесь речь идет не о том, что размещается какой-то массив в памяти, а размещается просто ссылка на соответствующий массив. При этом в начальный момент времени они никуда не указывают.

 

                   х


                   а

        

Соответствующие объекты должны быть обязательно проинициализированы с помощью оператора new.

х= new X;

a= new char[20];

Именно в момент выполнения операции new происходит инициализация ссылки на соответствующий объект


                        х                      Х

             

                    а                                                                   


В Delphi синтаксис немножко другой – там речь идет о вызове конструктора. То есть если мы описываем объект Х типа Т

X:T;

то опять же  размещается только ссылка на объект типа Т, в начале неинициализированная, а инициализировать ее мы с помощью явного вызова конструктора

X:= T.Create;

Это языки с так называемой ссылочной семантикой. И как следствие необходимо всегда помнить, что в этих языках, например, если у нас есть Х1, Х2 типа Т

X1,X2:T;

или соответственно в таких языках как C# или Java

Т Х1,Х2;

То присваивание

Х1=Х2

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

  То, что называется ссылкой в языке С++ - это несколько особый тип данных, который тоже служит для того, чтобы играть роль указателя, но в то же время ограниченного с точки зрения набора операций. Иначе говоря, ссылки в языке С++ это некоторый аналог имени объекта. Единственная операция, которую можно производить со ссылкой – это явная операция – обязательная инициализация. Т. е. если в языке С++ у нас есть переменная i

int i;

Есть ссылочный объект х.

int &x;

- такое объявление у нас не допустимо. Мы должны обязательно, если речь идет о переменной типа ссылка, проинициализировать

int &x= i;

Здесь речь идет о том, что у нас, фактически, ссылка х тождесственна переменной i.И теперь любые операции, которые производятся со ссылкой, например

x=3;

эквивалентны тому, что вместо х употребляется i. Разумеется подобного рода стиль программирования мягко говоря нехорош, хотя допустим в С++.

  Основное назначение ссылок – это новый способ передачи параметров, а именно, если в С у нас есть единственный способ передачи параметров – по значению, то появление ссылочного типа добавляет нам еще один способ передачи параметра - по адресу. Если у нас есть функция

int f(int);

int f(int&);

Понятно, что в первом случае компилятор при вызове

f(i);

передаст значение переменной i, а во втором случае он передаст адрес. Как следствие, раз в языке есть несколько способов передачи параметров, то есть требование обязательного прототипирования всех функций, т. е. все функции должны быть обязательно описаны перед их употреблением, иначе компилятор просто не будет знать по какому способу передавать параметры, синтаксически-то вызов будет одинаковым. Ну и дополнительно можно использовать такое полезное свойство ссылок, когда нам действительно нужен доступ к объекту. Скажем если у нас есть какой-то контейнер (массив, или какой-то более общий), то функция, которая возвращает ссылку, просто дает возможность прямого доступа к этому элементу.

  Не случайно ссылка – это не просто некоторый каприз Строуструпа, хотя, конечно понятие ссылки очень удобно именно для единообразного способа передачи праметров. Можно было бы, если речь шла чисто о передаче параметров, ввести, как в Паскале просто при описании синтаксиса переменных какое-то дополнительное ключевое слово, например, нечто типа ref

int f(ref int);

которое просто говорит о том, что соответствующий параметр передается по ссылке, и не нужно раздувать базис языка, не нужно добавлять новый тип данных. Спрашивается, почему Строуструп пошел по другому пути? Он, все таки, добавил новый тип данных в базис языка. Обратите внимание, что за исключением типа данных bool, который появился только в 90-ые годы, ссылочный тип данных - это единственное расширение базиса языка. С точки зрения базиса и С, и С++ совпадают с одним единственным исключением, а именно с ссылочным типом данных. Где еще нужен ссылочный тип данных? Вспомним, что уже на ранних стадиях развития языка С++ Стоуструп проводил идею перекрытия операций. Идея, как видите, плодотворная, поскольку во всех современных языках программирования идея перекрытия имен процедур и функций реализована. Она оказалась очень удобной. Единственное исключение – это язык Оберон, поскольку в принципе вещь удобная, но теоретически без нее обойтись можно. Перекрытие операций – вещь полезная, но кроме этого Строуструп реализовал перекрытие и стандартных знаков операций. При этом из всех языков, которые позволяют такое перекрытие, С++ обладает наиболее богатым объемом операций. Т. е., как  уже говорилось, нельзя перекрывать только операции доступа к элементам . , операцию разрешения области видимости :: , связанную с этим операция доступа к указателю на член класса .*, ну и нельзя еще перекрывать трехместную операцию ?: ,причем, как написал Строуструп: "только по причинам, что я не придумал ни одного нормального контекста, в котором это перекрытие могло бы оказаться полезным", а так, почему бы и эту не перекрыть. Все остальные операции, включая и операцию вызова функции ( ) , и операцию разименования указателя -> и многие другие операции можно перекрывать, в том числе и операцию индексирования [ ]. Спрашивается, а как можно перекрыть операцию индексирования, чтобы она была функционально эквивалентна обычной операции индексирования в том же самом языке, как С.Если у нас есть массив

int a[10];

у нас есть операция индексирования объекта, например

a[0];

Cпрашивается, какой тип данных возвращает а[0]?

Если a[0] возвращает просто int, то мы можем писать

int i = a[0];

но мы можем писать и

a[0] = i;

Здесь это уже не просто значение целого типа данных, мы не можем суда подставить значение целого типа данных – в левой части оператора присваивания должно содержатся нечто, что имеет адрес. Указатель, разименнованный указатель – ради бога, поскольку *р слева мы можем писать потому, что она имеет адрес, константу не можем писать, поскольку константа адреса не имеет. То есть a[0] должно обозначать нечто, что дает адрес. Но  с другой стороны, адрес у нас есть – это указатель, но указатель нужно еще разименовывать. И без введения типа данных ссылка нельзя было перекрыть ряд операций, поскольку операция индексирования возвращает именно ссылку на объект, и используя эту ссылку мы можем, например, модифицировать содержание этого объекта, брать из него значение, все, что угодно. Все, что можно делать с объектом, мы можем делать и с ссылкой. И все так называемые операции над ссылкой, за исключением операции инициализации это и есть операции с объектами на которые ссылается данная ссылка. И следовательно, введение ссылочного типа данных – это не просто каприз, а расширение базисной системы типов на всего один только тип ссылка позволило действительно сделать язык достаточно мощным. Без ссылок перекрытие ряда стандартных операций просто никакого бы смысла не имело. С этой точки зрения, конечно, ссылки в языки С++ несколько особый класс, который немножко отличен от понятия ссылки в таких языках, как С#, Delphi, Java.

  У нас остался еще один тип данных, а именно – функциональный тип данных. Значениями функционального типа данных служат процедуры и функции. Такой тип данных есть например в расширении Паскаль, Турбо Паскаль, например, и как следствие в языке Delphi, Функциональный тип данных появился в языке Модула 2, в частности для того, чтобы можно было передавать процедуры и функции как параметр потому, что, вспомним – в стандартном Паскале у нас в дополнение к двум обычным способам передачи параметров, а именно – по значению и по ссылке с помощью  ключевого слова var, добавились еще 2 специальных способа параметра, а именно – параметр-процедура и параметр-функция. Вот в Турбо Паскале  уже параметров процедур и функций просто нет потому, что там появился функциональный тип данных. То же самое произошло и с языком Модула 2 и, как следствие, с Обероном. Там появились соответствующие функциональные типы данных. Например, если   хотеть написать некоторый тип

type Prc = procedure(var integer);

Prc – процедура, у которой есть один формальный параметр. Это синтаксис Модула 2, в Турбо Паскале и в Delphi здесь необходимо указывать переменную, но в общем смысл один и тот же.  И вот теперь у нас есть тип данных, мы можем описывать как переменные, так и формальные параметры типа Рrc. Что может являться значениями такого функционального типа данных? Имена других процедур и функций, которые описаны в программе. Каким образом реализуется такой функциональный тип данных? Заметьте, что и в С и в С++ можно описать это логически. Это будет нечто такого вида

typedef void (*f(int &));

f является теперь именем соответствующего функционального типа данных. С точки зрения реализации функциональные типы данных – это адреса соответствующих процедур потому , что теперь мы вполне можем описать

VAR X: Prc;

Присвоить Х какую-то процедуру Proc

X:= Proc;

где Proc – это процедура, которая имеет такой прототип и описана у нас в программе. Ну и понятно, что с точки зрения реализации теперь это просто адрес процедуры. Мы теперь можем писать

Х(i);

и вызывать соответствующую процедуру. Получается так, что вместе с функциональным типом данных в наш язык пролезает понятие указателя, и именно из этих соображений функциональные типы данных тоже можно считать ненадежным, прежде всего потому, что указатель можно забыть проинициализировать и прочее, и прочее и прочее. И поэтому, конечно, в языке, который особое внимание уделяет надежности, понятие функционального типа данных должно быть урезано. Отметим, что в традиционных языках программирования основное назначение процедурного типа данных – передача процедур и функций как формальных параметров, ну и кроме этого еще это обработчики всякого рода событий и всего того, что в английском языке именуется словом callback. Callback – это какая-то процедура или функция, которая вызывается в ответ на какие-то внешние воздействия, например передачу сообщений . Они обычно реализуются с помощью функционального типа данных,

т. е. с помощью указателя на процедуру или функцию, со всей, связанной с этим ненадежностью и т.д. и т.п..

  Как с точки зрения надежности пытаются решить проблему функциональных типов данных дизайнеры языков программирования? Возьмем язык Ада. Язык Ада, вообще говоря, всегда очень аккуратно поступает с указателями. Спрашивается: что он будет делать с указателем на функцию? Тут ситуация следующая: в языке Ада 83 все было очень просто, а именно функционального типа данных там вообще не было. А как же тогда написать процедуру вычисления интеграла? Очевидно, в этой процедуре в качестве одного из параметров должна передаваться подынтегральная функция, а писать для каждого интеграла особую процедуру, которая будет вызывать эту функцию, вообще говоря, достаточно не удобно. Но создатели языка Ада решили эту проблему следующим образом. Как уже было сказано, основное назначение процедурных типов данных – это либо передача параметров процедур и функций, либо моделирование понятия callback. И то и другое оказалось возможным именно за счет ограниченности функционального типа данных. Значения его ограничены заранее известным, то есть статическим набором процедур и функций. Те процедуры и функции, которые описаны в вашей программе – это и может быть допустимое значение функционального типа (с нужным прототипом). Следовательно, все возможные значения статически известны, и следовательно, связывание формальных и фактических параметров можно провести не на стадии выполнения, передавая адреса соответствующих процедур и функций, а на стадии трансляции. В Аде есть так называемое понятие родовых сегментов (generic segment). Они несколько похожи на шаблоны языка С++. Как шаблоны языка С++, так и родовые сегменты Ада – это есть статически параметризованные конструкции. В отличие от процедур и функций, которые параметризовывать можно только объектами данных, родовые сегменты можно параметризовать именами типов, именами процедур и функций и т.д.. Когда мы будем разбирать статическую параметризацию – это отдельная глава в языках Ада и С++ (в других языках, которые мы рассматриваем статической параметризации просто нет) мы увидим, каким образом можно написать, например, ту же самую процедуру интеграл, но сделать ее не динамически настраиваемой, а статически настраиваемой. И в принципе, с точки зрения гибкости использования это никак на эффективность использования языка не влияет. То же самое можно сделать и с концепцией callback, т. е. некоторой процедурой - обработчиком внешних событий. В языке Ада 83 есть понятие задачи, а именно параллельного процесса. Так вот задачный тип данных  как раз не опасен потому, что он служит абстракцией понятия процесса, а процесс это все-таки не указатель, это другая сущность. А подпрограммный тип данных был не нужен. Интересно то, что в Аде 95, в которой было много расширений языка, в частности мы обсуждали расширение понятия указателей, подпрограммный тип данных появился. Спрашивается: что же изменилось? Как и с концепцией указателя язык изменился за счет того, что изменились требования к языку, изменилась программная среда вокруг. В Аде 95 подпрограммные типы данных понадобились из тех же соображений, почему понадобились указатели на нединамические объекты – потому, что необходимо было обеспечивать интерфейсы с другими ЯП. И если интерфейс с языком С, то там без понятия указателя не обойтись, и как следствие появилась новая концепция – расширение концепции указателей. Но кроме этого, обойтись без передачи функций как параметров в языке С не возможно и, следовательно появился подпрограммный тип данных. При этом, если бы речь шла о программировании на одном и только одном языке, можно было бы обойтись без этого типа данных. А в случае, когда нужно обеспечивать интерфейсы с другими ЯП, подпрограммный тип данных оказался просто необходим. Но тем не менее, проблема с ненадежность функционального типа данных все равно остается. Решают ее в разных языках по-разному. В языке Delphi эта проблема никак не решена потому, что Delphi обеспечивает совместимость с Турбо Паскаль, и там все старые средства Турбо Паскаль, в том числе функциональный тип данных, остались. Язык Java также решил эту проблему очень просто – функциональный тип данных в языке Java отсутствует. Спрашивается: а как же тогда можно передавать процедуры и функции в качестве параметров? А не нужно передавать процедуры и функции как параметры. Java отличается от языка Ада 83 тем, что там есть очень мощное понятие класса. Класс интегрирует в себе и данные и операции. Поэтому, например, одно из решений этой проблемы это, вообще говоря, если нам нужна функция, мы завертываем ее в особый класс. Те, кто занимался изучением стандартной библиотеки шаблонов в языке С++ STL, вспомнят специальные, так называемые функции-классы, которые есть в стандартной библиотеки шаблонов. Это некоторый класс, в котором переопределяется операция вызова функции ( ). Основным назначением такого класса – держать эту функцию. Из этогокласса не с помощью механизма вывода, а с помощью статической параметризации, для эффективности, мы переопределяем соответствующим образом, настраиваем эту  операцию вызова, а все алгоритмы, которые работают с этим классом, знают, что он реализует функцию, которую можно достать по имени объекта класса, применяя к нему эту перекрытую операцию ( ). То же самое предлагается  сделать на языке Java, а именно: мы определяем некоторый класс. В языке Java, правда, статической параметризации нет, но зато там есть динамическое связывание методов. И вот мы определяем некоторый класс. У него, соответственно, определяем некоторую функцию f. И в результате, если нам нужно, скажем, написать процедуру интеграл, то соответственно мы говорим, что вместо функции мы вызываем некоторый класс с, и в качестве подынтегральной функции у него будет f. И в результате, если нам надо написать процедуру интегрирования, для какой-то подынтегральной функции ff. Мы выводим новый экземпляр класса с и в нем переопределяем функцию f так, чтобы она вызывала или делала тоже самое, что и функция ff. В результате проблема с передачей функций как параметров решена с помощью ведения такого понятия как класс-функция. Это не языковое понятие, это понятие методов программирования. Когда мы будем рассматривать подробнее концепцию производных класса, в частности в языке Java, мы увидим, что в Java появились даже специальные конструкции - анонимные классы, которые как раз упрощают программирование такого рода классов, чтобы не придумывать дополнительные имена.  

  Что же сделано в C# с этой точки зрения? Традиционный процедурный тип данных там реализовывать не стали, из-за проблем, связанных с ненадежностью понятия процедурного типа данных. Там естественно можно использовать метод языка Java, но разработчики C# пошли несколько по другому пути – они ввели понятие делегата. Сейчас мы не будем обсуждать понятие делегата в языке C# - мы вернемся к нему позднее, когда будем рассматривать концепцию класса и наследования. Сейчас просто отметим, что понятие делегата, с одной стороны, выглядит как тип данных, а с другой стороны, его значениями могут являться только функции. При этом объекты этого типа данных мы также должны с помощью соответствующей операции new, указывая конкретную функцию, которая будет служить в качестве значения этого делегата. При этом приятная особенность делегата, например, по сравнению с данными обычных функциональных типов – то, что, на самом деле, делегат – это не один указатель на какую-то функцию, а его можно рассматривать как совокупность указателей. Т. е. у нас есть понятие callback – какого-то обработчика внешних событий и, если при традиционном стиле программирования у нас это единственный указатель на одну функцию, который вызывается в случае возникновения какого-то внешнего события, то с делегатом даже проще: делегат – это совокупность обработчиков. Т.е. можно на одно и тоже событие повесить несколько обработчиков – так называемое broadcast , т.е. широковещательная обработка события и, в результате вызова делегата по очереди вызываются все функции, которые были добавлены в соответствующий делегат. В частности, например в языке C# есть отдельный тип данных событие, который представляет из себя частный случай делегата. И эти события, вообще говоря, интегрированы в систему языка. Когда мы будем говорить об объектно-ориентированном программировании, мы вернемся к понятию делегатов и к понятию событий в языке C#. На этом давайте закончим обсуждение простых типов данных.

          Глава 2. Составные типы данных.

  Мы достаточно подробно рассмотрели атомарные типы данных в ЯП. Прежде, чем более подробно характеризовать виды составных типов данных, которые существуют в базисах, попробуем охарактеризовать общую тенденцию. Вообще, с точки зрения номенклатуры составных типов данных наиболее "богатый" язык в классе языков, которые мы рассматриваем – это стандартный Паскаль. В Паскале были следующие типы составных данных:

массивы

записи

множества

файлы

Интересно, что минимальным вариантом с этой точки зрения является такой язык как Фортран, который в 60-ые г.г. был одним из самых популярных ЯП. В Фортране из всех составных типов данных есть массив. Это другой полюс. В принципе понятно, что без составных типов данных нельзя, но ясно, что минимальный набор – это массив. На базе массива можно смоделировать все остальные виды, например запись. С точки зрения современных языков программирования, рассмотрим такую неявную наследницу Паскаля, как язык Ада. В базисе Ады остались только массивы и записи. Для более современных ЯП: C++, Delphi, Java, C# характерна следующая тенденция - остались, разумеется, массивы, как базисная структура программирования, а все остальное перешло в понятие класса. Понятие класса оказалось очень мощным как средство развития, т.е. класс не относится к базисной структуре языка, класс - это средство развития. Так вот оказывается на базе класса можно смоделировать и множество, и файл, и многие другие коллекции, поскольку понятия массив, запись, множество возможных коллекций в ЯП не исчерпывает. Т.е. общая тенденция среди этих языков такая: оставлено понятие массива, введено понятие класса, и на базисе понятия класса все остальные понятия уже моделируются как часть стандартной библиотеки языка, и единственным исключением является понятие строка. Это еще один базисный тип данных, который есть во многих ЯП, прежде всего в современных. Важность строк обусловлена тем, что они несут текстовую информацию, а большинство языков именно текстовую информацию обрабатывают. Интересно при этом, что в некотором смысле даже понятие массива в таком языке, как С++ с точки зрения практического программирования практически не нужно. Можно смоделировать надежный массив на базе класса, причем не просто массив, а произвольный вид массива, что ассоциативный массив, что динамический массив, что массив с фиксированными границами и т.д. – все это можно смоделировать на базе класса. Интересно, что в С++ нет даже понятия строки. Вместо этого в большинстве реализаций языка С и в том числе в стандартной библиотеке шаблонов есть специальный шаблонный класс из стандартной библиотеки, который полностью моделирует соответсвующую функциональность строки, но тем не менее, в такие языки как C#, Java, Delphi понятие строки добавлено как  базисный тип. Почему они не использовали подход, скажем Строуструпа, который не стал раздувать базис языка, а вместо этого сделал мощным понятие класса, это мы обсудим позже.

  Теперь давайте для каждого из этих типов данных охарактеризуем более подробно то как они реализованы для тех или иных языков.

  Самой важной структурой данных безусловно является массив. Какие проблемы связаны с определением массива? Вспомним: каждый объект данных имеет атрибуты. На первых лекциях мы водили базовые атрибуты объектов данных, а конкретно мы вводили их 6. Тогда же было сказано, что, естественно, для частных типов данных у нас набор атрибутов существенно расширяется. В частности, самый главный атрибут для массива – это его размер.  Основная проблема массива такая: каким именно атрибутом является длинна и чей это атрибут.

  У нас есть атрибут длина, и он может быть:

статическим,

квазистатическим,

динамическим.

В случае, если длина массива может меняться динамически, мы и говорим о так называемых динамических массивах. Способ реализации таких массивов, как правило в динамической памяти, но не обязательно – мы увидим, что можно реализовывать динамический массив немножко по-другому. Ну и мы увидим, что может существовать еще квазистатический атрибут длина. Вспомним виды распределения памяти: статическое, квазистатическое и динамическое. То же самое можно сказать и про атрибут длина. Статический – это значит он не меняется в процессе выполнения программ, квазистатический - значит, что он меняться может, но на протяжении определенного участка программы, чаще всего это так называемый блок, этот атрибут остается постоянным, ну и динамический, это значит, что атрибут длина может, вообще говоря, измениться в любое время, т.е. строго под контролем программиста.

  Ну и есть еще один аспект: у нас есть объекты данных (ОД) и у нас есть тип данных (ТД). Атрибут длина – то ли это атрибут ОД, то ли это атрибут ТД.

1) Самое жесткое решение принято в языке Паскаль.(Ну и аналогичное решение принято в языке С.) Атрибут длина статический и принадлежит ТД, а следовательно, этот атрибут наследуется всеми ОД этого типа. Это означает, что длину мы связываем с типом данных. Эта длина не может у нас поменяться. Как следствие в стандартном Паскале нельзя было написать процедуру скалярного произведения произвольных векторов. Полезная процедура, которая в ряде проблемных областей вычисляется очень часто. Процедуру такого рода написать никак нельзя потому, что мы обязаны именовать соответствующие формальные параметры, приписывать им тип данных, это очевидно  тип данных массив. Атрибут длина статический, следовательно, эта процедура будет считать скалярное произведение только для массивов фиксированной длины. Для массивов другого типа нужно переписывать соответствующую процедуру. Это самый большой недостаток языка Паскаль. Ведь массив – это базисная структура для любых видов программирования. В чем приимущества такого подхода , почему Вирт его использовал? Во-первых, очень высокая эффективность, и это конечно главное, а во-вторых – надежность. Т.е., с одной стороны, чем больше атрибутов мы связываем с ТД, тем больше надежность и эффективность языка, поскольку все проверки, связанные с типом данных, в таких языках как Паскаль осуществляются статически, поэтому и эффективность высока и надежность высока, но страдает гибкость программирования, поэтому надо что-то ослаблять.

2) Посмотрим производные языка Паскаль, та же самая Модула 2. Там с одной стороны сохранены свойства языка Паскаль, что атрибут длина является статическим и свойством ТД. Но с другой стороны существуют ОД, а именно формальный параметр, у которого этот атрибут является квазистатическим, а именно: от вызова к вызову длина формального параметра может меняться, но на протяжении вызова она остается постоянной. Это пример квазистатического атрибута, т.е. некоторая форма динамизма, для особого вида ОД, а именно для формальных параметров. В частности в Модуле 2 и в языке Оберон введено понятие параметра-массива - открытого массива.

      PROCEDURE P(X:ARRAY OF T);

      По правилам Паскаля мы обязаны были здесь указать имя соответствующего типа. Но в                                                     случае формального параметра – массива можно написать ARRAY OF T .  Соответствующим фактическим параметром может быть любой массив, элементы которого имеют тип данных Т, т.е. массив любой длинны, главное, чтобы у него базисный элемент был типа данных Т. У этого массива есть специальный атрибут длинна, есть специальная псевдофункция HIGH(X), которая дает максимальный размер индекса по его измерению. Очевидно, что теперь мы можем обрабатывать элементы этого массива, используя цикл с фиксированным числом повторений и конструкцию HIGH(X). Это значительно снижает проблему гибкости. Но такой подход всетаки черезмерно статический. Т.е. решается одна частная проблема - то, что можно писать универсальные процедуры обработки массивов, которые не привязываются к длинне соответствующего массива. При этом очевидно, что  с соответствующим формальным параметром компилятор должен одновременно передавать его длинну, доступ к которой имеется с помощью функции HIGH.

  Но существует другая проблема: если у нас есть 2 массива одинаковой длинны, но у них различные диапазоны (например, один массив у нас описан от 0 до 9, второй от 1 до 10), можно ли считать, что это массивы одного типа или это массивы разных типов? Язык Модула 2 отвечает однозначно – это массивы разных типов. Их присваивать друг другу нельзя, но с другой стороны их можно передавать в качестве формальных параметров. Здесь есть некоторая концептуальная нестыковка. Т.е. если эти объекты разных типов, тогда их вообще никак смешивать нельзя, в часности нельзя вызывать процедуру  р с параметром одного типа а потом процедуру р с параметром другого типа. Это противоречие. Это некоторая концептуальная проблема, в языке Оберон, кстати, эта проблема снята очень просто, а именно - в языке Оберон, как и во многих других языках, и это вообще характерно для всех современных ЯП, отказались от того, что у нас произвольный диапазон индексов. Если в таких языках как Паскаль, Модула 2, Ада можно было брать массив у которого произвольный дискретный диапазон индексов.  В качестве индексного типа может выступать любой дискретный тип, например перечислимый. В современных языках такую свободу сочли абсолютно излишней. В современных ЯП в качестве индексного типа данных выступает целый тип данных integer, а в качестве нижней границы всегда выступает 0. В результате единственной характеристикой массива является число элементов в этом массиве N и, соответственно, максимальный индекс - это N-1. И, судя по тому, что эта точка зрения принята во всех современных ЯП, которые мы обсуждаем, это не является существенным недостатком с точки зрения программирования. А достоинство – очень высокая эффективность обращения. Но в тоже время в языках, которые допускают большую гибкость, а именно: допускают произвольный дискретный тип в качестве индексного, произвольную левую и правую границы, проблема о совместимости массивов одной длинны, но с разными диапазонами остается.

3) Наиболее радикально эта проблема решена в языке Ада. В языке Ада, вообще говоря, наиболее развитое понятие массива. Массивы языка Ада безусловно самые мощные массивы из тех, которые мы рассматриваем. С одной стороны атрибут длина статический атрибут, а с другой стороны в языке Ада он является свойством ОД. Он связан с ОД, и для ОД он статический, а для ТД он может быть как статическим, так и квазистатическим. В Аде с одной стороны введено понятие неограниченного типа данных, а с другой стороны понятие типов и подтипов, которые мы уже обсуждали, когда обсуждали простые типы данных. Что в Аде можно параметризовать в массиве? Прежде всего длину.

  В Аде разрешено общее объявление массива, которое похоже на объявление массива в Паскале(ограничимся пока одномерными массивами):

type ARR is array(IndexType range L..R) of T;

Сразу видна особенность языка Ада. Есть только 2 языка, в которых особая форма индексирования – через ( ), это FORTRAN и Ада. Во всех остальных языках индексирование через [ ]. Обратим внимание, что это очень похоже на конструкцию языка Паскаль, только в Аде диапазоны более общего вида. Здесь мы указываем какой-то дискретный тип данных и сразу же должны указать его диапазон. Если у нас индексный тип данных INTEGER, то он может быть опущен вместе с ключевым словом range. Поэтому, в принципе, можно написать:

type ARR is array(L..R) of T;

где L, R должны быть константными выражениями соответствующего типа. Т.е. это решение в стиле языка Паскаль, и можно программировать в стиле языка Паскаль, когда у вас атрибут длина является атрибутом типа, и любой ОД наследует этот атрибут, но теряется гибкость. Поэтому введено понятие неограниченного массива.

type TArr is array(Index range <>) of T;

Спрашивается, что с таким типом данных можно делать? Во-первых, объявление

X:TArr;

не подходит, компилятор на этом месте выдаст ошибку, потому что компилятор не может отвести память под переменную такого типа. Потому такое объявление переменной является ошибочным. Спрашивается: а зачем нужен соответствующий ТД? Очень просто, объекты этого ТД не могут быть переменными, но могут быть формальными параметрами процедур. В результате это похоже на концепцию открытого массива, вот только она расширена за счет того, что в ТД входит базовый индексный тип и, если для открытого массива в таких языках как Оберон и Модула 2 считается, что индексы его элементов от 0 до HIGH(X), то здесь можно устраивать произвольный диапазон индексов. А именно: у нас у любого ОД типа массив есть специальные атрибуты. К атрибуту длинна в языке Ада можно обращаться явно

X'Length

В качестве Х может выступать объект типа массив и может выступать ТД типа массив. Если у нас есть процедура, у которой формальный параметр описан как неограниченный массив

procedure P(X:TArr)

то мы знаем длину Х, кроме этого есть атрибуты X'FIRST –нижняя граница, X'LAST – верхняя граница и X'Range, который выдает соответствующий диапазон. Мы например можем писать так:

begin

 for i in X'Range loop

здесь мы можем ссылаться на X(i).

X'Range =  range X'FIRST . . X'LAST

Разумеется мы явным образом с помощью X'FIRST можем ссылаться на нижнюю границу. И в результате мы вполне можем работать с элементами этого массива. Соответствующим фактическим параметром может быть любой массив типа TArr. Мы можем объявлять объекты этого типа в случае, если мы их уточним. Объявление вида

X: TArr(0..10);

корректно. Здесь сразу же в скобках  после имени типа мы уточняем диапазон индексов. Теперь компилятор знает соответствующий диапазон индексов. Теперь это переменная типа TArr, которую можно передавать как формальный параметр. Мы можем описать

Y: TArr(1..11);

Z: TArr(0..20);

Теперь возникает вопрос: являются ли переменные X, Y, Z переменными одного типа? Ответ – да, являются, поскольку имя типа у них одинаковое. Поэтому их и можно передавать в процедуру Р. Но естественно, что атрибуты Length, FIRST, LAST для всех этих ОД зафиксированы в момент объявления. Т.е. атрибуты  Length, FIRST, LAST, Range являются статическими атрибутами для ОД. Но для неограниченных типов, объектами которых могут быть формальные параметры, соответствующие атрибуты  Length, FIRST, LAST являются квазистатическими потому, что естественно атрибуты Length, FIRST, LAST не меняются в ходе выполнения процедуры P , но от вызова к вызову они меняются. И они являются нестатическими, но они и не настоящими динамические – типичный пример квазистатического атрибута, т.е. атрибут, который сохраняет свое значение на период выполнения некоторого участка программы. В результате у нас имеется полная гибкость работы с массивом. И, кстати, решена еще одна проблема, а именно коль скоро переменные X, Y и Z относятся к одному типу, следовательно, их вроде бы можно присваивать. Как интерпретировать присваивание

Z:=X;

И вот тут как раз приходит на помощь концепция типов и подтипов. Считается, что все переменные, которые описаны, как переменные неограниченного типа, но с указанием ограничения, относятся к разным подтипам одного и того же типа. Данные одного типа можно присваивать друг другу, но если данные относятся к разным подтипам, то тогда при присваивании происходит квазистатический контроль. Это мы обсуждали, когда обсуждали концепцию диапазона. Если диапазон объявлен как подтип какого-то базового дискретного типа, то объекты двух диапазонов можно друг другу присваивать – они же относятся к одному ТД, но при этом происходит квазистатический контроль, т.е. компилятор подставляет проверки, которые проверяют корректность этого присваивания.

То же самое верно и для массивов, т.е. с точки зрения компилятора в таком присваивании проблем нет – это один и тот же тип, но при этом компилятор проверяет: совпадают ли у них диапазоны. Правило следующее: если ОД массива относятся к одному ТД, тогда присваивание разрешено, но только для массивов, которые совпадают по атрибуту длина. Следовательно, объекту Х можно присваивать объект Y, и наоборот, поскольку они имеют одну и ту же длину, а конкретно 11 элементов. А вот Z присваивать Y, и наоборот, Y присваивать Z нельзя. При этом, поскольку речь идет об ОД - переменных, а у переменных атрибут длина статический, то компилятор может его проверить. Однако, если у нас есть процедура Р, у которой  два параметра

procedure P(p1, p2: inout TArr)

Спрашивается: можно ли присваивать эти массивы?

begin

p1:=p2;

Ответ: поскольку они принадлежат одному и тому же типу данных, эти объекты присваивать друг другу можно. Поскольку они могут относиться к разным подтипам, и компилятор не знает к каким именно подтипам они относятся, он вставит здесь квазистатическую проверку, что если длина р1 = длине р2, тогда присваивание проходит, в противном случае - нет. Это та же самая квазистатическая проверка, которая имеет место при присваивании переменных диапазонного типа данных. Все то же самое. Но с другой стороны проблемы и надежности и гибкости в языке Ада решены. Более того, создатели языка Ада даже несколько расширили концепцию массива, а именно: добавили понятие динамического массива, который правда, с точки зрения нашей терминологии, динамическим не является. Настоящие динамические массивы – это массивы в таких языках как Java или C#. А то, что называется  динамическим массивом в языке Ада – это частный случай понятия, а именно мы говорили о том, что у всех ОД в языке Ада атрибут длина и связанные с ним атрибуты обязаны быть статическими. Они могут быть объектами неограниченного типа данных, но при этом мы обязаны указывать конкретное уточнение с помощью константных выражений. Для локальных массивов допускается, чтобы атрибут длина был динамическим. Иначе говоря, мы можем написать какую-то процедуру, у которой есть формальный параметр N типа INTEGER, и объявив в теле А локальную переменную х

procedure A(N: INTEGER)

 x: array(1..N) of T

 begin

 end A;

Здесь, как мы видим, N не является константным выражением. Следовательно, соответствующий атрибут будет динамический, а если уточнить – квазистатический потому, что естественно этот атрибут вычисляется, но при этом внутри блока тела процедуры А он является неизменным. Т.е. это чистый квазистатический атрибут. Для таких массивов левая и правая границы не обязаны быть константными выражениями. И это сделано опять же для улучшения гибкости программирования, ну а на машинах с современной стековой архитектурой накладные расходы на реализацию квазистатических массивов невелики. В результате у нас появляется, с одной стороны, надежная схема, которая позволяет обеспечить полный контроль за целостностью массива, а с другой стороны, является достаточно гибкой. В результате само понятие массива в языке Ада выглядит весьма наворочено.

4)  Современные ЯП применяют другой подход. Настоящие динамические массивы (ненастоящие динамические массивы – это массивы в языке Ада, которые на самом деле являются квазистатическими) имеются в языках C#, Java. И, кстати, понятием динамического массива в стиле Java, C# наряду с массивом из Турбо Паскаль обладает также язык Delphi. В этих языках длина является динамическим атрибутом ОД. Т.е. с каждым массивом хранится его длина. Как говорилось ранее, все массивы от 0 до N-1, поэтому никаких других атрибутов, кроме длины массива с ним хранить не надо. Как уже было сказано, массивы в этих языках ссылочные. Все ссылочные объекты реально размещаются в динамической памяти, и поэтому это настоящие динамические массивы. Они и в динамической памяти размещаются и их длина может постоянно меняться. Например на языке C# вполне  можно написать

char[]s= new char[10];

потом соответственно описать

char[]s1= new char[20];

поработать с элементами этого массива, например

s[0]= s1[1];

ну и поработав можно сделать так:

s1= s;

В результате этого присваивания динамически меняется соответствующий атрибут. Таким образом эти массивы, считается, принадлежат одному типу, поскольку длина является атрибутом динамическим. Более того, синтаксически объекты массивов ведут себя как элементы какого-то класса, псевдокласса массивов. И у этого класса, например, можно всегда запросить соответствующий атрибут длина с помощью функции s1.Length(). Ну и более того, в этих языках можно в любой момент поменять длину с помощью функции

s1.SetLength(50);

Это похоже на массивы, скажем, языка С и языка С++, которые мы отводим в динамической памяти используя постоянно процедуру realloc, т.е. это действительно настоящие динамические массивы. Но как следствие они имеют ссылочную структуру, поэтому присваивание

s1:= s2;

на самом деле не копирование значения массива, а копирование ссылок. Т.е. если до этого у нас было:

                  s1     s2

 

      

                               

              

            

                

               

s1 ссылалось на один массив, s2 на другой массив символьных значений, то после присваивания

                   s1     s2

 

      

                              

               

            

                

               

Если на этот массив никто больше не указывал, то тогда этот массив помечается как удаленный, и уйдет из динамической памяти тогда, когда менеджер динамической памяти сочтет это нужным. В случае, если нам нужно действительно копировать объект массива, то у нас есть специальные методы. В C# есть метод, который побитово копирует одну структуру в другую, создавая новые элементы этой структуры, а в языке Java есть метод, который называется

s.Clone();       

и в частности для любых объектов, не только для массивов, но и для объектов любого типа метод Clone отводит новую копию объекта s. Подробнее к проблеме копирования мы вернемся когда будем обсуждать концепцию класса, а сейчас просто отметим, что программист, который начинает работать на языках Java, Delphi и C# должен представлять себе ссылочную природу массивов: присваивание одного массива другому – это просто присваивание ссылок, никакого реального копирования информации не происходит.

  Осталось только обсудить несколько дополнительных проблем, связанных с массивами. Какие еще навороты могут быть связаны с  массивами? Т.е. первая проблема – это проблема самая главная – динамичности атрибута длина, которую мы как раз и обсуждали. Вторая проблема – это проблема индексов. Как мы видим современная тенденция такова, что атрибут длина становится полностью динамическим, а индексы, наоборот, упрощаются, от 0 до N-1.

  И еще одна проблема, связанная с массивами – это многомерные массивы. Обычно многомерные массивы - это все равно сплошной кусок памяти, в котором массивы размешаются по строкам. Единственный язык, который не соблюдает этого правила, в котором массивы в памяти расположены по столбцам, это язык Фортран. Создатели Фортрана почему-то решили размещать по столбцам, но во всех остальных ЯП без исключения применяется вот такая схема

                                                      

                                                      

Ну и связанная с многомерными массивами проблема – это проблема так называемых сечений, а именно если у нас есть подобного рода квадратная матрица

                                                        

                               

                     

                         

можем ли мы, вычленить из этой матрицы подматрицу? Например, можем ли мы взять отдельно строку этой матрицы, или столбец этой матрицы или какой-то прямоугольный элемент этой матрицы. Вот с точки зрения сечений из языков которые мы рассматривали, сечения в ограниченном виде существуют только в языке Ада. В Аде можно взять непрерывное сечение одномерного объекта. Если у нас массив описан А(0..10) мы можем написать нечто типа А(2..5) и это будет подмассив. Считается, что он является подтипом типа А, с такой уменьшенной длиной. Это частный случай сечения. Более мощные сечения есть в языке Фортран 90. Т.е. в Фортран 90 можно вычислять произвольные прямоугольные сечения, причем в массивах произвольной размерности. Т.е. можно из куба, например, выделить некоторое прямоугольное подмножество, но понятно, что это связано прежде всего с накладными расходами. Но т.к. основная структура данных в языке Фортране 90 - это массив, то создатели Фортрана 90 проигнорировали в данном случае проблему с эффективностью во имя удобства. В других же ЯП сечений как таковых нет.

  Ну и еще один наворот, связанный с языком C#, а именно: там возможны как обычные многомерные массивы, так и так называемые ступенчатые массивы. Просто одномерный массив на языке C# мы обязаны проинициализировать

int[ ] a= new int[20];

Вот пример двумерного массива

int[ , ] x= new int[3,3];

"," указывает на то, что у него есть 2 измерения. Это значит, что отводится 9 элементов строчно по 3 элемента в виде единого блока памяти. Что такое ступенчатый массив? Ступенчатый массив - это на самом деле одномерный массив элементами которого являются ссылки на другие массивы. Т.е. конструкция

int[ ][ ] a= new int[ ][3];

это и есть ступенчатый массив. Ступенчатый массив выглядит следующим образом

                                                                           

                                                                            

                              

                         

                              

                             

Мы отводим 3 элемента, каждый из которых является указателем на массив. В начале после этого new все указатели нулевые. Но после этого мы можем писать

a[0]= new int[10];

У нас теперь а[0] указывает на массив длины 10.

                                                                           

                                                                            

                              

                         

                              

                             

              

           

             

            

               

           

                

          

a[1]= new int[5];

a[2]= new int[8];

У нас получился действительно ступенчатый массив

                                                                          

                                                                            

                              

                          

                              

                             

             

           

             

Рекомендуем посмотреть лекцию "2 - Информация и ее свойства".

            

               

           

                

          

Таким образом можно моделировать массивы с переменной размерностью. Например треугольные матрицы, и подобного рода структуры данных. Спрашивается: является ли такая конструкция абсолютно необходимой? Ответ – нет. В качестве упражнения можете подумать как та же самая вещь может быть описана на языке Java. Поскольку ступенчатый массив – это есть массив ссылок на массивы, такой же тип спокойно можно написать на языке Java. Т.е. эти ступенчатые массивы просто еще одно средство некоторой перенавороченности языка C#.

Свежие статьи
Популярно сейчас
Как Вы думаете, сколько людей до Вас делали точно такое же задание? 99% студентов выполняют точно такие же задания, как и их предшественники год назад. Найдите нужный учебный материал на СтудИзбе!
Ответы на популярные вопросы
Да! Наши авторы собирают и выкладывают те работы, которые сдаются в Вашем учебном заведении ежегодно и уже проверены преподавателями.
Да! У нас любой человек может выложить любую учебную работу и зарабатывать на её продажах! Но каждый учебный материал публикуется только после тщательной проверки администрацией.
Вернём деньги! А если быть более точными, то автору даётся немного времени на исправление, а если не исправит или выйдет время, то вернём деньги в полном объёме!
Да! На равне с готовыми студенческими работами у нас продаются услуги. Цены на услуги видны сразу, то есть Вам нужно только указать параметры и сразу можно оплачивать.
Отзывы студентов
Ставлю 10/10
Все нравится, очень удобный сайт, помогает в учебе. Кроме этого, можно заработать самому, выставляя готовые учебные материалы на продажу здесь. Рейтинги и отзывы на преподавателей очень помогают сориентироваться в начале нового семестра. Спасибо за такую функцию. Ставлю максимальную оценку.
Лучшая платформа для успешной сдачи сессии
Познакомился со СтудИзбой благодаря своему другу, очень нравится интерфейс, количество доступных файлов, цена, в общем, все прекрасно. Даже сам продаю какие-то свои работы.
Студизба ван лав ❤
Очень офигенный сайт для студентов. Много полезных учебных материалов. Пользуюсь студизбой с октября 2021 года. Серьёзных нареканий нет. Хотелось бы, что бы ввели подписочную модель и сделали материалы дешевле 300 рублей в рамках подписки бесплатными.
Отличный сайт
Лично меня всё устраивает - и покупка, и продажа; и цены, и возможность предпросмотра куска файла, и обилие бесплатных файлов (в подборках по авторам, читай, ВУЗам и факультетам). Есть определённые баги, но всё решаемо, да и администраторы реагируют в течение суток.
Маленький отзыв о большом помощнике!
Студизба спасает в те моменты, когда сроки горят, а работ накопилось достаточно. Довольно удобный сайт с простой навигацией и огромным количеством материалов.
Студ. Изба как крупнейший сборник работ для студентов
Тут дофига бывает всего полезного. Печально, что бывают предметы по которым даже одного бесплатного решения нет, но это скорее вопрос к студентам. В остальном всё здорово.
Спасательный островок
Если уже не успеваешь разобраться или застрял на каком-то задание поможет тебе быстро и недорого решить твою проблему.
Всё и так отлично
Всё очень удобно. Особенно круто, что есть система бонусов и можно выводить остатки денег. Очень много качественных бесплатных файлов.
Отзыв о системе "Студизба"
Отличная платформа для распространения работ, востребованных студентами. Хорошо налаженная и качественная работа сайта, огромная база заданий и аудитория.
Отличный помощник
Отличный сайт с кучей полезных файлов, позволяющий найти много методичек / учебников / отзывов о вузах и преподователях.
Отлично помогает студентам в любой момент для решения трудных и незамедлительных задач
Хотелось бы больше конкретной информации о преподавателях. А так в принципе хороший сайт, всегда им пользуюсь и ни разу не было желания прекратить. Хороший сайт для помощи студентам, удобный и приятный интерфейс. Из недостатков можно выделить только отсутствия небольшого количества файлов.
Спасибо за шикарный сайт
Великолепный сайт на котором студент за не большие деньги может найти помощь с дз, проектами курсовыми, лабораторными, а также узнать отзывы на преподавателей и бесплатно скачать пособия.
Популярные преподаватели
Добавляйте материалы
и зарабатывайте!
Продажи идут автоматически
5168
Авторов
на СтудИзбе
438
Средний доход
с одного платного файла
Обучение Подробнее