Історія мов програмування


Скачати 2.03 Mb.
Назва Історія мов програмування
Сторінка 5/17
Дата 05.04.2013
Розмір 2.03 Mb.
Тип Документи
bibl.com.ua > Інформатика > Документи
1   2   3   4   5   6   7   8   9   ...   17

Деструктор класу.


Деструктор (фіналізатор – finalyzer) класу – це метод класу, що викликається автоматично в момент знищення екземпляру. Його призначення – вивільнити певні ресурси, які, можливо, використав конструктор при створенні об’єкту. На відміну від класичного варіанту мови С++, де втрачені посилання постають великою проблемою, у .NET існує система GC – Garbage Collector автоматичного знищення об’єктів, посилань на які не існує в даний момент. Якщо у класі визначений деструктор (а це, як ми бачили, зовсім не обов’язково), то він напевне буде викликаний, проте не можна точно визначити момент, коли об’єкт буде фізично знищеним. Це накладає певні застереження на використання деструкторів, якщо вони мають виконувати якісь важливі дії на певний момент часу.

Деструктор має ідентифікатор, що починається із знака операції (~), після якого слідує ідентифікатор класу. Так само як конструктор, деструктор не має типу результату, крім того, він не має специфікатору доступу та параметрів – отже, не перевантажується.

У наступному прикладі клас Destructor містить цілочисельний член класу kod та конструктор, що його ініціалізує та інформує про створення об’єкту. Деструктор класу містить одну інструкцію – повідомлення про знищення об’єкту. Мета цього прикладу – дослідити порядок викликів деструкторів. З цією метою у програму включимо ще один метод void creator(int k), у якому створюється локальний об’єкт Destructor d. Цей об’єкт існує лише протягом роботи функції creator. Посилання на нього втрачає сенс в момент завершення функції creator. Але в який момент він буде знищений фізично? Щоб відповісти на це питання, у функції Main створимо у циклі дуже багато звертань до функції creator. При кожному звертанні створюватиметься новий локальний об’єкт класу Destructor, а кожний попередній помічатиметься як посилання, не пов’язане з жодним об’єктом. В залежності від швидкодії вашого комп’ютера кількість ітерацій циклу можна зменшити або збільшити. Через кожні 10000 кроків циклу змоделюємо затримку, щоб краще відслідкувати результат. Ви побачите, що об’єкти знищуються не зовсім у хронологічному порядку.

using System;

namespace Destructor

{

class Program

{

class Destructor

{

public int kod;

public Destructor(int kod_)

{

kod = kod_;

Console.WriteLine("Створюється екземпляр {0}", kod);

}

~Destructor()

{

Console.WriteLine("Знищується екземпляр {0}", kod);

}

}

static void creator(int k)

{

Destructor d = new Destructor(k);

}

static void Main()

{

for (int i = 1; i < 40000; i++)

{

creator(i);

if ((i % 10000) == 0) Console.ReadLine();

}

}

Отже, на відміну від мови С++, в якій об’єкти створюються командою new , а знищуються командою delete , яка гарантує в цей момент виклик деструктора, в мові С# знищенням об’єктів керує система GC, а тому наявність деструктора не є обов’язковою для звичайних класів.

Система GC працює автоматично, вона гарантує, що непотрібні об’єкти знищуються та причому дана операція виконується лише один раз, а також, що знищуються лише ті об’єкти, на які немає жодного посилання. Тим самим виключаються стандартні проблеми при роботі з динамічною пам’яттю – наприклад, проблема втрачених посилань або витік пам’яті (коли на об’єкт немає посилань, тобто до нього неможливо отримати доступ, але він не знищений і займає місце у пам’яті) або проблема завислих вказівників (коли об’єкта вже немає, а посилання на відповідне місце у пам’яті існує).

Система GC використовує два варіанти роботи очистки динамічної пам’яті – окремо для порівняно малих об’єктів та порівняно великих. Збирач сміття запускається через певні проміжки часу і для малих об’єктів знищує вже непотрібні і одразу дефрагментує динамічну пам’ять, переписуючи дані так, що об’єкти стають розташовані поруч (тобто створює неперервну вільну область динамічної пам’яті), при цьому відповідним чином автоматично змінює посилання на ті об’єкти, які залишаються у динамічній пам’яті. Для порівняно великих об’єктів алгоритм роботи збирача сміття ускладнюється та використовується поняття покоління, до якого належить об’єкт. Всього поколінь три: 0, 1 та 2. В момент створення усі об’єкти належать поколінню 0. Через деякий час збирач сміття видаляє непотрібні об’єкти, а ті, що залишилися, переводяться у покоління 1. Покоління 1 «чиститься» рідше за покоління 0, але за тим самим принципом. Об’єкти покоління 1, які залишаються у пам’яті після видалення непотрібних, переходять у покоління 2 (воно є останнім можливим).

