5669

Языки программирования. Курс лекций

Конспект

Информатика, кибернетика и программирование

Программа курса Языки программирования Основные понятия языков программирования: данные, операции и связывание. ОД(объект данных) - переменные, константы - абстрактное место, где могут храниться данные. Виды связывания: 1)...

Русский

2014-12-23

316 KB

27 чел.

I. Программа курса «Языки программирования»

1. Введение

Основные понятия языков программирования: данные, операции и

связывание.

ОД(объект данных) – переменные, константы – абстрактное место, где могут храниться данные.

Виды связывания:

1) Статическое – в момент трансляции, сборка(линковка) – с точки зрения ЯП.

2) Динамическое – во время работы программы - между ОД и его значением: V=30;

3) Квазистатическое (константы: const C = 20;)

Большинство императивных ЯП – статическое связывание.

Java Scrypt, php… – динамическое связывание. (в основном такие языки являются интерпретируемыми).

Ада 83: Любой ОД имеет тип.

В традиционных ЯП – любой ОД имеет единственный тип.

ООЯП – любой ОД имеет статический тип. (Base d;)

Однако некоторые ОД могут иметь динамический тип.

С++: указатели и ссылки.(Base* p;)

Определяющее вхождение исключений – это ловушка:

catch(T e) { … }

Происходит динамически (даже в статических ЯП – С++)

Статические проверки – проверки при компиляции программы. Проверяемые данные не меняются в различных вызовах функции (запусках программы)

A:array [1..100] of integer;

……….

a[10] = 10;   Статическая проверка 

Квазистатические проверки - проверки при выполнении. Квазистатическим они называются потому, что проверяемые данные могут меняться в различных вызовах  

функции (запусках программы). Поэтому эти проверки делаются именно при выполнении.  

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

a:array [1..100] of integer;

……….

a[n] = 10;  n не известна в момент компиляции. Если язык   поддерживает

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

В машинных языках нет квазистатических проверок. Поэтому нет и в Си.

В каких классах памяти могут размещаться данные в языках

программирования? В каких классах памяти размещаются объекты классов

языка С#?

Классы памяти:

• статическая;

• квазистатическая;

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

В языке C# объекты классов размещаются только в динамической памяти.

2. Базисные типы данных в языках программирования: простые и

составные типы данных, операции над ними

Арифметические типы данных: целые, плавающие, фиксированные.

Фиксация представления: Java, C#.

Диапазон: все остальные

Проблемы представления чисел и способы их решения в ЯП.

Беззнаковые.

Есть неявное приведение: С++ и С.

Приведение только явное: в Модуле-2 существовали два типа INTEGER и CARDINAL. И приведение одного в другой допускалось только явное (существовал специальный оператор для такого присваивания):

 I:INTEGER;  J:CARDINAL; I := INTEGER(J);

В Обероне существует лишь один беззнаковый тип byte(0..255)

Так в С# решили допускать только расширяющиеся преобразования (так как они безопасные). Обратные преобразования допускались только явно.

Ада: Они ввели обобщенные числовые типы данных. Объекты разных типов были несовместимы ни по какому множеству операций, но были совместимы объекты подтипов. То есть разные подтипы совместимы между собой и со своим предком.

Type Length is new integer;   

Type Width is new integer;

Length и Width – новые целочисленные типы данных, при этом их нельзя ни присваивать друг другу, ни сравнивать.  Однако можно делать преобразования явным.

Type Length in new integer range 0..MAXN.

В чем плюс  обобщенных  типов, так это что все ошибки обнаруживались на этапе компиляции.

Если требовались неявные преобразования, вводились подтипы.  

Sybtype t1 is t2 range 0..N.  

И тогда преобразования из T1 в T2 допускались.  При этом  компилятор сам выбирает

оптимальное представление для таких чисел. Теперь компилятор транслируя присваивание таких типов данных либо проверяет возможность такого присваивания на этапе трансляции, или же вставляет код по проверке допустимости такого присваивания (квазистатический контроль).  В случае ошибки выхода за границу в Ада возбуждается range error.
Символьные и логические типы данных

Aда: type Boolean is (False, True); // представление типа Boolean

p : Boolean := True;

...

if p then

 ...

end if;

C#, C, Java, Algol: bool p;

Кстати С единственный язык с неявным преобразование int в bool

Delphi, Pascal: var p:boolean 
Порядковые типы: диапазоны и перечисления. Особенности
 реализации перечислений в современных ЯП.

Диапазоны

Паскаль

   var  

       x: L..R;

Модула 2

   var

        x: [0..N];

        y: CARDINAL[0..N];

        i:INTEGER;

        j:CARDINAL;

   x := 0;          Проверки не будет

   x := i;           Вставка квазистатической проверки, если нельзя вычислить i

   x := y;          Проверки не будет.  

Ада 

   Type Diap is range 0..N;

   Type Pos is new INTEGER range 0..MAX_INT;    новый тип данных.

    ….

    I:integer;

    Subtype Natural is range 1..MAX_INT;

    J:Natural

    ………

    I := J;           Проверки не будет

    J := I;           В этой строчке будет проверка

Ни в одном из современных языков программирования нет типов-диапазонов, т.к. в современных языках чётко определены индексы массив:  0 <= i <= N-1, а основной областью применения диапазонов было именно задание типов индекса массива.
Перечисления

Паскаль:

 Type EnumTyp = (va1, …, valN);

Операции:

:=, =, <, >, <>, >=, <= - основаны на функциях succ(x), pred(x), ord(x) = 0…N-1 (Элементы упорядочены)

Модула-2

 type ET = (v1, …, vN);

   Преобразования :

       ord(x);       ET -> integer

       val(T,i);   ET -> integer. Либо выдаёт ошибку,  либо выдаёт значение val(ET,v2) = 1;

В Си-89 добавили такую конструкцию:
   enum ET(v0,…,vN);

которая эквивалентна последовательности строк

   #define v0 0;

   #define v1 1;

   ……..

В Си++ можно задавать значения константам перечисления

   Enum FileAccept

   {

        FileRead = 1;

        FileWrite = 2;

        FileReadWrite = FileRead | FileWrite;

   }

Удобство использования такое же, как и для int
Ада

Типы BOOLEAN и CHARACTER являются перечислимыми типами  данных из пакета  

STANDARD

Проблема неявного импорта:

   type SendColor is (Red,Yellow,Green)

   type BasicColor is (Red,Green,Blue)    

Ввели понятие «литерал» перечисления. Им является либо идентификатор, либо символ (‘символ’)

    type Latin is (‘A’, ‘B’,’C’,… ,’Z’)

    type ASCII Latin(‘A’, ‘B’,’C’,… ,’Z’, ‘a’, ’b’,…)

Литерал перечисления – функция без параметров, имя которой совпадает с литералом и

возвращает нужную константу

Решение проблемы неявного импорта:

Уточнение типа -  T’expr – выражение expr трактовать как выражение типа Т

   P(BasicColor’Red) – правильно!

Представление:

   for BasicColor use (Red => FF00000x, Green => FF00x, Blue =>FFx)

   for BasicColor’Size use 24; - 24- количество битов

«’» специальная операция, применимая именами типов, позволяющая получить доступ к

некоторым атрибутам

C#

По умолчанию базовый_целый_тип — это int.

Каждая константа в списке может быть инициализирована своим

значением (как в Си++).

    enum BasicColor

   {

        Red = 0xFF0000;

        Green = 0xFF00

        Blue = 0xFF

   };

   enum SendColor: Byte

   {

        Red;

        Yellow;

        Green;

    };
Преобразования из перечислимых типов в целочисленные в современных языках только

явные.   

Атрибуты – средства сообщать компилятору некоторую информацию о реализации типа.

[FLAGS] – указывает, что к элементам перечисления применимы побитовые операции | и &.

    [FLAGS]

   enum FileAcces

   {

        FileRead = 1;

        FileWrite = 2;

        FileReadWrite = FileRead | FileWrite;

   }

теперь, если вывести на экран FileAccept. FileReadWrite получим «FileRead, FileWrite». Без

использования атрибута [FLAGS] получим 3.

 С#, Java

Неявный импорт

Константы из перечислимого типа видимы только потенциально и при

обращении должны уточняться именем типа: тип.имя_константы.

Пример:

enum Color : long

{

Red,

Green = 50,

Blue

}

Color c = Color.Red

    FileAcces x;

   X = FileAcces.Read; //Уточнение перечисления

Для каждого типа-значения существует класс-обёртка

С#  int - Int32,bool – Boolean, long – Int64

Java  int – Integer,bool – Boolean, long – Long

В С# все обёртки находятся в .Net

Для всех перечислений имеется один класс-обёртка – «Enum»  всем перечислениям доступны методы класса Enum ToString(), GetValues() и т.д.

преобразования:

EnumType -> Integer  безопасно

Integer -> EnumType  Небезопасно. Нужны проверки.

uses (pascal, Ада) Вместе с перечислимым типом неявно импортируются все его константы на том же уровне видимости. Таким образом значения констант могут конфликтовать и перекрываться для разных перечислимых типов (в т.ч. из разных модулей).

Перечислимых типов нет: «Оберон»(1988) и «Оберон-2»(1993), Java (1995), но Java (2005)  - расширение Java, в том числе добавление перечислимых типов, оформленных в виде полноценных классов.

Ссылки и указатели. Управление памятью. Автоматическая сборка

мусора. Объектно-референциальная модель в современных ЯП.

Строгие

Стандартный Паскаль, Модула-2, Ада, Оберон (со сборкой)

 Pascal

   Type PT = ^T;     

{Modula-2 

     Type PT = pointer to T;}

var  

        i :T;

Инициализировать указатель можно только двумя способами – либо другим указателем,

либо выделением новой памяти NEW(p : PT). Поэтому все данные чётко разделяются на

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

Не строгие

C, C++, Turbo Pascal

Можно получать адрес любого объекта с помощью операции взятия адреса «&»

Существует абстрактный указатель «void *»

T * => void * автоматически

void * к  T * автоматически не приводится

   T * p;

   void *pp;

    pp = p;

    p = (T *)pp;
Проблемы строгих и нестрогих указателей:

•  Удаление памяти (Dispose(p : PT))  не в своё время, что приводит к появлению «висячих ссылок» -  указателей, которые должны на что-то указывать, но не указывают.

•  Накопление мусора – памяти, на которую не указывает ни один указатель.

Различают системы с динамической сборкой мусора и без таковой.

Строгий язык с динамической сборкой мусора довольно надёжен.

От висячих ссылок защиты нет

Ада

В чистой Аде есть только new(p). В модуле STANDARD есть UNCHECKED_DEALLOCATION(p) – подчёркивается небезопасность этой операции. Так процедура UNCHECKED_DEALLOCATION(P) является аналогом delete(p), dispose(p) в ЯП, где используется динамическая сборка мусора.

C#, Java - понятие указателя отсутствует (точнее в C# такое понятие есть, но только в небезопасных блоках unsafe)

X a; // Если в C#, Java – то объект является ссылкой и пока не существует.

a = new X(); //Вызов конструктора обязателен.

a = b; // Присваивание ссылок, а не копирование.

Ада 83:  

PT – указатель на тип T.

type PT is access T;

x: PT;

x := new T;

y: T; - Получить адрес у стандартными средствами нельзя.

Ада 95:

Если: type PT is access T;
То инициализация возможна только так:  

x: PT;

x := new T;

Если же: type PTT is access all T; (на все объекты типа Т)

xx: PTT;

xx := new T;

Однако можно ссылаться и на другие переменные:

z: aliased T;

zz: T;

z ’access – операция взятия адреса.

x := z’access; - нельзя, т.к. без aliased.

xx := z’access; -можно.

xx := x; - можно.

x := xx; - нельзя.

В современных ЯП ссылки – это средства доступа к объекту.

В C#, Java, Delphi – имеются референциальные ТД. “имена” = ссылки.

В С++ добавили отдельный базисный тип - ссылочный.

2. Назовите две причины большей надежности указателей языка Ада 83

по сравнению с указателями языка Си++.

Указатели языка Ада 83 ссылаются только на объекты из динамической памяти. Указатель языка Си++ может ссылаться на любой объект данных (динамический, локальный, статический), что может приводить к труднообнаружимым ошибкам.

Также в языке Ада отсутствует адресная арифметика (арифметические операции над указателями), что также уменьшает вероятность появления ошибки работы с памятью.

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

Массивы и их особенности в современных ЯП.

Последовательность однотипных элементов.

D x … x Dn

A[i] – операция индексирования. i – индексное выражение.

Атрибуты массива:

1)Базовый тип (тип элементов) – D

2)Тип индекса (i)

3)Диапазон индекса (длина)

В разных ЯП:

Связывание базового типа статически (везде).

Тип индекса – С/С++/С#/Java/Оберон – всегда тип int (статическое связывание).

Фиксируется нижняя граница значения индекса.

Длина – статическая и динамическая.

Динамическая  –  чисто-динамическая (можно изменить в любое время) и квазистатическая (значение получено динамически, но изменять нельзя).

Можно длину сделать статической (жестко), оттого сделали квазистатической.

