ТЕМА 18: Конструктори и деструктори.

exept

ексепшъни

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace exceptionsTest
{
    class Program
    {
        static void Main(string[] args)
        {
            try
            {
                Student ivan = new Student("Ivan", 11);
                Console.WriteLine("I am {0} and i am in {1} class.", ivan.Name, ivan.Clas);
                Student gosho = new Student("Georgi", 13);

               
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
            }

            string userInput = Console.ReadLine();
            int number;
            try
            {
                number = int.Parse(userInput);
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
            }
        
        }
    }
}
 

klas

 

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace exceptionsTest
{
    class Student
    {
        private string name;
        private int clas;

        public Student(string name, int clas)
        {
            this.Name = name;
            this.Clas = clas;
        }

        public string Name
        {
            get { return this.name; }
            set
            {
                if (!string.IsNullOrEmpty(value))
                {
                    this.name = value;
                }
                else
                {
                    throw new ArgumentException("The name can not be null or empty!");
                }
            }
        }

        public int Clas
        {
            get { return this.clas; }
            set
            {
                if (value >= 1 && value <= 12)
                {
                    this.clas = value;
                }
                else
                {
                    throw new ArgumentException("Invalid class number!");
                }
            }
        }
    }
}

 

Конструктори и деструктори. Ключова дума this

 

         В предишните примери променливите в обект от клас Vehicle бяха инициализирани ръчно със следната последователност от оператори:

        minivan.passengers=7;

        minivan.fuelcap=16;

        minivan.mpg=21;

        sportcar.passengers=2;

        sportcar.fuelcap=14;

        sportcar.mpg=12;

Такъв начин никога не се използва в професионално написани програми на C#. Тъй като, първо, при ръчно въвеждане винаги съществува вероятност от случайни грешки. Например да се забрави да се присвои стойност на някоя от променливите. И, второ, в С# е предвиден по-ефективен начин за решаване на тази задача, а именно използване на конструктор. Конструкторът на класа инициализира обекта при неговото създаване. Той има същото име като на класа и синтактично прилича на метод, но в конструкторите не се задава явно тип на връщания резултат. Той има следния общ вид:

Име_на_класа(списък_от_параметри)

{

   тяло_на_конструктора;

}

Като правило, конструкторите се използват за присвояване на начални стойности на променливите в обект на класа и за изпълнение на всякакви други действия по инициализация, необходими за създаване на напълно сформиран обект. Всички класове имат конструктори, независимо от това дали те са дефинирани или не. По подразбиране в С# е предвидено наличието на конструктор, който присвоява нулеви начални стойности на всички променливи от обикновен тип в обекта и стойност null, за променливи от указателен тип. Ако в класа явно е дефиниран конструктор, то конструкторът по подразбиране не се използва. Да разгледаме един пример:

using System;

 

class MyClass

{

    public int x;

    public MyClass()

    {

        x = 10;

    }

};

Конструкторът е дефиниран като public, тъй като се извиква от програмата извън границите на класа. 

using System;

 

class MyClass

{

    public int x;

    public MyClass()

    {

        x = 10;

    }

};

 

class ConstrDemo

{

    public static void Main()

    {

        MyClass t1 = new MyClass();

        MyClass t2 = new MyClass();

        Console.WriteLine(t1.x + "  " + t2.x);

    }

};

Конструкторът се извиква от операцията new при създаване на обект. Тук той се извиква при създаване на обекта t1 и при създаване на обекта t2.

По-често се дефинира конструктор с параметри, които се използват както в метод. Да модифицираме нашия пример като за инициализиране на променливата х се използва конструктор с един параметър:

using System;

 

class MyClass

{

    public int x;

    public MyClass(int i)

    {

        x = i;

    }

};

 

class ParamConstrDemo

{

    public static void Main()

    {

        MyClass t1 = new MyClass(5);

        MyClass t2 = new MyClass(10);

        Console.WriteLine(t1.x + "  " + t2.x);

    }

};

Сега да добавим конструктор към класа Vehicle, който при създаване на обект автоматично да инициализира променливите passengers, fuelcap и mpg:

using System;

 

class Vehicle

{

    public int passengers;

    public int fuelcap;     //капацитет на резервоара в галони

    public int mpg;     //miles per gallon

    public Vehicle(int p, int f, int m)

    {

        passengers = p;

        fuelcap = f;

        mpg = m;

    }

    public int range()

    {

        return mpg * fuelcap;

    }

    public double fuelneeded(int miles)

    {

        return (double)miles / mpg;

    }

};

class CompFuel

{

    public static void Main()

    {

        Vehicle minivan = new Vehicle(7, 17, 21);

        Vehicle sportcar = new Vehicle(2, 14, 21);

        double gallons;

        int dist = 252;

        gallons = minivan.fuelneeded(dist);

        Console.WriteLine("За да измине {0} мили, на микробуса са му нижни {1} галона гориво", dist, gallons);

        gallons = sportcar.fuelneeded(dist);

        Console.WriteLine("За да измине {0} мили, на спортната кола са й нужни {1} галона гориво", dist, gallons);

    }

};

Операцията new има следния общ вид:

клас променлива=new клас(списък_от_аргументи);

Операцията new отделя динамично памет, т.е. по време на изпълнение на програмата за обект от зададения клас и връща адреса на разпределената памет, който се присвоява на променливата, която е от указателен тип. Също така, new извиква конструктора на класа, който използва списъка от аргументи за инициализация на създадения обект на класа. Ако няма достатъчно памет възниква изключителна ситуация при изпълнение на програмата.