  1. Методи в мові C#

Раніше вже було наголошено, що звичайні змінні value-типу передаються у метод за значенням, а змінні reference-типу – за посиланням. Також обговорювались відмінності передачі через список параметрів методу змінних-значень та змінних-посилань. Для детальнішого розуміння цієї різниці розглянемо наступний простий приклад. В ньому визначається класс MyClass, який містить цілу змінну i та конструктор, що її ініціалізує значенням свого параметру. У класі Program визначені 2 функції – MyFunc1 та MyFunc2, перша з яких збільшує на одиницю свій цілочислений параметр, а друга – інкрементує член класу MyClass . У функції Main змінна i ініціалізується значенням 10, а також створюється екземпляр класу MyClass, змінна і в якому має те саме значення 10.

using System;

namespace Object_Param

{

class MyClass

{

public int i; // Просто ціла змінна

public MyClass(int i_) // Конструктор класу

{ i = i_; }

}

class Program

{ // Функція одержує параметр by-value

static void MyFunc1(int val)

{ val++; }

// Функція одержує об'єкт by-referebce

static void MyFunc2(MyClass m)

{ m.i++; }

static void Main( )

{

int i = 10; // Ціла змінна із значенням 10

MyClass m = new MyClass(10);

Console.WriteLine("Перед MyFunc1: ");

Console.WriteLine("Змiнна i = " + i); //Друкуємо

MyFunc1(i); // У функції MyFunc1 ізбільшилась

Console.WriteLine("Пiсля MyFunc1: ");

// Але тут і не змінилась!

Console.WriteLine("Змiнна i = " + i);

Console.WriteLine("Перед MyFunc2: ");

Console.WriteLine("Змiнна i = " + m.i);// Друкуємо і

MyFunc2(m); // У функції MyFunc2 і збільшилась

// Тут і дійсно збільшилась

Console.WriteLine("Пiсля MyFunc2: ");

Console.WriteLine("Змiнна i = " + m.i);

}

}

}

Після запуску програми одержуємо наступний результат.

Перед MyFunc1:

Змiнна i = 10

Пiсля MyFunc1:

Змiнна i = 10

Перед MyFunc2:

Змiнна i = 10

Пiсля MyFunc2

Змiнна i = 11

Цей результат показує, що функція MyFunc1, яка одержала параметр i за значенням, не мала доступу до самого аргументу i, використовуючи лише його значення 10, на відміну від функції MyFunc2, яка одержала об’єкт за посиланням, а отже всі зміни, виконані функцією над своїм параметром, реально відбулись з об’єктом. Причина цього полягає у тому, що параметр m функції MyFunc2 є посиланням на об’єкт MyClass. І хоча сам аргумент m передається у функцію за значенням, проте його значення – це адреса розташування екземпляра MyClass у пам’яті, а отже, функція має доступ до членів цього об’єкту.

Щоб підкреслити важливість виділеної у попередньому абзаці фразі, розберемо ще один приклад. В цьому прикладі функція MyFun1 на перший погляд міняє місцями 2 об’єкти. Точніше кажучи, вказівники на два об’єкти. У функції ж MyFun2 міняються місцями значення членів двох об’єктів класу MyClass. З’ясуємо, як зміняться значення об’єктів autumn та winter – екземплярів класу MyClass після викликів цих функцій.

using System;

namespace Object_Param_1

{

class MyClass

{

public string text;

public MyClass(string s) // Конструктор класу

{ text = s; }

}

class Program

{

static void MyFun1( MyClass m1, MyClass m2)

{ // Переставляємо об'єкти

MyClass m = m1;

m1 = m2;

m2 = m;

}

static void MyFun2(MyClass m1, MyClass m2)

{ // Переставляємо змінні об'єктів

string s = m1.text;

m1.text = m2.text;

m2.text = s;

}

static void Main()

{

MyClass autumn = new MyClass("осiнь");

MyClass winter = new MyClass("зима");

MyFun1(autumn, winter); // Чи помінялись об'єкти?

Console.WriteLine("Пiсля MyFun1:");

Console.WriteLine(autumn.text); // Це зима? - Ні!

Console.WriteLine(winter.text); // Це осінь? - Ні!

// Чи помінявся зміст об'єктів?

MyFun2(autumn, winter);

Console.WriteLine("Пiсля MyFun2:");

Console.WriteLine(autumn.text); // Це зима? - Так!

Console.WriteLine(winter.text); // Це осінь? - Так!

}

}

}

Після запуску програми на екрані побачимо:

Пiсля MyFun1:

осінь

зима

Пiсля MyFun2:

зима

осінь

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