T[] a = new T[N]; //0..N-1

Модула-2:  

Понятие открытого массива: зафиксирован базовый тип.

TYPE Arr = ARRAY Index of D;

TYPE Index = [1..N];

ARRAY of D;

Для типа CARDINALL:

Index = CARDINALL[0..N]; //В данном случае индекс задается статически.

//процедура суммирования элементов массива

PROCEDURE SUM (VAR A: ARRAY OF REAL): REAL;

//для любого открытого массива применима функция HIGH –она возвращает               //максимальное  значение индекса или -1.

VAR S: REAL; I: INTEGER;

BEGIN

       S:=0.0;

       FOR I:=0 TO HIGH(A) DO

           S:=S+A[I];

       END;

       RETURN S; 

END SUM;

Общий синтаксис объявления массива в Обероне 

TYPE Arr = ARRAY N OF D;

При таком объявлении N, очевидно, является константой, длина массива – статический атрибут, что позволяет максимально эффективно распределять память. Таким образом

  1.  распределение памяти отчасти чисто статическое
  2.  квазистатические атрибуты длины возможны в качестве формальных аргументов

А собственно память распределяется чисто статически.

Механизм типа-подтипа в Ада

Подтип может ограничивать значения базового типа.

Пример

Тип диапазон:

TYPE NAT IS NEW INTEGER RANGE 1..MAX_INT (NEW – означает новый тип)

X: NAT;  

Подтип:

SUBTYPE POS IS INTEGER RANGE 0..MAX_INT;

X: POS; I: INTEGER;
I:=X; X:=I ~ X:=POS(I);

Неограниченный тип массива.

D,I – зафиксированы.

L..R – не фиксируемые левые и правая границы.

Итак, в Ада существует 2 типа массивов:

  1.  регулярный ограниченный – тут фиксируется ВСЕ

TYPE TARR IS ARRAY INTEGER RANGE 0..N of REAL;

  1.  неограниченный регулярный тип

Разница между первым и вторым типами заключается во времени связывания атрибута длины.

Атрибуты регулярного типа:

A’LENGTH – длина массива

A’FIRST = L

A’LAST = R

A’RANGE = RANGE A.FIRST..A.LAST

У неограниченных типов данных – атрибуты динамические, у ограниченных – статические.

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

Зачем нужны неограниченные типы данных: они нужны для выведения из них других типов данных.

X1: TARR; //ограниченный тип данных

Таким образом, объявление переменной корректно, если компилятор может распределить память

X2: Arr; //Нельзя. Компилятор не может распределять память.

Надо: X2: Arr range 0..N;

Мы вполне можем написать функцию, к примеру, суммирующую все элементы неограниченного регулярного массива:

Function SUM(A: Arr)return real is

S: real := 0.0;

Begin

       for i in A’RANGE loop S:=S+A(i); end loop;  

 

       return S;

end SUM.

Смысл такой же: описав объект неограниченного типа, мы можем передавать в качестве фактического параметра конкретный объект, принадлежащий, естественно, ограниченному типу.

a:=SUM(X2);

Статически все подтипы одного типа совместимы между собой как объекты одного типа. Но при присваивании объекта одного типа другому компилятор вставляет квазистатический контроль.

В стандарте языка Ада принято решение: если длины массивов совпадают, то присваивание возможно, даже если диапазоны рознятся. 
Динамический массив (квазистатический).
 

Такой массив может быть только локальной переменной.

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

procedure P(N: integer) is

A: array(1..N) of real;

В языке Oberon есть разница между чисто статическими объектами данных и открытыми массивами. В C# и Java длина массива квазистатическая.

C#, Java:

T[] имя;

new имя[len];

int[] a = new int{1,2,3,4,5};  (С#)

В C# и Java a[i](выход за границы массива) контролируется квазистатически.

Многомерные массивы.

С, С++: int a[N1][N2];

C#: int [,] a = new a[N1,N2];

int a[ ][ ]; //ступенчатый массив, он же разрывный.

Элементы ступенчатого массива представляют собой ссылки на соответствующие объекты (массивы).

Пример инициализации подобного массива:

a = new int [ ] [3];

a[0] = new int[1];

a[1] = new int[2];

a[2] = new int[3];

Вырезка: подмножество элементов массива.

Фортран:

А(N1,N2)

А(1,*) – 1-я строчка.

А(*,1) – 1-й столбец.

A(2..5,*), A(1..3,2..4) – прямоугольная вырезка.

Ада поддерживает только одномерные непрерывные вырезки.

В остальных нет.

Замечание: в языках Оберон и Модула-2 длина формальных

параметров — открытых массивов является динамическим атрибутом. В

других случаях длина массива — статический атрибут. В Аде формальные параметры неограниченных типов-массивов также имеют динамический

атрибут-длину (равно как и динамические массивы-локальные переменные).

Записи. Недостатки системы типов в традиционных ЯП. Объединения

как средство преодоления этих недостатков. Проблемы, связанные с

объединениями.

struct name {поля}

record последовательность полей end;

Запись – это набор переменных, с которыми можно обращаться как с единым целым.  

Задавая набор полей, мы задаем запись( id – идентификатор поля, T – тип поля):

(id1, T1)

(id2, T2)

(idN, TN) 

Разные типы: тип класс обертка (упаковка, распаковка)

Java: отсутствует запись – она не нужна. В Обероне есть, но с другим смыслом.

С++: класс – обобщение структуры. (у структур в С собственное пространство имен)

Отличия:

1) Имена (структура может быть анонимной)

2) struct – public, class – private.

Delphi:  

Новое – класс.

Старое – record.
C#
: Типы – значения.

struct c {}

class F{}

С а = new C();

Отличия от класса:

1)память распределена под типы-значения (если локально – то память под структуры выделяется в стеке) 

2) все структуры неявно наследуются из класса Object, но сами наследоваться не могут.

Следует отметить одно из странных свойств структуры в языке С#: у нее нельзя переопределять конструктор по умолчанию. Сделано это было с целью увеличения эффективности языка C#, чтобы впоследствии сделать его моноязыком под .Net.

В случае, когда мы заводим массив из объектов классов, в силу того, что классы – это объекты референциального типа, мы получаем не массив из объектов, а массив из ссылок на объекты.

class Point { int x,y; }

Point[] pointArray = new Point[1000];

Для создания самих объектов необходимо для каждого i написать:

Pts[i]=new Point(); //Неоправданные затраты памяти и времени. Здесь лучше использовать

структуру.
Объединение типов
(или запись с вариантами) — это конструкция, объединяющая в один тип несколько различных структур (вариантов). Все варианты в объединении начинаются с одного адрес и занимают одну и ту же память.

Размеченное объединение типов содержит одно выделенное поле (дискретного типа данных) — общее для всех вариантов. Такое поле называется дискриминантом. Значение дискриминанта определяет, по какому варианту выделена память в переменной-экземпляре

размеченного объединения

Запись с вариантами: эквивалентна по мощности, и более синтаксически наглядна. Есть размеченные и неразмеченные объединения. Есть объект типа Т1, Т2. Union просто позволяет их адресовать с одного места памяти:

(C)

union {

 T1 t1;

 T2 t2;

 ...

 Tn tn;

};

При этом программисты на C/C++ постоянно вынуждены делать однообразные структуры, и потом switch по соответствующему типу, который идентифицируется идентификатором в начале каждого типа.

В Pascal — вариантная часть:

(Pascal)

case  ... T of

 v1: часть 1;

 v2: часть 2;

 ...

 vN: часть N;

end;

(Pascal)

case Boolean of

 false: (i: integer);

 true: (b: packed array [1..n] of  boolean)

end;

Дискриминант отсутствует.

(Pascal)

case family : integer of

 SOCKADDR_UN: (...);

 SOCKADDR_IN: (...);

end;

family — специальное поле. Если дискриминант описан, то он принадлежит постоянной части, но идейно – вариантной.

Выделение памяти, расширенный синтаксис:

(Pascal)

new(p,t1,..,tn);

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

Запись с вариантами решает очень важную проблему объединения типов. Ни одна графическая система не обходится без записей с вариантами.

Неограниченная запись — запись с дискриминантом, параметризованная запись.

В Ada не бывает неразмеченных объединений вообще.

(Ada)

type SOCKADDR(Family : SAType) is record

 case Family: SAType of

   when SOCKADDR_IN => ...

   when SOCKADDR_UN => ...

   when others => ...

 end case

end record;

В момент размещения в памяти всё должно быть известно (аналогично динамические массивы: P : TArr нельзя, P : Tarr[0..N] можно; Y : SOCKADDR нельзя, Y : SOCKADDR(SOCKADDR_IN) можно).

(Ada)

type Paddr is access SOCKADDR;

PA : Paddr;

PA := new Paddr(SOCKADDR_UN);

Резюме какого-то другого лектора таково: решение проблемы не лучшее.

(Ada)

procedure P(X: RecVal) is

begin

 case X.Dic... of

   when val1 => ...

   when val2 | val => ...

 end case

end P;

При изменении системы надо расширить тип и все участки, где используется тип данных. Беда. Надо перекомпилировать весь проект.

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

Страуструп не хотел до последнего включать RTTI, но так как каждые начали включать свои механизмы, пришлось их включить в целях унификации. Но это позволяет программированть свичами — источник ошибок.

Файлы

Вся языки, начиная с Фортран, Алгол и Cobol, имели встроенные средства ввода-вывода. Первый язык без встроенных средств ввода-вывода – .

Стандарт Pascal:

file of T;

Множества также впервые появились в Паскале.

Cтандартный базис операций на множествами:

•  set  of  T :  T – множество

•  a in T: принадлежит ли a множеству T?
•  * S1+S2:   объединение множеств

•  S1*S2 :   пересечение множеств

•  S1-S2 :   разность двух множеств

•  [x] + S:   добавить элемент х в множество

•  S - [x]:   вычесть элемент из множества
Modula
-2

SET OF T;

X: BITSET;

INCL(S, X)

EXCL(S, X)

Из Оберона SET OF T исчез. Там «обязан был быть» диапазон [1..K].

В С++ множество «ушло» в стандартную библиотеку языка(STL: map, set, multiset).

Java

Также есть set, map и multiset, реализованные в виде битовых шкал. Пользователь также может реализовывать свои алгоритмы.

Строки

В С++ понятие строки входит в стандартную библиотеку. В Delphi, C#, Java оно встроены в язык.

3. Операторный базис языков программирования. Управление

последовательностью вычислений

Понятие о структурном программировании. Разновидности

управляющих конструкций в современных языках программирования.

Иногда он еще называется блоком.

begin s1;  … ; sn end   [Паскаль]

{  ……………………  }  [Си, C++ и другие]

Не во всех языках был реализован составной оператор. Во многих языках(АДА, Модула 2, Оберон) отказались от понятия составного оператора, там группа операторов явно замыкается специальным оператором.

Например:

 IF (TRUE)  

   <OPERATOR1>

   <OPERATOR2>

     ...

   <OPERATORN>

   END

Условные операторы и многовариантные развилки. Циклы. Оператор

перехода, связанные с им проблемы и способы их решения в современных

ЯП.

IF

С,C++:

 if (B) then
   Operator1

 else     

   Operator2

 АДА:

 if B then

   S1;S2;S3;...

 End if

Оберон:

 IF B THEN

   S1;S2;S3;S4...

 ELSE

   S1;S2;S3;S4...

 END

Оператор выбора - дискретный случай.

в Паскале:

Case Expr of  

 список вариантов, Вариант имеет вид const: оператор;

End 

В чистом Паскале  нет else(default) константы.

В С, C++, Java, C#: 

switch (expr) of {

 case 1: ...  break;

   ...

 case n: ... break;

 default: ... break;

}   

break - указатель на переход на конец структуры, если его нет, то дальше выполнится следующий case. Если в С++, С, Java - break было писать не обязательно после каждого case, то в С# стало обязательным (ошибка компиляции). Если в С# мы хотим после завершения данного case перейти на следующий надо использовать оператор перехода.

Модула - 2:   

CASE expr OF

       Список значений1: s11; s12; … ; s1N |

       Список значений2: s21; s22; … ; s2N |

      ….еще списки значений

       ELSE ……

  END

Ввиду возникающей синтаксической неоднозначности со списком значений вводится спецсимвол – вертикальная черта |

Ада:

Case expr of  

   when <список констант иди диапазонов> => оператор1

   ...

   when <список констант иди диапазонов> => операторN
   
when others => операторы

End case;

Операторы циклы.

Выделяют 4 вида цикла:

 1. Пока  

   While B loop .. End loop  (ADA)

   WHILE B DO .. END (Modula - 2)

   While (B) S(C, C++)

   While B do S(Pascal)

 2. До  

   REPEAT UNTIL B; (Modula - 2)

   do S while (B); (С, C++)

 3. FOR

for v:= r1 to t2 do S(Pascal).

for(e1; e2; e3) S; (C++, C)

