Записи по ЯП (1161118), страница 4
Текст из файла (страница 4)
Референциальный тип – тип, объекты которого передаются по ссылке. Если где-то нужен объект такого типа, то отводится место под адрес. Референциальными типами являются классы(любой массив – наследник класса Array, строка это объект класса String, и т.д.)
В принципе, в C# можно передавать объекты простых типов в функции с помощью классов-обрёток, но C# также поддерживает явное описание передачи параметров по ссылке – ключевые слова ref и out. «ref» реализует семантику inout, а «out» реализует семантику out. При использовании out компилятор вставляет квазистатический контроль на наличие в методе присваивания значения, зато не требует инициализированность фактического параметра перед вызовом метода.
void f(ref int x){x = -3;}
….
int a = 0;
f( ref a); // а будет передано по ссылке, если бы объект «а» был структурой, то он так же // передался бы по ссылке
Разрешать или не разрешать функции с переменным количеством параметров?
Си
В Си можно было создавать функции с переменным количеством параметров при помощи библиотеки «stdargs»: va_list, va_start, va_end, va_next и т.д
Си#
void f(param object [] args){…. args [0] = …; }
void g(object [] args){…. args [0] = …; }
….
f(a , b);
f();
f(1);
f(new X[]{new X(). new X()});// Ошибка!!
f(new X(), new X()); // Правильно!
g(new X[]{new X(). new X()});// Правильно!!
java
void func(Object a[])
{
for(int i = 0;i < a.length; i++)
System.out.println(a[i]);
}
Для java 1.5 – func(1,2,3, new Object(), "word");
Для java 1.4 – func(new Object[] {1, 2, "some string"});
Передача параметров по имени
Алгол-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 ) { … ; }
Классы
Принципиальное отличие класса от модуля заключается в том, что класс – это тип данных, а модуль нет. Но во многих вещах они похожи.
Тип Данных = Структура Данных + Множество Операций над этими данными
В C#, Java всё является классами или находится в классах в качестве статических челнов. Даже математические функции находятся в классе – System.Math. И для вызова функции cos(x) требуется написать Math.Cos(x);
Наибольшее сходство между классом и модулем достигается если класс содержит только статические методы и поля. При этом такой класс, как правило, реализуется в виде модуля.
п1. Понятие членов класса
синтаксис в C++.Java,C#:
class Name
{
….
Определение членов класса
…..
}
В Си++ допускается вынесение определений, т.е. В Си++ можно члены класса лишь объявлять. В Java, C# все определения должны быть внутри класса
синтаксис в Delphi:
type T =
class (наследование)
обявление членов класса
end;
Члены класса:
-
Члены-данные
-
Члены-типв
-
Члены-функции(методы)
Чем члены-функции отличаются от обычных функций?
Такой функции при вызове всегда передаётся указатель «this »(в Delphi «self ») на объект в памяти, от имени которого вызывается функция.
Java,C#, T x; x = new T(«параметры конструктора»); В первой строчке определяется ссылка на объект (выделяется память для хранения ссылки), место в динамической памяти под объект не отводится. Во второй непосредственно отводится место в динамической памяти («куче») для объекта и адрес присваивается ссылке на объект. | C++. T x; T x(«параметры конструктора»); T x = T(«параметры конструктора»); В этих определениях выделяется место не под ссылку на объект, а под сам объект (не в динамической памяти). Чтобы выделить место в динамической памяти, нужно использовать операцию «new» | Dephpi x : T x – ссылка, её ещё надо проинициализировать. |
Ещё раз о структура в Си#:
struct Name // не имеет ссылочной семантики
{
….
<определения членов>
….
}
…….
Name x;// память отводится здесь же
x = new Name(<параметры>); // Явное выделение динамической памяти
Name [] arr = new Name[50];// В памяти отведётся место под 50 объектов типа «Name», //а не под 50 указателей
От структур нельзя наследовать. Сами структуры неявно наследуются от класс System.Struct
Обращение
«имя_объекта».«имя_члена»
Для членов-данных по определению выполнение операции обращения к элементу класса «.»(class member acces operator) является просто смещением относительно адреса «this»(«self»)
class X
{
….
объявления \ определения для Си++, определения для C#, Java
…..
}
Внутри всех функций-членов члены класса видимы непосредственно.
Однако формальные параметры метода класса относятся к блоку метода и могут закрывать члены класса. Тогда доступ к членам классам можно получить через указатель this при помощи операции обращение к элементу класса «.»: this.«имя члена».
В Delphi формальные параметры функций-членов находятся в той же области видимости, что и все остальные члены класса и, следовательно, не могут с ними совпадать.
Члены-типы
STL – набор соглашений. Одно из соглашений – контейнеры должны сами себя описывать. В 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)
….
}
Паттерн Singleton (Одиночка)
Если установка соединения между клиентом и сервером слишком трудна, требует больших затрат ресурсов и времени, то неэффективно каждый раз устанавливать его заново. Нужно иметь единственный экземпляр соединения и запретить произвольное создание экземпляров (для этого можно, например, сделать конструктор приватным).
class Singleton
{
static private Singleton *Instance;//объявление, не путать с определением
private:
Singleton();
Singleton(const Singleton &);
public:
static getInstance()
{
if(Instance == NULL)
Instance = new Singleton(); //здесь нужен конструктор
return instance; //здесь нужен конструктор копирования
}
}
Определение объекта подразумевает размещение его в памяти.
Т.к. внутри класса имеется только объявление static private Singleton *Instance, то где-то вне класса нужно произвести определение этого статического члена:
Singleton * Singleton::Instance = NULL; //отличается от определения (размещения) глобальной переменной только тем, что кроме этого места Singleton::Instance нигде нельзя будет далее использовать, т.к. это скрытый член класса.
Это единственный случай, когда можно инициализировать скрытые члены класса
Вложенные типы данных (классы)
class Outer
{
….
class Inner //Inner – обычный класс, но с ограниченной областью
{ //видимости
…..
};
….
};
Все классы являются статическими членами. Ко всем именам правила прав доступа применяются одинаково, т.е. специфика имени не участвует в разрешении прав доступа.
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»
public class Outer // «Outer» является владельцем «Inner»
{
….
static class Inner //Тоже самое, что и в C#, C++ без «static»
{
…..
};
….
};
Декларируются внутри основного класса. В отличие от статических внутренних классов, имеют доступ к членам внешнего класса, например «Outer.this». Не могут содержать определение (но могут наследовать) статических полей, методов и классов (кроме констант).
Инкапсуляция
Единица защиты – тип класса или экземпляр класса
Атом защиты – член класса.
В современных языках единицей защиты является тип класса. Правила защиты во всех языках определяются для всех экземпляров одинаково. Нельзя один экземпляр защитить больше чем другой.
Управление инкапсуляцией:
-
Управление доступом – C++, C#, D
-
Управление видимостью – Java
Управление видимостью – «private»-членов как бы просто нет для других классов, они «невидимы».
Управление доступом – все не скрытые (не переопределённые) члены видны, т.е. компилятор постоянно «знает» об их существовании, но при обращении проверяются права на доступ. При попытке обращения к недоступному члену выдаётся ошибка.
class X
{
public:
virtual void f();
void h();
};
class Y: public X
{
private:
virtual void f();
void h();
};
class Z: public Y
{
public:
virtual void f(); //В Java заместится функция, видимая в данный момент – X::f
void g(){ … h();…. } //В Си++ для этой строчки будет выдана ошибка – с точки зрения управления доступом попытка вызова функции Y::h() незаконна, т.к. она приватна(к ней нет доступа вне класса Y).
В Java спокойно вызовется функция X::h() и никакой ошибки не будет, т.к. функция Y::h() просто вычеркнута из рассмотрения (невидима)
}
Три уровня инкапсуляции:
-
public
-
private
-
protected
«свой» - член данного класса
«чужой» - все внешние классы
«свои» - члены наследованных классов