  1. Метод Main() в мові C# та його параметри

На завершення вивчення перевантаження методів, розглянемо, які існують різні синтаксичні форми методу Main() . Можливих варіантів всього чотири – по-перше, метод Main() може повертати результат типу int або не мати результату – void; і по-друге, він може мати параметр args типу string[] або не мати параметрів. Повернення результату необхідне, якщо програма або система, що викликає ваш С#--проект, аналізує результат завершення – нульовий результат зазвичай означає успішне завершення, всі інші сигналізують про наявність помилки того чи іншого роду.

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

У наступному прикладі метод Main() друкує свої аргументи. Зверніть увагу, що наявність або відсутність аргументів у командному рядку неявно аналізується значенням args.Length – в разі, коли аргументи відсутні, на екрані побачимо повідомлення «У командному рядку маємо 0 аргументiв», а цикл for не виконається жодного разу.

using System;

namespace Main_args

{

class Program

{

static int Main(string[] args)

{ // Аналізуємо аргументи методу Main ()

Console.WriteLine(

"У командному рядку маємо {0} аргументiв", args.Length);

for (int i = 0; i < args.Length; i++)

{ // Друкуємо аргументи командного рядка

System.Console.WriteLine("Arg[{0}] = {1}",i, args[i]);

}

Console.ReadLine();

return 0; // Повертаємо результат

}

}

}

Перевірити роботу цієї програми можна, запустивши її з командного рядка.


