Студопедия

КАТЕГОРИИ:


Архитектура-(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)

Конструктори




Ключове слово this

Кожен об'єкт містить свій екземпляр полів класу. Методи знаходяться в пам'яті в єдиному екземплярі і використовуються всіма об'єктами спільно, тому необхідно забезпечити роботу методів нестатичних екземплярів з полями саме того об'єкту, для якого вони були викликані. Для цього в будь-який нестатичний метод автоматично передається прихований параметр this, в якому зберігається посилання на екземпляр, що викликав функцію.

У явному вигляді параметр this застосовується для того, щоб повернути з методу посилання на об'єкт, що викликав, а також для ідентифікації поля у випадку, якщо його ім'я збігається з ім'ям параметра методу, наприклад:

 

class Demo

{

double у;

public Demo T() // метод повертає посилання на екземпляр

{

return this;

}

public void Sety (double у)

{

this.y = у; // полю у привласнюється значення параметра

}

}

 

 

Конструктор призначений для ініціалізації об'єкту. Він викликається автоматично при створенні об'єкту класу за допомогою операції new. Ім'я конструктора збігається з ім'ям класу. Нижче перераховані властивості конструкторів:

§ Конструктор не повертає значення, навіть типу void.

§ Клас може мати декілька конструкторів з різними параметрами для різних видів ініціалізації.

§ Якщо програміст не вказав жодного конструктора або якісь поля не ініціалізовані, полям значущих типів привласнюється нуль, полям посилальних типів - значення null.

§ Конструктор, що викликається без параметрів, називається конструктором за умовчанням.

 

До цих пір ми задавали початкові значення полів класу при описі класу (лістинг 5.1). Це зручно у тому випадку, коли для всіх екземплярів класу початкові значення деякого поля однакові. Якщо ж при створенні об'єктів потрібно привласнювати полю різні значення, це слід робити в конструкторі. У лістингу 5.6 в клас Demo доданий конструктор, а поля зроблені закритими (непотрібні в даний момент елементи відсутні). У програмі створюються два об'єкти з різними значеннями полів.

 

Лістинг 5.6. Клас з конструктором

 

using System;

namespace ConsoleApplicationl

{

class Demo

{

public int a = 1;

public const double с = 1.66;

double y;

 

public Demo(int a, double y) // конструктор з параметрами

{

this.a = a;

this.y = y;

}

 

public double Gety() // метод отримання поля у

{

return y;

}

 

 

}

 

class Classl

{

static void Main()

{

Demo a = new Demo(300, 0.002); // виклик конструктора

Console.WriteLine (a.Gety()); // результат: 0,002

Demo b = new Demo(1,5.71); // виклик конструктора

Console.WriteLine(b.Gety()); // результат: 5,71

}

}

}

 

Часто буває зручно задати в класі декілька конструкторів, щоб забезпечити можливість ініціалізації об'єктів різними способами. Приклад:

 

class Demo

{

public Demo(int a) // конструктор 1

{

this.a = a;

this.у = 0.002;

}

 

public Demo(double у) // конструктор 2

{

this.a = 1;

this.у = у;

}

}

Demo x = new Demo(300); // виклик конструктора 1

Demo у = new Demo(5.71); // виклик конструктора 2

 

Всі конструктори повинні мати різні сигнатури.

Якщо один з конструкторів виконує які-небудь дії, а інший повинен робити те ж саме плюс ще що-небудь, зручно викликати перший конструктор з другого. Для цього використовується вже відоме вам ключове слово this в іншому контексті, наприклад:

 

class Demo

{

public Demo(int a) // конструктор 1

{

this.a = a;

}

public Demo(int a, double у): this(a) // виклик конструктора 1

{

this.у = у;

}

}

 

Конструкція, що знаходиться після двокрапки, називається ініціалізатором, тобто тим кодом, який виконується до початку виконання тіла конструктора.