в Java, C# аналогично С, только там на каждой итерации осуществляется квазистатический контроль, то есть A[i] - должен на самом деле существовать, чтобы   не вызвать ошибки.

 FOR V:= E1 TO E2[STEP E3(целое значение)] DO END(Модула - 2) - можно было задавать шаг. Он мог быть как отрицательным так и положительным. E1, E2 - типы, к которым применима операция сложения и вычитания.

В 1993 - вышел Оберон - 2,  который по идее является минимальным полным языком для написания любой программы,  куда вошел и цикл for.

   for v in <диапазон> loop .. end loop

   for i in A'RANGE loop S:= S + A(i); end loop

   for i in A'FIRST..A'LAST loop S:= S + A(i); end loop; (Ада)

    4. Бесконечный цикл

   LOOP  

     ...  IF B THEN ... EXIT

   END (Модула - 2)

 

   while (1) {... break...}

   for(;;) {... break ...}  (C++, C)

 

   Loop

       ...

       when B => exit 

       ...

   end loop (Ada)
goto
- перейти на помеченное место в программе. В Модуле - 2, Java.
Обероне
отсутствует.

Для организации не локальных переходов:

setjmp, longjmp – В Си++ используются для обработки ошибок.

throw, trace        – Обработка исключений

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

Lock(obj) {блок}    – Си#. Поток управления блокируется, если блок кем-то используется

accept, select – Ада 
Особенности реализации циклов-итераторов в современных ЯП.

Очень полезен. Когда мы ищем максимальный и минимальный элемент контейнера, например.

Представим себе массив:

int[ ] a = new int[N];

for(int i=0; i<a.Length; i++)

{

    Что-то делаем с a[i]………

}.

Не лучший способ записи. Результат может зависеть от порядка просмотра.

a[i] – это всегда некоторое вычисление. Что есть в C#, а в С не будет никогда? Конечно, проверка выхода за границу массива.

foreach(double v in a)

{

.............

};//управляющая переменная v просто последовательно принимает значение элементов массива.

Так как квазистатический контроль на каждой итерации цикла считается неэффективным в С#  был придуман еще один вариант цикла for - особая форма цикла.

   foreach (T o in C) S;

   int a[];

   foreach (int x in a) S = S + x;

   C - произвольная коллекция.
   тип
T должен наследоваться от IEnumerable, в которой входит такой метод, как получить следующий  элемент. В Java сей цикл был реализован в 2005 году.     

В общем случае контейнер языка C# - это класс, поддерживающий интерфейс IEnumerable. И компилятор будет обрабатывать конструкции подобных классов специфическим образом.

GetEnumerator(); //выдает iterator, нужный для пробегания по элементам, а не по индексам.

Также у класса поддерживающего интерфейс IEnumerable, есть свойства Current и метод MoveNext()

Создатели языка Java сделали еще метод hasNext().

Java

используется для поэлементного просмотра коллекций (цикл for-each). Она имеет вид;

for (T v:Coll)S

Здесь Coll — коллекция элементов (типа T или приводимых к типу T).

Переменная v на каждой итерации цикла принимает значение очередного элемента коллекции.

Для того, чтобы объекты класса-коллекции могли появляться в цикле for-each, класс должен реализовать интерфейс Iterable

4. Процедурные абстракции

Подпрограммы и сопрограммы. Операторы возврата и возобновления.

Подпрограммы

Почему «подпрограмма?» Управление входит в подпрограмму только через имя блока. И возвращается туда, где была вызвана подпрограмма, и ни в какую другую точку вернуться не может.

CALLER – вызывающий подпрограмму(надпрограмма)

CALLEE – вызываемая подпрограмма
Сопрограммы

Впервые механизм сопрограмм был придуман для компилятора COBOL.

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

Сопрограммы – фактически квазипараллельные процессы.

 Модула-2

Вызов сопрограммы аналогичен длинному переходу на некоторый абстрактный адрес, по которому находится команда сопрограммы, с которой нужно начать выполнение. Но, помимо этого, нужно ещё как-то запомнить адрес возврата и другую служебную информацию, передать входные параметры, наследуется часть контекста. Для этой цели Вирт в своём языке Модула-2 ввёл тип данных ADDRESS (то же самое, что и

«void *»).

Этот тип данных является потенциальной дырой в системе безопасности, т.к. любой указатель «Т *» автоматически приводится к «void *», и возможно обратное явное преобразование «Т *»  = (Т *)«void *».

Итак, в Модуле-2 вызов сопрограммы имеет такой вид:

NEWPROCESS(P, C,N);

Где P – процедура без параметров типа PROCEDURE, который является встроенным, C – переменная типа ADDRESS. N  -  размер области для «запоминания» информации.  Область  начинается с адреса C

PROCEDURE NEWPROCESS(P : PROCEDURE;  VAR C : ADDRESS; N : INTEGER);

Передача управления от одного процесса другому на уровне сопpогpамм осуществляется процедурой "Передать управление от процесса P1 процессу P2". В Модуле-2 эта процедура выглядела как  

PROCEDURE TRANSFER(VAR P1,P2 : ADDRESS);

 При этом в переменную P1 записывается запись реактивации этого процесса, а значение переменной P2 определяет запись активации процесса P2.  
RESUME; – оператор языка.

В современных языках сопрограммы трансформировались в понятие потока.

.Net    Thread      Квазипараллельный поток

C# 2.0:

   foreach(T x in C)

Тип T должен реализовывать интерфейс IEnumerable. Этот интерфейс содержит метод GetEnumerator(), который возвращает объект некоторого типа, который должен реализовывать интерфейс IEnumerator со свойствами Current, методом Reset и методом bool MoveNext(). Любой класс, поддерживающий интерфейс IEnumerable должен содержать класс, поддерживающий IEnumerator.

yield-операторы в  C# 2.0:

    yield return <expression>;

   yield break;

Итератор – процесс(сопрограмма), выдающий последовательно очередные элементы коллекции тогда, когда они понадобятся. yield-оператор используется в блоке итератора для предоставления значения объекта перечислителя или для сообщения о конце итерации. Т.е. это не простой «return» или «break», а оператор, совмещающий в себе дополнительно работу по переходу между сопрограммами (от процесса-итератора в

основной процесс). Выражение expression вычисляется и возвращается в виде значения объекту перечислителя; выражение expression должно неявно преобразовываться в тип результата итератора.

    Public class List
   {

       //using System.Collections;

       public static IEnumerable Power(int number, int exponent)

      {

           int counter = 0;

           int result = 1;

           while (counter++ < exponent)

           {

               result = result * number;

               yield return result;

           }

       }

 

       static void Main()

       {

          // Display powers of 2 up to the exponent 8:

           foreach (int i in Power(2, 8))

           {

              Console.Write("{0} ", i);

           }

       }

   }

   /* Output:

    2 4 8 16 32 64 128 256 */

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

Процедуры и функции в современных ЯП.

Передача параметров: семантика и способы реализации.

  •  Через глобальные данные
  •  Через параметры

Виды формальных параметров(семантика):

•  Входные (in)      – должны быть определены до входа

•  Выходные (out) – должны быть определены к моменту выхода

•  Вх/Вых(InOut)   – и то и другое  

Способ передачи – способ связывания фактических и формальных параметров:

•  По значению (семантика - in) (При вызове подпрограммы фактические параметры

копируются в Запись Активацаии)

•  По результату (семантика – out)  (При выходе из подпрограммы из записи активации

формальные параметры копируются в фактические)

•  По значению/результату (семантика – InOut) (При вызове подпрограммы фактические параметры копируются в Запись Активацаии, При выходе из подпрограммы из записи активации формальные параметры обратно копируются в фактические)

•  По адресу(по ссылке) (семантика - любая) (При передаче по Адресу в Запись активации

копируется адрес фактического параметры. Именование и разыменование происходят

автоматически )

•  По имени  

В Pascal 2 способа передачи параметров – по ссылке и по значению.

Если у параметра присутствует Var, то объект передается по адресу. А если не присутствует, то по значению.

 Ада83:

Квалификаторы: in, out, inout

    Procedure P(int X:T;inout Y:T;out Z:T);

X может быть выражением типа T. Компилятор может вставлять квазистатические проверки. Эффект процедуры – модификация Y и Z. Каков способ передачи определяет компилятор (что не есть хорошо, т.к. различные компиляторы в одной и той же ситуации могут выбрать разные способы передачи, что приведёт к различной работе программ).

Запись активации

…..

……

Место для формальных параметров

Энтропия – явление, при котором программа может выдавать различные результаты при одних и тех же исходных данных. Если в программе есть энтропия, то это очень плохо. Очевидно, что при программировании на Ада риск энтропии значительно повышается, т.к. не известно какой способ передачи выберет в этот раз компилятор

В Фортране обычно параметры передаются по адресу, но когда передаётся простой объект данных, чтобы не происходило лишних операций разыменования, можно передавать «по значению и результату»(   /<параметр>/)

•  В Ада-95 – по значению, по ссылке

•  В Си – не определяется семантика использования. Способ передачи только по значению

•  В Си++ – по значению, по адресу (ссылке). Контроль семантики: in – ссылка константная, out, inout – не константна

void f( T &); //Компилятор считает, что f будет менять значение => константный объект                               

//передавать нельзя

Это указание для компилятора, чтобы он следил за соблюдением семантики.
В
C#, Delphi(TurboPascal+объектно-ориентированная надстройка) – языках с референциальной моделью данных – все параметры автоматически передаются по ссылке.

Типы данных  C#:

  •  референциальные
  •  типы-значения
    •  примитивные типы данных (все передаются по значению)

типы данных структуры

•  С#

       void f( T x) {….;}

      ……  

      T a; // a – ссылка, если Т – объект

      f(a); // передаётся ссылка

