И.Г. Головин, И.А. Волкова - Языки и методы программирования (1160773), страница 18
Текст из файла (страница 18)
В языке SmallTalk (первом«чистом» объектно-ориентированном языке) используется довольноудачная терминология: статические члены-данные называются переменными класса, а нестатические — переменными экземпляра.Обращение к статическим членам класса происходит через имякласса:Hello.Greet();System.out.println(Hello.msg);85Таким образом, класс — это область непосредственной видимости для имен его членов (это относится не только к статическимчленам). Поэтому, чтобы обратиться к статическому члену класса,следует указать имя класса. Говорят, что имена членов класса необходимо «уточнять» именем класса (полная аналогия с обращениемк константам перечислимого типа в C# и Java). Однако преимущество функций-членов состоит в том, что они могут обращатьсяк членам своего класса без уточнений, поэтому функция Greet обращается к члену message своего класса Hello без уточнения.
Такаявозможность делает текст функций-членов более выразительными компактным.Зачем нужны статические члены? В языке C++ статические членыможно уподобить глобальным переменным и глобальным функциям.Отличие (в пользу членов класса) состоит в том, что имена методовлокализованы в классе, их надо уточнять (в C++ синтаксис уточненияимени отличается: вместо точки должен стоять знак «: :», например,Hello: : Greet ()), и поэтому их проще выбирать по сравнениюс глобальными именами, т. е.
меньше вероятность конфликта имен.Еще одно важное отличие в пользу статических членов состоит в возможности функций-членов обращаться к закрытым (инкапсулированным) членам класса (см. подразд. 7.3). Однако если в C++ ещеможно (хотя и не рекомендуется) обходиться без статических членов,то в C# и Java никакая программа без них не обходится. Дело в том,что в этих языках программа состоит только из классов (правда,можно еще объявлять перечисления и интерфейсы, но это не меняет дела). Кроме того, в этих языках отсутствует понятие глобальнойфункции и глобальной переменной. Любая функция и любой объектданных должны принадлежать какому-либо классу. Такая ситуациявообще характерна для объектно-ориентированных языков.В приведенном примере на Java есть только два описания класса,но нет ни одного их экземпляра (ведь в программе нет ни одного обращения к операции new).
По правилам языка статическая функцияс прототипом static void main (String [] args) в одном изклассов (любом) играет роль функции main в C++, которая вызывается при запуске консольной программы (аргументы команднойстроки передаются как элементы параметра args). Такой класс называется главным, и перед ним, а также перед методом main долженстоять модификатор public. Этот модификатор означает разрешениеиспользования класса и соответствующего метода в любом местепрограммы.
Понятие статической функции позволяет обойтись безсоздания каких-либо экземпляров классов при запуске программы.В языке C# действует такое же правило для консольных программ,только функция называется Main (с прописной буквы М).Необходимость и смысл статических функций можно увидеть напримере класса Math в языках C# и Java. Этот класс содержит определения математических функций (тригонометрических, экспоненты86и др.) и констант (PI, Е).
Очевидно, что все они реализованы какстатические члены. Такие классы являются контейнерами операцийи не содержат никаких нестатических членов данных. Объекты такихклассов, как Math, не имеет смысла размещать в памяти. ,В языке C# есть специальное понятие статического класса. Еслиперед определением класса стоит модификатор static, то такойкласс может иметь только статические члены, и создавать объектытакого класса запрещено.
Примером стандартного статическогокласса, конечно, является Math.Приведенный пример можно переписать и на С#, для чего надотолько переименовать main, изменить модификатор final на constи заменить вывод на System. Console .WriteLine (Hello .msg) ;.Однако статические члены, конечно, играют вспомогательнуюроль.
Основными в классе являются нестатические члены класса.Нестатические члены-данные играют роль, аналогичную полямзаписи (структуры). Каждый экземпляр класса имеет свой наборчленов-данных, поэтому к нестатическому члену-данному нельзяобращаться через имя класса: необходимо уточнять, какому именнообъекту принадлежит член. В языках C# и Java обращение к нестатическому члену происходит только через ссылку на конкретный объект (ведь сами объекты анонимные). В C++ для обращения к членуиспользуется имя объекта. Например (считаем, что класс X содержитнестатический целый член к):// фрагмент на C# и JavaX а = new X ();а .к = 0 ;X b = new X () ;b .к = а .к ;// фрагмент на C++X obj; // компилятор C++ сам разместит объектobj.k = -1 ;X * рХ = new X ();(*рХ).к = -1;рХ -> к = -1; // другая форма записиПоясним последнюю строчку.
Если указатель рХ ссылается наобъект какого-либо класса X (другими словами, имеет тип X*), дляобращения к члену к этого класса вместо громоздкой записи ( *рХ) .кможно использовать операцию «->»:рХ -> к.Теперь рассмотрим нестатические методы класса.Каждый нестатический метод класса X содержит неявный формальный параметр — ссылку на объект класса х, для которого вызывается метод. Эта ссылка обозначается ключевым словом t h i s (в C++t h i s не ссылка, а указатель). Через эту ссылку метод класса может87обращаться ко всем членам класса (для краткости будем опускатьслово «нестатический», так как члены во всех языках по умолчаниюнестатические).
Передача этой ссылки производится неявно транслятором, а сама ссылка берется из обращения к методу. Вспомним,что обращение к члену должно быть через ссылку на объект, котораяи будет ссылкой this.Рассмотрим пример на Java:class Simple{int x = 0, у = 1;void SwapMembers(){int tmp = this.x;this.x = this.y; this.у = tmp;}void O u t (){System.out.println(this.x);System.out.println(this.y);}}public static void main(String[]args){Simple s = new Simple();s .O u t ();s .SwapMembers();s .O u t ();}При вызове main будут напечатаны сначала 0 и 1, а затем 1 и О(каждое значение на новой строке).Однако использование this внутри функций-членов необязательно (как необязательно было использование имени класса длястатических членов внутри статической функции). В приведенномпримере можно опустить this (транслятор сам подставит эту ссылку для имен членов класса). Имена членов класса непосредственновидимы внутри методов класса, поэтому код тел методов получаетсяболее компактным и выразительным.
Далее мы будем явно употреблять this только там, где это необходимо.Приведем более содержательный пример класса на языке Java,в котором реализуется тип данных стек. Стек — это контейнер, хранящий данные по принципу «последний поступивший выдаетсяпервым». Аналогом может служить стопка тарелок (первой беремверхнюю тарелку, которую положили последней). Следует иметьв виду, что стек — очень популярная структура данных, поэтомукаждый индустриальный объектный язык включает класс-стек в стан88дартную библиотеку (в Java класс Stack находится в библиотечномпакете java.util).
Так что пример, конечно, носит модельный характер,но при этом демонстрирует возможности Java по созданию новыхтипов данных.Стек реализуется в виде массива. Индекс первого свободногоэлемента обозначен как top. Функция Pop () извлекает элементиз стека, а функция Push (х) заносит элемент в стек. ФункцииIsEmpty () и Is Full () проверяют стек на пустоту и полноту соответственно.
Например:class Stack{int [] body;int top;Stack(int size){body = new int[size];top = 0;}int Pop() { return body[--top]; }void Push (int x) { body[top++] = x; }boolean IsEmpty() { return top ==0; }boolean IsFullO {return top == body.length;}}Поясним, что представляет собой первая функция-член класса.Она имеет «странный» заголовок: Stack (int size) , странностькоторого заключается в отсутствии указания возвращаемого типа и втом, что имя метода совпадает с именем класса. Если мы попробуемпроделать такое с другим методом или членом-данным, то программане оттранслируется.Эта «странная» функция называется конструктором.
Конструкторавтоматически вызывается при размещении объекта соответствующего класса в памяти. Фактические параметры вызова конструкторауказываются в операции new в виде new Stack (128). При этомв конструктор будет передано значение 128 — длина массива, гдебудут храниться данные стека. Конструктор относится к категорииспециальных функций-членов. Подробнее эти функции рассматриваются в подразд.
7.2.Конечно, приведенный стек обладает рядом недостатков, главнымиз которых является отсутствие инкапсуляции и неустойчивостьк ошибкам, а точнее, неадекватные сообщения в случае возникновения ошибок (эти недостатки будут рассматриваться в подразд. 7.3и в гл. 9), здесь же отметим, что несмотря на компактность кода,созданный тип данных вполне работоспособен.89Приведем пример использования нашего стека: выдадим элементымассива в обратном порядке:public static void main(String[]args){Stack s = new Stack(128);int [] x = {1,2,3,4,5,6,7};for (int k : x) s.Push(k);while (!s .IsEmpty())System.out.println(s.Pop()) ;}Еще раз напомним, что в языках C# и Java объекты размещаютсяв динамической памяти. Иногда это приводит к некоторой неэффективности.
Рассмотрим небольшой класс Point на языке Java,который реализует понятие точки на плоскости (в целочисленнойсистеме координат), которой может служить, например экран монитора.class Point{int х,у;// координаты точки, других членов данных нетPoint(int х, int у){ this.x = х; this.у = у; }void Move (int dx, int dy){ x+= dx; у += dy; }// другие методы ...}Пусть у нас есть объект screen, имеющий экранную функциюрисования линий DrawLine, которая принимает массив точек и вырисовывает ломаную линию, задаваемую массивом (каждые два соседних элемента массива задают отрезок, а весь массив — ломаную),в соответствии со следующим алгоритмом:static final int MAX_POINTS = 1024;void DrawSample(){Point [] points = new Point[MAX_POINTS];int i = 0;while (есть_еще_точки) {int x,y;ввести координаты очередной точки х и уPoint р = new Point(х,у);points[i++] = р;// проверка на переполнение массиваif (i == MAX POINTS) break;90}screen.DrawLine (points);}Проблема в том, что массив points содержит не точки, а ссылки.А объекты-точки надо еще разместить в памяти, что и происходитв цикле while.
Общий максимальный расход памяти определяетсяв видеMAX_POINTS * (sizeof (ссылка)+sizeof(Point))Тут важно то, что сам объект класса Point невелик: всего двацелых значения. Следует понимать, что методы не занимают памятьобъекта, поэтому расход памяти под ссылки сравним с общим объемом памяти для хранения точек. К тому же автоматический сборщикмусора должен сначала утилизировать память под объекты-точки,а затем только освободить массив из ссылок.Язык Java не позволяет как-либо обойти эту проблему, но в языке C# существует специальное понятие, позволяющее уменьшитьнакладные расходы на размещение объектов, которое называетсяструктурой. Структура языка C# — это средство определения новыхтипов с урезанной по сравнению с классами функциональностью.Внешне определение структуры имеет вид определения класса, толькоздесь вместо ключевого слова class используется struct:struct Point{int x,y;public Point(int x, int y){ this.x = x; this.у = у; }public void Move (int dx, int dy){ x+= dx; у += dy; }// другие методы ...}}Однако структура — это не класс, а тип-значение.