Кратко съдържание



страница26/73
Дата21.07.2018
Размер9.03 Mb.
#76887
1   ...   22   23   24   25   26   27   28   29   ...   73

Структури


Структурите в .NET Framework представляват съвкупност от полета с данни. Te приличат много на класовете, но за разлика то тях са стойност­ни типове. Инстанциите на структурите имат поведение като примитив­ните числени типове – разполагат в стека за изпълнение на програмата, предават се по стойност и се унищожават при излизане от обхват.

За разлика от структурите класовете са типове по референция и се разполагат в динамичната памет заради което създаването и унищожава­нето им е по-бавно. При предаване като параметри се предава само техният адрес в динамичната памет (т. нар. референция).

Структурите, както и класовете, могат да дефинират конструктори, полета, свойства, индексатори и други членове.

Въпреки, че синтаксисът на езика C# го допуска, не се препоръчва в структурите да има методи с логика. Структурите трябва да се използват за да съхраняват някаква структура от данни (съвкупност от полета).

При правилна употреба заместването на класове със структури може значително да увеличи производителността. Ще се спрем по-подробно на класовете и структурите в темата "Обща система от типове".

Структурите се дефинират по същия начин, както и класовете, но вместо запазената дума "class" се използва запазената дума "struct".


Структури – пример


За да демонстрираме работата със структури, ще дадем няколко примера:

struct Point

{

public int mX, mY;



}
struct Color

{

public byte mRedValue;



public byte mGreenValue;

public byte mBlueValue;

}
struct Square

{

public Point mLocation;



public int mSize;

public Color mBorderColor;

public Color mSurfaceColor;

}


Както виждаме, структурите много приличат на класове, но основното им предназначение е да съхраняват данни.

Предаване на параметрите


В C# има три различни режима на предаване на параметрите. Ще ги разгледаме накратко, след което ще се спрем по-подробно на всеки от тях и ще илюстрираме разликите между тях с примери. Параметрите при извикване на метод могат да се предават по следните начини:

  • out (изходни параметри за връщане на стойност)

Параметрите могат да не бъдат инициализирани преди предаването им. Инициализацията се извършва от извиквания метод, а преди нея до­стъ­път е само за писане и в тялото на метода, и в кода, който го извиква.

  • ref (входно-изходни параметри за предаване по референция)

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

  • in (входни параметри за предаване по стойност)

Това е подразбиращият се режим на предаване на параметрите в C#. При изпълнението на метода в стека се записват стойностите на па­ра­мет­ри­те, с които методът работи и след излизането от тялото му, когато се изтрие върха на стека, промените в параметрите остават изгубени, като стойността на локална променлива, излязла от обхват.

Параметри за връщане на стойност (out)


Предаването на out параметри се задейства, като маркираме параметъра с ключовата дума out, и в дефиницията на метода, и при извикването му. Целта на тези параметри е не методът да приема като входни данни тях­ната стойност, а единствено да я инициализира и да я върне като резул­тат от изпълнението си. По тази причина те се наричат изходни пара­метри.

Изходните параметри се предават по адрес в случай на стойностен тип и по адрес на референцията (двоен указател) в случай на референтен тип. Благодарение на това при промяна на стойността им в даден метод тази промяна директно се отразява на променливата, подадена от извикващия метод.

Тъй като върнатата стойност от даден метод в C# може да бъде само една, използвайки out параметри можем да върнем на кода, из­ви­кал метода, повече стойности. Нека разгледаме следния пример за да илюстрираме връщането на стойност чрез out параметри:

public struct Point

{

public int mX, mY;


public Point(int aX, int aY)

{

mX = aX;



mY = aY;

}

}


public struct Dimensions

{

public int mWidth, mHeight;



public Dimensions(int aWidth, int aHeight)

{

mWidth = aWidth;



mHeight = aHeight;

}

}


public class Rectangle

{

private int mX, mY, mWidth, mHeight;


public Rectangle(int aX,int aY, int aWidth,int aHeight)

{

mX = aX;



mY = aY;

mWidth = aWidth;

mHeight = aHeight;

}
public void GetLocationAndDimensions(

out Point aLocation, out Dimensions aDimensions)

{

aLocation = new Point(mX, mY);



aDimensions = new Dimensions(mWidth, mHeight);

}

}