Як ви пам'ятаєте, всі класи в С# мають загального предка - клас object. Конструктор будь-якого класу, якщо не вказаний ініціалізатор, автоматично викликає конструктор свого предка. Це можна зробити і явним чином за допомогою ключового слова base, що позначає конструктор базового класу. Таким чином, перший конструктор з попереднього прикладу можна записати і так:

 

public Demo(int а): base() // конструктор 1

{

this.а = а;

}

 

Конструктор базового класу викликається явним чином в тих випадках, коли йому потрібно передати параметри.

До цих пір мова йшла про “звичайні ” конструктори, або конструктори екземпляра. Існує другий тип конструкторів - статичні конструктори, або конструктори класу. Конструктор екземпляра ініціалізував дані екземпляра, конструктор класу - дані класу.

У класі, що складається тільки із статичних елементів (полів і констант), описувати статичний конструктор не обов'язково, початкові значення полів зручніше задати при їх описі.

В лістингу 5.7 приведений приклад статичного класу.

 

Лістинг 5.7. Статичний клас

using System;

namespace ConsoleApplicationl

{

static class D

{

static int a = 200;

static double b = 0.002;

 

public static void Print ()

{

Console.WriteLine("a="+a);

Console.WriteLine("b="+b);

}

}

 

class Classl

{

static void Main()

{

D.Print();

}

}

}

 

Як приклад, на якому демонструватиметься робота різними елементами класу, створимо клас Monster, моделюючий персонаж комп'ютерної гри. Для цього потрібно задати його властивості (наприклад, кількість щупальців, силу або наявність гранатомета) і поведінку. Опис класу Monster приводиться на лістингу 5.8.

 

Лістинг 5.8. Клас Monster

using System;

namespace ConsoleApplication1

{

class Monster

{

public Monster()

{

this.name = "Noname";

this.health = 100;

this.ammo = 100;

}

 

public Monster(string name): this()

{

this.name = name;

}

 

public Monster(int health, int ammo, string name)

{

this.name = name;

this.health = health;

this.ammo = ammo;

}

 

public string GetName()

{

return name;

}

 

public int GetHealth()

{

return health;

}

 

public int GetAmmo()

{

return ammo;

}

 

public void Passport()

{

Console.WriteLine("Monster {0} \t health = {1} ammo = {2}",

name, health, ammo);

}

string name; // закриті поля

int health, ammo;

}

 

class Classl

{

static void Main()

{

Monster X = new Monster();

X.Passport();

Monster Vasia = new Monster("Vasia");

Vasia.Passport();

Monster Masha = new Monster(200, 200, "Masha");

Masha.Passport();

}

}

}

 

Результат роботи програми:

Monster Noname health = 100 ammo = 100

Monster Vasia health = 100 ammo = 100

Monster Masha health = 200 ammo = 200

 

У класі три закриті поля (name, health і ammo), чотири методи (Getname, Gethealth, Getammo і Passport) і три конструктори.

 

5.6. Властивості

 

Властивості служать для організації доступу до полів класу. Як правило, властивість пов'язана із закритим полем класу і визначає методи його отримання і установки. Синтаксис властивості:

[ атрибути ] [ специфікатори ] тип ім’я_властивості

{

[ get код_доступа ]

[ set код_доступа ]

}

Значення специфікаторів для властивостей і методів аналогічні. Частіше за всю властивість оголошуються як відкриті (із специфікатором public), оскільки вони входять в інтерфейс об'єкту.

 

Код доступу є блоками операторів, які виконуються при отриманні (get) або установці (set) властивості. Може бути відсутньою або частина get, або set, але не обидві одночасно.

Якщо відсутня частина set, властивість доступна тільки для читання (read-only), якщо відсутня частина get, властивість доступна тільки для запису (write-only).

Введена зручна можливість задавати різні рівні доступу для частин get і set. Наприклад, в багатьох класах виникає потреба забезпечити необмежений доступ для читання і обмежений - для запису.

Специфікатори доступу для окремої частини повинні задавати або такий же, або більш обмежений доступ, ніж специфікатор доступу для властивості в цілому. Наприклад, якщо властивість описана як publiс, її частини можуть мати будь-який специфікатор доступу, а якщо властивість має доступ protected internal, її частини можуть оголошуватися як internal, protected або private. Синтаксис властивості має вигляд:

 