Оба языка поддерживают идею примитивных типов (которые в C# являются подмножеством типов-значений — value types), и оба для трансляции примитивных типов в объектные обеспечивают их автоматическое «заворачивание» в объекты (boxing) и «разворачивание» (unboxing) (в Java — начиная с версии 1.5).

Тип-значение(value type) – тип, объекты которого передаются по значению. Если где-то нужен объект такого типа, то отводится место под сам объект. типами-значениями являются простые (примитивные) типы данных и структуры

Референциальный тип – тип, объекты которого передаются по ссылке. Если где-то нужен объект такого типа, то отводится место под адрес. Референциальными типами являются  классы (любой массив – наследник класса Array, строка это объект класса String, и т.д.)

В принципе, в C# можно передавать объекты простых типов в функции с помощью классов-обрёток, но C# также поддерживает явное описание передачи параметров по ссылке – ключевые слова ref и out. «ref» реализует семантику inout, а «out» реализует семантику out. При использовании out компилятор вставляет квазистатический контроль на наличие в методе присваивания значения, зато не требует инициализированность фактического параметра перед вызовом метода.

    void f(ref int x){x = -3;}

   ….

   int a = 0;

   f( ref a); // а будет передано по ссылке, если бы объект «а» был структурой, то он так же                          // передался бы по ссылке
Java:

Все передается по значению, но для классов в созданный объект копируется адрес (ну то есть реально это все равно передача по ссылке)

Структур в Java нет.  

Передача объектов примитивных типов в методы «как по ссылке» выполняется через классы-обёртки:

   void f(Integer X){…X = ….; }  

   …..

   int i = 1;

   Integer px = new Integer(i);

   f(px);

   i = px;

Integer – класс-обёртка для примитивного типа «int». Суть способа – преобразовать объект примитивного типа в объект класса и работать внутри функции с объектом класса.  

Алгол-60 

Передаём сам объект «как он есть» (по имени). Фактически передаётся его идентификатор как строчка. Далее в теле процедуры идентификатор формального параметра заменяется на идентификатор фактического.

Обоснование невозможности написание процедруры swap на Algol-60:

   procedure Swap(a,b); //a, b передаются по имени

   Begin

       T tmp;

        tmp := a; a:= b; b := tmp;

   End;
   ….

swap(i, A[i]);  

  T tmp;

   tmp := i;

   i := A[i];

  A[i] := tmp;// A [ A[i] ] := i; неправильно!!!

swap(A[i], i);

  T tmp;

   tmp := A[i];

   A[i] := i;

   i:= tmp;//  i := A[i] – всё правильно

Решение проблемы: С каждым параметром передавалась процедура thunk, которая выполнялась при каждом обращении к фактическому параметру.
В С++ можно задавать параметры по умолчанию: void f(int a = 4){ …;}

В C# эту возможность убрали. Вместо этого предлагается использовать перегрузку:

f(){ f(1, 2); }

f(int i ){ f(i , 2) ;}

f(int i,  int j ) { … ; }

Статический полиморфизм и перегрузка имен подпрограмм.

Подпрограммные типы данных. Проблемы, связанные с

подпрограммными типами данных и способы их решения в современных ЯП.

Ада 83, Java – нет.

Передача подпрограмм, как параметров.

Присваивание [:=]

Вычисление [()]

В Pascal введено 2 вида подпрограмм как формальных параметров: процедуры и функции. Аналогичный подход можно заметить и в других языках.

Значения подпрограммного типа данных – это набор констант, образующийся именами соответствующих процедур и функций.

Ада 83: generic не только для ТД, но и для процедур, функций.

//generic – параметризованный тип данных. Отметим, что параметризация в родовых сегментах происходит во время трансляции

с/с++: typedef void (*f)(int);

Отсюда, процедурный тип – указатель.

Проблема в том, что  в Ада 83 и Java отказались от указателей, т.е. и от П/П ТД.

//В Java целиком, в Ада частично.

Ада 95:

type Func_Pointer is access function (L,R: float) return Boolean;

function Compare (X,Y: float) return Boolean … end Compare;

F: Func_Pointer

F:=Compare’access

С++, Modula-2, Оберон

void f(int) – это прототип функции, объявление некоторой функциональной константы. Если перед ней поставить typedef, что имя функции станет синонимом нового типа:

Модула-2, Оберон:

TYPE FUNC_INT = PROCEDURE (L,R: REAL): BOOLEAN;

PROCEDURE COMPARE (X,Y: REAL): BOOLEAN;

VAR F: FUNC_INT;

F:=COMPARE;
C#:
Делегаты(расширение П/П ТД)

Операции делегата:

  •  присваивание (инициализация)(=)
  •  добавление делегата(+=)
  •  «убирание» делегата(-=)

Вызов (скобочки: ())

this хранится вместе с указателем на функцию. Делегатом может быть и статический и нестатический член класса.

1.Параметр функции – делегат.

delegate int Processor(int i);

Prcessor p = new Processor(func);

Толькo += или -=.
Механизм делегатов
инкапсулирует свойства процедурных типов данных, сводя к минимуму недостатки данного типа

Таким образом, делегат в С# - заменитель процедурного типа данных.

5. Определение новых типов данных. Логические модули. Классы.

Концепция уникальности типа в традиционных языках

программирования и понятие строгой типизации.

Чем по большому счёту отличаются традиционные ЯП от ОО. ОО опираются на традиционную, но немного отличаются. ОО отличаются понятием типа.

Понятие типа опирается на 4 аксиомы уникальности типа:

  1.  Каждый объект данных принадлежит единственному типу данных. - самое сильное ограничение традиционных ЯП
  2.  У каждого типа есть имя, для анонимных – неявное. Например, когда в Паскале описываем var A : array [1..N] of integer; 1..N – анаонимный тип. В Аде это два типа. Типы считаются различными, если их имена различны. Эта аксиома носит название именная эквивалентность типов
  3.  Каждая операция над типом данных имеет фиксированный тип и порядок операндов. Понятие типа характеризуется набором операций.
  4.  Разные типы данных несовместимы по операциям.

Все 4 аксиомы уточняют понятие строгой типизации. Чем строгая отличается от сильной, нигде не сказано. Аналогично со слабой типизацией. В любых языках со строгой или сильной типизацией выполняется 1 аксиома.  

2 аксиома. Рад языков от неё отходит. Например: Type T = T1. То есть, часто в ЯП вводится синонимия типов. В Си фактически typedef и есть определение синонима. Но если про неё забыть, то если типы не имеют синонимов, то они различны. В Аде пошли дальше: type T1 is new T . В некоторых ЯП существует структурная эквивалентность типов – типы эквивалентны, если их структура совпадает. Как только возникают ссылочные типы, то задача может стать алгоритмически неразрешимой.

Любой современный ЯП удовлетворяет двум аксиомам.

А вот от четвёртой аксиомы многие отступают. Например, операция присваивания. Слева и справа могут стоять разные типы. В Си правила преобразования сложны, в Си++ ещё сложнее. 4 аксиома ослабляется тем, что в ЯП есть неявные преобразования. 4 аксиома нарушается чаще всего за счёт неявных преобразований.

Язык Ада обладает наиболее строгой типизацией, так как удовлетворяет всем 4 аксиомам.

Какие преимущества несёт концепция уникальности ТД:

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

Проблемы:

1. Полиморфизм операции. 3 аксиома говорит, что каждый ТД характеризуется своим набором операций. Любой ЯП является полиморфным. Это справедливо в первую очередь для встроенных операций. Тип операции выводится из типов операндов. То есть все стандартные операции полиморфны. Современные ЯП решают проблему полиморфизма с помощью перекрытия операций. Там допускается делать пользовательские полиморфные операции (в J и D – нельзя перекрывать стандартные операции, в C# и C++ - можно).

Ада: есть overloading – перекрытие операции – статический полиморфизм, то есть выбор производится статически, во время компиляции. Это недостаток. Современные ЯП допускают динамический полиморфизм. Интересно, что в О-2 отсутствует статический, и присутствует динамический полиморфизм.

  1.  Янус проблема – объекты реального мира играют разные роли. Как решается проблема в традиционных ЯП – объединение типов.

И решение 1 проблемы перекрытием, и янус проблемы объединением, их проблема в том, что они статические. При этом отсутствует какой-либо динамический контроль.

Конструкции объявления новых типов данных. Тип данных как

множество значений и множество операций. Походы к определению новых

типов данных: модули и классы.

Контейнер  -  средство создания новых типов данных. В некоторых языках контейнером выступают модули (Модула – 2, Ада, С#(namespace), Java(package)), в других -  классы, структуры(С++, С). Delphi – гибрид, cодержащий как модули, так и классы

Понятие логического модуля. Использование модулей для определения

новых типов данных. Особенности понятия модуля в современных ЯП.

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

DEFINITION MODULE STACKS;

TYPE stack=RECORD

                         B: Array[0..N] of T;

                         TOP: [0..N];

                    END; //конец записи

PROCEDURE PUSH(VAR S: STACK; X: T);

PROCEDURE POP(VAR S:STACK; VAR X: T);

Пусть внутри модуля находятся процедуры

IsEmpty

IsFull

PROCEDURE Init(VAR S: stack);//(инициализация стека)

и переменная

VAR  done: Boolean;//сообщающая последний результат операции над стеком

END STACKS;

В модуле определений (модуле реализации) соответствующие процедуры должны быть полностью описаны.

Переменная Done говорит нам о том, выполнилась ли какая-то операция над стеком или нет. В чем недостаток? Дело в том, что мы вынуждены экспортировать переменную на полный доступ – а это не есть хорошо. Выход из данной ситуации:

PROCEDURE Init*(VAR S:stack);

VAR DONE * BOOLEAN    

PROCEDURE GetError(): Boolean;

Но тогда переменная DONE может быть испорчена. Функция Get Error, очевидно, должна возвращать значение переменной DONE. Но вызов функции – это нелокальный переход, и с точки зрения современной архитектуры он плох.

Импорт и экспорт имен. Видимость имен: непосредственная и

потенциальная. Управление видимостью. Области видимости и пространства

имен.

Модуль – набор взаимосвязанных ресурсов, которые служат для использования   других модулях.

Видимость  подразумевает возможность обращения с объектом,  то есть, к примеру, знаем его имя и тип. непосредственно видимы в глобальном пространстве имен только модули. Имена полей из модуля видимы только потенциально.

Модуль-ресурсы = ОД, ТД, П/П.

Интерфейс = определение ресурсов + реализация.
Пространство имен в ООЯП заменено на модули.

М-2:

1)Главный модуль //Один

2)Библиотечный модуль

3)Локальный модуль //параллельное программирование.

Глобальное пространство имен =>  видимость для всех

Непосредственная видимость: имя использует ASIS.

Потенциальная видимость: имя с уточнением.

DEFINITION MODULE имя;

Определение ресурсов.

END имя;

IMPLEMENTATION MODULE имя;
Реализация всех процедур/функций из DEF + дополнительные ресурсы.

END имя;
TP, DELPHI
:  

init имя;

interface

implementation

End имя;

Все имена экспортируются в глобальное пространство имен ПОТЕНЦИАЛЬНО.

IMPORT M; //Первые в модуле => клиент.

IMPORT InOut; //Видимы потенциально.

InOut.WriteString(“counter”);

InOut.WriteInt(out);

InOut.Writeln;

FROM InOut Import Writeln, WriteInt.

Видны непосредственно!

uses список имен; //видимы непосредственно.

Оберон: оставлен только библиотечный модуль.

MODULE M;

ENDM;

* - экспорт имени.

//имя *

MODULE ST

TYPE STACK* = …

PROCEDUR PUSH* (VAR S: STACK); … PROCEDURE P…

END. => псевдомодуль.

DEFINITION ST;
TYPE STACK = …

PROCEDURE PUSH = …

END st.  

=> IMPORT список_имен_модулей.//В Обероне только так.

=> ST.STACK //интерфейс.

Или FROM ST IMPORT R; => R//реализация.

Ада:  пакет(спецификация, тело)

Спецификация:

package M is 

Определение типов, переменных, констант, заголовков процедур

end M;

Тело:

package body M is

Реализация всех процедур и функций

end M;

package STANDART; //пакет стандартных имен.

Пользовательские пакеты встраиваются в STANDART.

Для подключения пакета используется оператор with: with <Имя пакета>. В случаях,

когда использование полной точечной нотации для доступа к ресурсам пакета обременительно, можно использовать инструкцию спецификатора использования контекста use. Это позволяет обращаться к ресурсам которые предоставляет данный пакет без использования полной точечной нотации, так, как будто они описаны непосредственно в этом же коде. Для обращения к функции из пакета используется оператор «.»: <имя пакета>.<имя функции>

Любой пакет можно вложить в другой.

STANDART

package M1 is

       package M1.2 is  

       …

end M1.2; end M1;

package M2 is

       package M2.1 is

       package M2.2 is  

       

end M2.2; end M2.1; end M2;
Тела вкладываются также, как и спецификации!

Более близкое описание скрывает менее близкое описание одинаковых имен. Как только возникает конфликт имен, имя становится видимым только через уточняющую форму, и непосредственная видимость исчезает.

Область действия любого имени начинается с его определения. Имена пакетов видимы непосредственно в той области, где они были объявлены.

В пакете М1 видимость обьектов из М12 потенциальная. А из внутренней области видимости мы имеем непосредственный доступ во все объемлющие структуры.

Неявный импорт: вместе с одним именем неявно импортируется другое.

переименование:

a renames b; //a теперь описано уже тут

Можно также переименовать операцию «+»:

function “+”(X, Y: vectors.Vector) return vector renames vectors;

Ада, Модула - 2 предоставляет программисту возможность осуществлять переименования. Переименование не создает нового пространства для данных. Оно просто создает новое имя для уже присутствующей сущности.

В С#         using

В Java      import имя_пакета;//ошибка-нельзя экспортировать пакет в Java!

                import имя_класса; //Ok!

                import имя_пакета.* ;//Ok! – мы импортируем все имена из пакета

В Pascal, Delphi      uses

В Оберон 2 переменную можно экспортировать только на чтение.

1.Сравните между собой конструкции "uses" в языке Delphi и "use" в

языке Ада (для чего применяются, сходства, отличия).

Конструкция языка Delphi «uses список_имен_модулей» служит для импорта всех имен, объявленных в интерфейсе модулей из списка. При этом импортированные имена становятся непосредственно видимыми (если нет конфликтов с именами из других модулей).

Конструкция языка Ада «use список_имен_пакетов» обеспечивает непосредственную видимость имен из спецификаций пакетов из списка (если нет конфликтов).

Сходство: конструкции обеспечивают непосредственную видимость имен из интерфейсов (спецификаций) при отсутствии конфликтов.

Различие: в Delphi «uses» импортирует имена из интерфейсов библиотечных модулей, в Аде импорт имен обеспечивается другими конструкциями, а «use» служит только для разрешения непосредственной видимости.

Модульность и технология программирования: проектирование

«сверху-вниз» и «снизу-вверх».

Существует два подхода к проектированию древовидной иерархии модулей:

  1.  сверху вниз(top-down- подход) - сначала проектируется модуль верхнего уровня, а затем мы опускаемся до более низких уровней.
  2.  снизу вверх(bottom-up- подход) – сначала проектируются самые нижние модули – они в данной иерархии инкапсулированы и ничего не знают о вышестоящих модулях. Подставляют виртуальные сервисы универсального характера, которые нужны всем.(P. S. Современные объектно-ориентированные системы в данном случае предлагают нам сетевые структуры.) на их базе строятся сервисные модули более высоких уровней – и так далее, пока не дойдем до единого главного модуля всей системы. Недостаток такого подхода: мы никогда точно не знаем, что нам понадобится в будущем.

Понятие класса. Класс как тип данных. Члены класса: функции,

данные. Статические и нестатические члены. Члены - вложенные классы.

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

Тип Данных = Структура Данных + Множество Операций над этими данными

В C#, Java всё является классами или находится в классах в качестве статических челнов.

синтаксис в C++.Java,C#: 

    class Name 

   {

        ….

        Определение членов класса

        …..

   }

В Си++ допускается вынесение определений, т.е. В Си++ можно члены класса лишь объявлять. В Java, C# все определения должны быть внутри класса

Java,C#,  

T x;

x = new T(«параметры конструктора»);

В первой строчке определяется ссылка на объект (выделяется память для хранения ссылки), место в динамической памяти под объект не отводится. Во второй непосредственно отводится место в динамической памяти («куче») для объекта и адрес

присваивается ссылке на объект.