class TestOutParameters

{

static void Main()



{

Rectangle rect = new Rectangle(5, 10, 12, 8);

Point location;

Dimensions dimensions;


// location and dimension are not previously initialized

rect.GetLocationAndDimensions(

out location, out dimensions);

Console.WriteLine("({0}, {1}, {2}, {3})",

location.mX, location.mY,

dimensions.mWidth, dimensions.mHeight);

// Result: (5, 10, 12, 8)

}

}



В горния пример са дефинирани структурите Point и Dimensions, които методът GetLocationAndDimensions() на кла­са Rectangle използва за да връща чрез изходните си параметри техни инстанции.

Трябва да обърнем внимание на употребата на ключовата дума out и на това, че променливите location и dimensions не са инициализирани ни­къде в тялото на метода Main(). Ако параметрите не бяха указани като такива за връщане на стойност, това не би било допустимо – получава се грешка при компилация "Use of un­assigned local variable".

Примерът извлича с едно извикване на метод две стойности – место­поло­жението и размерите на даден правоъгълник, като ги записва в инстанции на структурите Point и Dimensions.

Предаване по референция (ref)


Предаването на параметрите по референция се активира като добавим ключовата дума ref към описанието на даден параметър в дефиницията на метода и при извикването му. Такива параметри се наричат входно-изходни.

При предаване на параметри по рефе­ренция при стартиране на метода в стека не се записват копия на стойностите на параметрите, а указатели към адреса в паметта на оригиналните им стойности. Така извиканият метод може както да чете информация от подадените му параметри, така и да ги изменя и да връща стойности на кода, който го е извикал.

Параметрите поп референция се предават по адрес в случай на стойнос­тен тип и по адрес на референцията (двоен указател) в случай на рефе­рентен тип. Благодарение на това при промяна на стойността им в даден метод тази промяна директно се отразява на променливата, подадена от извикващия метод. Ще илюстрираме това с пример:

public struct Point

{

internal int mX, mY;


public static void IncorrectMultiplyBy2(Point aPoint)

{

aPoint.mX *= 2; aPoint.mY *= 2;



}
public static void MultiplyBy2(ref Point aPoint)

{

aPoint.mX *= 2; aPoint.mY *= 2;



}
static void Main()

{

Point p = new Point();



p.mX = 5;

p.mY = -8;

Console.WriteLine("p=({0},{1})", p.mX, p.mY); // 5,-8

IncorrectMultiplyBy2(p);

Console.WriteLine("p=({0},{1})", p.mX, p.mY); // 5,-8

MultiplyBy2(ref p);

Console.WriteLine("p=({0},{1})", p.mX, p.mY); // 10,-16

}

}



При изпълнение на примера се вижда, че в тялото на метода Main() не се отразяват промените в предадения в подразбиращия се режим (в случая по стой­ност) параметър p при извикването на метода IncorrectMultiplyBy2(). Когато, обаче, параметърът е маркиран като ref, методът MultiplyBy2() успява да удвои членовете му, тъй като този метод променя директно подадената стойност, а нейно копие.

Предаване по стойност (in)


В подразбиращия се режим при извикване на метод му се подават копия от стойностите на параметрите. Реално стойностните типове се предават по стойност (предава се тяхно копие), а референтните типове се предават по референция (предава се копие на тяхната адреса в динамичната памет, към който сочат).

В по-горния пример видяхме как въпреки промяната в тялото на метода IncorrectMultiplyBy2() предадената по стойност променлива p не измени реалната си стойност. Трябва да обър­нем внимание, че p е от стойностен тип (инстанция на структурата Point). Ако p беше референтен тип, промените в членовете му щяха да бъдат видими за кода, из­викал метода. Защо това е така, въпреки че предаваме параметъра по стой­ност? На този въпрос ще си отговорим след като разгледаме следващия пример:



public class ClassPoint

