Swift 4 Değer Türleri ve Referans Türleri, Array #12

Tıpkı C#’ta olduğu gibi Swift’te de türler kategori olarak iki kısma ayrılmaktadır: Değer türleri (value types) ve referans türleri (reference types). T bir tür olmak üzere T türünden bir değişken eğer değerin doğrudan kendisini tutuyorsa T türü kategori olarak değer türlerine ilişkindir. Eğer T türünden değişken değeri değil de değerin bulunduğu bellek adresini tutuyorsa T kategori olarak referans türlerine ilişkindir. Fiziksel bellekte her bir byte’ın ilk byte sıfır olmak üzere artan sırada bir adres değeri vardır.

Bellekteki her bir byte’a ilk byte sıfır olmak üzere artan sırada bir adres karşı düşürülmüştür. Dolayısıyla her bir değişkenin bellekte bir adresi vardır. Bir byte’tan uzun olan değişkenlerin ve nesnelerin adresleri onların yalnızca en düşük adres değeriyle ifade edilir. Örneğin:

Burada x’in adresi 100110'dur.

Java, C# ve Swift’teki referans türleri aslında C ce C++’taki gösterici (pointer) türleri gibidir. Swift’te gösterici kavramı yoktur. Ancak referans türleri gösterici gibi davranmaktadır.

Swift “değer türleri ve referans türleri” ayrımını C#’tan almıştır. Swift’te struct ve enum türleri kategori olarak değer türlerine ilişkindir. Fakat sınıflar ve protokoller referans türlerine ilişkindir. Şimdiye kadar görmüş olduğumuz Int, Double, String türlerinin hepsi aslında birer yapıdır. Dolayısıyla bunlar değer türlerine ilişkindir. Yani örneğin biz Int türden bir değişken bildirdiğimizde o değişken doğrudan değeri tutar. Diğer dillerin aksine Swift’te String ve Array türleri de birer struct biçiminde bildirilmiştir.

Swift’te Diziler

Elemanları aynı türden olan ve bellekte ardışıl biçimde bulunan veri yapılarına dizi (array) denir. Diziyi dizi yapan iki temel özellik vardır:

1) Dizi elemanlarının hepsi aynı türdendir.

2) Dizi elemanları bellekte ardışıl biçimde tutulur. Böylece dizilerde elemana erişmek çok hızlı (sabit zamanlı) yapılabilmektedir.

Swift’te T bir tür belirtmek üzere dizi türleri [T] biçiminde gösterilir. Örneğin:

var a: [Int]

a burada [Int] türündendir yani Int türden bir dizidir.

Swift’te [T] türü tamamen standart kütüphanedeki Array<T> türü ile aynı anlamdadır. Yani:

var a: [Int]

bildirimi ile:

var a: Array<Int>

bildirimi tamamen eşdeğerdir. (Array yapısının generic bir yapı olduğuna dikkat ediniz. Generic konusu son bölümlerde ele alınmaktadır.)

Bir dizi iki biçimde yaratılır:

1) Array<T> yapısının başlangıç metodu yoluyla: Bu yöntemde dizi doğrudan Array<T> yapısının başlangıç metotları yoluyla yaratılmaktadır. Örneğin:

var a: [Int] = [Int]()

Burada Int türden içi boş bir dizi yaratılmıştır. Bu bildirimin eşdeğeri şöyledir:

var a: [Int] = Array<Int>()

Tabii aşağıdaki bildirim de yukarıdakiyle eşdeğerdir:

var a: Array<Int> = Array<Int>()

Belli bir elemandan belli miktarda olacak biçimde de dizi yaratılabilir. Örneğin:

var a:[Int] = [Int](repeating: 100, count: 10)

Burada a dizisi 10 elemanlı olarak yaratılacaktır ve dizinin her elemanında 100 bulunacaktır.

2) Köşeli parantez ifadesi yoluyla: Dizi nesneleri köşeli parantezler içerisine ilkdeğer listesi yazılarak da yaratılabilirler. Örneğin:

var a:[Int] = [1, 2, 3]

Burada dizi Int türden olduğu için köşeli parantezler içerisinde verilen ilkdeğerlerin de Int türden olması zorunludur.

Tür belirtmeden de dizi nesneleri yaratılabilir. Örneğin:

var a = [1, 2, 3]

Burada verilen ilkdeğerler Int olarak değerlendirildiği için dizi de Int türdendir. Fakat örneğin:

var a = [1, 2.2, 3]

Burada verilen ilkdeğerlerden biri Double olarak değerlendirildiği için dizi de Double türünden kabul edilir. Köşeli parantezler içerisinde noktasız tamsayı ya da noktalı gerçek sayı dışında başka türler varsa dizi NSObject türünden olur. Örneğin:

var a = [1, “Ali”, 3] as [Any] // a değişkeni [NSObject] türünden

print(type(of: a))

Aslında bu durumda dizinin NSObject türünden olması Swift referans kitabında belirtilmemektedir. Ayrıca böylesi köşeli parantezlerin [NSObject] olarak yorumlanabilmesi içinm Foundation modülü de import edilmelidir.