C++.

T x;

T x(«параметры конструктора»);  

T x = T(«параметры конструктора»);

В этих определениях выделяется место не под ссылку на объект, а  под сам объект (не в динамической памяти). Чтобы выделить место в динамической памяти, нужно использовать операцию «new»
синтаксис в
Delphi:

    type T =  

   class (наследование)

       обявление членов класса

   end;

Члены класса:

•  Члены-данные

•  Члены-типв

•  Члены-функции (методы)

Чем члены-функции отличаются от обычных функций?

Такой функции при вызове всегда передаётся указатель «this»(в Delphi «self») на объект в памяти, от имени которого вызывается функция.
Dephpi

x : T

x – ссылка, её ещё надо проинициализировать.
В Delphi формальные параметры функций-членов находятся в той же области видимости, что и все остальные члены класса и, следовательно, не могут с ними совпадать.

В Delphi членов-типов нет.
Статические члены  

 SmallTank  class variable 

                    instance variable 

class variable – члены-данные класса, которые принадлежат всем экземплярам класса.

instance variable – принадлежат экземплярам класса, у экземпляра своя instance variable.

С точки зрении Си++ статические члены классов отличаются от глобальных только областью видимости.

    class T

   {
       ….  

       static int x;

       static void f();

       ……

   }

   …

   T t;

    t.x;//операция доступа

    T::x//операция разрешения видимости

     t.f();//операция доступа

    //или, что тоже самое

    T::f() //операция разрешения видимости

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

Видимость статических членов потенциальная и снять ее, в отличие от модулей, нельзя.

В C#,Java,Delphi обращение к статическим членам происходит только через тип класса.

В статических функциях нет ths/self => в них нельзя использовать нестатические члены класса, т.к. по умолчанию все обращения к нестатическим членам идут через указатель ths/self 

В C#, Java статические члены используются намного чаще, чем в Си++, Delphi, т.к. в C#, Java нет глобальных функций и переменных:

public class Name 

{

   ….

   public static void Main(string [] args)

   ….

}
Вложенные типы данных (классы)

    class Outer 

   {

       ….

        class Inner  //Inner – обычный класс, но с ограниченной областью                                                         

        {  //видимости  

             …..

        };

        ….

   };

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

Все классы являются статическими членами. Ко всем именам правила прав доступа применяются одинаково, т.е. специфика имени не участвует в разрешении прав доступа.

 C#

В C# имеется понятие «статического класса»

В C# статический класс – это особый вид класса, внутри которого есть только статические члены-функции и только статические члены-данные.

   static class Mod

   {

       public static void f () { ….;}

       public static int i;

   }

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

Нельзя создавать объекты статических классов. Статические классы подчёркивают их чисто модульную природу.

Без «static» - обычный класс в понятии Си++. На вложенность классов не влияет.

Статические члены – набор ресурсов, независимых от какого-либо экземпляра.

 Java
Статический импорт – импорт всех статических членов класса. Часто применяется к математическим функциям.

Статические классы в Java:

  public  class Outer 

   {

       ….

        public static class Inner    //Тоже самое, что и в C#, C++ без «static»                                                  

        {  

             …..   

        };

        ….

   };

Это сделано для того, что доступ к Inner был через Outer 

Декларируются внутри основного класса и обозначаются ключевым словом static. Не имеют доступа к членам внешнего класса за исключением статических. Может содержать статические поля, методы и классы, в отличие от других типов внутренних классов в языке Java.

Внутренние классы в Java: без «static»

Декларируются внутри основного класса. В отличие от статических внутренних классов, имеют доступ к членам внешнего класса, например «Outer.this». Не могут содержать определение (но могут наследовать) статических полей, методов и классов (кроме констант).
Понятие специальных функций-членов. Проблема инициализации

объектов и способы ее решения. Конструкторы, деструкторы, операторы

using и try-finally.

Функции-члены, обладающие семантикой обычных функций-членов, о которых компилятор имеет дополнительную информацию.

Конструктор – порождение, инициализация