{

internal int mX, mY;


public static void MultiplyBy2(ClassPoint aPoint)

{

aPoint.mX *= 2; aPoint.mY *= 2;



}
public static void IncorrectErase(ClassPoint aPoint)

{

aPoint = null;



}
static void Main()

{

ClassPoint p = new ClassPoint();



p.mX = 5;

p.mY = -8;

Console.WriteLine("p=({0},{1})", p.mX, p.mY); // 5,-8

MultiplyBy2(p);

Console.WriteLine("p=({0},{1})", p.mX, p.mY); // 10,-16

IncorrectErase(p);

Console.WriteLine("p=({0},{1})", p.mX, p.mY); // 10,-16

}

}



Забелязваме, че при обръщението към метода MultiplyBy2() дори и без да указваме, че параметърът се предава по референция, полетата на p успешно се удвояват. Това е така, защото класът ClassPoint е референ­тен тип и променливата от този тип представлява указател към паметта, където е записана същинската стойност на обекта.

При извикване на метод с предаване на параметрите по стойност в стека се прави копие на подадената про­мен­ли­ва, която в случая е указател (референция) и операциите с това копие из­менят реално оригиналната стойност на променливата в динамичната памет.

При промяна на даден па­ра­метър, подаден по стойност, например при изменянето на параметъра aPoint в тялото на метода IncorrectErase(), се изменя единствено ко­пи­ра­ният на стека указател, а не реалната стой­ност на обекта. Затова при излизане от метода IncorrectErase(), въпре­ки че за па­ра­метъра aPoint е зададена стой­ност null, променливата p не е променена и сочи към обекта, който е бил подаден при извикването.

Променлив брой параметри


В C# можем да дефинираме методи с променлив брой параметри. Пример за такъв ме­­тод, който неведнъж сме ползвали в нашите примери, е Console.WriteLine(…). Предаването на променлив брой параметри в C# се реализира чрез следния синтаксис:

static int Sum(params int[] aValues)

{

int retval = 0;



foreach(int arg in aValues)

{

retval += arg;



}

return retval;

}
static void Main()

{

int sum = Sum(1, 2, 3, 4, 5);



Console.WriteLine(sum); // The result is 15

}


В горния пример дефинирахме метода Sum(…), който изчислява сумата на произволен брой цели числа от тип int. Указваме, че методът приема произволен брой параметри със служебната дума params. Тя може да се използва най-много веднъж в дефиницията на даден метод и за­дъл­жи­тел­но се прилага към последния изреден параметър, който трябва да бъде ма­сив, приемащ множеството от параметрите. На метода от примера могат да бъдат подадени както произволен брой променливи от тип int, така и ма­сив от тип int, т. е. допустими извиквания са както Sum(1,2,3), така и Sum(new Object[]{1,2,3}).

Предаване на променлив брой параметри от различен тип


В някои случаи е възможно да се нуждаем от метод, който да приема про­изволен брой параметри, но не задължително от един и същ тип. Нап­ри­мер такъв би могъл да бъде методът, който изчислява сума на произволен брой целочислени параметри, включително и такива, зададени като символни ни­зо­ве. За този метод допустими обръщения биха били sum(1, 2, 3) както и sum(1, "2", 3).

За да реализираме такъв метод, можем да предаваме па­ра­метрите чрез масив от по-общ тип, например чрез масив от инстанции на типа System.Object, който е базов тип за всички типове в .NET Framework. Така можем да работим с произволно множество от пара­метри, чиито тип различаваме с помощта на оператора за принадлежност към тип is. В нашия случай за да използваме параметри от тип символен низ, първо ги конвертираме към желания целочислен тип. Ето примерна реализация на описаната идея:



int Sum(params Object[] values)

{

int retval = 0;



foreach(Object arg in values)

{

if (arg is int)



{

retval += (int)arg;

}

else if (arg is string)



{

retval += int.Parse((string)arg);

}

}

return retval;



}

В този вариант методът Sum() извлича целочислената стойност от низа, ко­гато се натъкне на такъв. Забелязваме употребата на оператора is, който връща true ако първият му аргумент "е" от типа, подаден като вто­ри аргумент и false в противен случай.

В примера сме използвали операциите (int)arg и (string)arg, които на­ри­чаме преобразуване на типове. На тях ще се спрем след малко, ко­га­то разглеждаме предефинирането на оператори и наследяване.





Сподели с приятели:
1   ...   22   23   24   25   26   27   28   29   ...   73




©obuch.info 2024
отнасят до администрацията

    Начална страница