лекции (2008) (Фингеров Александр_ Кононов Алексей_ Кузин Сергей) (1160833), страница 14
Текст из файла (страница 14)
Int width{
get{return _width;}
set{_width = value ; _area =_width*_heidth;} // где переменная value, это то что предается переменной width;
}
Private int _area;
Private int _width;
Public int area{ get {return _area;}}
В С# можно реализовывать конструктор не описывая его:
Class Point{
Public int x{ get; set; }
Public int y{ get; set; }
};
Point p=new Point{x=0; y=0;}
Если при описании свойств перед get или set написать private, то это означает, что к ним можно обращаться только из класса.
Глава 7. Раздельная трансляция и разбиение программы на модули.
П1. Виды трансляции.
Исторически, самая первая трансляция была цельная. Вся программа подается компилятору целиком. Цельную трансляцию использовать очень неудобно, потому что приходится подгружать сторонние библиотеки.
Пошаговый вид трансляции. Программа подается на вход транслятору пошагово и сразу после трансляции, при возможности, программа пытается выполниться.
Инкрементная трансляция. Устаревший вид трансляции и сейчас он особо не рассматривается.
Раздельная трансляция делится на два типа:
1) Раздельная независимая трансляция и 2) Раздельная зависимая трансляция. При трансляции программа переводится на особый язык, а потом уже компонует части.
В языке с независимой трансляции контекст берется из самой единицы компиляции. Все современные языки имеют зависимую трансляцию.
Лекция 27.
Раздельная трансляция делится на зависимую и независимую, при раздельной трансляции возникает понятие модуля компиляции.
Транслятор на выходе выдаёт объектный модуль, получая единицу компиляции. Контекст берется из единицы трансляции, соответственно нужен механизм его указания. Необходимо как-то дублировать контекст.
При раздельной зависимой трансляции выдаётся не только объектный код, но и данные к трансляционной библиотеке. То, что составляет объектный модуль, это программная библиотека. Если посмотреть на оттранслированный компилятором Delphi текст - он чётко разделяется на таблицу имён и программную библиотеку.
Для C# и Java характерна рефлексия - для данного класса можно вытащить его тип, и по типу - значение поля, установить, считать его значения. В результате - исходный текст, практически, полностью находит себе отражение в оттранслированных файлах. Есть специальные дизассемблеры, позволяющие достаточно точно восстановить исходный код программы.
При независимой трансляции возникает много возможностей для ошибок при межмодульных связях. В C/C++ дублируется контекст трансляции, разработана технология, позволяющая этого не делать. Каждый модуль разбивается на 2 части - .c (.cpp) и .h - модуль интерфейса и модуль реализаций. Ни в коем случае не употреблять extern, а сразу делать соответствующий #include. Реализовано это к сожалению не средствами компилятора, а средствами препроцессора.
Есть, например, M.h => (используется) M1.h и есть M2.c, который использует оба этих файла, соответсвенно 2 #include. Получается, что M.h включен 2 раза. Беда повторного включения в объявлении типов - мы его просто переносим, получается, что класс может быть описан дважды. Поэтому придумывается какой-то уникальный идентификатор и пишется #infndef _M_h_ #define _M_h_ <текст объявлений> #endif. Это гарантирует то, что как только файл включился один раз, этот символ уже определен. Эта технология фактически заимствует то, что делают программисты на более развитых языках. Побочный эффект такого решения - при включении больших header-файлов, объём нашего кода от 5 до 10% всего.
п.2. Одностроннее и двустороннее связывание
ЕК (единица компиляции) -клиент (использует с помощью предложений импорта) => ЕК-сервис такая связь – односторонняя
В языках Delphi,М-2,Оберон ЕК совпадают с транслируемыми модулями.
Кольцевая ссылка запрещена (в M1.h include M.h, а в M.h include M1.h), компилятор её находит. Если в Delphi-реализации при зависимости M1 и M2 с помощью uses, поставить в одном случае implementation, то это ошибкой не будет.
Ада:
- односторонние
- двусторонние
Есть понятия первичных и вторичных единиц компиляций. Первичные нужны для трансляций других модулей - это спецификация пакета + процедура.
Вторичная ЕК - тело пакета.
Односторонняя связь это когда специальная директива указания контекста - WITH список_первичных_ЕК; [USE список2;] текст единицы компиляции
Происходит то же что и при импорте в языке Модула 2. WITH подгружает имена в пространство имён, видимы они потенциально; если мы напишем USE, то они видимы непосредственно. Мы видим, что в односторонней связи языка Ада ничего выдающегося нет, но модульная структура отличается - ведь логические модули могут быть и вложенными.
package Outer is
...
package Inner is
...
end Inner;
procedure P(x:T1,y:T2);
end Outer;
Первичная ЕК - определение Outer, вторичная - его тело.
ЕК2:
package body Outer is
package body Inner is separate;
procedure P(x:T1,y:T2) is separate;
end Outer;
Мы указываем раздельную трансляцию, это называется stub – заглушка.
ЕК3: неправильный вариант:
WITH Outer;
USE Outer;
package body Inner is
...
end Inner;
возникает двусторонняя связь между единицами компиляции, правильно записывается - с контекстом целиком.
ЕК3:
separate (Outer.Inner)
package body Inner is
...
end Inner;
ЕК4:
separate (Outer)
procedure P(x:T1,y:T2) is
...
end P;
В общем, зависимости должен указывать сам программист (например используя make-файл), после чего компилятор сам будет их отслеживать.
п.3 Управление пространствами имен.
КТ (контекст трансляции) - как управлять?
Дистрибуция - что собственно является единицей дистрибуции?
Понятие библиотека собственно к языкам отношения не имеет, это просто собрание модулей.
В языке Java есть понятие пакета - это некая совокупность классов. В C/C++ ЕК - это файл. Что мы туда запишем, это уже другой вопрос.
В Java ЕК - тоже файл, но файл должен содержать в себе первым дело предложение package имя_пакета; , что указывает к какому пакету относится файл.
Есть доступ пакетный, и есть доступ публичный. Любой класс, который используется в пакете и будет использоваться в другом месте, должен быть помечен спецификатором public. Получается, что есть ЕК - файл, но все они объединяются в пакет. Пакет одновременно служит и для указания КТ, и является единицей дистрибуции, в этом плане это конечно плюс. Имя пакета должно отражать принадлежность к дереву пакетов на сервере. В результате имя пакета одновременно говорит, где находится ресурс, и его можно найти и запустить.
Import имя_пакета.* - делает все имена видимыми. Более того, можем писать и без всякого импорта - например Java.lang.Int32. Если мы целиком используем имя пакета нам даже ключевое слово не нужно. Иерархическое имя не имеет ничего общего с реальной зависимостью пакетов - и это может сильно путать.
Более мощное обобщение пакета в языке Java - понятие пространства имён, и вот они действительно могут быть вложенными. Если мы не находим имя на одном уровне, то мы идём на уровень выше.
Лекция 28.
Пространство имен C#, C++.
В С++ есть два стиля – старый (для совместимости со старыми версиями программ) и новый (на основе пространства имен). Таким образом, язык поддерживает два стиля программирования (<stdio.h> и std::iostream).
С++ и С# поддерживают вложенность пространства имен, так следующие примеры (эквивалентны):
Namespace N{
Namespace N_in{ }
}
Namespace N{
Namespace N.N_in{}
}
В C, C++, C# единицей компиляции является файл. В С# единицей контекста является пространство имен. Сложность в С++ исходит из необходимости включения заголовочных файлов. После включения к объектам из пространства имен можно обращаться через квалификатор (например std::cin >> c). В C# не требуется подключения заголовочных файлов, так как доступ к объектам из пространства имен осуществляется посредством имени пространства имен, например System.Windows.Forms.
Отсюда возникает понятие единицы дистрибуции. Похожую ситуацию можно наблюдать в Java – единица компиляции файл, единица контекста - пакет.
Package P1.P2
Import P1*;
Import P1.x;
Static import Math*;
Статический импорт позволяет писать x = exp (i*k); , то есть использовать exp как функцию, а не как метод (Math.exp).
Написав Using System.Windows.Forms - все классы и типы из этого пространства имен мы видим непосредственно (естественно, если они не конфликтуют). Если имена из текущего пространства имен перекрывают имена из импортируемых пространств имен, то используется имя из текущего пространства, если же два и более импортируемых имени конфликтуют, то имя становится невидимым. Правда использовать директиву using не рекомендуется.
namespace N1{
Class X;
};
namespace N2{
class Y;
};
N1::X a;
N2::Y b;
f (a,b);
В данном примере не понятно, где будет искаться функция f. В первую очередь естественно в текущем пространстве имен, но если ее там нет, то в пространстве имен ее аргументов.
Объектно-ориентированные языки программирования.
Три свойства, которыми должны обладать эти языки:
-
инкапсуляция
-
наследование
-
динамический полиморфизм
Глава 1. Наследование.
Специальное отношение между данными, означающее, что выводится производный класс из базового (производный класс наследует все данные базового класса):
Base => Derived.
У языков первой группы (C#, Java, Delphi) вся иерархия наследования начинается с класса Object.
В языках второй группы (C++, Оберон, Ада 95) любой класс можем стать корнем наследования.
C++:
class Derived: (public/private) Base
{
объявление новых членов;
}
С++ единственный ЯП, который поддерживает модификации прав доступа.
В С# :
class Derived: Base{