Köşeli parantez yoluyla boş bir dizi de yaratılabilir. Örneğin:

var a:[Int] = []

Dizi elemanlarına diğer dillerin çoğunda olduğu gibi [ ] operatörü ile erişilmektedir. Dizinin ilk elemanı sıfırıncı indekstedir. Bu durumda n elemanlı bir dizinin sonuncu elemanı da n-1'inci indekste olacaktır. Aslında Swift’te tıpkı C#’ta olduğu gibi bir sınıf türünden referansın ya da bir yapı değişkeninin köşeli parantez operatörü ile kullanılabilmesi için ilgili sınıf ya da yapıda subscript isimli bir elemanın olması gerekir. Subscript C#’taki indeksleyiciler gibidir. Array<T> yapısının da elemana erişmekte kullanılan Int parametreli ve Range<Int> parametreli subscript’leri vardır. Range<Int> parametreli subscript sayesinde biz dizinin belli elemanlarını alabiliriz ya da değiştirebiliriz.

Dizi elemanlarına erişim Java ve C#’ta olduğu gibi programın çalışma zamanı sırasında denetlenmektedir. Dizinin olmayan bir elemanına erişilmek istendiğinde exception oluşur.

Dizi bildirimi let ile yapılırsa dizi elemanları da read-only (başka bir deyişle immutable) olur. Bu durumda biz dizi elemanlarını değiştiremeyiz. Örneğin:

let a = [1, 2, 3]

a[0] = 10 // error! dizi read-only

Dizilerin uzunlukları da tıpkı Java ve C#’ta olduğu gibi dizi nesnesinin içerisinde saklanmaktadır. Dizi uzunluğu Array<T> yapısının count property’si yoluyla elde edilmektedir.

Örneğin:

var a = [1, 2, 3, 4, 5]

print(a.count) // 5

Örneğin:

func getMax(a:[Int]) -> Int
{
var max = a[0]
for var i = 1; i < a.count; ++i {
if a[i] > max {
max = a[i]
}
}
return max
}
var a:[Int]
a = [120, 23, 17, 21, 9, -5, 34]
print(getMax(a))