[ атрибути ] [ специфікатори ] тип ім'я_властивості

{

[ [ атрибути ] [ специфікатори ] get код_ доступу ]

[ [ атрибути ] [ специфікатори ] set код_доступу ]

}

 

Приклад опису властивостей:

 

public class Button: Control

{

private string caption; // закрите поле, з яким пов'язана властивість

public string Caption // властивість

{

get { // спосіб отримання властивості

return caption;

}

 

set { // спосіб установки властивості

if (caption!= value)

caption = value;

}

}

}

Двокрапка між іменами Button і Control в заголовку класу Button означає, що клас Button є похідним від класу Control.

При зверненні до властивості автоматично викликаються вказані в нім методи читання і установки.

Синтаксично читання і запис властивості виглядають майже як методи. Метод get повинен містити оператора return, що повертає вираз, для типу якого повинне існувати неявне перетворення до типу властивості. У методі set використовується параметр із стандартним ім'ям value, який містить встановлюване значення.

Взагалі кажучи, властивість може і не зв'язуватися з полем. Фактично, воно описує один або два методи, які здійснюють деякі дії над даними того ж типу, що і властивість. На відміну від відкритих полів, властивості забезпечують розділення між внутрішнім станом об'єкту і його інтерфейсом і, таким чином, спрощують внесення змін до класу.

За допомогою властивостей можна відкласти ініціалізацію поля до того моменту, коли його фактично буде потрібно, наприклад:

 

class А

{

private static ComplexObject x; // закрите поле

public static ComplexObject X; // властивість

{

get

{

if (х = = null)

x = new ComplexObject(); // створення об’єкта при 1-му зверненні

return x;

}

}

}

 

Додамо в клас Monster, описаний в лістингу 5.8, властивості, що дозволяють працювати із закритими полями цього класу. Властивість Name зробимо доступною тільки для читання, оскільки ім'я об'єкту задається в конструкторі і його зміна не передбачена, у властивостях Health і Ammo введемо перевірку на додатність встановлюваної величини.

 

Лістинг 5.9. Клас Monster з властивостями

using System;

namespace ConsoleApplication1

{

class Monster

{

public Monster()

{

this.name = "Noname";

this.health = 100;

this.ammo = 100;

}

 

public Monster(string name): this()

{

this.name = name;

}

 

public Monster(int health, int ammo, string name)

{

this.name = name;

this.health = health;

this.ammo = ammo;

}

 

public int Health // властивість Health пов'язана з полем health

{

get

{

return health;

}

set

{

if (value > 0) health = value;

else health = 0;

}

}

 

public int Ammo // властивість Ammo пов'язана з полем ammo

{

get

{

return ammo;

}

set

{

if (value > 0) ammo = value;

else ammo = 0;

}

}

 

public string Name // властивість Name пов'язана з полем name

{

get

{

return name;

}

}

public void Passport()

{

Console.WriteLine("Monster {0} \t health = {1} ammo = {2}",

name, health, ammo);

}

string name; // закриті поля

int health, ammo;

}

class Classl

{

static void Main()

{

Monster Masha = new Monster(200,200, "Masha");

Masha.Passport ();

--Masha. Health;

Masha.Ammo += 100;

Masha.Passport ();

}

}

}

Результат роботи програми:

 

Monster Masha health=200 ammo = 200

Monster Masha health=199 ammo = 300

 

5.7. Рекомендації по програмуванню

При створенні класу, тобто нового типу даних, слід добре продумати його інтерфейс. Інтерфейс добре спроектованого класу інтуїтивно ясний.

Поля переважно робити закритими (private). Це дає можливість згодом змінити реалізацію класу без змін в його інтерфейсі, а також регулювати доступ до полів класу за допомогою набору що надаються користувачеві властивостей і методів. Важливо пам'ятати, що поля класу вводяться тільки для того, щоб реалізувати характеристики класу, представлені в його інтерфейсі за допомогою властивостей і методів.

