КАТЕГОРИИ: Архитектура-(3434)Астрономия-(809)Биология-(7483)Биотехнологии-(1457)Военное дело-(14632)Высокие технологии-(1363)География-(913)Геология-(1438)Государство-(451)Демография-(1065)Дом-(47672)Журналистика и СМИ-(912)Изобретательство-(14524)Иностранные языки-(4268)Информатика-(17799)Искусство-(1338)История-(13644)Компьютеры-(11121)Косметика-(55)Кулинария-(373)Культура-(8427)Лингвистика-(374)Литература-(1642)Маркетинг-(23702)Математика-(16968)Машиностроение-(1700)Медицина-(12668)Менеджмент-(24684)Механика-(15423)Науковедение-(506)Образование-(11852)Охрана труда-(3308)Педагогика-(5571)Полиграфия-(1312)Политика-(7869)Право-(5454)Приборостроение-(1369)Программирование-(2801)Производство-(97182)Промышленность-(8706)Психология-(18388)Религия-(3217)Связь-(10668)Сельское хозяйство-(299)Социология-(6455)Спорт-(42831)Строительство-(4793)Торговля-(5050)Транспорт-(2929)Туризм-(1568)Физика-(3942)Философия-(17015)Финансы-(26596)Химия-(22929)Экология-(12095)Экономика-(9961)Электроника-(8441)Электротехника-(4623)Энергетика-(12629)Юриспруденция-(1492)Ядерная техника-(1748) |
Встроенные типы данных 4 страница
4. Даны текстовая строка и слово (например, ab). Напечатать все слова, входящие в эту текстовую строку, заканчивающиеся на буквы заданного слова (например, abcdab, ab, kab), используя методы класса String или StringBuilder. 5. Даны текстовая строка и слово. Определить, какие слова из этой текстовой строки предшествуют заданному слову в лексикографическом порядке, используя методы класса String или StringBuilder. 6. Дан набор слов и произвольная текстовая строка. Выбрать из текстовой строки все слова, входящие в данный набор и вывести их на печать, используя методы класса String или StringBuilder. 7. Дана текстовая строка. Определить, упорядочены ли ее слова по алфавиту, указать первое слово, нарушающее порядок, используя методы класса String или StringBuilder. 8. Из заданной текстовой строки распечатать только те слова, которые начинаются и оканчиваются одной и тоже буквой, используя методы класса String или StringBuilder. 9. Из заданной строки, распечатать все слова без повторений, используя методы класса String или StringBuilder. 10. Дана текстовая строка. Сформировать строку, состоящую из третьих букв каждого слова заданной текстовой строки, используя методы класса String или StringBuilder, и вывести ее на экран. Например, если задана строка adcd ef ghi jklmno, то на экран должно быть выведено слово сil. 11. Дана текстовая строка. Распечатать строку, состоящую из слов заданной, расположенных в лексикографическом порядке, используя методы класса String или StringBuilder. 12. Дана текстовая строка. Распечатать из нее все слова, имеющие наименьшую и наибольшую длину, используя методы класса String или StringBuilder.
Глава 7. Описание классов Принцип инкапсуляции в ООП реализуются с помощью механизма классов. Напомним, что инкапсуляция – это идея объединения данных с функциями их обработки в сочетании со скрытием ненужной для использования этих данных информации. Класс представляет собой абстракцию, которая описывает общее свойства (данные) и общее поведения (функции) для объектов одного типа. Объекты класса также называют экземплярами класса. Объекты одного класса обычно отличаются друг от друга значениями свойств. Класс является типом данных, определяемым пользователем, и содержит описание данных и функций для работы с этими данным. Описать класс возможно следующим образом: [атрибуты][спецификаторы] class имя_класса [:предки] {[список_элементов]} Имя_класса – это идентификатор, определяющий имя нового тип данных. Список элементов может быть пустым. Приведем пример описания простейшего класса: class A{} Классы могут быть описаны внутри пространства имен или внутри другого класса. Спецификаторы класса определяют его свойства и доступность класса для других элементов программы. Спецификаторы доступа приведены в таблице 7.1. Другие спецификаторы и атрибуты класса будут рассмотрены позднее. Таблица 7.1. Спецификаторы доступа классов
По умолчанию используется спецификатор internal.
Элементы класса (компоненты класса, member) делятся на элементы, представляющие данные (данные-члены, элементы данных) и элементы, представляющие функции обработки этих данных (функциональные элементы, компонентные функции, функции-члены). Кроме того, в классе могут быть определены локальные типы данных, например, другой класс. К элементам данных относят поля и константы. Синтаксис описания полей и констант класса в целом соответствует синтаксису описания переменных, однако при их описании можно указывать специальные атрибуты и спецификаторы. К элементам, представляющие функции обработки данных, относят методы, события, делегаты. Синтаксис описания методов класса в целом соответствует синтаксису описания функций в языке С, но имеется ряд отличий. События и делегаты будут рассмотрены позднее. Работа с полями и методам класса отличается от работы с обычными переменными и функциями. Для того чтобы обратиться к элементу класса (полю или методу), используют операцию уточненного имени имя_класса.имя_элемента или имя_объекта_класса.имя_элемента Объект класса (экземпляр класса) может быть создан явным образом (программистом) или неявным образом (системой). Для создания объекта явным образом используется операция new: имя_класса имя_объекта = new имя класса(); При создании объекта класса выделяется память под его поля, но функциональные элементы не тиражируются. Напомним, что классы относятся к ссылочным типам данных, следовательно, присваивание объектов одного и того же класс приведет к некорректным результатам (две ссылки будут указывать на одну и туже область памяти). Чтобы разработать класс, прежде всего надо продумать какие поля и константы он должен содержать. Синтаксис описания полей имеет следующий вид. [ атрибуты ] [ спецификаторы ] [ const ] тип имя [ = начальное_значение ] Если указано ключевое слово const и начальное значение, то это значит, что описана константа. Константы хранят неизменные значения, связанные с классом, а не с конкретным объектом этого класса. Если слово const отсутствует, то описано поле. У различных объектов класса значения одного и того же поля могут различаться. Спецификаторы полей и констант определяют их свойства и доступность для других элементов программы. Спецификаторы доступа приведены в таблице 2. Другие спецификаторы и атрибуты класса будут рассмотрены позднее.
Таблица 7.2. Спецификаторы доступа полей и констант класса
По умолчанию вид доступа – private. Поля класса рекомендуется делать закрытыми, именно этот прием позволяет в полной мере реализовать принцип инкапсуляции. Если поле класса является открытым, то любой другой элемент программы имеет доступ к нему, а значит, может его изменить, тем самым нарушив целостность объекта. Например, если у класса «Студент» поля «Специальность» и «Факультет» сделать открытыми, то их можно будет изменять независимо друг от друга, что логически неверно. Еще одним важным спецификатором для полей является спецификатор static. Если поле описано с данным спецификатором, то оно называется статическим. Статические поля и константы применяются для хранения данных, общих для всех объектов класса. Память под статические поля и константы выделяется один раз при описании класса независимо от числа созданных объектов (экземпляров класса) и даже при их отсутствии. Статические поля и константы доступны только как через имя класса. Если класс содержит только статические элементы и константы, то создавать экземпляры класса не требуется. namespace ConsoleApplication1 { class A { public const char a = 'a'; // открытая константа public int b; // открытое поле данных public static string c = "Test"; // статическое открытое поле класса double d; // закрытое поле данных }
class Class1 { static void Main() { A x = new A(); // создание экземпляра класса A Console.WriteLine(A.a); // обращение к константе //Console.WriteLine(x.a); // недопустимое обращение к константе Console.WriteLine(x.b); //обращение к полю класса //Console.WriteLine(A.b); //недопустимое обращение к полю класса Console.WriteLine(A.c); // обращение к статическому полю //Console.WriteLine(x.c); // недопустимое обращение к статическому полю //Console.WriteLine(x.d);// недопустимое обращение к закрытому полю Console.ReadKey(); } } } Поле класса также может быть описано со спецификатором readonly. Такие поля предназначены только для чтения. Установить значение такого поля можно либо при его описании, либо в конструкторе (специальном методе класса, см. ниже). Для констант данный спецификатор не используется. Если поле не инициализировано в классе, и отсутствует конструктор (как в вышеприведенном примере), то полям значимых типов присваивается нуль, полям ссылочных типов — значение null. Отметим, в качестве полей класса следует описывать только необходимые свойства объекта. Поля класса существенным образом влияют на память, необходимую программной системе при работе с экземплярами класса. Если проектируется класс Account, задающий банковский счет, то можно включить в его состав поле balance, задающее текущее значение счета. Но тогда сотни тысяч объектов, задающих счета клиентов, будут иметь это поле, и суммарная память, требуемая системе, существенно возрастет. Альтернативой является включение в класс Account метода balance, который будет вычислять баланс, всякий раз, когда тот понадобится. Возрастет объем вычислений, но существенно сократится требуемая память. На этот компромисс между памятью и временем всегда приходится идти при проектировании класса. Перейдем к рассмотрению методов класса. Как уже было отмечено, синтаксис описания методов класса в целом соответствует синтаксису описания функций в языке С, однако имеется ряд особенностей. Метод - это именованная логически законченная группа операторов языка, которую можно вызвать для выполнения по имени любое количество раз из различных мест программы. Синтаксис определения функции в языке С#: [ атрибуты ] [ спецификаторы ] тип имя_метода ([ параметры ]) тело_метода Атрибуты методов будут рассмотрены позднее. Методы могут иметь такие же спецификаторы доступа, как и поля класса (см. таблицу 2). Так как поля класса, в соответствии с принципом инкапсуляции, рекомендуется делать закрытыми, то работа с объектами класса на практике чаще всего осуществляется с помощью открытых методов. Говорят, что открытые элементы класса – это его интерфейс. Закрытыми делают вспомогательные методы, которые используются для работы открытых методов. Очевидно, что создавать класс, в котором все поля и методы закрыты бессмысленно – работать с таким классом невозможно. Методы также могут иметь спецификатор static. Это так называемые статические методы, которые могут быть вызваны только через имя класса, без создания объектов этого класса. Например, метод WriteLine класса Console является статическим. Заголовком метода (или объявлением, прототипом, сигнатурой) называют запись [ атрибуты ] [ спецификаторы ] тип имя_метода ([ параметры ]) К написанию тела метода можно приступать только, если полностью описан его заголовок. Тип метода – это тип, вычисляемого в нем значения (так называемого, возвращаемого значения). Если в методе не вычисляется какое-либо значение, то тип метода void. Параметры метода - это объекты, необходимые для его работы. Список параметров определяет величины, которые требуется передать в метод при ее вызове. Элементы списка параметров разделяются запятыми. Для каждого параметра указывается тип и имя. Параметры, перечисляемые в заголовке метода, называются формальными, а параметры, записанные в операторе вызова метода, называются фактическими. Метод возвращает значение с помощью оператора return выражение; или return;. Выражение в операторе return должно иметь тот же тип что и тип метода. Если метод имеет тип void, то этот оператор можно опустить. Основное отличие нестатического метода в языке C# от функции в языке С заключатся в следующем. Вызов функции в языке С осуществляется по имени имя_функции (список_фактичесикх_параметров), Вызов нестатического метода осуществляет для конкретного объекта с помощью операции уточненного имени: имя. объекта.имя_метода (список_фактичесикх_параметров);
Пример описания функции сложения двух целых чисел в языке С: int sum (int a, int b) { int c=a+b; return c;}
Пример вызова функции: printf(“%d”,sum(2,3)); Пример описания метода сложения двух целочисленных полей класса в языке С#: class A { int a=1, b=2; public int sum() { int c = a + b; return c; } } Пример вызова метода: A x = new A(); // создание экземпляра класса A Console.WriteLine(x.sum()); // обращение к методу
Обратите внимание, что при описании функции два целых числа передаются в нее как параметры. При описании нестатического метода класса, поля класса не предаются в качестве параметров. Дело в том, что в любой нестатический метод автоматически передается скрытый параметр this, в котором хранится ссылка на вызвавший функцию экземпляр. В явном виде параметр this применяется для того, чтобы возвратить из метода ссылку на вызвавший объект, а также для идентификации поля в случае, если его имя совпадает с именем параметра метода, например: class A { int a=1, b=2; public void output() { Console.WriteLine("a={0}\tb={1}\n",a,b); } public A set(int a, int b) { this.a = a; this.b = b; // имя поля совпадает с именем параметра return this;// возвращает ссылка на вызвавший метод объект } }
class Class1 { static void Main() { A x = new A(); // создание экземпляра класса A x.output(); // a=1 b=2 (x.set(3, 4)).output();// a=3 b=4 } } В статические методы ссылка this не передается. Если по каким-либо причинам необходимо создать статический метод, складывающий поля объектов класcа A, то он будет описан и вызван следующим образом: class A { int a = 1, b = 2; static public int sum(A ob) { int c = ob.a + ob.b; return c; } }
class Class1 { static void Main() { A x = new A(); // создание экземпляра класса A Console.WriteLine(A.sum(x)); // } } При вызове любого метода в первую очередь вычисляются выражения, стоящие на месте фактических параметров, затем в стеке выделяется память под формальные параметры функции в соответствии в их типом, и каждому из них присваивается значение соответствующего фактического параметра. В стеке также сохраняется адрес команды, вызвавший функцию. Именно его и извлекает из стека оператор return. Если в функции, тип которой не void, оператор return отсутствует, то возврат в место вызова не произойдет. В этом случае будут выполняться все команды из памяти подряд, что потенциально приводит к аварийному завершению программы. Последовательность фактических параметров в вызове метода и их тип должны точно совпадать с формальными параметрами этого метода. Существуют два способа передачи параметров в метод: по значению и по ссылке. При передаче по значению метод получает копии значений аргументов, и операторы метода работают с этими копиями. Доступа к исходным значениям аргументов у метода нет, а, следовательно, нет и возможности их изменить. При передаче по ссылке (по адресу) метод получает копии адресов параметров, он осуществляет доступ к ячейкам памяти по этим адресам и может изменять исходные значения аргументов, модифицируя параметры. В C# для обмена данными между вызывающим и вызываемым методом предусмотрено четыре типа параметров, представленных в таблице 3. Таблица 7.3. Виды параметров методов
Заметим, что все рассмотренные выше методы классов имели параметры-значения. Рассмотрим пример метода, в котором используются параметры всех четырёх типов. static int Calculate(int a, ref int b, out int c, params int[] d) { a++; b++; c=3; int sum = a + b + c; foreach (int x in d) { sum+=x; } d[0] = 4; return sum;
} static void Main() {
int x=1,y=1,z; int [] m ={1,1,1}; Console.WriteLine(Calculate(x, ref y, out z,m));//10 Console.WriteLine("x={0}, y={1}, z={2}, m[0]={3}",x, y, z, m[0]);//1 2 3 4 Console.ReadKey(); }
Отметим, что методы c переменным количеством параметров (со спецификаторов params) реализуются менее эффективно, чем обычные. Следовательно, если в метод надо передавать один или два или три параметра, лучше написать не метод с переменным количеством параметров, а три перегруженных метода. Перегруженные методы - это методы с одним именем и разными параметрами. Перегрузка методов является проявлением одного из основных принципов ООП – полиморфизма. На практике перегрузка методов осуществляется очень часто. Например, метод WriteLine перегружен 19 раз для разных типов параметров. Например, Console.WriteLine("\nHello, World!"); Console.WriteLine("\na={0}, b={1}", 10, 5); Приведем пример описания перегруженных методов // Возвращает наибольшее из двух целых: int max(int a, int b) {if (a>b) return a; else return b;} // Возвращает наибольую из длин двух строк: int max (string a, string b) {if (a.Length>b.Length) return a.Length; else return b.Length;} // Возвращает наибольшее из числа и длины строки: int max (string a, int b) {if (a.Length>b) return a.Length; else return b;} Console.WriteLine(max(1, 2)); //2 Console.WriteLine(max(“aaa”, “bbbb”));// 4 Console.WriteLine(max(“aaa”, 1));//3
Если требуется написать перегруженные методы, выполняющие действия с одинаковыми параметрами (что синтаксически недопустимо), то обычно добавляют фиктивный параметр или изменяют способ передачи параметра в метод, например ниже праведны заголовки двух перегруженных методов int f (int a); int f(ref int a); Любой класс содержит специальные методы, которые называются конструкторами. Конструктор – это специальный метод класса, позволяющий создавать объекты класса. Именно конструктор вызывается автоматически при создании объекта класса с помощью операции new. Так как при создании объекта класса происходит выделение памяти под его поля, то в конструкторе обычно происходит инициализация всех полей. Опишем свойства конструкторов. 1. Имя конструктора совпадает с именем класса 2. Конструктор не возвращает значение, даже типа void. 3. Класс может иметь несколько перегруженных конструкторов (с разными параметрами) 4. Конструктор без параметров, называется конструктором по умолчанию. 5. В каждом классе есть хотя бы один конструктор. 6. Если в классе не описан ни один конструктор, то в нем автоматически создаться конструктор по умолчанию. Если поля не были инициализированы при описании, то данный конструктор полям значимых типов присвоит нуль, полям ссылочных типов — значение null. 7. Если в классе явно описан хотя бы один конструктор, то конструктор по умолчанию автоматически не создаться. Таким образом, описание класса, всегда начинается с описания полей и определения конструкторов и их тестирования. Самый простой способ проверить правильность работы конструкторов, создать в функции Main объекты класса с помощью различных конструкторов и вывести все поля созданных объектов на экран. В нашем примере для вывода полей объекта на экран используется метод output. Пока не проверена правильность работы конструкторов, определять другие нестатические методы класса не имеет смысла, так как они будут вызываться для конкретных объектов, созданных тем или иным конструктором. Рассмотрим еще два специальных вида методов класса – свойства класса и перегруженные операции. Свойства (Properties)– это специальные методы класса, предназначенные для обеспечения эффективной работы с полями класса, а именно для организации доступа к ним. Напомним, что правильной стратегией является закрытие полей - поля объявляются с модификатором protected или private. Это облегчает возможную модификацию класса в будущем. Класс может изменить представление данных, сохранив интерфейс, предоставляемый клиентам. В этом случае изменения в полях не отразятся на клиентах. Однако, закрытие полей не означает, что пользователи не могут работать с данными, хранящимися в полях класса. Возможны различные стратегии доступа клиента к закрытым полям класса, например: чтение, запись (Read, Write); чтение, запись при первом обращении (Read, Write-once); только чтение (Read-only); только запись (Write-only); ни чтения, ни записи (Not Read, Not Write). Свойства позволяют эффективно реализовать эти стратегии. Как правило, свойство определяет методы его получения значения поля и его установки. Синтаксис свойства: [ атрибуты ] [ спецификаторы ] тип имя_свойства { [[ атрибуты ] [ спецификаторы ] get код_доступа ] [[ атрибуты ] [ спецификаторы ] set код_доступа ] } Тип совпадает с типом поля, к которому свойство обеспечивает доступ. Код доступа представляет собой блоки операторов, которые выполняются при получении (get) или установке (set) значения поля. Может отсутствовать либо часть get, либо set, но не обе одновременно. Если отсутствует часть set, свойство доступно только для чтения (read-only), если отсутствует часть get, свойство доступно только для записи (write-only). Значения спецификаторов такие же как и для обычных методов. Чаще всего свойства объявляются со спецификатором public. Спецификаторы доступа для отдельной части должны задавать такой же, либо более ограниченный доступ, чем спецификаторы доступа для свойства в целом. Таким образом, возможно задавать разный уровень доступа для частей get и set. Метод записи set обычно содержит действия по проверке допустимости устанавливаемого значения. В методе set используется параметр со стандартным именем value, который содержит устанавливаемое значение. Метод чтения get должен содержать оператор return. Тип выражения в операторе return должен совпадать с типом свойства. Чаще всего в качестве выражения используют имя поля, к которому свойство обеспечивает доступ. Данный метод может содержать, например, поддержку счетчика обращений к полю. В программе обращение к свойствам выглядит как обращение к полям класса. При обращении к свойству автоматически вызываются указанные в нем методы чтения и установки. Например, Заметим, что синтаксически, свойство может не связываться с полем, а просто описывать один или два метода, которые осуществляют некоторые действия над данными того же типа, что и свойство. Но это не имеет никакого преимущества перед описанием обычных методов. Перейдем к рассмотрению следующего вида методов – перегруженных операций. Если в программе, которая использует объекты класса CStudent написать CStudent s1 = new CStudent(); CStudent s = new CStudent(); if (s1 == s) Console.WriteLine("Yes"); else Console.WriteLine("No"); то она выведет на экран слово No, так как сравниваются ссылки на объекты. А если написать if (s1>s), то копмилятор выдаст ошибку, т.к. операция > не определена для объектов класса CStudent. Однако в С# можно переопределить (перегрузить) большинство операций так, чтобы при использовании с объектами конкретного типа они выполняли заданные функции. Это дает возможность использовать собственные типы данных точно также как стандартные. Операции перегружают в основном для классов, с помощью которых задают какие-либо математические понятия, т.е. тогда, когда знаки операции имеют общепринятую семантику. На практике Вы уже использовали перегруженные операции, например операция + для класса string. Перегрузка операций осуществляется с помощью методов специального вида, которые называют операциями класса. Синтаксис описания операции класса [атрибуты ] public static тип operator операция (список параметров) {тело} Правила описания перегруженных операций: - параметры в операцию передаются только по значению; - заголовки всех операций должны различаться; - типы, используемые в операции, должны быть иметь не меньшие права доступа, чем сама операция. - при перезагрузке операций сохраняется количество аргументов, приоритеты операций и правила ассоциации; - для стандартных типов данных переопределять операции нельзя. В языке С# можно переопределять унарные операции, бинарные и операцию преобразования типа. Из унарных операций в классе можно переопределить следующие: + -! ~ ++ -- true false Перегруженная унарная операция всегда описывается только с одним параметром, который должен иметь тип класса, для которого определяется операция. Перегруженная унарная операция должна возвращать: - для операций +, -,! и ~ величину любого типа; - для операций ++ и -- величину типа класса, для которого она определяется; - для операций true и false величину типа bool. Операции не должны изменять значение передаваемого им операнда. Префиксный и постфиксный инкремент не различаются (для них может существовать только одна реализация, которая вызывается в обоих случаях). Из бинарных операций в классе можно переопределить следующие: + - * / % & | ^ << >> ==!= > < >= <= Перегруженная бинарная операция должна иметь два параметр, хотя бы один из которых должен иметь тип класса, для которого она определяется. Операция может возвращать величину любого типа. Операции отношений (== и!=, > и <, >= и <=) определяются только парами и обычно возвращают логическое значение. Чаще всего в классе определяют операции сравнения на равенство и неравенство для того, чтобы обеспечить сравнение объектов, а не их ссылок, как определено по умолчанию для ссылочных типов. Перегрузка операций отношений осуществляется с использованием интерфейсов и будет рассмотрена в теме «Интерфейсы». Сокращенные операции присваивания (+=, -=, *=, /=, %=, &=, |=, ^=, <<=, >>=) не могут быть перегружены программистом, они перегружаются автоматически, если перегружена соответствующая бинарная операция. Рассмотрим пример перегруженной операции + для класса рациональных чисел class Rational {
Дата добавления: 2014-12-27; Просмотров: 1110; Нарушение авторских прав?; Мы поможем в написании вашей работы! Нам важно ваше мнение! Был ли полезен опубликованный материал? Да | Нет |