Եթե ​​դուք նոր եք սկսում Go-ի հետ, կարող եք լսել երկու նմանատիպ հնչյունավոր տվյալների մասին, որոնք կոչվում են «զանգվածներ» և «հատվածներ»: Առաջին հայացքից այս երկու տեսակները կարող են միմյանց փոխարինելի թվալ, բայց նրանք ունեն որոշ կարևոր տարբերություններ, որոնք արժե հասկանալ: Այս գրառման մեջ մենք ավելի մանրամասն կանդրադառնանք Go-ի զանգվածներին և հատվածներին և կբացատրենք, թե ինչն է դրանք առանձնացնում:

Զանգվածներ

Զանգվածը նույն տեսակի տարրերի ֆիքսված չափի հավաքածու է: Երբ զանգվածը հայտարարված է, դրա չափը չի կարող փոխվել:

var arr [5]int // this creates an array of 5 integers, with default values of 0

Շերտեր

Մյուս կողմից, Slice-ը դինամիկ փոփոխական երկարությամբ տեսք է զանգվածի մեջ: Հատված է ստեղծվում make() ֆունկցիայի միջոցով, որն ընդունում է երկու արգումենտ՝ տարրի տեսակը և հատվածի սկզբնական չափը:

var s []int = make([]int, 5) // this creates a slice of 5 integers, with default values of 0

Հատվածները փոխանցվում են հղումով, ինչը նշանակում է, որ հատվածում կատարված ցանկացած փոփոխություն կազդի հիմնական զանգվածի վրա.

Մի վայրկյան սպասե՞լ: Ի՞նչ նկատի ունեք «հիմնական զանգված» ասելով:

Հատվածը չի պատկանում այն ​​տվյալներին, որոնց վրա նա մատնանշում է, այն պարզապես դիտում է հիմքում ընկած զանգվածի մեջ: Սա նշանակում է, որ երբ հատվածը փոխանցվում է ֆունկցիայի, այն փոխանցվում է հղումով, և ֆունկցիայի շրջանակում կատարված ցանկացած փոփոխություն կարտացոլվի սկզբնական հատվածում: Ի հակադրություն, երբ զանգվածը փոխանցվում է ֆունկցիայի, այն փոխանցվում է արժեքով, ինչը նշանակում է, որ ֆունկցիայի ներսում զանգվածում կատարված ցանկացած փոփոխություն չի արտացոլվի սկզբնական զանգվածում։

Բայց մենք ասացինք, որ զանգվածները ֆիքսված չափի են, իսկ կտորները՝ դինամիկ, ապա ինչպե՞ս կարող է լինել, որ շերտերը դինամիկ են, եթե գլխարկի տակ երկուսն էլ զանգված են:

Հասկանալու համար, որ մենք պետք է հասկանանք մեկ այլ հասկացություն. Go-ում կտորն ունի և՛ երկարություն, և՛ տարողություն: Կտորի երկարությունը այն տարրերի քանակն է, որը ներկայումս պարունակում է, մինչդեռ կտորի տարողությունը այն տարրերի քանակն է, որոնք այն կարող է պարունակել: Կտորի հզորությունը որոշվում է հիմքում ընկած զանգվածի չափով, որին այն մատնանշում է: Երբ հատվածը ստեղծվում է make() ֆունկցիայի միջոցով, հատվածի հզորությունը սահմանվում է նույն արժեքով, ինչ երկարությունը:

Այնուամենայնիվ, զանգվածների հակառակը, դուք կարող եք մեծացնել հատվածի հզորությունը՝ օգտագործելով append() ֆունկցիան: Երբ դուք որևէ տարր եք ավելացնում հատվածին, Go-ն ստուգում է՝ արդյոք հատվածը բավարար կարողություն ունի նոր տարրը պահելու համար: Եթե ​​կտորն ունի բավականաչափ հզորություն, նոր տարրը ավելացվում է կտորին, իսկ հատվածի երկարությունը մեծանում է 1-ով: պատճենում է տարրերը հին զանգվածից նոր զանգվածին, ավելացնում է նոր տարրը նոր զանգվածին և թարմացնում է հատվածը՝ նոր զանգվածին մատնանշելու համար: Այս գործընթացը կոչվում է վերաբաշխում:

Դուք կարող եք նաև հստակորեն սահմանել հատվածի հզորությունը՝ օգտագործելով cap() ֆունկցիան:

s := make([]int, 5, 10) // create a slice of length 5 and capacity 10

Այս օրինակում դուք կարող եք օգտագործել append() ֆունկցիան՝ կտորին տարրեր ավելացնելու մինչև իր 10 տարողությունը: Հենց որ գերազանցեք հատվածի հզորությունը, տեղի կունենա վերաբաշխում, և հատվածի հզորությունը կկրկնապատկվի մինչև 20:

Խորամանկ օրինակ

Հիմա տեսնենք, թե ինչ է տեղի ունենում, եթե հատվածին վերագրեք զանգված (կամ զանգվածի որոշ տարրեր):

arr := [5]int{1, 2, 3, 4, 5}
slice := arr[1:4] // slice points to elements {2, 3, 4} of the array
slice[0] = 10     // modify the first element of the slice
fmt.Println(arr)  // prints [1 10 3 4 5]

Երբ Go-ում զանգվածից հատված եք նշանակում, հատվածը և զանգվածը կիսում են նույն հիմքում ընկած տվյալները: Սա նշանակում է, որ եթե դուք փոփոխեք հատվածի տարրերը, ապա զանգվածի համապատասխան տարրերը նույնպես կփոփոխվեն։

Այս վարքագիծը կարող է օգտակար լինել որոշ դեպքերում, օրինակ, երբ ցանկանում եք ստեղծել գոյություն ունեցող զանգվածի ենթաբազմություն և փոփոխել այդ ենթաբազմության տարրերը: Այնուամենայնիվ, կարևոր է տեղյակ լինել այս մասին՝ շերտերի հետ աշխատելիս հիմքում ընկած տվյալները ակամա փոփոխելուց խուսափելու համար:

Եթե, մյուս կողմից, ցանկանում եք ստեղծել մի հատվածի պատճեն՝ առանց հիմքում ընկած տվյալները կիսելու բնօրինակ զանգվածի հետ, կարող եք օգտագործել copy() ֆունկցիան՝ նոր հատված ստեղծելու և սկզբնական հատվածից տարրերը պատճենելու նոր հատվածում: Ահա մի օրինակ.

arr := [5]int{1, 2, 3, 4, 5}
slice := arr[1:4]   // slice points to elements {2, 3, 4} of the array
copySlice := make([]int, len(slice))
copy(copySlice, slice)  // copy the elements of slice into copySlice
copySlice[0] = 10   // modify the first element of copySlice
fmt.Println(arr)    // prints [1 2 3 4 5], since the original array has not been modified

Հուսով եմ, որ այս հոդվածը ձեզ համար հետաքրքիր էր, և եթե ցանկանում եք ավելին իմանալ այն այլ թեմաների մասին, որոնցով ես կրքոտ եմ, ինչպիսիք են ծրագրավորումը, ամպը և ընդհանուր առմամբ SRE-ն, մի հապաղեք հետևել ինձ Medium-ում այս մեկի նման այլ պատմությունների համար 😃 — Ալեսիո Տրիվիսոննո

Շնորհավոր 🙌