DEV Community

Cover image for Սովորում ենք C# | Մեթոդներ, stack և heap, արժեքային և հղումային տիպեր
Narek Babajanyan
Narek Babajanyan

Posted on

Սովորում ենք C# | Մեթոդներ, stack և heap, արժեքային և հղումային տիպեր

Ի՞նչ է մեթոդը

Մեթոդ (method) կամ ֆունկցիա (function) է կոչվում մեկ անվան տակ խմբավորված հրամանների հավաքածուն։ Մեթոդները հիմնականում օգտագործվում են հրամանների խումբը մի քանի անգամ կատարելու անհրաժեշտության դեպքում՝ միևնույն կոդի կրկնությունից խուսափելու համար։ Մեթոդը կարող է ընդունել փոփոխվող պարամետրեր և վերադարձնել ինչ֊որ արժեք։ Մեթոդի հայտարարման համար նշվում է՝

  • վերադարձվող արժեքի տիպը (return type)
  • մեթոդի անվանումը
  • փակագծերի ներսում՝ պարամետրերի ցանկը
  • ձևավոր փակագծերի ներսում՝ մեթոդի մարմինը (method body), այսինքն՝ դրա մեջ մտնող հրամանները
// Վերադարձվող արժեքի տիպը կարող է լինել
// մեզ արդեն ծանոթ տիպերից ցանկցածը
// ինչպես նաև void տիպը
int AddNumbers(int x, int y) 
{
    return x+y;
}

// տվյալ հրամանը կանչում է AddNumbers() մեթոդը
// և վերադարձված արժեքը վերագրում sum փոփոխականին
int sum = AddNumbers(33, 7);    // sum = 10
Enter fullscreen mode Exit fullscreen mode

void տիպն օգտագործվում է այն ժամանակ, երբ մեթոդից վերադարձվող արժեք չի ակնկալվում։

void SendMessage(string address, string message) { /* ... */ }
// ...
SendMessage(); 
// մեթոդը չի վերադարձնում արժեք, հետևաբար, ոչնչի վերագրվել չի կարող
// սակայն այն կարելի է ուղղակի կանչել, առանց վերագրման

float result = SendMessage(); // այս կոդը չի կոմպիլացվի
Enter fullscreen mode Exit fullscreen mode

C# ծրագրերի մեջ գոյություն ունի հատուկ մեթոդ՝ Main()֊ը։ Վերջինս հանդիսանում է ծրագրի մուտքային կետ (entry point), այսինքն՝ ցանկացած ծրագրի կատարում սկսվում է հենց այս մեթոդի կանչից։ Ըստ այդմ, ձեր ծրագրերի ամբողջ տրամաբանությունը պետք (օրինակ՝ այլ մեթոդների կանչերը) է կենտրոնացված լինի հենց Main()֊ի շուրջ։

Դիտարկենք օրինակ՝ փորձենք ստեղծել ծրագիր, որը տրված a, b և c գործակիցների համար կլուծի ax^2 + bx + c = 0 տեսքի քառակուսային հավասարումը։

using System;

public class QuadraticSolver
{
    public static void Main()
    {
        double a = 1;
        double b = 3;
        double c = -4;
        SolveQuadraticEquation(a, b, c);
    }

    private static double FindDiscriminant(double a, double b, double c)
    {
        return (b*b - 4*a*c);
    }   