Деструктор – уничтожение (В Java и C# не деструкторов, вместо это можно сделать собственный метод Destroy() )

У конструктора нет возвращаемого значения.  

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

X = new X(); // В Си++ X * a = new X;

 Синтаксис конструкторов и деструкторов:

 C++. C#, Java

class X

{

    X(«параметры»);// В С# и Java обязательно определение тела

}

 

Delphi

type X = class

   constructor Create;   // Имя конструктора произвольное

   destructor Destroy;   // имя деструктора произвольное

end;

…..

a:X;

….

a := X.Create;

В C++, C#, Java конструкторы не наследуются, но могут автоматически генерироваться компилятором по определённым правилам.

Классификация конструкторов:

1.Конструктор умолчания X();

2.Конструктор копирования X(X &); X(const X & );

3.Конструктор преобразования X(T); X(T &); X(const T &);

В классах из простанства имён System платформы .NetFramework не определены конструкторы копирования. В С++ автоматически могут генерироваться конструктор умолчания и конструктор копирования

Вместо этого, если это предусмотрено проектировщиками, имеется метод clone();

Java, C#, D – в базовом типе object есть конструктор Create и деструктор Destroy. Соответственно здесь ничего создавать не надо, они уже есть и наследуются (если).

Конструктор умолчания

X a; - подразумевается вызов конструктора по умолчанию (неявно).

X a(); - нельзя, т.к. это прототип функции.

X* px = new X; - нельзя в Java и С#, в С++ - можно.

X* px = new X();  //В C# и Java можно только так

Java, C#: Понятие КУ остается.

Есть понятие инициализация объектов:

class X 

{

       Z z = new Z(); // Z z; - значение неопределенно.

       Int I = 0; //простые инициализации можно выполнять непосредственно в коде самого класса

}

Вызов конструктора базового класса в Java может быть только первым оператором тела конструктора. Если первый оператор отличен от вызова super, то компилятор автоматически вставляет super();//вызов конструктора умолчания базового класса.

Пример на Java:

class A

{

public A(int I) { … }

...

}

class B extends A

{

public B() { super(0); … }

...

}

Пример на C#:

class A

{

public A(int I) { … }

...

}

class B : A

{

public B() : base(0) { … }

...

}

Существует статический конструктор, который вызывается 1 раз до первого использования и до первого обращения к любым членам класса. (С#)

static X() {…………};      //полная форма статического конструктора по умолчанию в языке C#

Java

static{…………….};        //аналог статического конструктора в Jav

M-2, Ада:

Init(); – явная инициализация.

Destroy();

Для определения собственного конструктора и деструктора достаточно было переопределить эти функции.

Конструктор копирования

С++

void f(X x); // передача фактического параметра функции по значению

X f() {return X();}

X a = b; //Вызов КК. ~ X a(b); «Инициализация присваиванием»

(1) X(X&);

(2) operator=(X&)

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

С#: object.

В классе Object(общего предка для всех классов), есть защищенный метод MemberwiseClone, возвращающий копию объекта.

По умолчанию копировать нельзя, но в произвольном классе можно самим переопределить.

ICLoneable ￿ Clone();

Java:

В этой точки зрения наиболее адекватно проблема решена в Javа. Там существует 4 уровня поддержки копирования.

Интерфейс-маркер – по определению пустой интерфейс(не содержит членов).

Интерфейс – это просто набор методов. Он определяет некий контракт, говорящий о том, что если класс поддерживает некий интерфейс, он должен реализовывать определенный набор методов. А если интерфейс пустой, то все его члены-методы «зашиты» в компилятор.

Интерфейс называется сloneable, когда он пустой.

В Java был введен пустой интерфейс cloneable, содержащий метод Clone(), осуществляющий побитовое поверхностное копирование.

protected object Clone();

Уровни поддержек:

  •  Полная поддержка копирования – возможность явной реализации. Класс X реализует интерфейс Cloneable:

Сlass X: Cloneable{

//Тут мы должны написать:

   public X Clone();

   //Допускается также:

   public Object Clone();

Метод Clone() может использовать любые члены класса(и приватные тоже.)

};

  •  Возможна и другая  ситуация: полный запрет копированияя: при попытке скопировать объект выбрасываем исключение. Подменяем соответсвующий защищенный метод clone():

class X{  

   protected Object Clone(){         throw CloneNotSupportedException;         }

   ………………………………….

};  

  •  Условная поддержка: элементы, которые копируются, могут быть под полным запретом.

Пример: коллекция умеет себя копировать, а элементы, из которых она состоит – нет.

class X: Cloneable{

  public X Clone throwing CloneNotSupportedException

  {

         //Для каждого элемента коллекции вызывается метод Clone();

  };

  …………………………………………………..

};

  •  Еще одна ситуация – когда мы не наследуем метод Clone()

Метод копирования нельзя переопределять.

D: inherited Create; //inherited – вызов соответствующего конструктора.

Деструктор – функция, которая вызывается автоматически, когда уничтожается объект.
С++,
D, C# - ОО модель.

C++

delete p;

Отличие С++ от Delphi – в нем происходит автоматический вызов деструктора.

Общая проблема - в процессе функционирования объекты получают некий ресурс.

В С++ и Delphi мы всегда контролируем, когда ресурсы освобождаются.

Специальные функции в Delphi:

X:=I.Create();

X.Create();

X.Free

C#, Java – Автоматическая сборка мусора.

При динамической сборке мусора сложно определить, когда объект больше не используется.

Image.FromFile(fname);

Image.SaveToFile(fname);

Освободить файл можно, когда мы указываем, что с ним не буде работать.

Т.е. сборка мусора здесь не поможет.

=> C#, Java:

try  

{

} finally {…}

D:

try 

{

} finally 

End 

Такая вещь, как finally, очень важна. Она будет выполнена независимо от того, как кончился блок.(Это необходимо, так как в C# и в Delphi нету вызова конструктора по умолчанию в конце блока)

IDispose  - общий интерфейс освобождения памяти. (C#) Данный метод вызывает финализатор объекта и ставит его в очередь на уничтожение, обеспечивая выполнение деструктора

Dispose();

try { im = … } finally {im.Dispose;}

Вводится специальная конструкция:

using(инициализатор) //инициализатор – T x=expr; x=expr;

 блок

~

try {инициализатор} finally {x.Dispose;}

С#, Java: object 

Учитывая то, что в Java есть сборщик мусора, там не существует деструктора. В классе Object существует защищенный метод:

protected void finalize();

public void Close();

Есть  методики,  которые  позволяют  возродить  уничтоженный  объект.  Но  finalize  –  полностью  его  удаляет (нельзя вызывать дважды).

В случае, если класс на протяжении своего существования должен освобождать ресурсы не один раз, он обязан содержать метод Close(), который будет это делать. Метод Dispose() вызывается один раз , а close должен быть запрограммирован таким образом, чтобы можно было вызывать его много раз.

Close() – ресурсы освобождены.

В Java метод finalize() вызывается сборщиком мусора. В C# существует деструктор – тонкая обертка для финализзатора finalize().

C#: ~X(){…} – нельзя вызывать явно.

System.Object.finalize – можно вызвать явно.

Сбощик мусора:

mark_and_sweep 

Живые и мертвые объекты (ссылки).

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

Можно  построить  КЭШ  объектов.  Если  объект  мертвый,  то  нм  нужен  он.  Но  он  еще  не  утилизирован  (не успели).

Strong reference – на живой объект.

Weak reference – объект готовится к уничтожение, не пока еще не нуничтожен.

Преобразование типов и классы. Явные и неявные преобразования.

Управление преобразованиями в современных ЯП: проблемы и способы их

решения.

Неявные (автоматически вставленные компилятором).

Int ￿ long 

А может ли их задавать пользователь.

В Java и Delphi нет возможности описания пользователем неявных преобразований (в Java нельзя перегружать стандартные операторы).

С++, C# – неявные преобразования, задаваемые пользователем разрешены.

Ф: v = expr – можно считывать различные типы данных.

C#:  

«Неплоский» класс – это класс, который сожержит в  себе ссылки на другие объекты.

Class Vector

{

       T* body;
       int size;

public:

       Vector(int sz) {body = new[size = sz];}

}

Vector v(20); //ok

Vector t(10);

v = t;

v =1; ~ v = Vector(1); // ошибки не будет: сработате оператор преобразования: у нас «случайно» получился конструктор преобразования.

Решение: если мы не хотим, чтобы наш конструктор «использовался» в подобных целях, то с помощью ключевого слова explicit конструктор может стать обычным конструктором.  Вот так:

 explicit vector(int sj){Body = new T[size=sj];}

Теперь наш конструктор может вызываться только явноV = Vector(1);…

В C# explicit принято по умолчанию. Существует в C# и ключевое слово implicit.

Cвойства (properties).

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

В С++ нет. Все данные по определению закрытые, а вместо операций для свойста есть геттеры и сеттеры.

Есть в Delphi, C#.

Пример для языка Delphi – целое свойство Prop:

type PropSample = class

private

procedure SetPropVal(V : integer);

function GetPropVal:integer;

public

property Prop: integer read SetPropVal write GetPropVal;

end;

published//все опубликованные свойства появляются в интегрированной среде разработки.

property X: T read_x; write_x;

С#:

class X

{

       T prop {get{…} set{…}} //value является зарезервированным в set.

}

X a = new X();

a.prop = t1;

T t2 = a.prop;

Классы и перегрузка имен. Перегрузка встроенных знаков операций.

В Java нет перегрузки.

С++: T operator+(T x1, T x2);

В Си++ typedef задаёт синоним типа, а не создаёт новый тип.

   typedef int a;

void f(a x)

void f(int x)  Перегрузки не будет, т.к. «a» не новый тип(=> будет ошибка)

Ада: function “+” (x1, x2: T) return T;

Перегружаемый операторы в C#:

  •  базисный
  •  арифметический
  •  побитовый

[ ] в C# перегружать нельзя(в отличие от С++, в которых Страуструп решил проблему в общем случае). Общий синтаксис:

T operator *(операнд) {…………………}

Один из недостатков перегрузки операций – несимметричность операндов:

a * b  => a.operator*(b)

Так перегружаются операции в C#:

static T operator *(T t1, T t2) {………………………} ;//в шарпе – только статический.

Аналогично и с операторами преобразования: они обызаны быть статическими члеами:

static operator T(X a){…………………}

Итераторы и индексаторы.

Механизм индексаторов в C# - компенсирует отсутствие возможности перегрузки операции индексирования.

Синтаксис:

T this (I index){…………………  }

Java – вложенные статические и нестатические классы

class Outer{

static class Inner{…….};

Нестатический блок-класс имеет ссылку на Outer.this 

Inner in = this.newInner();//если мы пишем, естественно, внутри класса Outer.

А так – вместо this может стоять ссылка на любой оъект класса Outer.

Outer invoice;

Inner = invoice.newInner();

В Java2 появилось понятие локального внутреннего класса

Iterable f(object[] objs)  

{

       class Local: Iterable {int i; Local() {i = 0;}… if (i>objs) …}

        return new Local(C);

}

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

Iterable f(final Object{ } obj)

{

    class Local Implements Iterable{

           .................................

    };

    return new Local();

}

Такой класс не может быть сделан внешним.

Наличие локальных классов, заметим, позволяет более компактно записывать код.

Если локальные переменные final, то это значит, что они не могут менять своего значения в теле функции. сlosure(«захват» в переводе) – это замыкание блока. Означает, что локальные переменные блока остаются связанными, даже еси мы выходим из блока.

Делегат – прообраз функционального типа данных. Вызывать делегат – это значит по очереди выбрать все элементы из цепочки.

Пример.

 delegate int Processor(int i);

Общий синтаксис:

delegate прототип функции

Наш делегат – это именно список функций.

В C# появились:

p = new delegate(int i) {……тело соответствующей функции………….}

Хитрость анонимных делегатов в том, что они тоже могут замыкать соответствующие переменные.

int k;

p = new delegate(int i){ return k+i;   }

//переменная k попадает в замыкание, становится захваченной

Теперь p  - это функция, которая к k прибавляет i.

int j = p(3);

k=1;

j=p(3);

Классы и стандартные библиотеки. Встроенные классы стандартной

библиотеки.

6. Инкапсуляция и абстрактные типы данных.

Понятие инкапсуляции. Единицы и атомы защиты. Понятие

абстрактного типа данных (АТД) и его достоинства.

Абстрактный тип данных = множество операций. (основной вид современного программирования)

Абстрактный тип данных  - это набор процедур, объявленный в отдельном пакете. Здесь, оказывается, есть инкапсуляция:

  •  единицы инкапсуляции: тип или экземпляр типа
  •  атомы инкапсуляции: отдельные поля и члены типа или весь тип.

В большинстве языков программирования единица инкапсуляции – тип.

В языке Оберон есть защита отдельных членов, что позволяет по отдельности экспортировать отдельные поля.

Ада и Modula-2 инкапсулируют целиком весь тип: такая тактика вынуждает нас к полной инкапсуляции

Атомы инкапсуляции – минимально возможные данные, которые можно скрыть от пользователя.  Для всех

класс – ориентированных языков атомы инкапсуляции – это поля класса, а таких языках как Ада, Модула – 2 – минимальным атомом является весь класс, то есть хороший программист на таких языках всегда использует абстрактные типы данных.

Инкапсуляция и логические модули. Управление видимостью.

Реализация АТД в модульных языках программирования (Ада, Оберон,

Модула).

М-2:  скрытые ТД

DEFINITION MODULE STACKS;

FROM MYTYPES IMPORT T; //возможность получить доступ к типу, который описан в другом модуле

TYPE STACK; (*скрытый ТД*) //компилятор не знает, что это.

PROCEDURE PUSH

                       INIT

                       DESTROY

END STACKS.

DEF -> транслируется в SYM(таблица символов) и OBJ(реализация).  

STACK ~ INTEGER или POINTER  

TYPE STACK = POINTER TO STACKREC

STACKREC = RECORD … END

:=  (shallow, copy), = (равно), # (не равно)

Ада 83:

приватный ТД (~скрытый ТД)

ограниченно приватный ТД 

￿

 

package stacks is type stack is private;

… - описание всех заголовков.

private

… - описание всех приватных структур данных.

:=, =, /=

ограниченно приватный:

type T is limited privaty;

… - оперции.

private type T is … ;
Инкапсуляция
 и классы. Управление видимостью и управление

доступом. Пространства имен и инкапсуляция. Реализация АТД с помощью

понятия класса.

Управление инкапсуляцией:

•  Управление доступом – C++, C#, D

•  Управление видимостью – Java

Управление видимостью – «private»-членов как бы просто нет для других классов, они «невидимы».  

Управление доступом – все не скрытые (не переопределённые) члены видны, т.е. компилятор постоянно «знает» об их существовании, но при обращении проверяются права на доступ. При попытке обращения к недоступному члену выдаётся ошибка.

Три уровня инкапсуляции:

1.public   

2.private

3.protected

«свой» - член данного класса

«чужой» - все внешние классы

«свои» - члены наследованных классов

public разрешает доступ всем

private разрешает доступ только «своему»

protected разрешает доступ «своим» и «своему»

Если требуется, чтобы доступ к приватным членам был не только у «своего», можно для этой этого объявить нужную дружественную конструкцию в теле класса:

friend «объявление друга»;// Можно писать сразу определение. Другом может быть    

 функция или целый класс

friend «прототип глобальной функции»

friend «прототип функции-члена другого класса»

friend class «имя класса-друга»;// Все методы этого класса становятся дружественными

В Delphi, C#, Java друзей нет

В них реализованы этот механизм реализован немного по-другому:

 Delphi   UNIT

Java   package

C#   assembly

В Java по умолчанию пакетный доступ. Это значит, что использовать класс может каждый класс из этого пакета. Если класс объявить как «public class …», то он будет доступен и вне пакета.  Использовать класс – наследовать, создавать объекты.

C#:
Сборка – надъязыковое понятие в .NerFramework. Сборка представляет собой совокупность файлов + манифест сборки, Любая сборка, статическая или динамическая, содержит коллекцию данных с описанием того, как ее элементы связаны друг с другом. Эти метаданные содержатся в манифесте сборки. Манифест сборки содержит все метаданные, необходимые для задания требований сборки к версиям и удостоверения безопасности, а также все метаданные, необходимые для определения области действия сборки и разрешения

ссылок на ресурсы и классы.  

Внутри сборки идёт разделение на пространства имён, которые содержат описания классов.  

Для использования какого-либо пространства имён нужно сначала подключить сборку, содержащую его.

Пространство имён может быть «размазана» по нескольким сборкам.

В C# для членов классов имеются следующие квалификаторы доступа:

•  public    

•  private // по умолчанию

•  protected  

•  internal – член доступен только в классах из сборки

•  internal protected – член доступен только в классах-наследниках, находящихся в сборке

Для самих классов:

•  public – класс можно использовать в любых классах

•  internal – класс можно использовать только в классах из его сборки (по умолчанию)

Delphi  

type T = class

   

   …. // здесь объявляются члены, видимые везде их данного модуля и не видимые      

 //  других 

   public

       ….

   protected

       ….

   private

       …..

end;

UNIT – единица дистрибуции
Принцип разделения определения, реализации и использования

(РОРИ). Эволюция принципа РОРИ в современных ЯП.

РОРИ –  метод, когда реализация скрыта от пользователя. Пользователю доступен  лишь интерфейс ,  а реализация инкапсулирована.

Тип данных  =  множество значений + множество операций

Абстрактный тип данных  =  множество операций

7. Модульность и раздельная трансляция

Виды трансляции. Физические модули. Программная и трансляционная

библиотеки. Раздельная трансляция: зависимая и независимая. Недостатки

независимой трансляции и способы их преодоления.

Особенности зависимой трансляции в современных ЯП. Одностороняя

и двустороняя связь модулей и раздельная трансляция.

Раздельная трансляция и пространства имен.

«раздельная независимая трансляция».

Есть: Си, Си++

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

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

двусторонняя связь между модулями при раздельной трансляции

есть: Ада

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

Вложенный модуль обозначается «заглушкой» во внешнем модуле:

procedure Outer is

–- заглушка

procedure Inner is separate;

. . .

end Outer;

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

separate(Outer)

procedure Inner is

. . .

end Inner;

Связь «заглушка-заголовок» - пример двусторонней связи.

8. Исключительные ситуации и обработка ошибок

Понятие исключительной ситуации (ИС) и его эволюция. ИС и ошибки

в программах. Четыре аспекта рассмотрения ИС: определение,

возникновение, распространение и обработка. Воплощение этих аспектов в

современных ЯП.

8. Смоделируйте на языке Си++ функции void f() throw (E1,E2,E3) { g(); h(); } предполагая, что конструкция throw не допускается компилятором.

void f()

{

try {

g(); h();

} catch (E1){

throw;

} catch (E2){

throw;

} catch (E3){

throw;

} catch (...) {

unexpected();

}

}

Пример для языка Delphi:

if ptr = nil then

raise Exception.Create('Invalid pointer');

Два подхода к обработке ИС: семантика возобновления и семантика

завершения. Их сравнение. Семантика завершения и современные ЯП.

Семантика возобновления: после обработки исключения управление может вернуться непосредственно в точку, где возникло исключение (варианты: на следующий оператор или на любой оператор из того же блока).

Пример языка: Visual Basic.

Семантика завершения: после возникновения исключения блок, в котором оно возникло, обязательно завершается. Обработка исключения происходит в блоках, вызвавших блок с исключением.

Пример языка: Си++.

В языке Си++ реализована другая семантика: завершения, но в некоторых случаях семантика возобновления может быть смоделирована, например, в случае выделения возобновляемого ресурса (типа динамической памяти):

Resource GetResource() {

for (;;)

try {

Resource r = … // попытка получить ресурс, например

// выделить память

if (success) return r;

throw NoResourceException();

} catch (NoResourceException) {

// попытка найти дополнительные ресурсы (например,

// динамически собрать мусор)

if (!success) throw;

}

}

Свертка стека. Оператор try-finally.

Дополнительные особенности ИС: спецификация ИС, проверяемые и

непроверяемые ИС.

9. Наследование типов и классов

Концепция уникальности типов в традиционных языках и строгая

типизация в объектно-ориентированных языках. Понятие единичного

наследования. Единичное наследование в современных ЯП. Наследование и

модель представления объекта в памяти. Преобразование из производного

типа в базовый. Иерархии типов, статические и динамические типы в

объектно-ориентированных ЯП.

У ссылок и указателей появляется понятие динамического типа. (Статический тип определяется при объявлении. Собственно сами объекты данных обладают только статическим типом.) Динамический тип – это тип объекта, на который ссылка или указатель ссылаются в даный момент. Собственно объекты данных свой тип менять не могут.

С++:     class Derived : [модификатор] Base {               

   // обьявление новых членов                  };

   [модификатор] ::= {private, public, protected}              

по умолчанию приватное. Чаще всего приватное наследование используется в написании интерфейсов.  Public – не меняет модификатор доступа свойств наследуемого

класса в производном, protected – делает все публичные  свойства наследуемого класса защищенными, а private – все свойства наследуемого класса делаем закрытыми (модификатор доступа private).

С#:     class Derived : Base {                   

   //определение новых членов                  }

Java:     class Derived extends Base {                 

   // определение новых членов                 }

 Java использует слово extends для выражения единственного типа наследования, соответствующего публичному наследованию в C++. Java не поддерживает множественное наследование. Классы Java тоже происходят от общего базового класса.

Oberon,  Object Pascal, Turbo Pascal:

   TYPE Derived = RECORD (Base)  
         // определение новых членов  

   END;

Delphi:

   TYPE Derived     =   class (Base)

                // определение новых членов

   END;

 В этих языках при наследовании используется не ключевое слово, а специальный синтаксис  -  добавление в скобках имени базового класса. Эти языки поддерживают только один тип наследования, который в C++ называется публичным.  

Ada:    type Base is tagged record                 

   // члены                      end;

    Type Derived is new Base with record               

   // определение новых членов                 end;

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

Type Derived is new Base with null record;    

package P is

   type Base is tagged private;

………………………………………..

Где-то в конце есть спецификация пакета:

private

  type Base is tagged record

….............................................

end P;  

Где будем наследовать Base?

Если в этом же пакете, то делаем это так:

type Derived is new Base with private

..............................................................

Если это определение в том же модуле, то структура этого Derived должна быть описана в приватной части пакета.

Но если оба типа описываются в одном модуле, и у типа Base есть приватные члены, даже тогда относительно derived  у него нет ничего приватного.

Защищенных членов в Ада и Оберон нет, таким образом, в этих языках всего два вида доступа:

  1.  публичный
  2.  приватный – его по большому счету тоже нет, так как в Ада есть дочений пакет.

package P.P1

Это означает, что фактически определяемое в нем как бы приписывается в конец приватной части в конце пакета.   

Замечания:

1.В Java, C#, Delphi каждый класс происходит по крайней мере от некоторого базового класса по умолчанию. Этот класс обладает некоторыми основными способностями, доступными всем классам. Этот подход является общим ещё и потому, что так первоначально делалось в Smalltalk. Также любой класс может стать первым в иерархии классов в таких языках, как Оберон, Ада – 95.

2.Единственный язык, поддерживающий множественное наследование из языков, проходимых в курсе ООП – язык С++.

3. Единственный язык, поддерживающий модификацию прав доступа свойств базового класса в производном  – язык С++.

Наследование и области видимости имен. Замещение, перегрузка и

скрытие имен при наследовании. Наследование и инкапсуляция. Управление

видимостью и доступом при наследовании.

class Base {

 void f();

} ;

 

class Derived: public Base {

 int f;

};

1)перегрузке(overloading) (в одной области действия)

2)скрытии(hiding)

3)переопределении(overriding) (динамическое связывание  - рассмотрим чуть – чуть позднее)

Перегрузка функций(! Именно функций, так как не существует перегрузки  свойств)

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

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

Скрытие:    скрытие  происходит  при  наследовании,  в  унаследованном  классе,    если  в  производном классе есть такое же имя как в базовом.

Для того, чтобы ращличать две этих N, используются ключевые слова super, base, inherited:

super.N;

base.N;

inherited.N;

(Эти слова очень удобны, они позволяют держать в глове имя базового класса)

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

Запрещение наследования для классов и методов.

В  С++  нет  никаких  ограничений  с  точки  зрения  наследования,  то  есть нет  никаких  языковых методов  запретить  наследования  данного  класса.  

В  Java можно  запретить  наследование  с  помощью ключевого слова final. Если это слово стоит перед именем метода, то этот метод не может переопределяться в производных классах. Если же оно стоит перед определением класса, то класс нельзя наследовать.

В C# такую роль играет sealed (там sealed имеет смысл ставить только около виртуальных методов). Sealed может стоять или перед определением, или перед замещением виртуального метода.

Любой сатический класс в C#3.0 запечатан.

Наследование и специальные функции. Понятие о множественном

наследовании.

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

синтаксис.

class A { ... };

class B { ... };

class C: public A, public B { ... };

Базовый класс не может появиться несколько раз в этом списке явно (ситуация ‘одна база – два раза’). Однако

может возникнуть такая ситуация:

class L { public: int n; ... };

class A: public L { ... };

class B: public L { ... };

class C: public A, public B { ... };

Например, мы напишем: C.c; c.n = 0; Возникает неоднозначность: какую c нам вызвать, ту которая пришла к нам через A, или же ту, которая наследовалась через B. Здесь мы можем уточнить: c.A::n = 5; или c.B::n = 7;. Перед оператором разрешения контекста мы указываем точку, от которой начинается поиск переменной.

Замечание: в других языках тоже существует оператор уточнения (x::N ~ super.N ~ base.N ~ inherited.N)
10. Динамический полиморфизм

Статическое и динамическое связывание методов. Динамический тип

данных и динамическое связывание. Замещение функций и динамическое

связывание. Особенности динамического связывания в современных ЯП.

Виртуальность метода означает динамическое связывание метода при вызове метода через ссылку на объект (базового) класса. Будет вызван метод для объекта, на который в настоящее время указывает ссылка. Этот объект может относиться не к базовому, а к производному классу. если в производном классе метод замещен, то будет вызван не метод из базового класса (как в случае невиртуальных методов), а его заместитель.

Механизм виртуальных функций.

•  Если  вызываем  метод  через  объект  (h.print()),  то  виртуальность  не  работает, статическое определение по типу объекта, от которого его вызываем.

•  При явном указании класса виртуальность не работает, даже если функция вызвана через указатель, то есть pp->Person::print(); – статическое определение.

С#, Delphi

Виртуальность метода обрывается, если мы не указываем virtual в очередном переопределении.

Ключевое слово override означает, что метод, в объявлении которого оно появляется, является заместителем виртуального метода из базового класса.

ADA 1995

Если есть параметр tегированного типа, для вызова есть специальные типы:

CWT – class wide types – классовый тип

Классовый тип – потенциально бесокнечное множество объектов, включающее все объекты класса Т и все производные объекта.

type T is tagged record …… end

T – класс, Тклассовый тип

type A is array (index range <>) of T;

X:A; //-ошибка!

X: A (range L..R); //нормально

X:T’

Тprocedure P (X: T);

T1procedure P (X: T1);

Представим, что существует некоторая глобальная процедура

procedure CALL(A : T); // P(A); -- P(X:T)

procedure CALLV (A : T’class) //P(A);

Тогда:

X: T;

Y:T1;

CALL(X); // - P(T)

CALL(Y); // - P(T)

CALLV(X); //  - P(T)

CALLV(Y); // - P(T1)

Если речь идет о вызове классовго типа, то он может быть только динамический.

Оберон -2 отличается от Оберон тем, что в нем есть процедуры и динамическое приведение к типу – по определению аналог виртуальных методов.

Пример

TYPE T  RECORD
  ……………………..

              END;

TYPE T1  RECORD(T)
  ……………………..

              END;

PROCEDURE(VAR X: T) P; //виртуаьная функция, Х передается как ссылка

Перекрытия нет, а замещение есть:

PROCEDURE(VAR X: T1) P; //динамическая(виртуальная) функция

VAR X: T;

       Y: T1;

X.P; //   -------------P(T)

Y.P; // --------------P(T1)

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

PROCEDURE CALL (VAR A: T); //обычная функция

Если имя функции написано после скобочек – функция виртуальная, если перед ним – то самая обычная.

Достоинства и недостатки динамического связывания. Снятие

динамического связывания. Механизм реализации динамического

связывания на примере языка Си++. Таблица виртуальных методов.

Понятие о мультиметодах.

Вообще говоря, Ада наиболее близко подошла к концепции МУЛЬТИМЕТОДА (метода, связанного по нескольким параметрам). Тем не менее, из соображений эффективности он в ней не реализован.

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

CALL_W(X:T’ class, Y: W’ class);

P(X, Y);

Пример использования мультиметодов можно легко привести из компьютерной графики: пересечение объектов – вызываем ту или иную функцию в зависимости от способов пресечения.

Мультиметоды есть, например, в языке CLOS(Common Lisp With Object Systems) – достаточно известный язык в довольно узких кругах.

11. Абстрактные классы и интерфейсы

Понятие абстрактного класса (АК). Необходимость понятия АК при

проектировании иерархий классов. Воплощение концепции АК в

современных ЯП.

Абстрактные классы и интерфейсы. Интерфейс как языковая

конструкция. Связь интерфейсов и других языковых конструкций

(итераторов, сохраняемых объектов и т.д.). Интерфейсы и иерархии классов.

Сами по себе иерархии классов бесполезны, если в них нету динамически связанных методов.

•  Если существуют признаки, общие для всех объектов в класса иерархии, то эти признаки целесообразно поместить в базовый класс

•  У каждого объекта класса имеется состояние (текущие значения параметров) и поведение (методы класса)

•  Некоторая функциональность (особенности поведения), общая для всех классов в иерархии, может быть реализована только при дальнейшей детализации в производных классах.  

=> приходим к понятию абстрактного базового класса

В языках Delphi, Оберон-2, TP 5.5, C++, Java, C# имеется языковая поддержка абстрактных классов.  

Пример:

Figure – абстрактная фигура

Общие данные – (x,y) – координаты фигуры

Общие методы – Draw(), Move() – не могут быть реализованы для абстрактной фигуры

Что будет если просто не определить витуальную функцию в базовом классе:

Полиморфный класс – класс, в котором есть виртуальные функции (и. следовательно, таблица виртуальных функций)

 class X 

{

   public:

       virtual void f(); //Объявлена, но не определена

       void g();//Объявлена, но не определена

}

class Y: public X

{  

    public

        virtual void f() {…;}//Замещена

}

….
X
* py = new Y(); // уже здесь компилятор выдаст ошибку из-за того,  т.к. непонятно что   записывать в таблицу виртуальных функций

py -> f();

X a; // здесь компилятор также выдаст ошибку из-за того,  из-за того, что не сможет   заполнить таблицу виртуальных функций. Если бы функция X:: f() не была   виртуальной, то ошибки здесь не было бы.

a.g();// ошибка, т.к X::g() не определена

Решение проблемы – языковая поддержка

В C#, Java:

abstract перед классом и перед функцией, для которой не существует реализации на данном уровне детализации. Такие функции и классы, их содержащие, называют аббстрактными

abstract class base //Абстрактный класс

{

   abstract public void Draw(); // Функция без реализации

 }

С#

class  D: base{
   
public override void draw(){……};

};

Java

class  D: extends base{
       
public override void draw(){……};

};

//обязательно поставить тело Draw!

В C++: 

Чисто виртуальная функция (абстрактная) – virtual «прототип» = 0;

В Аде:

procedure P( X : T ) is abstract;

где T – тегированный тип.

Объекты абстрактных классов нельзя создавать.

При вызове виртуальной функции в конструкторе виртуальность вызова снимается.

Существует метод, который не должен быть чисто виртуальным – деструктор. Он всегда должен быть виртуальным и реализованным.

Различие между абстрактными классами и абстрактными типами данных:

В абстрактных классах абстрагируются от реализаций некоторых методов. В абстрактных типах данных абстрагируются от всей структуры.

Абстрактный тип данных (АТД) — это тип с полностью инкапсулированной структурой. Использовать объекты АТД возможно только при помощи явно определенных в интерфейсе типа операций.

Абстрактный класс (АК) — это класс, содержащий хотя бы один абстрактный метод.

class Iset

{

   virtual void include(const T &) = 0;

   virtual exclude(const T &) = 0;

   «статические члены»

}  

Такой класс называется класс-интерфейс. В таких классах не имеет смысл объявлять нестатические поля, т.к. не реализовано ни одного методы для работы с ними.

Множественное наследование интерфейсов. Реализация интерфейсов и ее

особенности современных ЯП. Явная и неявная реализация интерфейсов.

В C# и Java, в отличие от C++, существует языкового понятия интерфейса:

 interface «имя»

{

    «объявления членов»

}

Внутри интерфейса могут быть статические поля. Поля без «static» будут восприняты как статические. Также членами интерфейса могут быть методы и свойства, которые считаются чисто виртуальными и публичными.  

Т.е. интерфейс – чистый контракт, не реализующий структуру.

Если класс наследует интерфейс и не определяет все методы интерфейса, то он становится абстрактным.

Если написать

   int pi{gte; set}

   то оно превратится в свойство.

Кстати, перед определением свойства может стоять слово virtual. То есть свойства могут быть виртуальными или чисто виртуальными.

В Java вместо get и set есть getpair и setpair.

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

C#

class D: имя, интерфейсы:

Java

Class D extends Base implements именя интерфейсов

Интерфейсы могут содержать внутри себя любые вложенные классы.

В С# появляется так называемая явная и неявная реализация интерфейсов.

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

Пример неявной реализации интерфейсов:

interface ISample{

   void f(); };

class CoClass: ISample{

 public void f(){………………..}; //если  тут не поставить public, компилятор заругается.

};

Явная реализация интерфейсов:

class CoClass2: ISample{

    void ISample f() {…………….}

   //тут спецификатор public отсутствует, потому что попытка написать тут public карается

};

Как же вызвать данный метод, если он не публичный?

D * px;

px->f();  //непонятно, какой метд я хочу вызывать – ошибка.

Но px->I1::f(); //снова ошибка: попытка вызова чисто виртуальной функции.

Мы же хотим вызвать заместителя для I1. Это длается так:

((I1*)px)->f();

Замечание. Слова «явный» и «неявный» должны относиться к приведению типов, а не к классам(интерфейсам).

CoClass2 x;

(ISample)x.f();//явное приведение

При явной реализации вызов возможен лишь ри явом приведении.

Явная реализация интерфейса означает, что вызов метода

интерфейса может происходить только через ссылку на интерфейс, но неможет происходить через ссылку на класс, реализующий интерфейс. Перед

вызовом интерфейсного метода необходимо явно преобразовать ссылку на

объект реализующего класса к ссылке на интерфейс. Концепция явной

реализации полезна, например, при конфликте имен между унаследованными

интерфейсами.

В C# реализованные методы интерфейсов считаются по умолчанию «sealed» - запечатанными. В наследниках они перекрываются.

 class ExampleClass : IControl
{

   public void Paint(){  … ;}

}

class FancyComBox: ExampleClass

{

   public void Paint(){….; } // Компилятор выдаст предупреждение для этой строчки, в   котором сообщит, что «FancyComBox.Paint() скрывает   унаследованный метод ExampleClass.Paint(). Используйте   ключевое слово «new», если это сделано целенаправленно. Т.е. если поставить «new» перед определением, то   предупреждение исчезнет.

Замечание2. Интерфейсы-маркеры.Реализованы в Ада. Являюются разновидностью стандартных интерфейсов, относящихся к категории ссылочных типов. Цель маркеров состоит в том, чтобы вызов через интерфейс был равносиелн по эффективности вызову виртуального метода.

Java: Iterable.

Интерфейсы-маркеры – это стандартные интерфейсы, доведенные до абсолюта. Контракт интерфейсов-маркеров описан только в документации. Это пустые интерфейсы, мы не можем увидеть их с своем коде.

Пример: интерфейс Cloneable – класс, поддерживающий этот интерфейс, обязан реализовать метод Clone

12. Множественное наследование

Множественное наследование в языке Си++. Ромбовидное

наследование и его примеры. Проблемы множественного наследования:

конфликт имен, реализация динамического связывания. Особенности

реализации множественного наследования при наследовании интерфейсов.

С какими проблемами мы сталкиваемся при множественном наследовании?

  •  Конфликт имен
  •  Виртуальные методы.

Конфликт имен решается через явное указание имени базы или через приведение к ссылке на базу.

Но существует ещё одна проблема – эффективность динамического полиморфизма (виртуальных функций)

А теперь рассмотрим множественное

class A

{

public:

    A(){};

    virtual void a(){ a1 = 1;};

    virtual void second(){..;}

    int a1, a2, a3;

};

class B

{

public:

   B(){};

   virtual void bar(){};

   virtual void bbar(){};
   int b1, b2, b3;

};

class C : public A

{

public:

    C() : A(){};

    virtual void goo(){};// Собственная новая виртуальная функция

    void a(){}; // переопределение

    void bar();// переопределение

    int c1;

};

….

C c;

Тут надо обратить внимание на следующее:

•  Таблица виртуальных методов самого нижнего класса в иерархии доступна через первый указатель vptr.

•  Каждый подобъект, который содержит виртуальные методы, имеет свою таблицу виртуальных функций.

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

Такой алгоритм становится понятен, если рассмотреть возможные преобразования типов:

•  С -> A. Через указатель на класс A можно вызывать только методы, которые прописаны в этом классе.

•  C -> B. Ситуация аналогична, только мы можем вызывать виртуальные методы, определенные в классе B.

Новые виртуальные методы (которых нет в родительских классах) можно использовать только через указатель на класс C. В этом случае всегда используется первая таблица виртуальных функций.

Сложность реализации  заключается в следующем:

Во время преобразования типов меняется адрес указателя:
C c;

B *p = &c;

Указатель p  будет содержать адрес объекта c + смещение подобъекта B. Т.е. все вызовы методов через такой указатель будут использовать вторую таблицу виртуальных методов объекта C. Но ведь в такой ситуации при вызове переопределённой в C функции через указатель на B в эту функцию передастся неправильный указатель this! Он будет указывать не на C, как это нужно, а на B.  

Приходится расширять таблицу виртуальных функций добавлением в неё смещения от указателя на объект класса до таблицы виртуальных функций для каждой функции. Если виртуальная функция из B переопределена в C, то для неё такое смещение будет равно (-смещение подобъекта B). Если же не была переопределена, то оно будет равно нулю. Для всех виртуальных функций из класса A это смещение будет нулевым, т.к. указатель на подобъект A совпадает с указателем на  весь объект C(объект А находится в начале

объекта C).  Теперь в функцию можно передать правильный указатель:

this = current_this + offset

где current_this – на подобъект, через который вызывается функция. offset – значение, которое берётся из расширенной таблицы виртуальных функций.  

Без наследования по данным таких проблем не возникает, т.к. указатель на таблицу виртуальных функций всегда один.

Ромбовидное и не ромбовидное наследование

Не ромбовидное: : В объекте Z будет два экземпляра объекта A с разными реализациями таблицы виртуальных функций

сlass A{ .. ;}

class X:public A{ …; }

class Y:public A{… ; }

class Z: public X, public Y {…;}
Ромбовидное: В объекте
Z будет только один экземпляр объекта A 

 сlass A{ .. ;}

class X: public virtual A{ …; }

class Y: public virtual A{… ; }

class Z: public X, public Y {…;}
13. Динамическая идентификация типа

Понятие о динамической идентификации типа (ДИТ). Достоинства и

недостатки использования ДИТ. Особенности ДИТ в современных языках

программирования.

14. Понятие о родовых объектах. Обобщенное программирование

Понятие о статической параметризации и родовых объектах.

Достоинства статической параметризации. Статическая параметризация и

ООП.

Родовые модули и подпрограммы в языке Ада.

Порождение нового пакета порождало новый экземпляр данного типа.

package Stack – два параметра – тип элемента размер стека:

generic

type T is private;

size: integer;

package Stack is

Push

Pop

end Stack;

Можно ли так запрограммировать процедуры, чтобы они эффективно работали для любого размера стека? Конечно, да!

Конкретизация:

package IStack is new Stack(integer, 128);

Тривиальная реализация – простая макроподстановка integer и 128. Плюс – крайняя простота. Минус – очень сильное разбухание кода – сколько объявлений, столько и различных процедур.

В Аде большое разнообразие типов формальных параметров родового модуля. Формальные параметры родовых модулей:

параметр-переменная <=> любое константное выражение
type T is private <=> любой тип с операцией присваивания
type T is range <=> любой дискретный тип с упорядоченностью и функциями «следующий» и «предыдущий»
type T is delta <=> любое плавающее выражение
< > <=> при конкретизации процедуры мы можем не указывать этот вариант (параметр по умолчанию).

Пусть у нас есть:

procedure StrSort is new G_SORT(String, Integer, TARR);

C помощью < > компилятор находит функцию < для строк и подставляет как параметр по умолчанию. Родовые сегменты – абсолютно необходимая в Аде конструкция, потому что там нет передачи процедур и функций по параметру. Компилятор должен видеть не только спецификацию данной абстракции, но и тело. Гибкость повышается, но тело родовой абстракции должно быть доступно в любой момент конкретизации. В Аде-83 механизм родовых модулей – единственный, который поддерживал ОО. Как вы думаете, что самое главное, что появилось в Аде-95? Правильно, класс – тегированная запись:

type T is tagged

Механизм шаблонов в языке Си++. Шаблоны-классы и шаблоны-

функции. Параметры шаблонов. Вывод параметров шаблонов. Генерация

кода по шаблонам. Частичная специализация шаблонов. Обобщенное

программирование на языке Си++: функторы, свойства, стратегии, шаблоны

выражений.

Vector <const char *>a;

Sort(a);

Будет подставлена функция < для указателей, и ошибки в программе не будет, но работать она будет неправильно.

template <class T> bool less (T& x, T& y) {

 return x<y;

}

Это описание шаблонной функции. Хорошая функция сортировки должна работать через шаблонную функцию сравнения. Но проблема со * остаётся. Что делать? А вот что.

Пишем:

template <> bool less <const char *> (const char *p1, const char *p2) {

  return strcmp(p1,p2);

}

template <> class Vector <void *>;

Подставь вместе T void * и сгенерируй код. (?)

Для векторов возможны такие реализации:

1) template <class T> class Vector {…}
2) template <> class Vector <T*>;</br>

Vector <long > V;

Vector <int> V1;

Оба по первой реализации и разные.

Vector <const char *> v2;

Vector <int *>v3;

Оба по второй и одинаковые.

Будет также эффективно, как и встроенный в язык вектор. Механизм частичной специализации очень эффективен. Вспомним:

Find(It first, It last, …)

Для каждого конкретного типа можно написать частичную специализацию Find

  •  I == X (?)

Можно передавать третьим параметром compare, и вызывать но это неэффективно, так как это срывает работу конвейера. Выход – функтор. Перекрываем операцию ( ):

сlass Finder {

 public:

   bool operator ()(T&x, …) {

     …

  }

}

Сравнение механизма шаблонов Си++ и родовых объектов Ады.

Особенности родовых объектов в языках С# и Java.

C#, Java – обобщенные классы 

У них очень простой синтаксис и похожая идея:

class Stack <T> {…}

interface IComparable <T> {…}

Параметризовать можно как классы, так и интерфейсы и методы:

public void add <T> (T x) {…}

Слово class просто синтаксически не нужно. Компилятор производит первичный синтаксический анализ и перевод в MIL/Байт-код. Конкретизация же происходит при выполнении кода. Код генерируется оптимальным образом.

В Java в качестве параметризованных типов только классы. Вместо Stack <int> S надо Stack <Integer> S

В C# на такое пойтить не могли. Там же есть структуры! Если аргумент – класс или интерфейс, код разный. Для ссылок одинаковый.

В Java: if (Stack<Integer>.getClass==Stack<String>.getClass()) даст true

В C#:

сlass Dictionary <K, V> {

}

K – тип ключа, V – тип значения.

Явно при реализации должно быть нечто вроде:

public void Add( K Key, V Value) {

 

 if (K<K1) …

 …

}

На это будет выдана ошибка. Потому что функции < может не быть.

Надо:

if ((IComparable)K<K1)

Вместо этого можно:

class Dictionary <K, V> where K:Icomparable

Конструкция where используется в языке C# для определения ограничений на параметры родовых (другое название - обобщенных) конструкций.

Ее вид: where имя_типа : список_ограничений.

Виды ограничений:

– интерфейс — означает, что параметр-тип должен реализовывать

этот интерфейс;

– имя класса (может быть только одно такое ограничение в списке)

— означает, что параметр-тип должен быть наследником этого класса;

– struct или class – означает, что параметр-тип должен быть

структурой или классом;

– new() - означает, что параметр-тип должен иметь конструктор

умолчания (без параметров).


 

А также другие работы, которые могут Вас заинтересовать

42587. Культура как объект социологического познания, ее элементы и функции 15.71 KB
  В социологическом изучении культуры выделяют два основных аспекта: культурную статику и культурную динамику, первая предполагает анализ структуры культуры, а вторая – развитие культурных процессов.
42588. Дослідження засобів документування програм 366.5 KB
  Документування ПЗ – це один з процесів підтримки життєвого циклу ПЗ, який полягає у фіксуванні інформації,яка створюється у межах діяльностей. Результатом процесу документування є документи (документація), які випускаються згідно з планом документування. Однією із складових процесу документування ПЗ є документування програмного коду з метою створення документів, які містять високорівневу структуровану інформацію для розробників. Документування включає створення вербальних та графічних представлень програмного коду (узагальнюючого тексту, діаграм тощо), їх структуризацію,форматування та фіксацію на носії. Ці дії можуть виконуватися із використанням автоматизованих засобів.
42590. Основные идеи П.Л. Лаврова и Н.К. Михайловского 15.57 KB
  Видное место в социологии народничества занимает субъективное направление. Субъективное направление возникло в конце 60-х годов XIX в. и просуществовало до Октябрьской революции, подвергнувшись значительной эволюции
42591. При натисненні на кнопку 1 змінити написання тексту деякої мітки (встановити атрібути курсив та підкреслений) 66.5 KB
  Розмістити на формі необхідні для виконання індивідуального завдання компоненти TEdit TLbel TButton. Програмний код: unit lb1; interfce uses Windows Messges SysUtils Vrints Clsses Grphics Controls Forms Dilogs StdCtrls; type TForm1 = clssTForm Button1: TButton; Button2: TButton; Edit1: TEdit; Lbel1: TLbel; procedure Button1ClickSender: TObject; procedure Button2ClickSender: TObject; privte { Privte declrtions } public { Public declrtions } end; vr Form1: TForm1;...
42592. Основные паспортные данные токарного станка ТВ -4 712 KB
  Ознакомиться с составлением паспорта станка. Изучить основные технические данные станка с целью выявления его технологических возможностей. Порядок выполнения работы: Пользуясь натуральным образцом станка плакатами учебными пособиями ознакомиться с конструкцией и управлением станка.
42593. П.Сорокин: концепции социальной конвергенции, социальной мобильности 15.39 KB
  Понятие социальной мобильности ввел П. Сорокин, определивший ее как «любой переход индивида, социального объекта или ценности, созданной или модифицированной благодаря деятельности, от одной социальной позиции к другой»
42594. Основи програмування 69.5 KB
  На формі знаходится одна група залежних та одна група незалежних перемикачів. В групі залежних перемикачів знаходяться три значення: іспит, залік, курсовий проект. В іншій групі знаходяться назви дисциплін поточного семестру. При виборі користувачем одного з значень залежних перемикачів (іспит чи залік) встановити прапорці біля відповідних дисциплін.