Една от ключовите компоненти на схемата за динамично разпределение на паметта е механизмът за освобождаване на паметта, заемана от вече неизползвани обекти, за да може да се разпредели за отново създавани обекти. В  много езици за програмиране освобождаването за динамично  разпределена памет става ръчно. Например в С++ за освобождаване на памет се използва операцията delete. За тази цел С# разполага с по-ефективен механизъм, наречен „събиране на боклук” (garbage collector). Когато в програмата на С# отсъстват обръщения към обект, то той се разглежда като „неизползващ се повече” и системата автоматично, без участието на програмист, освобождава заеманата от него памет, която по-нататък може да бъде разпределена за други обекти. Събирането на боклука се извършва периодично, като за началото на тази операция трябва да са изпълнени две условия. Първо, наличие на обекти, които могат да бъдат махнати от паметта и, второ, да има необходимост от такава операция. Тъй като процесът на събиране на боклука заема определено време, то изпълнителната система осъществява операцията само в случай на необходимост, като програмиста не може да определи точно момента, в който това ще стане.

В С# е предвидена възможност да се създаде метод, който се извиква непосредствено преди отстраняването на обекта от паметта с помощта на операцията „събиране на боклука”. Този метод се нарича деструктор и може да се използва за гарантиране на коректното отстраняване на обекта от паметта. Например с помощта на деструктора може да се гарантира, че файлът, към който е имало обръщение от дадения обект ще бъде затворен. Деструкторът има следния общ вид:

~име_на_класа()

{

      //тяло

}

Деструкторът се обявява по същия начин, както и конструктор, но с тази разлика, че пред името на класа се използва символа ~ (тилда) . Ще отбележим, че при дефиниране на деструктора не се задава тип на връщания резултат. Деструкторът е обикновен член на класа. Тялото на деструктора се дефинира като тяло на обикновен метод на класа. Деструктора се извиква винаги, когато обект на неговия клас трябва да бъде отстранен от паметта, непосредствено преди изпълнение на операцията „събиране на боклука”. По това деструктора в С# се отличава от същия в С++, където той се извиква, когато управлението излиза извън границите на областта на видимост, в която е бил създаден обекта. Това означава, че в С# е невъзможно да се определи точно кога ще се изпълни деструктора. Освен това, програмата може да завърши своята работа преди началото на операцията „събиране на боклука” и в този случай деструкторът въобще няма да бъде извикан. Ще разгледаме пример, в който се демонстрира използването на деструктор:

using System;

 

class Destruct

{

    public int x;

    public Destruct(int i)

    {

        x = i;

    }

    ~Destruct()

    {

        Console.WriteLine("Отстраняване на обекта от паметта" + x);

    }

    public void generator(int i)     //създава се обект, който веднага ще бъде

    {                                    // унищожен, но не и  веднага отстранен

        Destruct o = new Destruct(i);

    }

};

class DemoDestructor

{

    public static void Main()

    {

        Destruct ob = new Destruct(0);

        int count;

        for (count = 1; count < 100000; count++)

            ob.generator(count);

 

    }

};

Отначало, в този клас се създава обекта ob от тип Destruct. След това с помощта на този обект и извикването на generator() се създават 100 000 обекта. На определен етап от изпълнението, обектите ще започнат не само да се създават, но и унищожават, като на различни етапи ще се изпълняват операциите „събиране на боклука”. Колко често и кога именно ще става това, зависи от няколко фактора, а именно начален обем на свободната памет, тип на ОС и т.н. Но от определен момент на екрана ще започне да се появява съобщение, генерирано от деструктора. Ако такива съобщения не се извеждат трябва да се увеличи броя на създаваните обекти, чрез увеличаване стойността на променливата count.

При извикване на метод на него автоматично се предава неявен аргумент, който е псевдоним на указател към обекта, за който е извикан метода. Този псевдоним на указател се нарича this. За да илюстрираме използването му, ще разгледаме пример, в който се изчислява повдигане на число в цяла степен.

using System;

 

class Pwr

{

    public double b;

    public int e;

    public double val;

    public Pwr(double num, int exp)

    {

        b = num;  //this.b

        e = exp;  //this.e

        val = 1;

        if (exp == 0)

            return;

        for (; exp > 0; exp--)

            val *= b;   //this.val=this.val*this.b

    }

    public double getPwr()

    {

        return val; // this.val

    }

};

class DemoPwr

{

    public static void Main()

    {

        Pwr x = new Pwr(4.0, 2);

        Pwr y = new Pwr(2.5, 1);

        Pwr z = new Pwr(5.7, 0);

        Console.WriteLine(x.b + " на степен " + x.e + " = " + x.getPwr());

        Console.WriteLine(y.b + " на степен " + y.e + " = " + y.getPwr());

        Console.WriteLine(z.b + " на степен " + z.e + " = " + z.getPwr());

    }

};

Тук this сочи към обект, за който е бил извикан метода getPwr(). Например, ако методът е извикан за обекта х, то this ще сочи към х. Може да се каже, че записване на оператора, без използване на ключовата дума this е съкратена форма на записване, която винаги се използва от опитните програмисти.

В някои случаи, указателят this може да бъде полезен. Например, синтаксиса на С# позволява да се използват еднакви имена за параметрите или локалните променливи и за променливите на обекта. Когато това се случва, локалната променлива и параметъра скриват променливата на обекта и достъп до нея може да се получи като се използва this. Например, да разгледаме конструктор на класа Pwr, който е коректен, но такъв стил на програмиране не се препоръчва:

    public Pwr(double b, int e)

    {

        this.b = b;

        this.e = e;

        val = 1;

        if (e == 0)

            return;

        for (; e > 0; e--)

            val *= b;

}

В тази версия на конструктора, имената на параметрите и имената на променливите на екземпляра са еднакви. Затова се налага използването на ключовата дума this.

 

 

 

 

 

Търсене