    private static void SolveEquation(double a, double b, double c)
    {
        double D = FindDiscriminant(a, b, c);
        if(D > 0) 
        {
            // Math.Sqrt() մեթոդն օգտագործվում է քառակուսի արմատի հաշվարկման համար
            double x1 = (-b + Math.Sqrt(D)) / 2*a;
            double x2 = (-b - Math.Sqrt(D)) / 2*a;
            // Էկրանին արտածել երկու լուծումները
            Console.WriteLine(x1);
            Console.WriteLine(x2);
        } else if(D == 0)
        {
            double x = (-b - Math.Sqrt(D)) / 2*a;
            // Էկրանին արտածել լուծումը
            Console.WriteLine(x);
        } else
        {
            Console.WriteLine("Հավասարումը լուծում չունի");
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Փորձենք պատկերացնել տվյալ ծրագրի ընթացքը

  1. Առաջին հերթին CLR հարթակի կողմից կանչվում է Main() մեթոդը (ինչը նշանակում է որ ձեր ծրագիրը գործարկվել է),
  2. այնուհետև կանչվում է SolveEquation()-ը,
  3. որն իր հերթին կանչում է FindDiscriminant()-ը։

Ամեն անգամ, երբ կանչվում է նոր մեթոդ, նախորդի ընթացքը սառեցվում է՝ սպասելով նոր կանչվածի վերադարձված (return) արժեքին։

Պատկերացնենք այս ամենը տեսողական կերպով՝ մեթոդը կարելի է պատկերել որպես մեկ աղյուս, նոր մեթոդ կանչել նշանակում է նոր աղյուս դնել նախորդի վրա, իսկ մեթոդի ավարտը՝ այդ աղյուսի հեռացում։ Այսպիսով, մենք չենք կարող հեռացնել որևէ աղյուս, քանի դեռ դրա վերևում գտնվող բոլոր աղյուսները հեռացված չեն (այսինքն, քանի դեռ բոլոր հետագա կանչված մեթոդները չեն ավարտվել, ներկա մեթոդը չի կարող ավարտվել)։

Մեթոդների stack

Այս փոխհարաբերությունն ինֆորմատիկայում ներկայացվում է որպես տվյալների կառուցվածք (data structure), որը կոչվում է stack։ Վերջինս ըստ էության արգելում է աշխատանքը միջանկյալ տարրերի հետ, քանի դեռ դրանց վերևում կա այլ տարր, փաստացի միակ հասանելի գործողություններն են վերին տարրի հեռացումն ու վերևում նոր տարրի տեղադրումը։

Stack և Heap

Ի՞նչ է stack֊ը

Stack-ին ինֆորմատիկայում հատկացվում է բավականին մեծ կարևորություն։
Դրանով լուծվում է ծրագրավորման կարևոր խնդիր՝ մեթոդների ընթացքի և դրանց տեղական փոփոխականների (local variable) կառավարում։

CLR—ի կողմից մեր ծրարգի գործարկման ընթացքում, հիշողության մեջ հատկացվում է հատուկ գոտի, որը կոչվում է կանչերի stack (call stack)։ Ամեն անգամ, երբ կանչվում է նոր մեթոդ, դրա համար կանչերի stack-ում առանձնացվում է հատված, որը կոչվում է տվյալ մեթոդի շրջանակ (stack frame)։ Վերջինս իր մեջ պարունակում է՝

  • օբյեկտի հղումը, որում կանչվել է մեթոդը (թե ինչ է հղումը, կիմանանք շուտով)
  • կանչված մեթոդին տրված պարամետրերը,
  • մեթոդում հայտարարված տեղական փոփոխականները:

Ինչպես պարզ է դառնում, մեթոդի ավարտից հետո կանչերի stack֊ից հեռացվում է վերին շրջանակը, մեթոդի վերադարձված արժեքը փոխանցվում է նախորդին, իսկ տեղական փոփոխականներն ու պարամետրերը՝ ջնջվում։

Իսկ ի՞նչ անել եթե ցանկանում ենք հիշողության մեջ պահել "երկարակյաց" տվյալներ, որոնց պահպանումը կախված չէ որևէ մեթոդից։

Ի՞նչ է heap֊ը

Heap֊ը կարելի է պատկերացնել որպես զամբյուղ, որում իրերը կարելի է տեղավորել պատահական հերթականությամբ, քանի դեր զամբյուղի տարողունակությունը դա թույլ է տալիս։

Որոշ դեպքերում heap֊ում տվյալներ պահպանելիս առաջանում է խնդիր՝ տվյալը գտնվում է հիշողության մեջ, սակայն այն այլևս չի օգտագործվում։ Այս դեպքերի համար .NET հարթակում գործում է ավտոմատ "աղբահավաք" համակարգ (garbage collection), որն ինքնուրույն հայտնաբերում է չօգտագործված փոփոխականներն ու ջնջում դրանք հիշողությունից։

Արժեքային և հղումային տիպեր

Այժմ, քանի որ արդեն ծանոթ ենք C#֊ում հիշողության օգտագործման հնարավոր ձևերի մասին, տեսնենք թե որ տիպի փոփոխականներն ինչպես են դա անում։
Ըստ հիշողության մեջ պահպանման եղանակի, տիպերը C#-ում բաժանվում են երկու տեսակի՝ արժեքային տիպեր (value type) և հղումային տիպեր (reference types

Արժեքային տիպերի փոփոխականները տվյալները պահում են անմիջապես իրենց մեջ։ Մեզ արդեն ծանոթ պարզագույն տիպերը՝ ամբողջ թվեր (int short, long և այլն), կոտորակային թվեր (float, double, decimal) և նիշեր (char), պատկանում են արժեքային տիպերի կատեգորիային։

Հղումային տիպերի օբյեկտ ստեղծելիս, այն տեղադրվում է heap֊ում, և փոփոխականում պահվում է դրա պահպանման հասցեն։ Մեզ հայտնի տիպերից հղումային դասին է պատկանում string֊ը։

Դիտարկենք օրինակ՝

static void Main()
{
    int numInt = 13;
    string numStr = "13";
}
Enter fullscreen mode Exit fullscreen mode

Այժմ մանրամասնենք թե ինչ է կատարվում համակարգչի հիշողության մեջ։
Ինչպես արդեն նշեցի՝ ցանկացած մեթոդի համար կանչերի stack֊ում առանձնացվում է շրջանակ (stack frame), որում պահվում են տեղական փոփոխականները։ Քանի որ առաջին փոփոխականն արժեքային տիպի ներկայացուցիչ է, 13 արժեքն անմիջապես պահվում է numInt փոփոխականի մեջ, մինչդեռ երկրորդ փոփոխականի տիպը հղումային է, ինչը նշանակում է որ heap-ում ստեղծվում է string տիպի օբյեկտ՝ "13" արժեքով, որի հասցեն էլ պահպանվում է numStr փոփոխականում։

Հիշողության երկու գոտիներ

Հաջորդ գրառման մեջ կսովորենք ստեղծել մեր սեփական տիպերը class֊երի և struct֊երի միջոով։

Ամփոփում։

Այս գրառման մեջ ծանոթացանք՝

  • մեթոդներին,
  • stack֊ին և heap֊ին,
  • արժեքային (value) և հղումային (reference) տիպերին։

Top comments (0)