Главная страница
Навигация по странице:

  • Управление доступом к членам класса

  • Спецификаторы доступа C

  • Применение спецификаторов доступа public и private

  • Управление доступом: учебный проект

  • Передача объектов методам

  • Справочник по C# Герберт Шилдт ббк 32. 973. 26018 75 Ш57 удк 681 07 Издательский дом "Вильямс" Зав редакцией


    Скачать 5.05 Mb.
    НазваниеСправочник по C# Герберт Шилдт ббк 32. 973. 26018 75 Ш57 удк 681 07 Издательский дом "Вильямс" Зав редакцией
    АнкорC #.pdf
    Дата08.12.2017
    Размер5.05 Mb.
    Формат файлаpdf
    Имя файлаC #.pdf
    ТипСправочник
    #10795
    страница11 из 52
    1   ...   7   8   9   10   11   12   13   14   ...   52
    Глава 8
    Подробнее о методах и
    классах

    180
    Часть I. Язык C# этой главе мы снова обращаемся к рассмотрению методов и классов. Начнем с управления доступом к членам класса. Затем обсудим возможность передачи методам объектов и их возврата, после чего рассмотрим перегрузку методов, различные формы метода
    Main()
    , рекурсию и использование ключевого слова static
    Управление доступом к членам класса
    Поддерживая инкапсуляцию, класс обеспечивает два положительных момента. Во- первых, он связывает данные с кодом (мы используем это преимущество начиная с главы
    6). Во-вторых, он предоставляет средства управления доступом к членам класса. На этом мы сейчас и остановимся.
    Хотя в действительности дело обстоит несколько сложнее, но по сути существуют два базовых типа членов класса: открытые и закрытые. К открытому члену класса может свободно получить доступ код, определенный вне этого класса. До сих пор мы как раз и использовали члены такого типа, К закрытому же члену класса доступ могут получить методы, определенные только в этом классе. Благодаря использованию закрытых членов класса мы и имеем возможность управлять доступом.
    Ограничение доступа к членам класса — это фундаментальная часть объектно- ориентированного программирования, поскольку она предотвращает неверное использование объекта. Разрешая доступ к закрытым данным только посредством строго определенного набора методов, вы имеете возможность не допустить присвоение этим данным неподходящих значений, выполнив, например, проверку диапазона. Код, не принадлежащий классу, не может устанавливать закрытые члены напрямую. И именно программист управляет тем, как и когда будут использоваться данные объекта. Таким образом, при корректной реализации класс создает “черный ящик”, с которым можно работать, но внутреннее функционирование которого закрыто для вмешательства извне.
    Спецификаторы доступа C#
    Управление доступом к членам класса достигается за счет использования четырех
    спецификаторов доступа:
    public
    , private
    , protected и internal
    . В этой главе мы ограничимся рассмотрением спецификаторов public и private
    . Модификатор protected применяется только при включении интерфейсов и описан в главе 9.
    Модификатор internal применяется в основном при использовании компоновочных
    файлов (
    assembly
    ) и описан в главе 16.
    Спецификатор public разрешает доступ к соответствующему члену класса со стороны другого кода программы, включая методы, определенные внутри других классов.
    Спецификатор private разрешает доступ к соответствующему члену класса только для методов, определенных внутри того же класса. Таким образом, методы других классов не могут получить доступ к private
    -члену не их класса. Как разъяснялось в главе 6, при отсутствии спецификатора доступа член класса является закрытым (
    private
    ) по умолчанию. Следовательно, при создании закрытых членов класса спецификатор private необязателен.
    Спецификатор доступа должен стоять первым в списке спецификаторов типа любого члена класса. Вот несколько примеров: public string errMsg; private double bal; private bool isError(byte status) { // ...
    В

    Глава 8. Подробнее о методах и классах
    181
    Чтобы лучше понять разницу между спецификаторами public и private
    , рассмотрим следующую программу:
    // Сравнение доступа к открытым и закрытым членам класса. using System; class MyClass { private int alpha; // Явно задан спецификатор private. int beta; // Спецификатор private по умолчанию. public int gamma; // Явно задан спецификатор public.
    /*
    Методы для получения доступа к членам alpha и beta.
    Другие члены класса беспрепятственно получают доступ к private-члену того же класса. */ public void setAlpha(int a) { alpha = a;
    } public int getAlpha() { return alpha;
    } public void setBeta(int a) { beta = a;
    } public int getBeta() { return beta;
    }
    } class AccessDemo { public static void Main() {
    MyClass ob = new MyClass();
    /*
    Доступ к private-членам alpha и beta разрешен только посредством соответствующих методов. */ ob.setAlpha(-99); ob.setBeta(19);
    Console.WriteLine("Член ob.alpha равен " + ob.getAlpha());
    Console.WriteLine("Член ob.beta равен " + ob.getBeta());
    //
    К private-членам alpha или beta нельзя получить
    // доступ таким образом:
    // ob.alpha = 10;
    //
    Неверно! alpha — закрытый член!
    // ob.beta = 9;
    //
    Неверно! beta — закрытый член!
    //
    Можно получить прямой доступ
    // к члену gamma, поскольку он открытый член. ob.gamma = 99;
    }
    }
    Как видите, внутри класса
    MyClass член alpha явно определен как private
    -член, beta
    — private
    -член по умолчанию, a gamma определен как public
    -член. Поскольку

    182
    Часть I. Язык C# alpha и beta
    — закрытые члены, к ним нельзя получить доступ не из их “родного” класса. Следовательно, внутри класса
    AccessDemo к этим членам нельзя обратиться напрямую. К каждому из них необходимо обращаться только через открытые (
    public
    -) методы, например setAlpha()
    или getAlpha()
    . Если удалить символ комментария в начале строки
    // ob.alpha = 10; // Неверно! alpha -- закрытый член!, то вы бы не смогли скомпилировать эту программу по причине нарушения доступа к закрытому члену класса. Несмотря на то что доступ к члену alpha вне класса
    MyClass не разрешен, методы, определенные в классе
    MyClass
    (
    setAlpha()
    и getAlpha()
    ), могут к нему обращаться. Это справедливо и для члена beta
    Итак, к закрытым членам могут свободно обращаться другие члены того же класса, но не методы, определенные вне этого класса.
    Применение спецификаторов доступа public и private
    Надлежащее использование спецификаторов доступа public и private
    — залог успеха объектно-ориентированного программирования. Хотя на этот счет не существует жестких правил, все же программисты выработали общие принципы, которыми следует руководствоваться при программировании классов.
    1. Члены, которые используются только внутри класса, следует определить как закрытые.
    2. Данные экземпляров, которые должны находиться в пределах заданного диапазона, следует определить как закрытые, а доступ к ним обеспечить через открытые методы, выполняющие проверку вхождения в диапазон.
    3. Если изменение члена может вызвать эффект, распространяющийся за пределы самого члена (т.е. действует на другие аспекты объекта), этот член следует определить как закрытый и обеспечить к нему контролируемый доступ.
    4. Члены, при некорректном использовании которых на объект может быть оказано негативное воздействие, следует определить как закрытые, а доступ к ним обеспечить через открытые методы, предохраняющие эти члены от некорректного использования.
    5. Методы, которые получают или устанавливают значения закрытых данных, должны быть открытыми.
    6. Объявление переменных экземпляров открытыми допустимо, если нет причин делать их закрытыми.
    Безусловно, существует множество нюансов, не охваченных перечисленными выше принципами. Кроме того, в некоторых случаях одно или несколько правил приходиться нарушать, но чаще всего соблюдение этих принципов позволяет создать объекты с высоким
    “иммунитетом” к некорректному использованию.
    Управление доступом: учебный проект
    Учебный проект поможет вам глубже понять управление доступом к членам класса.
    Один из распространенных примеров объектно-ориентированного программирования — класс, реализуемый в стеке. (Стек — это структура данных, которая реализует список элементов по принципу: первым вошел — последним вышел. В качестве бытового примера стека можно привести стопку тарелок, из которых первая поставленная на стол тарелка, будет использована последней.)

    Глава 8. Подробнее о методах и классах
    183
    Стек — это классический пример объектно-ориентированного программирования, в котором сочетаются как средства хранения информации, так и методы получения доступа к этой информации. Для реализации этого наилучшим образом подходит класс, в котором члены, обеспечивающие хранение данных стека, являются закрытыми, а доступ к ним осуществляется посредством открытых методов.
    В стеке необходимо выполнить две операции: поместить данные в стек и извлечь их оттуда. Каждое значение помещается в вершину стека и извлекается также из его вершины.
    Извлеченное из стека значение удаляется и не может быть извлечено снова.
    В приведенном ниже примере создается класс stack
    , который реализует работу стека. Хранение данных стека обеспечивается на основе закрытого массива. Операции помещения данных в стек и извлечения их из него доступны через открытые методы класса
    Stack
    . Таким образом, механизм “первым вошел — последним вышел” обеспечивается открытыми методами. В нашем примере класс stack предназначен для хранения символов, но аналогичный механизм можно использовать для хранения данных любого другого типа.
    // Класс стека для хранения символов. using System; class Stack {
    //
    Эти члены закрытые. char[] stck; // Массив для хранения данных стека. int tos; // Индекс вершины стека.
    //
    Создаем пустой класс Stack заданного размера. public Stack(int size) { stck = new char[size]; // Выделяем память для стека. tos = 0;
    }
    //
    Помещаем символы в стек. public void push(char ch) { if(tos==stck.Length)
    {
    Console.WriteLine("
    -
    Стек заполнен."); return;
    } stck[tos] = ch; tos++;
    }
    //
    Извлекаем символ из стека. public char pop() { if(tos==0)
    {
    Console.WriteLine("
    -
    Стек пуст."); return(char)
    0;
    } tos--; return stck[tos];
    }
    //
    Метод возвращает значение true, если стек полон. public bool full() { return tos==stck.Length;
    }

    184
    Часть I. Язык C#
    // Метод возвращает значение true, если стек пуст. public bool empty() { return tos==0;
    }
    //
    Возвращает общий объем стека. public int capacity() { return stck.Length;
    }
    //
    Возвращает текущее количество объектов в стеке. public int getNum() { return tos;
    }
    }
    Рассмотрим класс stack подробнее. Его объявление начинается с объявления двух переменных экземпляров: char[] stck; // Массив для хранения данных стека. int tos; // Индекс вершины стека.
    Массив stck обеспечивает хранение данных стека, которыми в нашем случае являются символы. Обратите внимание на то, что память для массива здесь не выделяется.
    Это делается в конструкторе класса stack
    . Член tos содержит индекс вершины стека.
    Оба члена stck и tos по умолчанию объявлены закрытыми, и именно этот факт позволяет обеспечить функционирование механизма “первым вошел — последним вышел”.
    Если бы к массиву stck был разрешен открытый доступ, то к элементам стека можно было бы обращаться совершенно беспорядочно. Кроме того, поскольку член tos содержит индекс “верхнего” элемента стека, чтобы избежать искажения стека, необходимо предотвратить манипуляции над этим членом вне класса stack
    . Доступ пользователя к членам stck и tos должен быть организован косвенным образом, посредством специальных открытых методов. Вот как выглядит конструктор стека:
    // Создаем пустой класс Stack заданного размера. public Stack(int size) { stck = new char[size]; // Выделяем память для стека. tos = 0;
    }
    Этому конструктору передается необходимый размер стека. Поэтому он выделяет соответствующую область памяти для массива и устанавливает переменную экземпляра tos равной нулю. Таким образом, нулевое значение переменной tos служит признаком того, что стек пуст.
    Открытый метод push()
    помещает в стек один элемент. Вот определение этого метода:
    // Помещаем символы в стек. public void push(char ch) { if(tos==stck.Length)
    {
    Console.WriteLine("
    -
    Стек заполнен."); return;
    } stck[tos] = ch; tos++;
    }

    Глава 8. Подробнее о методах и классах
    185
    Элемент, помещаемый в стек, передается в качестве параметра ch
    . Прежде чем элемент будет добавлен в стек, выполняется проверка, хватит ли в массиве места, чтобы принять очередной элемент. Для выполнения этой проверки достаточно убедиться в том, что значение переменной tos не превышает длину массива stck
    . Если еще есть свободное место, элемент сохраняется в массиве stck по индексу, заданному значением переменной tos
    , после чего значение tos инкрементируется. Таким образом, переменная tos всегда содержит индекс следующего свободного элемента в массиве stck
    Чтобы удалить элемент из стека, необходимо вызвать метод pop()
    . Вот его определение:
    // Извлекаем символ из стека. public char pop() { if(tos==0)
    {
    Console. WriteLine(" - Стек пуст."); return(char) 0;
    } tos--; return stck[tos];
    }
    И здесь проверяется значение переменной tos
    . Если оно равно нулю, значит, стек пуст. В противном случае значение tos декрементируется, и по полученному индексу возвращается элемент стека.
    Несмотря на то что push()
    и pop()
    — единственные, жизненно необходимые для реализации стека методы, существуют и другие действия, которые были бы полезны для его функционирования, поэтому в классе
    Stack реализовано еще четыре метода (
    full()
    , empty()
    , capacity()
    и getNum()
    ). Эти методы предоставляют информацию о состоянии стека. Приведем их определения.
    // Метод возвращает значение true, если стек полон. public bool full() { return tos==stck.Length;
    }
    // Метод возвращает значение true, если стек пуст. public bool empty() { return tos==0;
    }
    // Возвращает общий объем стека,. public int capacity() { return stck.Length;
    }
    // Возвращает текущее количество объектов в стеке. public int getNum() { return tos;
    }
    Метод full()
    возвращает значение true
    , если стек полон, и значение false в противном случае. Метод empty()
    возвращает значение true
    , если стек пуст, и значение false в противном случае. Чтобы получить общий объем стека (т.е. количество элементов, которое он может содержать), достаточно вызвать метод capacity()
    . А чтобы узнать, сколько элементов хранится в стеке в данный момент, вызовите метод getNum()
    . Эти методы удобно использовать, поскольку для получения информации,

    186
    Часть I. Язык C# которую они предоставляют, требуется доступ к члену tos
    , который закрыт в рамках класса
    Stack
    Следующая программа демонстрирует работу стека.
    // Демонстрация использование класса Stack. using System; class StackDemo { public static void Main() {
    Stack stk1 = new Stack(10);
    Stack stk2 = new Stack(10);
    Stack stk3 = new Stack(10); char ch; int i;
    //
    Помещаем ряд символов в стек stk1.
    Console.WriteLine(
    "Помещаем символы от А до Z в стек stk1."); for(i=0; !stk1.full(); i++) stk1.push((char) ('A' + i)); if(stk1.full())
    Console.WriteLine("Стек stk1 полон.");
    //
    Отображаем содержимое стека stk1.
    Console.Write("Содержимое стека stk1: "); while( !stk1.empty() ) { ch
    = stk1.pop();
    Console.Write(ch);
    }
    Console.WriteLine(); if(stk1.empty())
    Console.WriteLine("Стек stk1 пуст.\n");
    //
    Помещаем еще символы в стек stk1.
    Console.WriteLine(
    "Снова помещаем символы от А до Z в стек stk1."); for(i=0; !stk1.full(); i++) stk1.push((char) ('A' + i));
    /*
    Теперь извлекаем элементы из стека stk1 и помещаем их в стек stk2.
    В результате элементы стека stk2 должны быть расположены в обратном порядке. */
    Console.WriteLine(
    "Теперь извлекаем элементы из стека stk1 и\n" +
    " помещаем их в стек stk2."); while(!stk1.empty()) { ch = stk1.pop(); stk2.push(ch);
    }
    Console.Write("Содержимое стека stk2: "); while( !stk2.empty() ) { ch
    = stk2.pop();
    Console.Write(ch);
    }

    Глава 8. Подробнее о методах и классах
    187
    Console.WriteLine("\n");
    //
    Помещаем 5 символов в стек stk3.
    Console.WriteLine("Помещаем 5 символов в стек stk3."); for(i=0; i < 5; i++) stk3.push((char) ('A' + i));
    Console.WriteLine(
    "Объем стека stk3: " + stk3.capacity());
    Console.WriteLine(
    "Количество объектов в стеке stk3: " + stk3.getNum());
    }
    }
    При выполнении этой программы получаем следующие результаты:
    Помещаем символы от А до Z в стек stk1.
    Стек stk1 полон.
    Содержимое стека stk1: JIHGFEDCBA
    Стек stk1 пуст.
    Снова помещаем символы от А до Z в стек stk1.
    Теперь извлекаем элементы из стека stk1 и помещаем их в стек stk2.
    Содержимое стека stk2: ABCDEFGHIJ
    Помещаем 5 символов в стек stk3.
    Объем стека stk3: 10
    Количество объектов в стеке stk3: 5
    Передача объектов методам
    До сих пор в качестве параметров методов мы использовали значения типа int или double
    . Наряду с параметрами в виде значений методам можно передавать объекты.
    Рассмотрим, например, следующую программу:
    // Демонстрация возможности передачи методам объектов. using System; class MyClass { int alpha, beta; public MyClass(int i, int j) { alpha = i; beta = j;
    }
    /*
    Метод возвращает true, если параметр ob содержит те же значения, что и вызывающий объект. */ public bool sameAs(MyClass ob) { if((ob.alpha == alpha) & (ob.beta == beta)) return true; else return false;
    }

    188
    Часть I. Язык C#
    // Создаем копию объекта ob. public void copy(MyClass ob) { alpha = ob.alpha; beta = ob.beta;
    } public void show() {
    Console.WriteLine("alpha: {0}, beta: {1}", alpha, beta);
    }
    } class PassOb { public static void Main() {
    MyClass ob1 = new MyClass(4, 5);
    MyClass ob2 = new MyClass(6, 7);
    Console.Write("ob1: "); ob1.show();
    Console.Write("ob2: "); ob2.show(); if(ob1.sameAs(ob2))
    Console.WriteLine(
    "ob1 и оb2 имеют одинаковые значения."); else
    Console.WriteLine(
    "ob1 и оb2 имеют разные значения.");
    Console.WriteLine();
    //
    Теперь делаем объект оb1 копией объекта оb2. ob1.copy(ob2);
    Console.Write("ob1 после копирования: "); ob1.show(); if(ob1.sameAs(ob2))
    Console.WriteLine(
    "ob1 и оb2 имеют одинаковые значения."); else
    Console.WriteLine(
    "оb1 и оb2 имеют разные значения.");
    }
    }
    Выполнив эту программу, получаем такие результаты: ob1: alpha: 4, beta: 5 ob2: alpha: 6, beta: 7 оb1 и ob2 имеют разные значения. оb1 после копирования: alpha: 6, beta: 7 оb1 и оb2 имеют одинаковые значения.
    Каждый из методов — sameAs()
    и сору()
    — принимает в качестве аргумента объект. Метод sameAs()
    сравнивает значения alpha и beta вызывающего объекта со значениями alpha и beta объекта, переданного в качестве аргумента ob
    . Этот метод

    Глава 8. Подробнее о методах и классах
    1   ...   7   8   9   10   11   12   13   14   ...   52


    написать администратору сайта