Не потрібно розширювати інтерфейс класу без необхідності. Збільшення кількості методів утрудняє розуміння класу користувачем. У ідеалі інтерфейс має бути повним, тобто надавати можливість виконувати будь-які розумні дії з класом, і одночасно мінімально необхідним - без дублювання і перетину можливостей методів.

Методи визначають поведінку класу. Кожен метод класу повинен вирішувати тільки одну задачу (не треба об'єднувати два короткі незалежні фрагменти коду в один метод). Розмір методу може варіюватися в широких межах, все залежить від того, які функції він виконує.

Якщо метод реалізує складні дії, слід розбити його на послідовність кроків, і кожен крок оформити у вигляді допоміжної функції. Якщо деякі дії зустрічаються в коді, хоч би двічі, їх також потрібно оформити у вигляді окремої функції.

Переважно, щоб кожна функція обчислювала рівно один результат, проте це не завжди виправдано. Якщо величина обчислюється усередині функції і повертається з неї через список параметрів, необхідно використовувати перед відповідним параметром ключове слово out. Якщо параметр значущого типу може змінити свою величину усередині функції, перед ним ставиться ключове слово ref. Величини посилального типу завжди передаються за адресою і, отже, можуть змінити усередині функції своє значення.

Необхідно прагнути до максимального скорочення зони дії кожної змінної, тобто до реалізації принципу інкапсуляції. Це спрощує відладку програми. Поля, що характеризують клас в цілому, тобто що мають одне і те ж значення для всіх екземплярів, слід описувати як статичні. Всі літерали, пов'язані з класом (числові і рядкові константи), описуються як поля-константи з іменами, що відображають їх сенс.

РОЗДІЛ 6. МАСИВИ І РЯДКИ

Елементи масиву мають одне і те ж ім'я, а розрізняються порядковим номером (індексом). Це дозволяє компактно записувати множину операцій за допомогою циклів.

Масив відноситься до посилальних типів даних, тобто розташовується в динамічній області пам'яті, тому створення масиву починається з виділення пам'яті під його елементи. Елементами масиву можуть бути величини як значущих, так і посилальних типів (зокрема масиви). Масив значущих типів зберігає значення, масив посилальних типів - посилання на елементи. Всім елементам при створенні масиву привласнюються значення за умовчанням: нулі для значущих типів і null - для посилальних.

На рис. 6.1 представлений масив, що складається з п'яти елементів будь-якого значущого типу, наприклад int або double, а рис. 6.2 ілюструє організацію масиву з елементів посилального типу.

Ось, наприклад, як виглядають оператори створення масиву з 10 цілих чисел і масиву з 100 рядків:

 

int[] w = new int [10];

string[] z = new string [100];

 

Рис. 6.1. Прості змінні і масив з елементів значущого типу

 

 

Рис. 6.2. Масив з елементів посилального типу

 

 

У першому операторі описаний масив w типу int[]. Операція new виділяє пам'ять під 10 цілих елементів, і вони заповнюються нулями.

У другому операторі описаний масив z типу string[]. Операція new виділяє пам'ять під 100 посилань на рядки, і ці посилання заповнюються значенням null. Кількість елементів в масиві (розмірність) не є частиною його типу, ця кількість задається при виділенні пам'яті і не може бути змінена згодом. Розмірність може задаватися не тільки константою, але і виразом. Результат обчислення цього виразу має бути додатним, а його тип повинен мати неявне перетворення до int, uint, long або ulong.

Приклад розмірності масиву, заданої виразом:

 

short n = 10;

string[] z = new string[n + 1];

 

Елементи масиву нумеруються з нуля, тому максимальний номер елементу завжди на одиницю менше розмірності (наприклад, в описаному вище масиві w елементи мають індекси від 0 до 9). Для звернення до елементу масиву після імені масиву указується номер елементу в квадратних дужках, наприклад:

 

w[4] z[i]

 

З елементом масиву можна робити все, що допустимо для змінних того ж типу. При роботі з масивом автоматично виконується контроль виходу за його межі: якщо значення індексу виходить за межі масиву, генерується виключення IndexOutOfRangeException.

Масиви одного типу можна привласнювати один одному. При цьому відбувається привласнення посилань, а не елементів, як і для будь-якого іншого об'єкту посилального типу, наприклад:

 

int[] а = new int[10];

int[] b = а; // b і а указують на один і той же масив

 

Всі масиви в С# мають загальний базовий клас Array, визначений в просторі імен System. У нім є декілька корисних методів, що спрощують роботу з масивами

У С# існують три різновиди масивів: одновимірні, прямокутні і ступінчасті.

 

6.1. Одновимірні масиви

Одновимірні масиви використовуються в програмах найчастіше. Варіанти опису масиву:

тип[] ім'я;

тип[] ім'я = new тип [ розмірність ];

тип[] ім'я = {список_ініціалізаторів };

тип[] ім'я = новий тип [] { список ініціалізаторів };

тип[] ім'я = new тип [ розмірність ] { список ініціалізаторів };

Приклади описів (один приклад для кожного варіанту опису):

 

int[] а; // 1 елементів немає

int[] b new int[4]; // 2 елементи дорівнюють 0

int[] c = { 61, 2, 5, -9 }; // 3 new мається на увазі

int[] d = new int[] { 61, 2, 5, -9}; // 4 розмірність обчислюється

int[] e = new int[4] { 61, 2, 5, -9}; // 5 надмірний опис

 

Тут описано п'ять масивів. Відмінність першого оператора від останніх полягає в тому, що в нім, фактично, описано тільки посилання на масив, а пам'ять під елементи масиву не виділена. Якщо список ініціалізації не заданий, розмірність може бути не тільки константою, але і виразом типу, що приводиться до цілого.

У кожному з решти масивів по чотири елементи цілого типу. Як видно з операторів 3-5, масив при описі можна ініціалізувати. Якщо при цьому не задана розмірність (оператор 3), кількість елементів обчислюється по кількості ініціалізованих значень. Для полів об'єктів і локальних змінних можна не вказувати операцію new, вона буде виконана за умовчанням (оператор 2). Якщо присутня і розмірність, і список ініціалізаторів, розмірність має бути константою (оператор 4).

Якщо кількість ініціалізованих значень не збігається з розмірністю, виникає помилка компіляції.

Як приклад розглянемо програму, яка визначає суму і кількість від’ємних елементів, а також максимальний елемент масиву, що складається з 6 цілочи-сельних елементів (лістинг 6.1).

 

Лістинг 6.1. Робота з одновимірним масивом

using System;

namespace ConsoleApplication1

{

class Classl

{

static void Main()

{

const int n = 6;

int i;

int[] a = new int[n] {3, 12, 5, -9, 8, -4};

Console.WriteLine("Початковий масив:");

for (i = 0; i<n; i ++)

Console.Write("\t" + a[i ]);

Console.WriteLine();

 

long sum = 0; // сума від’ємних елементів

int num = 0; // кількість від’ємних елементів

for (i = 0; i< n; i ++)

if (a[i] < 0)

{

sum += a[i];

num ++;

}

Console.WriteLine("Сума від’ємних елементів= " + sum);

Console.WriteLine("Кількість від’ємних елементів = " + num);

 

int max = a[0]; // максимальний елемент

for (i =1; i < n; i ++)

if (a[i ] > max) max = a[i];

Console.WriteLine("Максимальний елемент = " + max);

}

}

}

 

6.2. Прямокутні масиви

Прямокутний масив має більше одного вимірювання. Найчастіше в програмах використовуються двовимірні масиви. Варіанти опису двовимірного масиву:

 

тип[,] ім’я;

тип[,] ім’я = new тип [ розм _1, розм_2 ];

тип[,] ім’я = { список ініціалізаторів };

тип[,] ім’я = new тип [,] { список ініціалізаторів };

тип[,] ім’я = new тип [ розм_1, розм_2 ] {список ініціалізаторів};

Приклади описів (один приклад для кожного варіанту опису):

 

int [, ] a; //1 елементів немає

int [, ] b = new int [2,3]; //2 елементи дорівнюють 0

int [, ] с = {{1, 2, 3}, {4, 5, 6}}; //3 new мається на увазі

int [, ] d = new int[, ] {{1, 2, 3}, {4, 5. 6}}; //4 розмірність обчислюється

int [, ] e = new int[2,3] {{1, 2, 3}, {4, 5, 6}}; //5 надмірний опис

 

Якщо список ініціалізації не заданий, розмірності можуть бути не тільки константами, але і виразами типу, що приводиться до цілого. До елементу двовимірного масиву звертаються, указуючи номери рядка і стовпця, на перетині яких він розташований, наприклад:

 

а[1, 4] b[i, j ] b[j, i]

 

Як приклад розглянемо програму на лістингу 6.2, яка для цілочисельної матриці визначає середнє арифметичне її елементів і кількість додатних елементів в кожному рядку.

 

Лістинг 6.2. Робота з двовимірним масивом

using System;

namespace ConsoleApplication1

{

class Classl

{

static void Main()

{

 

const int n = 3, m = 4;

int i,j;

int[,] a = new int[n, m]

{

{ 2,-2, 8, 9},

{- 4,- 5, 6,- 2},

{ 7, 0, 1, 1}

};

Console.WriteLine("Початковий масив:");

for (i = 0; i < n; i ++)

{

for (j = 0; j < m; j ++)

Console.Write("\t" + a[i, j]);

Console.WriteLine();

}

 

double sum = 0;

int pel;

 

for (i = 0; i < n; i ++)

{

pel = 0;

for (j = 0; j < m; j ++)

{

sum += a[i, j];

if (a[i, j] > 0)pel ++;

}

Console.WriteLine("У рядку {0} {1} додат. елементів ", i, pel);

}

Console.WriteLine("Серед. ариф. значення елементів = " +sum/m/n);

}

}

}

 

6.3. Ступінчасті масиви

У ступінчастих масивах кількість елементів в різних рядках може розрізнятися. У пам'яті ступінчастий масив зберігається інакше, ніж прямокутний: у вигляді декількох внутрішніх масивів, кожен з яких має свій розмір. Крім того, виділяється окрема область пам'яті для зберігання посилань на кожен з внутрішніх масивів.

 

Опис ступінчастого масиву:

тип[][] ім’я;

 

Під кожен з масивів, складових ступінчастий масив, пам'ять потрібно виділяти явним чином, наприклад:

int[][] а = new int[3][]; // виділення пам'яті під посилання на три рядки

а[0] = new int[5]; // виділення пам'яті під 0-й рядок (5 елементів)

а[1] = new int[3]; // виділення пам'яті під 1-й рядок (3 елементи)

a[2] = new int[4]; // виділення пам'яті під 2-й рядок (4 елементи)

 

Тут а[0],а[1] і а[2] – це окремі масиви, до яких можна звертатися по імені. Інший спосіб виділення пам'яті:

 

int[][ ] а = { new int [5], new int[3], new int[4] };

 

До елементу ступінчастого масиву звертаються, указуючи кожну розмірність в своїх квадратних дужках, наприклад:

 

a[1][2] a[i][j] a[j][i]

 

У останньому випадку використання ступінчастих масивів не відрізняється від використання прямокутних. Невирівняні масиви зручно застосовувати, наприклад, для роботи з трикутними матрицями великого об'єму.

 




Поделиться с друзьями:


Дата добавления: 2014-12-27; Просмотров: 765; Нарушение авторских прав?; Мы поможем в написании вашей работы!


Нам важно ваше мнение! Был ли полезен опубликованный материал? Да | Нет



studopedia.su - Студопедия (2013 - 2024) год. Все материалы представленные на сайте исключительно с целью ознакомления читателями и не преследуют коммерческих целей или нарушение авторских прав! Последнее добавление




Генерация страницы за: 0.01 сек.