  1. Правила перевантаження методів в мові C#

Мова С# дозволяє реалізувати цікаву можливість при створенні методів – різні за змістом методи можуть мати однакові ідентифікатори. Ця технологія називається перевантаженням (overloading) методів. В програмуванні вживається термін «сигнатура методу» – сигнатура включає ідентифікатор методу та список його параметрів. Перевантаження дозволене для методів з різними сигнатурами. Тобто два або більше методів можуть мати однакові ідентифікатори при умові, що їх списки параметрів різняться або кількістю, або типами, або і тим, і іншим. Зверніть увагу, що результат, який повертається методом, до сигнатури не включається, отже, при перевантаженні методам недостатньо мати відмінності лише в типі результату. Якщо виникає питання про корисність такої можливості, то зауважимо, що однією із поширених студентських помилок при використання стандартної функції мови С++ для обчислення модуля числа є виклик функції abs(x), що повертає ціле значення модуля цілого числа x, в той час, коли необхідне звертання до функції fabs(x) з дійсними аргументом та результатом. А якщо згадати, що є ще функція labs(x) з довгим цілим результатом… Зрозуміло, що значно корисніше було б мати методи однакового призначення з однаковими ідентифікаторами для різних типів параметрів.

Розглянемо приклад, в якому перевантажені 4 методи MyMethod – з параметрами типів int, float, double та з двома цілими параметрами.

using System;

namespace Overloading

{

class Program

{// В цьому класі перезавантажуються методи

public void MyMethod (int x)

{

Console.WriteLine("Метод з цiлим параметром x = {0}", x);

}

public void MyMethod(float x)

{

Console.WriteLine("Метод з дiйсним параметром x ={0}",x);

}

public void MyMethod(double x)

{

Console.WriteLine(

"Метод з параметром подвоєної точностi x = {0}", x);

}

public void MyMethod(int x, int y)

{

Console.WriteLine("Метод з двома цiлими параметрами

x = {0} y = {1}", x, y);

}

static void Main()

{

Program pr = new Program();

pr.MyMethod(1);

pr.MyMethod(1.5);

pr.MyMethod(2.5F);

pr.MyMethod(1, 2);

}

}

}

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

Метод з цiлим параметром x = 1

Метод з параметром подвоєної точностi x = 1,5

Метод з дiйсним параметром x = 2,5

Метод з двома цiлими параметрами x = 1 y = 2

Виникає слушне питання, а що коли методу передається параметр, наприклад, цілий, але не int? Спробуємо виконати виклик функції MyMethod наступним чином:ї

byte b = 8;

pr.MyMethod(b);

Одержимо наступний результат:

Метод з цiлим параметром x = 8

Тобто викликається метод MyMethod (int x). Компілятор намагається підібрати серед методів, які перевантажуються, той, що найкращим чином відповідає фактичному аргументу (в даному разі типу byte) згідно правил приведення типів. Тому наступні звертання до методу MyMethod

pr.MyMethod(100L);

pr.MyMethod('A');

призведуть до цілком очікуваних наступних повідомлень:

Метод з дiйсним параметром x = 100

Метод з цiлим параметром x = 65

Тобто при першому виклику відбувається приведення фактичного аргументу до найближчого типу float, а у другому випадку тип char приводиться до типу int, і ми бачимо на екрані код символу 'A'.

Проте спроба виклику pr.MyMethod(1.1, 2.2); призведе до синтаксичної помилки, оскільки найбільш відповідним методом для цього виклику компілятор вважатиме MyMethod(int x, int y), але не матиме можливості виконати неявне приведення фактичних аргументів типу double до типу int.

Додамо тепер ще один метод MyMethod у клас Program:

public void MyMethod(ref int x)

{

x++;

Console.WriteLine("Метод з цiлим параметром

ref x = {0}", x);

}

Він теж має один параметр типу int , проте цей параметр використовується із модифікатором ref. Перевіримо, чи відрізнятиме компілятор сигнатури двох перевантажених методів MyMethod (int x) та MyMethod(ref int x). Внаслідок звертання

int i = 1;

pr.MyMethod(ref i);

одержимо результат Метод з цiлим параметром ref x = 2. Тобто модифікатори ref та out також впливають на сигнатури методів у випадку їх перевантаження. Але дуже важливо зауважити, що методи, які різняться лише модифікаторами ref та out, компілятор не зможе перевантажити. Для ілюстрації спробуємо додати ще один метод MyMethod у клас Program:

public void MyMethod(out int x)

{

x = 100;

Console.WriteLine("Метод з цiлим параметром

out x = {0}", x);

}

Результатом компіляції буде повідомлення про помилку:

«MyMethod» cannot define overloaded methods that differs only on ref and out

неможливо перевантажити методи, які розрізняються лише ref та out.

В той же час визначення та використання методу

public void MyMethod(out float x)

{

x = 100;

Console.WriteLine("Метод з дiйсним параметром

out x = {0}", x);

}

буде цілком успішним, адже два методи MyMethod(float x) та MyMethod(out float x) компілятор вважатиме «достатньо різними» і матиме можливість їх перевантаження.



  1. Особливості перевантаження конструкторів в мові С#, вказівник this

Оскільки конструктор – це метод класу, то конструктори також можуть перевантажуватись, отже, у класі можна визначити кілька конструкторів із різними списками параметрів. Це дозволяє створювати об’єкти класу по-різному в залежності від обставин. У наступному прикладі у класі Circle визначено 3 перевантажених конструктори: один ініціалізує своїми параметрами всі 3 дані-члени класу – координати x0, y0 центру кола та радіус r; другий має один параметр – значення радіусу, а центром кола вважається початок координат; нарешті, третій конструктор без параметрів взагалі – визначає коло одиничного радіусу із центром у початку координат. Визначення цього класу міститься в окремому файлі Circle.cs проекту.

using System;

namespace Overloading_Constructors

{

class Circle

{

double x0, y0; // координати центру кола

double r; // радіус кола

public Circle(double x0_, double y0_, double r_)

{ // Конструктор з трьома параметрами

x0 = x0_; y0 = y0_; r = r_;

Console.WriteLine("Створили коло - центр ({0};{1}),

радiус {2}", x0, y0, r);

}

public Circle(double r_)

{ // Конструктор з одним параметром

x0 = 0; y0 = 0; r = r_;

Console.WriteLine("Створили коло - центр ({0};{1}),

радiус {2}", x0, y0, r);

}

public Circle()

{ // Конструктор без параметрів

x0 = 0; y0 = 0; r = 1;

Console.WriteLine("Створили коло - центр ({0};{1}),

радiус {2}", x0, y0, r);

}

}

}

У файл Program.cs помістимо функцію Main , яка створює 3 екземпляри класу Circle з допомогою кожного з конструкторів. Зверніть увагу, що інструкцією Circle c1 = new Circle(); викликається не конструктор за замовчуванням, оскільки він взагалі не діє у цьому випадку – адже у класі існують власні конструктори – а саме написаний нами конструктор без параметрів.

using System;

namespace Overloading_Constructors

{

class Program

{

static void Main()

{

Circle c1 = new Circle();

Circle c2 = new Circle(10);

Circle c3 = new Circle(1, 2, 5);

}

}

}

На екрані одержимо наступні повідомлення:

Створили коло - центр (0;0), радiус 1

Створили коло - центр (0;0), радiус 10

Створили коло - центр (1;2), радiус 5

Таким чином, всі три об’єкти класу були створені різними конструкторами. Можливість перевантаження конструкторів надає гнучкості при створенні екземплярів. Ще більшого ефекту можна досягти, використовуючи можливість виклику одного конструктору іншим.
1   2   3   4   5   6   7   8   9   ...   17

Схожі:

Програма курсу програмування на мов і С++
Курс націлений на отримання знань і практичних навиків програмування на мовах C і C + + в рамках процедурно-орієнтованого програмування....
Основні методології (стилі, парадигми) програмування. Поняття програми....
Дів розробки програм Граді Буча “О’єктно-орієнтоване програмування (ООП) – це методологія програмування, яка заснована на представленні...
Курс програмування на С #
Зусилля, які ви витратите на вивчення С #, будуть винагороджені, так як Сі Шарп був розроблений в якості основної мови програмування,...
27. Процедурні мови програмування
Процедурні мови програмування. Характеристика процедурних мов програмування. Алфавіт. Основні поняття мови: числа, рядки, ідентифікатори,...
27. Методика навчання обєктно-орієнтованого програмування. Об'єктно́-орієнтоване́...
Не зважаючи на те, що ця парадигма з'явилась в 1960-тих роках, вона не мала широкого застосування до 1990-тих. На сьогодні багато...
29. Опис та використання підпрограм
Реалізація базових алгоритмічних структур процедурною мовою програмування. Опис процедур та функцій процедурною мовою програмування....
2. Дробово-лінійне програмування Постановка задачі дробово-лінійного...
Дослідження операцій”, “Економетрія”, “Моделювання економіки”, “Економічна кібернетика” а також дисциплін циклу загальноекономічної...
Методичні рекомендації щодо вивчення інформатики у 2012-2013 навчальному році
Особливо гостро стоїть питання поглибленого вивчення інформатики та сучасних мов програмування
ПОРЯДОК проведення відкритої Всеукраїнської студентської олімпіади з програмування
Першості світу) з програмування АСМ-ICPC (Association for Computing Machinery International Collegiate Programming Contest), яка...
1. Українська мова належить до: а східнослов’янської підгрупи мов;...
Яке з поданих слів має значення „спрямований вперед; який рухається у висхідному напрямі”
Додайте кнопку на своєму сайті:
Портал навчання


При копіюванні матеріалу обов'язкове зазначення активного посилання © 2013
звернутися до адміністрації
bibl.com.ua
Головна сторінка