Swift’in dizileri zaten büyütülebilen bir yapıdadır. Büyütme işlemi kapasite artırımı ile yapılır. Kapasite dizi elemanları için tahsis edilmiş olan uzunluktur. Count ise dizideki dolu olan eleman sayısıdır. Diziye eleman eklendiğinde Count bir artar. Count değeri Kapasite değerine geldiğinde tahsis edilmiş alan artırılmaktadır. Yani Array<T> yapısı kendi içerisinde daha büyük bir alanı tahsis edilmiş olarak tutmak ister. Böylece yeniden tahsisat (reallocation) işleminin daha etkin yapılmasını sağlar. Kapasite artırımının Swift’in dokümanlarında geometrik olarak (yani eskisinin belirli katı) yapıldığı belirtilmiştir. (Bu tür uygulamalarda genellikle diziler eski uzunluğun iki katı olacak biçimde büyütülmektedir.

Dizi için ayrılan alan Array<T> yapısının capacity property’si ile elde edilebilir. capacity property’si C#’ın aksine read-only’dir. Yani bunun değerine değiştiremeyiz. Ancak yapının reserveCapacity metodu bu işi yapmaktadır. Örneğin:

var a:[Int] = []
for i in 0…100 {
a.append(i)
print(“count \(a.count) capacity \(a.capacity)”)
}

Array<T> yapısının append metodu dizinin sonuna eleman eklemekte kullanılır. Metodun parametrik yapısı şöyledir:

mutating func append(_ newElement: Element)

Yapının insert metodu yeni eklenecek eleman belli bir indekste olacak biçimde eklemeyi yapar. Parametrik yapısı şöyledir:

mutating func insert(_ newElement: Element, atIndex i: Int)

Örneğin:

var a: [Int] = [1, 2, 3, 4, 5]
a.insert(100, at: 2)
print(a)

Yapının removeAtIndex isimşi metodu belli bir indeksteki elemanı siler. Metodun parametrik yapısı şöyledir:

mutating func removeAtIndex(_ index: Int) -> Element

Metod diziden atılan elemanı bize verir. Örneğin:

var a: [Int] = [1, 2, 3, 4, 5]
a.remove(at: 1)
print(a)

Yapının removeAll metodu tüm elemanları silmek için kullanılır:

mutating func removeAll(keepCapacity keepCapacity: Bool = default)

Örneğin:

var a: [Int] = [1, 2, 3, 4, 5, 6, 7, 8]
print(“\(a.count), \(a.capacity)”)
a.removeAll(keepingCapacity: false)
print(“\(a.count), \(a.capacity)”)

Yapının reserveCapacity metodu kapasiteyi belli bir değere çekmek için kullanılır. Bu metot biz zaten diziye belli miktarda eleman ekleyeceksek boşuna yeniden tahsisat yapılmasın diye kullanılabilmektedir. Metodun parametrik yapısı şöyledir:

mutating func reserveCapacity(_ minimumCapacity: Int)

Örneğin:

var a = [Int]()
a.reserveCapacity(500)
for i in 0…500 {
a.append(i)
}
print(“count = \(a.count), capacity = \(a.capacity)”)

Array<T> yapısının diğer elemanları Swift’in orijinal dokümanlarından incelenebilir.

Array<T> bir yapı olduğu için değer türlerine ilişkindir. Yani Array<T> türünden bir değişken dizi elemanlarının kendisini tutar. Böylece aynı türden iki dizi değişkenini birbirine atadığımızda bir kopyalama söz konusu olacaktır. Atama işleminden sonra birinde yapılan değişiklikler diğerini etkilemeyecektir. Örneğin:

var a, b: [Int]

a = [1, 2, 3, 4, 5]

b = a

a[0] = 100

a[1] = 200

print(a) // [100, 200, 3, 4, 5]

print(b) // [1, 2, 3, 4, 5]

Swift’te böylece dizilerin fonksiyonlara parametre yoluyla geçirilmesi de hep kopyalama yoluyla (call by value) yapılmaktadır. Örneğin:

var a: [Int] = [1, 2, 3, 4, 5]
func foo( b:[Int])
{
var b = b
print(b)
b[0] = 100
b[1] = 200
}
foo(b: a) // [1, 2, 3, 4, 5]
print(a) // [1, 2, 3, 4, 5]

Pekiyi dizilerin bu biçimde fonksiyonlara aktarılması performans kaybına yol açmaz mı? İşte eğer biz dizide değişiklik yapmıyorsak, örneğin parametre değişkeni olan dizi let ile bildirildiyse (default durumda zaten böyledir) derleyici arka planda bir optimizasyonla diziyi zaten adres yoluyla fonksiyona aktarmaktadır. Tabi dizi var ile bildirilmişse ve fonksiyon içerisinde dizi elemanları değiştirilmişse bu durumda derleyici diziyi kopyalama yöntemiyle fonksiyona aktarır. Aslında Array<T> yapısı “copy-on-write” özelliğine uygun bir biçimde de tasarlanmış olabilir. Yani belki de derleyici dizileri hep adres yoluyla aktarmaktadır. Ancak dizi elemanı ilk değiştirildiğinde onun kopyasını çıkarmaktadır.

Tabii Swift’te bir nesneyi adres yoluyla da fonksiyonlara aktarmak mümkündür. Örneğin biz gerçekten fonksiyon içerisinde yaptığımız değişikliğin asıl diziyi etkilemesini isteyebiliriz (sort işlemi yapan bir fonksiyon düşününüz). Bunun için inout belirleyicisi kullanılır. Bu konu ileride ele alınacaktır. Örneğin:

var a: [Int] = [1, 2, 3, 4, 5]
func foo(b:inout [Int]) {
print(b)
b[0] = 100
b[1] = 200
}
foo(b: &a) // [1, 2, 3, 4, 5]
print(a) // [100, 200, 3, 4, 5]

Aynı türden iki dizi + operatörü ile toplama işlemine sokulabilir. Bu durumda yeni bir dizi nesnesi elde edilir. Bu yeni nesnenin elemanları soldaki dizinin sonuna sağdaki dizinin elemanlarının eklenmiş halidir. Örneğin:

var a: [Int] = [1, 2, 3, 4, 5]
var b: [Int] = [6, 7, 8, 9, 10]
var c: [Int]
c = a + b
print(c) // [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

Benzer biçimde diziler için += işlemi de uygulanabilir. Zaten a = a + b her zaman a += b ile eşdeğerdir. Örneğin:

var a: [Int] = [1, 2, 3, 4, 5]
var b: [Int] = [6, 7, 8, 9, 10]
a += b
print(a)

Swift’te çok boyutlu dizi kavramı yoktur (tabii en azından şimdilik). Çok boyutlu diziler adreta dizi dizileri, biçiminde organize edilir. Örneğin [[Int]] türü Int dizileri tutan dizi türüdür. Bu tür Array<Array<Int>> ile eşdeğerdir. Örneğin:

var a:[[Int]] = [[1, 2, 3], [4, 5], [6, 7, 8]]
for x in a {
for y in x {
print(y, terminator: “ “)
}
print(“”)
}

Genel olarak bu biçimdeki, bir dizinin elemanlarına iki köşeli parantez a[i][k] biçiminde erişebiliriz. Burada eleman olan dizilerin aynı uzunlukta olma zorunluluğunun bulunmadığına dikkat ediniz. Zaten Java’da da çok boyutlu diziler ancak bu biçimde oluşturulmaktadır. C#’ta buna dizi dizisi (jagged array) denir. C# ayrıca çok boyutlu dizileri de desteklemektedir. C++’ta da hem çok boyutlu diziler hem de dizi dizileri oluşturmak mümkündür.

One clap, two clap, three clap, forty?

By clapping more or less, you can signal to us which stories really stand out.